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 lsp::CompletionContext;
76pub use lsp_ext::lsp_tasks;
77pub use multi_buffer::{
78 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
79 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
80 ToPoint,
81};
82pub use split::{SplittableEditor, ToggleSplitDiff};
83pub use split_editor_view::SplitEditorView;
84pub use text::Bias;
85
86use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
87use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
88use anyhow::{Context as _, Result, anyhow, bail};
89use blink_manager::BlinkManager;
90use buffer_diff::DiffHunkStatus;
91use client::{Collaborator, ParticipantIndex, parse_zed_link};
92use clock::ReplicaId;
93use code_context_menus::{
94 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
95 CompletionsMenu, ContextMenuOrigin,
96};
97use collections::{BTreeMap, HashMap, HashSet, VecDeque};
98use convert_case::{Case, Casing};
99use dap::TelemetrySpawnLocation;
100use display_map::*;
101use document_colors::LspColorData;
102use edit_prediction_types::{
103 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
104 EditPredictionGranularity, SuggestionDisplayType,
105};
106use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
107use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
108use futures::{
109 FutureExt,
110 future::{self, Shared, join},
111};
112use fuzzy::{StringMatch, StringMatchCandidate};
113use git::blame::{GitBlame, GlobalBlameRenderer};
114use gpui::{
115 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
116 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
117 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
118 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
119 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
120 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
121 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
122 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
123 pulsating_between, px, relative, size,
124};
125use hover_links::{HoverLink, HoveredLinkState, find_file};
126use hover_popover::{HoverState, hide_hover};
127use indent_guides::ActiveIndentGuidesState;
128use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
129use itertools::{Either, Itertools};
130use language::{
131 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
132 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
133 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
134 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
135 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
136 TreeSitterOptions, WordsQuery,
137 language_settings::{
138 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
139 all_language_settings, language_settings,
140 },
141 point_from_lsp, point_to_lsp, text_diff_with_options,
142};
143use linked_editing_ranges::refresh_linked_ranges;
144use lsp::{
145 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
146 LanguageServerId,
147};
148use markdown::Markdown;
149use mouse_context_menu::MouseContextMenu;
150use movement::TextLayoutDetails;
151use multi_buffer::{
152 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
153};
154use parking_lot::Mutex;
155use persistence::DB;
156use project::{
157 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
158 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
159 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
160 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
161 debugger::{
162 breakpoint_store::{
163 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
164 BreakpointStore, BreakpointStoreEvent,
165 },
166 session::{Session, SessionEvent},
167 },
168 git_store::GitStoreEvent,
169 lsp_store::{
170 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
171 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
172 },
173 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
174};
175use rand::seq::SliceRandom;
176use regex::Regex;
177use rpc::{ErrorCode, ErrorExt, proto::PeerId};
178use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
179use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
180use serde::{Deserialize, Serialize};
181use settings::{
182 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
183 update_settings_file,
184};
185use smallvec::{SmallVec, smallvec};
186use snippet::Snippet;
187use std::{
188 any::{Any, TypeId},
189 borrow::Cow,
190 cell::{OnceCell, RefCell},
191 cmp::{self, Ordering, Reverse},
192 collections::hash_map,
193 iter::{self, Peekable},
194 mem,
195 num::NonZeroU32,
196 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
197 path::{Path, PathBuf},
198 rc::Rc,
199 sync::Arc,
200 time::{Duration, Instant},
201};
202use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
203use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
204use theme::{
205 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
206 ThemeSettings, observe_buffer_font_size_adjustment,
207};
208use ui::{
209 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
210 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
211};
212use ui_input::ErasedEditor;
213use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
214use workspace::{
215 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
216 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
217 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
218 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
219 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
220 searchable::SearchEvent,
221};
222use zed_actions::editor::{MoveDown, MoveUp};
223
224use crate::{
225 code_context_menus::CompletionsMenuSource,
226 editor_settings::MultiCursorModifier,
227 hover_links::{find_url, find_url_from_range},
228 inlays::{
229 InlineValueCache,
230 inlay_hints::{LspInlayHintData, inlay_hint_settings},
231 },
232 scroll::{ScrollOffset, ScrollPixelOffset},
233 selections_collection::resolve_selections_wrapping_blocks,
234 semantic_tokens::SemanticTokenState,
235 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
236};
237
238pub const FILE_HEADER_HEIGHT: u32 = 2;
239pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
240const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
241const MAX_LINE_LEN: usize = 1024;
242const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
243const MAX_SELECTION_HISTORY_LEN: usize = 1024;
244pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
245#[doc(hidden)]
246pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
247pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
248
249pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
250pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
251pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
252pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
253
254pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
255pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
256pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
257
258pub type RenderDiffHunkControlsFn = Arc<
259 dyn Fn(
260 u32,
261 &DiffHunkStatus,
262 Range<Anchor>,
263 bool,
264 Pixels,
265 &Entity<Editor>,
266 &mut Window,
267 &mut App,
268 ) -> AnyElement,
269>;
270
271enum ReportEditorEvent {
272 Saved { auto_saved: bool },
273 EditorOpened,
274 Closed,
275}
276
277impl ReportEditorEvent {
278 pub fn event_type(&self) -> &'static str {
279 match self {
280 Self::Saved { .. } => "Editor Saved",
281 Self::EditorOpened => "Editor Opened",
282 Self::Closed => "Editor Closed",
283 }
284 }
285}
286
287pub enum ActiveDebugLine {}
288pub enum DebugStackFrameLine {}
289
290pub enum ConflictsOuter {}
291pub enum ConflictsOurs {}
292pub enum ConflictsTheirs {}
293pub enum ConflictsOursMarker {}
294pub enum ConflictsTheirsMarker {}
295
296pub struct HunkAddedColor;
297pub struct HunkRemovedColor;
298
299#[derive(Debug, Copy, Clone, PartialEq, Eq)]
300pub enum Navigated {
301 Yes,
302 No,
303}
304
305impl Navigated {
306 pub fn from_bool(yes: bool) -> Navigated {
307 if yes { Navigated::Yes } else { Navigated::No }
308 }
309}
310
311#[derive(Debug, Clone, PartialEq, Eq)]
312enum DisplayDiffHunk {
313 Folded {
314 display_row: DisplayRow,
315 },
316 Unfolded {
317 is_created_file: bool,
318 diff_base_byte_range: Range<usize>,
319 display_row_range: Range<DisplayRow>,
320 multi_buffer_range: Range<Anchor>,
321 status: DiffHunkStatus,
322 word_diffs: Vec<Range<MultiBufferOffset>>,
323 },
324}
325
326pub enum HideMouseCursorOrigin {
327 TypingAction,
328 MovementAction,
329}
330
331pub fn init(cx: &mut App) {
332 cx.set_global(GlobalBlameRenderer(Arc::new(())));
333 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
334
335 workspace::register_project_item::<Editor>(cx);
336 workspace::FollowableViewRegistry::register::<Editor>(cx);
337 workspace::register_serializable_item::<Editor>(cx);
338
339 cx.observe_new(
340 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
341 workspace.register_action(Editor::new_file);
342 workspace.register_action(Editor::new_file_split);
343 workspace.register_action(Editor::new_file_vertical);
344 workspace.register_action(Editor::new_file_horizontal);
345 workspace.register_action(Editor::cancel_language_server_work);
346 workspace.register_action(Editor::toggle_focus);
347 },
348 )
349 .detach();
350
351 cx.on_action(move |_: &workspace::NewFile, cx| {
352 let app_state = workspace::AppState::global(cx);
353 if let Some(app_state) = app_state.upgrade() {
354 workspace::open_new(
355 Default::default(),
356 app_state,
357 cx,
358 |workspace, window, cx| {
359 Editor::new_file(workspace, &Default::default(), window, cx)
360 },
361 )
362 .detach();
363 }
364 })
365 .on_action(move |_: &workspace::NewWindow, cx| {
366 let app_state = workspace::AppState::global(cx);
367 if let Some(app_state) = app_state.upgrade() {
368 workspace::open_new(
369 Default::default(),
370 app_state,
371 cx,
372 |workspace, window, cx| {
373 cx.activate(true);
374 Editor::new_file(workspace, &Default::default(), window, cx)
375 },
376 )
377 .detach();
378 }
379 });
380 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
381 Arc::new(ErasedEditorImpl(
382 cx.new(|cx| Editor::single_line(window, cx)),
383 )) as Arc<dyn ErasedEditor>
384 });
385 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
386}
387
388pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
389 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
390}
391
392pub trait DiagnosticRenderer {
393 fn render_group(
394 &self,
395 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
396 buffer_id: BufferId,
397 snapshot: EditorSnapshot,
398 editor: WeakEntity<Editor>,
399 language_registry: Option<Arc<LanguageRegistry>>,
400 cx: &mut App,
401 ) -> Vec<BlockProperties<Anchor>>;
402
403 fn render_hover(
404 &self,
405 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
406 range: Range<Point>,
407 buffer_id: BufferId,
408 language_registry: Option<Arc<LanguageRegistry>>,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 mode: ColumnarMode,
451 goal_column: u32,
452 },
453 Extend {
454 position: DisplayPoint,
455 click_count: usize,
456 },
457 Update {
458 position: DisplayPoint,
459 goal_column: u32,
460 scroll_delta: gpui::Point<f32>,
461 },
462 End,
463}
464
465#[derive(Clone, Debug, PartialEq)]
466pub enum ColumnarMode {
467 FromMouse,
468 FromSelection,
469}
470
471#[derive(Clone, Debug)]
472pub enum SelectMode {
473 Character,
474 Word(Range<Anchor>),
475 Line(Range<Anchor>),
476 All,
477}
478
479#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
480pub enum SizingBehavior {
481 /// The editor will layout itself using `size_full` and will include the vertical
482 /// scroll margin as requested by user settings.
483 #[default]
484 Default,
485 /// The editor will layout itself using `size_full`, but will not have any
486 /// vertical overscroll.
487 ExcludeOverscrollMargin,
488 /// The editor will request a vertical size according to its content and will be
489 /// layouted without a vertical scroll margin.
490 SizeByContent,
491}
492
493#[derive(Clone, PartialEq, Eq, Debug)]
494pub enum EditorMode {
495 SingleLine,
496 AutoHeight {
497 min_lines: usize,
498 max_lines: Option<usize>,
499 },
500 Full {
501 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
502 scale_ui_elements_with_buffer_font_size: bool,
503 /// When set to `true`, the editor will render a background for the active line.
504 show_active_line_background: bool,
505 /// Determines the sizing behavior for this editor
506 sizing_behavior: SizingBehavior,
507 },
508 Minimap {
509 parent: WeakEntity<Editor>,
510 },
511}
512
513impl EditorMode {
514 pub fn full() -> Self {
515 Self::Full {
516 scale_ui_elements_with_buffer_font_size: true,
517 show_active_line_background: true,
518 sizing_behavior: SizingBehavior::Default,
519 }
520 }
521
522 #[inline]
523 pub fn is_full(&self) -> bool {
524 matches!(self, Self::Full { .. })
525 }
526
527 #[inline]
528 pub fn is_single_line(&self) -> bool {
529 matches!(self, Self::SingleLine { .. })
530 }
531
532 #[inline]
533 fn is_minimap(&self) -> bool {
534 matches!(self, Self::Minimap { .. })
535 }
536}
537
538#[derive(Copy, Clone, Debug)]
539pub enum SoftWrap {
540 /// Prefer not to wrap at all.
541 ///
542 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
543 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
544 GitDiff,
545 /// Prefer a single line generally, unless an overly long line is encountered.
546 None,
547 /// Soft wrap lines that exceed the editor width.
548 EditorWidth,
549 /// Soft wrap lines at the preferred line length.
550 Column(u32),
551 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
552 Bounded(u32),
553}
554
555#[derive(Clone)]
556pub struct EditorStyle {
557 pub background: Hsla,
558 pub border: Hsla,
559 pub local_player: PlayerColor,
560 pub text: TextStyle,
561 pub scrollbar_width: Pixels,
562 pub syntax: Arc<SyntaxTheme>,
563 pub status: StatusColors,
564 pub inlay_hints_style: HighlightStyle,
565 pub edit_prediction_styles: EditPredictionStyles,
566 pub unnecessary_code_fade: f32,
567 pub show_underlines: bool,
568}
569
570impl Default for EditorStyle {
571 fn default() -> Self {
572 Self {
573 background: Hsla::default(),
574 border: Hsla::default(),
575 local_player: PlayerColor::default(),
576 text: TextStyle::default(),
577 scrollbar_width: Pixels::default(),
578 syntax: Default::default(),
579 // HACK: Status colors don't have a real default.
580 // We should look into removing the status colors from the editor
581 // style and retrieve them directly from the theme.
582 status: StatusColors::dark(),
583 inlay_hints_style: HighlightStyle::default(),
584 edit_prediction_styles: EditPredictionStyles {
585 insertion: HighlightStyle::default(),
586 whitespace: HighlightStyle::default(),
587 },
588 unnecessary_code_fade: Default::default(),
589 show_underlines: true,
590 }
591 }
592}
593
594pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
595 let show_background = language_settings::language_settings(None, None, cx)
596 .inlay_hints
597 .show_background;
598
599 let mut style = cx.theme().syntax().get("hint");
600
601 if style.color.is_none() {
602 style.color = Some(cx.theme().status().hint);
603 }
604
605 if !show_background {
606 style.background_color = None;
607 return style;
608 }
609
610 if style.background_color.is_none() {
611 style.background_color = Some(cx.theme().status().hint_background);
612 }
613
614 style
615}
616
617pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
618 EditPredictionStyles {
619 insertion: HighlightStyle {
620 color: Some(cx.theme().status().predictive),
621 ..HighlightStyle::default()
622 },
623 whitespace: HighlightStyle {
624 background_color: Some(cx.theme().status().created_background),
625 ..HighlightStyle::default()
626 },
627 }
628}
629
630type CompletionId = usize;
631
632pub(crate) enum EditDisplayMode {
633 TabAccept,
634 DiffPopover,
635 Inline,
636}
637
638enum EditPrediction {
639 Edit {
640 edits: Vec<(Range<Anchor>, Arc<str>)>,
641 /// Predicted cursor position as (anchor, offset_from_anchor).
642 /// The anchor is in multibuffer coordinates; after applying edits,
643 /// resolve the anchor and add the offset to get the final cursor position.
644 cursor_position: Option<(Anchor, usize)>,
645 edit_preview: Option<EditPreview>,
646 display_mode: EditDisplayMode,
647 snapshot: BufferSnapshot,
648 },
649 /// Move to a specific location in the active editor
650 MoveWithin {
651 target: Anchor,
652 snapshot: BufferSnapshot,
653 },
654 /// Move to a specific location in a different editor (not the active one)
655 MoveOutside {
656 target: language::Anchor,
657 snapshot: BufferSnapshot,
658 },
659}
660
661struct EditPredictionState {
662 inlay_ids: Vec<InlayId>,
663 completion: EditPrediction,
664 completion_id: Option<SharedString>,
665 invalidation_range: Option<Range<Anchor>>,
666}
667
668enum EditPredictionSettings {
669 Disabled,
670 Enabled {
671 show_in_menu: bool,
672 preview_requires_modifier: bool,
673 },
674}
675
676#[derive(Debug, Clone)]
677struct InlineDiagnostic {
678 message: SharedString,
679 group_id: usize,
680 is_primary: bool,
681 start: Point,
682 severity: lsp::DiagnosticSeverity,
683}
684
685pub enum MenuEditPredictionsPolicy {
686 Never,
687 ByProvider,
688}
689
690pub enum EditPredictionPreview {
691 /// Modifier is not pressed
692 Inactive { released_too_fast: bool },
693 /// Modifier pressed
694 Active {
695 since: Instant,
696 previous_scroll_position: Option<SharedScrollAnchor>,
697 },
698}
699
700impl EditPredictionPreview {
701 pub fn released_too_fast(&self) -> bool {
702 match self {
703 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
704 EditPredictionPreview::Active { .. } => false,
705 }
706 }
707
708 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
709 if let EditPredictionPreview::Active {
710 previous_scroll_position,
711 ..
712 } = self
713 {
714 *previous_scroll_position = scroll_position;
715 }
716 }
717}
718
719pub struct ContextMenuOptions {
720 pub min_entries_visible: usize,
721 pub max_entries_visible: usize,
722 pub placement: Option<ContextMenuPlacement>,
723}
724
725#[derive(Debug, Clone, PartialEq, Eq)]
726pub enum ContextMenuPlacement {
727 Above,
728 Below,
729}
730
731#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
732struct EditorActionId(usize);
733
734impl EditorActionId {
735 pub fn post_inc(&mut self) -> Self {
736 let answer = self.0;
737
738 *self = Self(answer + 1);
739
740 Self(answer)
741 }
742}
743
744// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
745// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
746
747type BackgroundHighlight = (
748 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
749 Arc<[Range<Anchor>]>,
750);
751type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
752
753#[derive(Default)]
754struct ScrollbarMarkerState {
755 scrollbar_size: Size<Pixels>,
756 dirty: bool,
757 markers: Arc<[PaintQuad]>,
758 pending_refresh: Option<Task<Result<()>>>,
759}
760
761impl ScrollbarMarkerState {
762 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
763 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
764 }
765}
766
767#[derive(Clone, Copy, PartialEq, Eq)]
768pub enum MinimapVisibility {
769 Disabled,
770 Enabled {
771 /// The configuration currently present in the users settings.
772 setting_configuration: bool,
773 /// Whether to override the currently set visibility from the users setting.
774 toggle_override: bool,
775 },
776}
777
778impl MinimapVisibility {
779 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
780 if mode.is_full() {
781 Self::Enabled {
782 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
783 toggle_override: false,
784 }
785 } else {
786 Self::Disabled
787 }
788 }
789
790 fn hidden(&self) -> Self {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 ..
795 } => Self::Enabled {
796 setting_configuration,
797 toggle_override: setting_configuration,
798 },
799 Self::Disabled => Self::Disabled,
800 }
801 }
802
803 fn disabled(&self) -> bool {
804 matches!(*self, Self::Disabled)
805 }
806
807 fn settings_visibility(&self) -> bool {
808 match *self {
809 Self::Enabled {
810 setting_configuration,
811 ..
812 } => setting_configuration,
813 _ => false,
814 }
815 }
816
817 fn visible(&self) -> bool {
818 match *self {
819 Self::Enabled {
820 setting_configuration,
821 toggle_override,
822 } => setting_configuration ^ toggle_override,
823 _ => false,
824 }
825 }
826
827 fn toggle_visibility(&self) -> Self {
828 match *self {
829 Self::Enabled {
830 toggle_override,
831 setting_configuration,
832 } => Self::Enabled {
833 setting_configuration,
834 toggle_override: !toggle_override,
835 },
836 Self::Disabled => Self::Disabled,
837 }
838 }
839}
840
841#[derive(Debug, Clone, Copy, PartialEq, Eq)]
842pub enum BufferSerialization {
843 All,
844 NonDirtyBuffers,
845}
846
847impl BufferSerialization {
848 fn new(restore_unsaved_buffers: bool) -> Self {
849 if restore_unsaved_buffers {
850 Self::All
851 } else {
852 Self::NonDirtyBuffers
853 }
854 }
855}
856
857#[derive(Clone, Debug)]
858struct RunnableTasks {
859 templates: Vec<(TaskSourceKind, TaskTemplate)>,
860 offset: multi_buffer::Anchor,
861 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
862 column: u32,
863 // Values of all named captures, including those starting with '_'
864 extra_variables: HashMap<String, String>,
865 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
866 context_range: Range<BufferOffset>,
867}
868
869impl RunnableTasks {
870 fn resolve<'a>(
871 &'a self,
872 cx: &'a task::TaskContext,
873 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
874 self.templates.iter().filter_map(|(kind, template)| {
875 template
876 .resolve_task(&kind.to_id_base(), cx)
877 .map(|task| (kind.clone(), task))
878 })
879 }
880}
881
882#[derive(Clone)]
883pub struct ResolvedTasks {
884 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
885 position: Anchor,
886}
887
888/// Addons allow storing per-editor state in other crates (e.g. Vim)
889pub trait Addon: 'static {
890 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
891
892 fn render_buffer_header_controls(
893 &self,
894 _: &ExcerptInfo,
895 _: &Window,
896 _: &App,
897 ) -> Option<AnyElement> {
898 None
899 }
900
901 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
902 None
903 }
904
905 fn to_any(&self) -> &dyn std::any::Any;
906
907 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
908 None
909 }
910}
911
912struct ChangeLocation {
913 current: Option<Vec<Anchor>>,
914 original: Vec<Anchor>,
915}
916impl ChangeLocation {
917 fn locations(&self) -> &[Anchor] {
918 self.current.as_ref().unwrap_or(&self.original)
919 }
920}
921
922/// A set of caret positions, registered when the editor was edited.
923pub struct ChangeList {
924 changes: Vec<ChangeLocation>,
925 /// Currently "selected" change.
926 position: Option<usize>,
927}
928
929impl ChangeList {
930 pub fn new() -> Self {
931 Self {
932 changes: Vec::new(),
933 position: None,
934 }
935 }
936
937 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
938 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
939 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
940 if self.changes.is_empty() {
941 return None;
942 }
943
944 let prev = self.position.unwrap_or(self.changes.len());
945 let next = if direction == Direction::Prev {
946 prev.saturating_sub(count)
947 } else {
948 (prev + count).min(self.changes.len() - 1)
949 };
950 self.position = Some(next);
951 self.changes.get(next).map(|change| change.locations())
952 }
953
954 /// Adds a new change to the list, resetting the change list position.
955 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
956 self.position.take();
957 if let Some(last) = self.changes.last_mut()
958 && group
959 {
960 last.current = Some(new_positions)
961 } else {
962 self.changes.push(ChangeLocation {
963 original: new_positions,
964 current: None,
965 });
966 }
967 }
968
969 pub fn last(&self) -> Option<&[Anchor]> {
970 self.changes.last().map(|change| change.locations())
971 }
972
973 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
974 self.changes.last().map(|change| change.original.as_slice())
975 }
976
977 pub fn invert_last_group(&mut self) {
978 if let Some(last) = self.changes.last_mut()
979 && let Some(current) = last.current.as_mut()
980 {
981 mem::swap(&mut last.original, current);
982 }
983 }
984}
985
986#[derive(Clone)]
987struct InlineBlamePopoverState {
988 scroll_handle: ScrollHandle,
989 commit_message: Option<ParsedCommitMessage>,
990 markdown: Entity<Markdown>,
991}
992
993struct InlineBlamePopover {
994 position: gpui::Point<Pixels>,
995 hide_task: Option<Task<()>>,
996 popover_bounds: Option<Bounds<Pixels>>,
997 popover_state: InlineBlamePopoverState,
998 keyboard_grace: bool,
999}
1000
1001enum SelectionDragState {
1002 /// State when no drag related activity is detected.
1003 None,
1004 /// State when the mouse is down on a selection that is about to be dragged.
1005 ReadyToDrag {
1006 selection: Selection<Anchor>,
1007 click_position: gpui::Point<Pixels>,
1008 mouse_down_time: Instant,
1009 },
1010 /// State when the mouse is dragging the selection in the editor.
1011 Dragging {
1012 selection: Selection<Anchor>,
1013 drop_cursor: Selection<Anchor>,
1014 hide_drop_cursor: bool,
1015 },
1016}
1017
1018enum ColumnarSelectionState {
1019 FromMouse {
1020 selection_tail: Anchor,
1021 display_point: Option<DisplayPoint>,
1022 },
1023 FromSelection {
1024 selection_tail: Anchor,
1025 },
1026}
1027
1028/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1029/// a breakpoint on them.
1030#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1031struct PhantomBreakpointIndicator {
1032 display_row: DisplayRow,
1033 /// There's a small debounce between hovering over the line and showing the indicator.
1034 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1035 is_active: bool,
1036 collides_with_existing_breakpoint: bool,
1037}
1038
1039/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1040/// in diff view mode.
1041#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1042pub(crate) struct PhantomDiffReviewIndicator {
1043 /// The starting anchor of the selection (or the only row if not dragging).
1044 pub start: Anchor,
1045 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1046 pub end: Anchor,
1047 /// There's a small debounce between hovering over the line and showing the indicator.
1048 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1049 pub is_active: bool,
1050}
1051
1052#[derive(Clone, Debug)]
1053pub(crate) struct DiffReviewDragState {
1054 pub start_anchor: Anchor,
1055 pub current_anchor: Anchor,
1056}
1057
1058impl DiffReviewDragState {
1059 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1060 let start = self.start_anchor.to_display_point(snapshot).row();
1061 let current = self.current_anchor.to_display_point(snapshot).row();
1062
1063 (start..=current).sorted()
1064 }
1065}
1066
1067/// Identifies a specific hunk in the diff buffer.
1068/// Used as a key to group comments by their location.
1069#[derive(Clone, Debug)]
1070pub struct DiffHunkKey {
1071 /// The file path (relative to worktree) this hunk belongs to.
1072 pub file_path: Arc<util::rel_path::RelPath>,
1073 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1074 pub hunk_start_anchor: Anchor,
1075}
1076
1077/// A review comment stored locally before being sent to the Agent panel.
1078#[derive(Clone)]
1079pub struct StoredReviewComment {
1080 /// Unique identifier for this comment (for edit/delete operations).
1081 pub id: usize,
1082 /// The comment text entered by the user.
1083 pub comment: String,
1084 /// Anchors for the code range being reviewed.
1085 pub range: Range<Anchor>,
1086 /// Timestamp when the comment was created (for chronological ordering).
1087 pub created_at: Instant,
1088 /// Whether this comment is currently being edited inline.
1089 pub is_editing: bool,
1090}
1091
1092impl StoredReviewComment {
1093 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1094 Self {
1095 id,
1096 comment,
1097 range: anchor_range,
1098 created_at: Instant::now(),
1099 is_editing: false,
1100 }
1101 }
1102}
1103
1104/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1105pub(crate) struct DiffReviewOverlay {
1106 pub anchor_range: Range<Anchor>,
1107 /// The block ID for the overlay.
1108 pub block_id: CustomBlockId,
1109 /// The editor entity for the review input.
1110 pub prompt_editor: Entity<Editor>,
1111 /// The hunk key this overlay belongs to.
1112 pub hunk_key: DiffHunkKey,
1113 /// Whether the comments section is expanded.
1114 pub comments_expanded: bool,
1115 /// Editors for comments currently being edited inline.
1116 /// Key: comment ID, Value: Editor entity for inline editing.
1117 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1118 /// Subscriptions for inline edit editors' action handlers.
1119 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1120 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1121 /// The current user's avatar URI for display in comment rows.
1122 pub user_avatar_uri: Option<SharedUri>,
1123 /// Subscription to keep the action handler alive.
1124 _subscription: Subscription,
1125}
1126
1127/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1128///
1129/// See the [module level documentation](self) for more information.
1130pub struct Editor {
1131 focus_handle: FocusHandle,
1132 last_focused_descendant: Option<WeakFocusHandle>,
1133 /// The text buffer being edited
1134 buffer: Entity<MultiBuffer>,
1135 /// Map of how text in the buffer should be displayed.
1136 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1137 pub display_map: Entity<DisplayMap>,
1138 placeholder_display_map: Option<Entity<DisplayMap>>,
1139 pub selections: SelectionsCollection,
1140 pub scroll_manager: ScrollManager,
1141 /// When inline assist editors are linked, they all render cursors because
1142 /// typing enters text into each of them, even the ones that aren't focused.
1143 pub(crate) show_cursor_when_unfocused: bool,
1144 columnar_selection_state: Option<ColumnarSelectionState>,
1145 add_selections_state: Option<AddSelectionsState>,
1146 select_next_state: Option<SelectNextState>,
1147 select_prev_state: Option<SelectNextState>,
1148 selection_history: SelectionHistory,
1149 defer_selection_effects: bool,
1150 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1151 autoclose_regions: Vec<AutocloseRegion>,
1152 snippet_stack: InvalidationStack<SnippetState>,
1153 select_syntax_node_history: SelectSyntaxNodeHistory,
1154 ime_transaction: Option<TransactionId>,
1155 pub diagnostics_max_severity: DiagnosticSeverity,
1156 active_diagnostics: ActiveDiagnostic,
1157 show_inline_diagnostics: bool,
1158 inline_diagnostics_update: Task<()>,
1159 inline_diagnostics_enabled: bool,
1160 diagnostics_enabled: bool,
1161 word_completions_enabled: bool,
1162 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1163 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1164 hard_wrap: Option<usize>,
1165 project: Option<Entity<Project>>,
1166 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1167 completion_provider: Option<Rc<dyn CompletionProvider>>,
1168 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1169 blink_manager: Entity<BlinkManager>,
1170 show_cursor_names: bool,
1171 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1172 pub show_local_selections: bool,
1173 mode: EditorMode,
1174 show_breadcrumbs: bool,
1175 show_gutter: bool,
1176 show_scrollbars: ScrollbarAxes,
1177 minimap_visibility: MinimapVisibility,
1178 offset_content: bool,
1179 disable_expand_excerpt_buttons: bool,
1180 delegate_expand_excerpts: bool,
1181 delegate_stage_and_restore: bool,
1182 delegate_open_excerpts: bool,
1183 enable_lsp_data: bool,
1184 enable_runnables: bool,
1185 show_line_numbers: Option<bool>,
1186 use_relative_line_numbers: Option<bool>,
1187 show_git_diff_gutter: Option<bool>,
1188 show_code_actions: Option<bool>,
1189 show_runnables: Option<bool>,
1190 show_breakpoints: Option<bool>,
1191 show_diff_review_button: bool,
1192 show_wrap_guides: Option<bool>,
1193 show_indent_guides: Option<bool>,
1194 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1195 highlight_order: usize,
1196 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1197 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1198 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1199 scrollbar_marker_state: ScrollbarMarkerState,
1200 active_indent_guides_state: ActiveIndentGuidesState,
1201 nav_history: Option<ItemNavHistory>,
1202 context_menu: RefCell<Option<CodeContextMenu>>,
1203 context_menu_options: Option<ContextMenuOptions>,
1204 mouse_context_menu: Option<MouseContextMenu>,
1205 completion_tasks: Vec<(CompletionId, Task<()>)>,
1206 inline_blame_popover: Option<InlineBlamePopover>,
1207 inline_blame_popover_show_task: Option<Task<()>>,
1208 signature_help_state: SignatureHelpState,
1209 auto_signature_help: Option<bool>,
1210 find_all_references_task_sources: Vec<Anchor>,
1211 next_completion_id: CompletionId,
1212 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1213 code_actions_task: Option<Task<Result<()>>>,
1214 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1215 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1216 debounced_selection_highlight_complete: bool,
1217 document_highlights_task: Option<Task<()>>,
1218 linked_editing_range_task: Option<Task<Option<()>>>,
1219 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1220 pending_rename: Option<RenameState>,
1221 searchable: bool,
1222 cursor_shape: CursorShape,
1223 /// Whether the cursor is offset one character to the left when something is
1224 /// selected (needed for vim visual mode)
1225 cursor_offset_on_selection: bool,
1226 current_line_highlight: Option<CurrentLineHighlight>,
1227 /// Whether to collapse search match ranges to just their start position.
1228 /// When true, navigating to a match positions the cursor at the match
1229 /// without selecting the matched text.
1230 collapse_matches: bool,
1231 autoindent_mode: Option<AutoindentMode>,
1232 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1233 input_enabled: bool,
1234 use_modal_editing: bool,
1235 read_only: bool,
1236 leader_id: Option<CollaboratorId>,
1237 remote_id: Option<ViewId>,
1238 pub hover_state: HoverState,
1239 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1240 prev_pressure_stage: Option<PressureStage>,
1241 gutter_hovered: bool,
1242 hovered_link_state: Option<HoveredLinkState>,
1243 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1244 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1245 active_edit_prediction: Option<EditPredictionState>,
1246 /// Used to prevent flickering as the user types while the menu is open
1247 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1248 edit_prediction_settings: EditPredictionSettings,
1249 edit_predictions_hidden_for_vim_mode: bool,
1250 show_edit_predictions_override: Option<bool>,
1251 show_completions_on_input_override: Option<bool>,
1252 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1253 edit_prediction_preview: EditPredictionPreview,
1254 edit_prediction_indent_conflict: bool,
1255 edit_prediction_requires_modifier_in_indent_conflict: bool,
1256 next_inlay_id: usize,
1257 next_color_inlay_id: usize,
1258 _subscriptions: Vec<Subscription>,
1259 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1260 gutter_dimensions: GutterDimensions,
1261 style: Option<EditorStyle>,
1262 text_style_refinement: Option<TextStyleRefinement>,
1263 next_editor_action_id: EditorActionId,
1264 editor_actions: Rc<
1265 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1266 >,
1267 use_autoclose: bool,
1268 use_auto_surround: bool,
1269 auto_replace_emoji_shortcode: bool,
1270 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1271 show_git_blame_gutter: bool,
1272 show_git_blame_inline: bool,
1273 show_git_blame_inline_delay_task: Option<Task<()>>,
1274 git_blame_inline_enabled: bool,
1275 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1276 buffer_serialization: Option<BufferSerialization>,
1277 show_selection_menu: Option<bool>,
1278 blame: Option<Entity<GitBlame>>,
1279 blame_subscription: Option<Subscription>,
1280 custom_context_menu: Option<
1281 Box<
1282 dyn 'static
1283 + Fn(
1284 &mut Self,
1285 DisplayPoint,
1286 &mut Window,
1287 &mut Context<Self>,
1288 ) -> Option<Entity<ui::ContextMenu>>,
1289 >,
1290 >,
1291 last_bounds: Option<Bounds<Pixels>>,
1292 last_position_map: Option<Rc<PositionMap>>,
1293 expect_bounds_change: Option<Bounds<Pixels>>,
1294 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1295 tasks_update_task: Option<Task<()>>,
1296 breakpoint_store: Option<Entity<BreakpointStore>>,
1297 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1298 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1299 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1300 /// Active diff review overlays. Multiple overlays can be open simultaneously
1301 /// when hunks have comments stored.
1302 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1303 /// Stored review comments grouped by hunk.
1304 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1305 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1306 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1307 /// Counter for generating unique comment IDs.
1308 next_review_comment_id: usize,
1309 hovered_diff_hunk_row: Option<DisplayRow>,
1310 pull_diagnostics_task: Task<()>,
1311 in_project_search: bool,
1312 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1313 breadcrumb_header: Option<String>,
1314 focused_block: Option<FocusedBlock>,
1315 next_scroll_position: NextScrollCursorCenterTopBottom,
1316 addons: HashMap<TypeId, Box<dyn Addon>>,
1317 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1318 load_diff_task: Option<Shared<Task<()>>>,
1319 /// Whether we are temporarily displaying a diff other than git's
1320 temporary_diff_override: bool,
1321 selection_mark_mode: bool,
1322 toggle_fold_multiple_buffers: Task<()>,
1323 _scroll_cursor_center_top_bottom_task: Task<()>,
1324 serialize_selections: Task<()>,
1325 serialize_folds: Task<()>,
1326 mouse_cursor_hidden: bool,
1327 minimap: Option<Entity<Self>>,
1328 hide_mouse_mode: HideMouseMode,
1329 pub change_list: ChangeList,
1330 inline_value_cache: InlineValueCache,
1331 number_deleted_lines: bool,
1332
1333 selection_drag_state: SelectionDragState,
1334 colors: Option<LspColorData>,
1335 post_scroll_update: Task<()>,
1336 refresh_colors_task: Task<()>,
1337 use_document_folding_ranges: bool,
1338 refresh_folding_ranges_task: Task<()>,
1339 inlay_hints: Option<LspInlayHintData>,
1340 folding_newlines: Task<()>,
1341 select_next_is_case_sensitive: Option<bool>,
1342 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1343 on_local_selections_changed:
1344 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1345 suppress_selection_callback: bool,
1346 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1347 accent_data: Option<AccentData>,
1348 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1349 semantic_token_state: SemanticTokenState,
1350 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1351 refresh_document_symbols_task: Shared<Task<()>>,
1352 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1353 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1354 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1355 sticky_headers_task: Task<()>,
1356 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1357}
1358
1359#[derive(Debug, PartialEq)]
1360struct AccentData {
1361 colors: AccentColors,
1362 overrides: Vec<SharedString>,
1363}
1364
1365fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1366 if debounce_ms > 0 {
1367 Some(Duration::from_millis(debounce_ms))
1368 } else {
1369 None
1370 }
1371}
1372
1373#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1374enum NextScrollCursorCenterTopBottom {
1375 #[default]
1376 Center,
1377 Top,
1378 Bottom,
1379}
1380
1381impl NextScrollCursorCenterTopBottom {
1382 fn next(&self) -> Self {
1383 match self {
1384 Self::Center => Self::Top,
1385 Self::Top => Self::Bottom,
1386 Self::Bottom => Self::Center,
1387 }
1388 }
1389}
1390
1391#[derive(Clone)]
1392pub struct EditorSnapshot {
1393 pub mode: EditorMode,
1394 show_gutter: bool,
1395 offset_content: bool,
1396 show_line_numbers: Option<bool>,
1397 number_deleted_lines: bool,
1398 show_git_diff_gutter: Option<bool>,
1399 show_code_actions: Option<bool>,
1400 show_runnables: Option<bool>,
1401 show_breakpoints: Option<bool>,
1402 git_blame_gutter_max_author_length: Option<usize>,
1403 pub display_snapshot: DisplaySnapshot,
1404 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1405 is_focused: bool,
1406 scroll_anchor: SharedScrollAnchor,
1407 ongoing_scroll: OngoingScroll,
1408 current_line_highlight: CurrentLineHighlight,
1409 gutter_hovered: bool,
1410 semantic_tokens_enabled: bool,
1411}
1412
1413#[derive(Default, Debug, Clone, Copy)]
1414pub struct GutterDimensions {
1415 pub left_padding: Pixels,
1416 pub right_padding: Pixels,
1417 pub width: Pixels,
1418 pub margin: Pixels,
1419 pub git_blame_entries_width: Option<Pixels>,
1420}
1421
1422impl GutterDimensions {
1423 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1424 Self {
1425 margin: Self::default_gutter_margin(font_id, font_size, cx),
1426 ..Default::default()
1427 }
1428 }
1429
1430 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1431 -cx.text_system().descent(font_id, font_size)
1432 }
1433 /// The full width of the space taken up by the gutter.
1434 pub fn full_width(&self) -> Pixels {
1435 self.margin + self.width
1436 }
1437
1438 /// The width of the space reserved for the fold indicators,
1439 /// use alongside 'justify_end' and `gutter_width` to
1440 /// right align content with the line numbers
1441 pub fn fold_area_width(&self) -> Pixels {
1442 self.margin + self.right_padding
1443 }
1444}
1445
1446struct CharacterDimensions {
1447 em_width: Pixels,
1448 em_advance: Pixels,
1449 line_height: Pixels,
1450}
1451
1452#[derive(Debug)]
1453pub struct RemoteSelection {
1454 pub replica_id: ReplicaId,
1455 pub selection: Selection<Anchor>,
1456 pub cursor_shape: CursorShape,
1457 pub collaborator_id: CollaboratorId,
1458 pub line_mode: bool,
1459 pub user_name: Option<SharedString>,
1460 pub color: PlayerColor,
1461}
1462
1463#[derive(Clone, Debug)]
1464struct SelectionHistoryEntry {
1465 selections: Arc<[Selection<Anchor>]>,
1466 select_next_state: Option<SelectNextState>,
1467 select_prev_state: Option<SelectNextState>,
1468 add_selections_state: Option<AddSelectionsState>,
1469}
1470
1471#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1472enum SelectionHistoryMode {
1473 #[default]
1474 Normal,
1475 Undoing,
1476 Redoing,
1477 Skipping,
1478}
1479
1480#[derive(Clone, PartialEq, Eq, Hash)]
1481struct HoveredCursor {
1482 replica_id: ReplicaId,
1483 selection_id: usize,
1484}
1485
1486#[derive(Debug)]
1487/// SelectionEffects controls the side-effects of updating the selection.
1488///
1489/// The default behaviour does "what you mostly want":
1490/// - it pushes to the nav history if the cursor moved by >10 lines
1491/// - it re-triggers completion requests
1492/// - it scrolls to fit
1493///
1494/// You might want to modify these behaviours. For example when doing a "jump"
1495/// like go to definition, we always want to add to nav history; but when scrolling
1496/// in vim mode we never do.
1497///
1498/// Similarly, you might want to disable scrolling if you don't want the viewport to
1499/// move.
1500#[derive(Clone)]
1501pub struct SelectionEffects {
1502 nav_history: Option<bool>,
1503 completions: bool,
1504 scroll: Option<Autoscroll>,
1505}
1506
1507impl Default for SelectionEffects {
1508 fn default() -> Self {
1509 Self {
1510 nav_history: None,
1511 completions: true,
1512 scroll: Some(Autoscroll::fit()),
1513 }
1514 }
1515}
1516impl SelectionEffects {
1517 pub fn scroll(scroll: Autoscroll) -> Self {
1518 Self {
1519 scroll: Some(scroll),
1520 ..Default::default()
1521 }
1522 }
1523
1524 pub fn no_scroll() -> Self {
1525 Self {
1526 scroll: None,
1527 ..Default::default()
1528 }
1529 }
1530
1531 pub fn completions(self, completions: bool) -> Self {
1532 Self {
1533 completions,
1534 ..self
1535 }
1536 }
1537
1538 pub fn nav_history(self, nav_history: bool) -> Self {
1539 Self {
1540 nav_history: Some(nav_history),
1541 ..self
1542 }
1543 }
1544}
1545
1546struct DeferredSelectionEffectsState {
1547 changed: bool,
1548 effects: SelectionEffects,
1549 old_cursor_position: Anchor,
1550 history_entry: SelectionHistoryEntry,
1551}
1552
1553#[derive(Default)]
1554struct SelectionHistory {
1555 #[allow(clippy::type_complexity)]
1556 selections_by_transaction:
1557 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1558 mode: SelectionHistoryMode,
1559 undo_stack: VecDeque<SelectionHistoryEntry>,
1560 redo_stack: VecDeque<SelectionHistoryEntry>,
1561}
1562
1563impl SelectionHistory {
1564 #[track_caller]
1565 fn insert_transaction(
1566 &mut self,
1567 transaction_id: TransactionId,
1568 selections: Arc<[Selection<Anchor>]>,
1569 ) {
1570 if selections.is_empty() {
1571 log::error!(
1572 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1573 std::panic::Location::caller()
1574 );
1575 return;
1576 }
1577 self.selections_by_transaction
1578 .insert(transaction_id, (selections, None));
1579 }
1580
1581 #[allow(clippy::type_complexity)]
1582 fn transaction(
1583 &self,
1584 transaction_id: TransactionId,
1585 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1586 self.selections_by_transaction.get(&transaction_id)
1587 }
1588
1589 #[allow(clippy::type_complexity)]
1590 fn transaction_mut(
1591 &mut self,
1592 transaction_id: TransactionId,
1593 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1594 self.selections_by_transaction.get_mut(&transaction_id)
1595 }
1596
1597 fn push(&mut self, entry: SelectionHistoryEntry) {
1598 if !entry.selections.is_empty() {
1599 match self.mode {
1600 SelectionHistoryMode::Normal => {
1601 self.push_undo(entry);
1602 self.redo_stack.clear();
1603 }
1604 SelectionHistoryMode::Undoing => self.push_redo(entry),
1605 SelectionHistoryMode::Redoing => self.push_undo(entry),
1606 SelectionHistoryMode::Skipping => {}
1607 }
1608 }
1609 }
1610
1611 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1612 if self
1613 .undo_stack
1614 .back()
1615 .is_none_or(|e| e.selections != entry.selections)
1616 {
1617 self.undo_stack.push_back(entry);
1618 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1619 self.undo_stack.pop_front();
1620 }
1621 }
1622 }
1623
1624 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1625 if self
1626 .redo_stack
1627 .back()
1628 .is_none_or(|e| e.selections != entry.selections)
1629 {
1630 self.redo_stack.push_back(entry);
1631 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1632 self.redo_stack.pop_front();
1633 }
1634 }
1635 }
1636}
1637
1638#[derive(Clone, Copy)]
1639pub struct RowHighlightOptions {
1640 pub autoscroll: bool,
1641 pub include_gutter: bool,
1642}
1643
1644impl Default for RowHighlightOptions {
1645 fn default() -> Self {
1646 Self {
1647 autoscroll: Default::default(),
1648 include_gutter: true,
1649 }
1650 }
1651}
1652
1653struct RowHighlight {
1654 index: usize,
1655 range: Range<Anchor>,
1656 color: Hsla,
1657 options: RowHighlightOptions,
1658 type_id: TypeId,
1659}
1660
1661#[derive(Clone, Debug)]
1662struct AddSelectionsState {
1663 groups: Vec<AddSelectionsGroup>,
1664}
1665
1666#[derive(Clone, Debug)]
1667struct AddSelectionsGroup {
1668 above: bool,
1669 stack: Vec<usize>,
1670}
1671
1672#[derive(Clone)]
1673struct SelectNextState {
1674 query: AhoCorasick,
1675 wordwise: bool,
1676 done: bool,
1677}
1678
1679impl std::fmt::Debug for SelectNextState {
1680 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1681 f.debug_struct(std::any::type_name::<Self>())
1682 .field("wordwise", &self.wordwise)
1683 .field("done", &self.done)
1684 .finish()
1685 }
1686}
1687
1688#[derive(Debug)]
1689struct AutocloseRegion {
1690 selection_id: usize,
1691 range: Range<Anchor>,
1692 pair: BracketPair,
1693}
1694
1695#[derive(Debug)]
1696struct SnippetState {
1697 ranges: Vec<Vec<Range<Anchor>>>,
1698 active_index: usize,
1699 choices: Vec<Option<Vec<String>>>,
1700}
1701
1702#[doc(hidden)]
1703pub struct RenameState {
1704 pub range: Range<Anchor>,
1705 pub old_name: Arc<str>,
1706 pub editor: Entity<Editor>,
1707 block_id: CustomBlockId,
1708}
1709
1710struct InvalidationStack<T>(Vec<T>);
1711
1712struct RegisteredEditPredictionDelegate {
1713 provider: Arc<dyn EditPredictionDelegateHandle>,
1714 _subscription: Subscription,
1715}
1716
1717#[derive(Debug, PartialEq, Eq)]
1718pub struct ActiveDiagnosticGroup {
1719 pub active_range: Range<Anchor>,
1720 pub active_message: String,
1721 pub group_id: usize,
1722 pub blocks: HashSet<CustomBlockId>,
1723}
1724
1725#[derive(Debug, PartialEq, Eq)]
1726
1727pub(crate) enum ActiveDiagnostic {
1728 None,
1729 All,
1730 Group(ActiveDiagnosticGroup),
1731}
1732
1733#[derive(Serialize, Deserialize, Clone, Debug)]
1734pub struct ClipboardSelection {
1735 /// The number of bytes in this selection.
1736 pub len: usize,
1737 /// Whether this was a full-line selection.
1738 pub is_entire_line: bool,
1739 /// The indentation of the first line when this content was originally copied.
1740 pub first_line_indent: u32,
1741 #[serde(default)]
1742 pub file_path: Option<PathBuf>,
1743 #[serde(default)]
1744 pub line_range: Option<RangeInclusive<u32>>,
1745}
1746
1747impl ClipboardSelection {
1748 pub fn for_buffer(
1749 len: usize,
1750 is_entire_line: bool,
1751 range: Range<Point>,
1752 buffer: &MultiBufferSnapshot,
1753 project: Option<&Entity<Project>>,
1754 cx: &App,
1755 ) -> Self {
1756 let first_line_indent = buffer
1757 .indent_size_for_line(MultiBufferRow(range.start.row))
1758 .len;
1759
1760 let file_path = util::maybe!({
1761 let project = project?.read(cx);
1762 let file = buffer.file_at(range.start)?;
1763 let project_path = ProjectPath {
1764 worktree_id: file.worktree_id(cx),
1765 path: file.path().clone(),
1766 };
1767 project.absolute_path(&project_path, cx)
1768 });
1769
1770 let line_range = file_path.as_ref().and_then(|_| {
1771 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1772 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1773 if start_excerpt_id == end_excerpt_id {
1774 Some(start_point.row..=end_point.row)
1775 } else {
1776 None
1777 }
1778 });
1779
1780 Self {
1781 len,
1782 is_entire_line,
1783 first_line_indent,
1784 file_path,
1785 line_range,
1786 }
1787 }
1788}
1789
1790// selections, scroll behavior, was newest selection reversed
1791type SelectSyntaxNodeHistoryState = (
1792 Box<[Selection<MultiBufferOffset>]>,
1793 SelectSyntaxNodeScrollBehavior,
1794 bool,
1795);
1796
1797#[derive(Default)]
1798struct SelectSyntaxNodeHistory {
1799 stack: Vec<SelectSyntaxNodeHistoryState>,
1800 // disable temporarily to allow changing selections without losing the stack
1801 pub disable_clearing: bool,
1802}
1803
1804impl SelectSyntaxNodeHistory {
1805 pub fn try_clear(&mut self) {
1806 if !self.disable_clearing {
1807 self.stack.clear();
1808 }
1809 }
1810
1811 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1812 self.stack.push(selection);
1813 }
1814
1815 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1816 self.stack.pop()
1817 }
1818}
1819
1820enum SelectSyntaxNodeScrollBehavior {
1821 CursorTop,
1822 FitSelection,
1823 CursorBottom,
1824}
1825
1826#[derive(Debug, Clone, Copy)]
1827pub(crate) struct NavigationData {
1828 cursor_anchor: Anchor,
1829 cursor_position: Point,
1830 scroll_anchor: ScrollAnchor,
1831 scroll_top_row: u32,
1832}
1833
1834#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1835pub enum GotoDefinitionKind {
1836 Symbol,
1837 Declaration,
1838 Type,
1839 Implementation,
1840}
1841
1842pub enum FormatTarget {
1843 Buffers(HashSet<Entity<Buffer>>),
1844 Ranges(Vec<Range<MultiBufferPoint>>),
1845}
1846
1847pub(crate) struct FocusedBlock {
1848 id: BlockId,
1849 focus_handle: WeakFocusHandle,
1850}
1851
1852#[derive(Clone, Debug)]
1853pub enum JumpData {
1854 MultiBufferRow {
1855 row: MultiBufferRow,
1856 line_offset_from_top: u32,
1857 },
1858 MultiBufferPoint {
1859 excerpt_id: ExcerptId,
1860 position: Point,
1861 anchor: text::Anchor,
1862 line_offset_from_top: u32,
1863 },
1864}
1865
1866pub enum MultibufferSelectionMode {
1867 First,
1868 All,
1869}
1870
1871#[derive(Clone, Copy, Debug, Default)]
1872pub struct RewrapOptions {
1873 pub override_language_settings: bool,
1874 pub preserve_existing_whitespace: bool,
1875}
1876
1877impl Editor {
1878 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1879 let buffer = cx.new(|cx| Buffer::local("", cx));
1880 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1881 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1882 }
1883
1884 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1885 let buffer = cx.new(|cx| Buffer::local("", cx));
1886 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1887 Self::new(EditorMode::full(), buffer, None, window, cx)
1888 }
1889
1890 pub fn auto_height(
1891 min_lines: usize,
1892 max_lines: usize,
1893 window: &mut Window,
1894 cx: &mut Context<Self>,
1895 ) -> Self {
1896 let buffer = cx.new(|cx| Buffer::local("", cx));
1897 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1898 Self::new(
1899 EditorMode::AutoHeight {
1900 min_lines,
1901 max_lines: Some(max_lines),
1902 },
1903 buffer,
1904 None,
1905 window,
1906 cx,
1907 )
1908 }
1909
1910 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1911 /// The editor grows as tall as needed to fit its content.
1912 pub fn auto_height_unbounded(
1913 min_lines: usize,
1914 window: &mut Window,
1915 cx: &mut Context<Self>,
1916 ) -> Self {
1917 let buffer = cx.new(|cx| Buffer::local("", cx));
1918 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1919 Self::new(
1920 EditorMode::AutoHeight {
1921 min_lines,
1922 max_lines: None,
1923 },
1924 buffer,
1925 None,
1926 window,
1927 cx,
1928 )
1929 }
1930
1931 pub fn for_buffer(
1932 buffer: Entity<Buffer>,
1933 project: Option<Entity<Project>>,
1934 window: &mut Window,
1935 cx: &mut Context<Self>,
1936 ) -> Self {
1937 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1938 Self::new(EditorMode::full(), buffer, project, window, cx)
1939 }
1940
1941 pub fn for_multibuffer(
1942 buffer: Entity<MultiBuffer>,
1943 project: Option<Entity<Project>>,
1944 window: &mut Window,
1945 cx: &mut Context<Self>,
1946 ) -> Self {
1947 Self::new(EditorMode::full(), buffer, project, window, cx)
1948 }
1949
1950 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1951 let mut clone = Self::new(
1952 self.mode.clone(),
1953 self.buffer.clone(),
1954 self.project.clone(),
1955 window,
1956 cx,
1957 );
1958 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1959 let snapshot = display_map.snapshot(cx);
1960 clone.display_map.update(cx, |display_map, cx| {
1961 display_map.set_state(&snapshot, cx);
1962 });
1963 snapshot
1964 });
1965 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1966 clone.folds_did_change(cx);
1967 clone.selections.clone_state(&self.selections);
1968 clone
1969 .scroll_manager
1970 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1971 clone.searchable = self.searchable;
1972 clone.read_only = self.read_only;
1973 clone
1974 }
1975
1976 pub fn new(
1977 mode: EditorMode,
1978 buffer: Entity<MultiBuffer>,
1979 project: Option<Entity<Project>>,
1980 window: &mut Window,
1981 cx: &mut Context<Self>,
1982 ) -> Self {
1983 Editor::new_internal(mode, buffer, project, None, window, cx)
1984 }
1985
1986 pub fn refresh_sticky_headers(
1987 &mut self,
1988 display_snapshot: &DisplaySnapshot,
1989 cx: &mut Context<Editor>,
1990 ) {
1991 if !self.mode.is_full() {
1992 return;
1993 }
1994 let multi_buffer = display_snapshot.buffer_snapshot();
1995 let scroll_anchor = self
1996 .scroll_manager
1997 .native_anchor(display_snapshot, cx)
1998 .anchor;
1999 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2000 return;
2001 };
2002 let buffer = buffer.clone();
2003
2004 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2005 let max_row = buffer.max_point().row;
2006 let start_row = buffer_visible_start.row.min(max_row);
2007 let end_row = (buffer_visible_start.row + 10).min(max_row);
2008
2009 let syntax = self.style(cx).syntax.clone();
2010 let background_task = cx.background_spawn(async move {
2011 buffer
2012 .outline_items_containing(
2013 Point::new(start_row, 0)..Point::new(end_row, 0),
2014 true,
2015 Some(syntax.as_ref()),
2016 )
2017 .into_iter()
2018 .map(|outline_item| OutlineItem {
2019 depth: outline_item.depth,
2020 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2021 source_range_for_text: Anchor::range_in_buffer(
2022 excerpt_id,
2023 outline_item.source_range_for_text,
2024 ),
2025 text: outline_item.text,
2026 highlight_ranges: outline_item.highlight_ranges,
2027 name_ranges: outline_item.name_ranges,
2028 body_range: outline_item
2029 .body_range
2030 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2031 annotation_range: outline_item
2032 .annotation_range
2033 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2034 })
2035 .collect()
2036 });
2037 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2038 let sticky_headers = background_task.await;
2039 this.update(cx, |this, cx| {
2040 this.sticky_headers = Some(sticky_headers);
2041 cx.notify();
2042 })
2043 .ok();
2044 });
2045 }
2046
2047 fn new_internal(
2048 mode: EditorMode,
2049 multi_buffer: Entity<MultiBuffer>,
2050 project: Option<Entity<Project>>,
2051 display_map: Option<Entity<DisplayMap>>,
2052 window: &mut Window,
2053 cx: &mut Context<Self>,
2054 ) -> Self {
2055 debug_assert!(
2056 display_map.is_none() || mode.is_minimap(),
2057 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2058 );
2059
2060 let full_mode = mode.is_full();
2061 let is_minimap = mode.is_minimap();
2062 let diagnostics_max_severity = if full_mode {
2063 EditorSettings::get_global(cx)
2064 .diagnostics_max_severity
2065 .unwrap_or(DiagnosticSeverity::Hint)
2066 } else {
2067 DiagnosticSeverity::Off
2068 };
2069 let style = window.text_style();
2070 let font_size = style.font_size.to_pixels(window.rem_size());
2071 let editor = cx.entity().downgrade();
2072 let fold_placeholder = FoldPlaceholder {
2073 constrain_width: false,
2074 render: Arc::new(move |fold_id, fold_range, cx| {
2075 let editor = editor.clone();
2076 FoldPlaceholder::fold_element(fold_id, cx)
2077 .cursor_pointer()
2078 .child("⋯")
2079 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2080 .on_click(move |_, _window, cx| {
2081 editor
2082 .update(cx, |editor, cx| {
2083 editor.unfold_ranges(
2084 &[fold_range.start..fold_range.end],
2085 true,
2086 false,
2087 cx,
2088 );
2089 cx.stop_propagation();
2090 })
2091 .ok();
2092 })
2093 .into_any()
2094 }),
2095 merge_adjacent: true,
2096 ..FoldPlaceholder::default()
2097 };
2098 let display_map = display_map.unwrap_or_else(|| {
2099 cx.new(|cx| {
2100 DisplayMap::new(
2101 multi_buffer.clone(),
2102 style.font(),
2103 font_size,
2104 None,
2105 FILE_HEADER_HEIGHT,
2106 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2107 fold_placeholder,
2108 diagnostics_max_severity,
2109 cx,
2110 )
2111 })
2112 });
2113
2114 let selections = SelectionsCollection::new();
2115
2116 let blink_manager = cx.new(|cx| {
2117 let mut blink_manager = BlinkManager::new(
2118 CURSOR_BLINK_INTERVAL,
2119 |cx| EditorSettings::get_global(cx).cursor_blink,
2120 cx,
2121 );
2122 if is_minimap {
2123 blink_manager.disable(cx);
2124 }
2125 blink_manager
2126 });
2127
2128 let soft_wrap_mode_override =
2129 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2130
2131 let mut project_subscriptions = Vec::new();
2132 if full_mode && let Some(project) = project.as_ref() {
2133 project_subscriptions.push(cx.subscribe_in(
2134 project,
2135 window,
2136 |editor, _, event, window, cx| match event {
2137 project::Event::RefreshCodeLens => {
2138 // we always query lens with actions, without storing them, always refreshing them
2139 }
2140 project::Event::RefreshInlayHints {
2141 server_id,
2142 request_id,
2143 } => {
2144 editor.refresh_inlay_hints(
2145 InlayHintRefreshReason::RefreshRequested {
2146 server_id: *server_id,
2147 request_id: *request_id,
2148 },
2149 cx,
2150 );
2151 }
2152 project::Event::RefreshSemanticTokens {
2153 server_id,
2154 request_id,
2155 } => {
2156 editor.refresh_semantic_tokens(
2157 None,
2158 Some(RefreshForServer {
2159 server_id: *server_id,
2160 request_id: *request_id,
2161 }),
2162 cx,
2163 );
2164 }
2165 project::Event::LanguageServerRemoved(_) => {
2166 editor.registered_buffers.clear();
2167 editor.register_visible_buffers(cx);
2168 editor.invalidate_semantic_tokens(None);
2169 editor.update_lsp_data(None, window, cx);
2170 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2171 if editor.tasks_update_task.is_none() {
2172 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2173 }
2174 }
2175 project::Event::LanguageServerAdded(..) => {
2176 if editor.tasks_update_task.is_none() {
2177 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2178 }
2179 }
2180 project::Event::SnippetEdit(id, snippet_edits) => {
2181 // todo(lw): Non singletons
2182 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2183 let snapshot = buffer.read(cx).snapshot();
2184 let focus_handle = editor.focus_handle(cx);
2185 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2186 for (range, snippet) in snippet_edits {
2187 let buffer_range =
2188 language::range_from_lsp(*range).to_offset(&snapshot);
2189 editor
2190 .insert_snippet(
2191 &[MultiBufferOffset(buffer_range.start)
2192 ..MultiBufferOffset(buffer_range.end)],
2193 snippet.clone(),
2194 window,
2195 cx,
2196 )
2197 .ok();
2198 }
2199 }
2200 }
2201 }
2202 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2203 let buffer_id = *buffer_id;
2204 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2205 editor.register_buffer(buffer_id, cx);
2206 editor.update_lsp_data(Some(buffer_id), window, cx);
2207 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2208 refresh_linked_ranges(editor, window, cx);
2209 editor.refresh_code_actions(window, cx);
2210 editor.refresh_document_highlights(cx);
2211 }
2212 }
2213
2214 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2215 let Some(workspace) = editor.workspace() else {
2216 return;
2217 };
2218 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2219 else {
2220 return;
2221 };
2222
2223 if active_editor.entity_id() == cx.entity_id() {
2224 let entity_id = cx.entity_id();
2225 workspace.update(cx, |this, cx| {
2226 this.panes_mut()
2227 .iter_mut()
2228 .filter(|pane| pane.entity_id() != entity_id)
2229 .for_each(|p| {
2230 p.update(cx, |pane, _| {
2231 pane.nav_history_mut().rename_item(
2232 entity_id,
2233 project_path.clone(),
2234 abs_path.clone().into(),
2235 );
2236 })
2237 });
2238 });
2239
2240 Self::open_transaction_for_hidden_buffers(
2241 workspace,
2242 transaction.clone(),
2243 "Rename".to_string(),
2244 window,
2245 cx,
2246 );
2247 }
2248 }
2249
2250 project::Event::WorkspaceEditApplied(transaction) => {
2251 let Some(workspace) = editor.workspace() else {
2252 return;
2253 };
2254 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2255 else {
2256 return;
2257 };
2258
2259 if active_editor.entity_id() == cx.entity_id() {
2260 Self::open_transaction_for_hidden_buffers(
2261 workspace,
2262 transaction.clone(),
2263 "LSP Edit".to_string(),
2264 window,
2265 cx,
2266 );
2267 }
2268 }
2269
2270 _ => {}
2271 },
2272 ));
2273 if let Some(task_inventory) = project
2274 .read(cx)
2275 .task_store()
2276 .read(cx)
2277 .task_inventory()
2278 .cloned()
2279 {
2280 project_subscriptions.push(cx.observe_in(
2281 &task_inventory,
2282 window,
2283 |editor, _, window, cx| {
2284 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2285 },
2286 ));
2287 };
2288
2289 project_subscriptions.push(cx.subscribe_in(
2290 &project.read(cx).breakpoint_store(),
2291 window,
2292 |editor, _, event, window, cx| match event {
2293 BreakpointStoreEvent::ClearDebugLines => {
2294 editor.clear_row_highlights::<ActiveDebugLine>();
2295 editor.refresh_inline_values(cx);
2296 }
2297 BreakpointStoreEvent::SetDebugLine => {
2298 if editor.go_to_active_debug_line(window, cx) {
2299 cx.stop_propagation();
2300 }
2301
2302 editor.refresh_inline_values(cx);
2303 }
2304 _ => {}
2305 },
2306 ));
2307 let git_store = project.read(cx).git_store().clone();
2308 let project = project.clone();
2309 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2310 if let GitStoreEvent::RepositoryAdded = event {
2311 this.load_diff_task = Some(
2312 update_uncommitted_diff_for_buffer(
2313 cx.entity(),
2314 &project,
2315 this.buffer.read(cx).all_buffers(),
2316 this.buffer.clone(),
2317 cx,
2318 )
2319 .shared(),
2320 );
2321 }
2322 }));
2323 }
2324
2325 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2326
2327 let inlay_hint_settings =
2328 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2329 let focus_handle = cx.focus_handle();
2330 if !is_minimap {
2331 cx.on_focus(&focus_handle, window, Self::handle_focus)
2332 .detach();
2333 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2334 .detach();
2335 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2336 .detach();
2337 cx.on_blur(&focus_handle, window, Self::handle_blur)
2338 .detach();
2339 cx.observe_pending_input(window, Self::observe_pending_input)
2340 .detach();
2341 }
2342
2343 let show_indent_guides =
2344 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2345 Some(false)
2346 } else {
2347 None
2348 };
2349
2350 let breakpoint_store = match (&mode, project.as_ref()) {
2351 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2352 _ => None,
2353 };
2354
2355 let mut code_action_providers = Vec::new();
2356 let mut load_uncommitted_diff = None;
2357 if let Some(project) = project.clone() {
2358 load_uncommitted_diff = Some(
2359 update_uncommitted_diff_for_buffer(
2360 cx.entity(),
2361 &project,
2362 multi_buffer.read(cx).all_buffers(),
2363 multi_buffer.clone(),
2364 cx,
2365 )
2366 .shared(),
2367 );
2368 code_action_providers.push(Rc::new(project) as Rc<_>);
2369 }
2370
2371 let mut editor = Self {
2372 focus_handle,
2373 show_cursor_when_unfocused: false,
2374 last_focused_descendant: None,
2375 buffer: multi_buffer.clone(),
2376 display_map: display_map.clone(),
2377 placeholder_display_map: None,
2378 selections,
2379 scroll_manager: ScrollManager::new(cx),
2380 columnar_selection_state: None,
2381 add_selections_state: None,
2382 select_next_state: None,
2383 select_prev_state: None,
2384 selection_history: SelectionHistory::default(),
2385 defer_selection_effects: false,
2386 deferred_selection_effects_state: None,
2387 autoclose_regions: Vec::new(),
2388 snippet_stack: InvalidationStack::default(),
2389 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2390 ime_transaction: None,
2391 active_diagnostics: ActiveDiagnostic::None,
2392 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2393 inline_diagnostics_update: Task::ready(()),
2394 inline_diagnostics: Vec::new(),
2395 soft_wrap_mode_override,
2396 diagnostics_max_severity,
2397 hard_wrap: None,
2398 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2399 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2400 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2401 project,
2402 blink_manager: blink_manager.clone(),
2403 show_local_selections: true,
2404 show_scrollbars: ScrollbarAxes {
2405 horizontal: full_mode,
2406 vertical: full_mode,
2407 },
2408 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2409 offset_content: !matches!(mode, EditorMode::SingleLine),
2410 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2411 show_gutter: full_mode,
2412 show_line_numbers: (!full_mode).then_some(false),
2413 use_relative_line_numbers: None,
2414 disable_expand_excerpt_buttons: !full_mode,
2415 delegate_expand_excerpts: false,
2416 delegate_stage_and_restore: false,
2417 delegate_open_excerpts: false,
2418 enable_lsp_data: true,
2419 enable_runnables: true,
2420 show_git_diff_gutter: None,
2421 show_code_actions: None,
2422 show_runnables: None,
2423 show_breakpoints: None,
2424 show_diff_review_button: false,
2425 show_wrap_guides: None,
2426 show_indent_guides,
2427 buffers_with_disabled_indent_guides: HashSet::default(),
2428 highlight_order: 0,
2429 highlighted_rows: HashMap::default(),
2430 background_highlights: HashMap::default(),
2431 gutter_highlights: HashMap::default(),
2432 scrollbar_marker_state: ScrollbarMarkerState::default(),
2433 active_indent_guides_state: ActiveIndentGuidesState::default(),
2434 nav_history: None,
2435 context_menu: RefCell::new(None),
2436 context_menu_options: None,
2437 mouse_context_menu: None,
2438 completion_tasks: Vec::new(),
2439 inline_blame_popover: None,
2440 inline_blame_popover_show_task: None,
2441 signature_help_state: SignatureHelpState::default(),
2442 auto_signature_help: None,
2443 find_all_references_task_sources: Vec::new(),
2444 next_completion_id: 0,
2445 next_inlay_id: 0,
2446 code_action_providers,
2447 available_code_actions: None,
2448 code_actions_task: None,
2449 quick_selection_highlight_task: None,
2450 debounced_selection_highlight_task: None,
2451 debounced_selection_highlight_complete: false,
2452 document_highlights_task: None,
2453 linked_editing_range_task: None,
2454 pending_rename: None,
2455 searchable: !is_minimap,
2456 cursor_shape: EditorSettings::get_global(cx)
2457 .cursor_shape
2458 .unwrap_or_default(),
2459 cursor_offset_on_selection: false,
2460 current_line_highlight: None,
2461 autoindent_mode: Some(AutoindentMode::EachLine),
2462 collapse_matches: false,
2463 workspace: None,
2464 input_enabled: !is_minimap,
2465 use_modal_editing: full_mode,
2466 read_only: is_minimap,
2467 use_autoclose: true,
2468 use_auto_surround: true,
2469 auto_replace_emoji_shortcode: false,
2470 jsx_tag_auto_close_enabled_in_any_buffer: false,
2471 leader_id: None,
2472 remote_id: None,
2473 hover_state: HoverState::default(),
2474 pending_mouse_down: None,
2475 prev_pressure_stage: None,
2476 hovered_link_state: None,
2477 edit_prediction_provider: None,
2478 active_edit_prediction: None,
2479 stale_edit_prediction_in_menu: None,
2480 edit_prediction_preview: EditPredictionPreview::Inactive {
2481 released_too_fast: false,
2482 },
2483 inline_diagnostics_enabled: full_mode,
2484 diagnostics_enabled: full_mode,
2485 word_completions_enabled: full_mode,
2486 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2487 gutter_hovered: false,
2488 pixel_position_of_newest_cursor: None,
2489 last_bounds: None,
2490 last_position_map: None,
2491 expect_bounds_change: None,
2492 gutter_dimensions: GutterDimensions::default(),
2493 style: None,
2494 show_cursor_names: false,
2495 hovered_cursors: HashMap::default(),
2496 next_editor_action_id: EditorActionId::default(),
2497 editor_actions: Rc::default(),
2498 edit_predictions_hidden_for_vim_mode: false,
2499 show_edit_predictions_override: None,
2500 show_completions_on_input_override: None,
2501 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2502 edit_prediction_settings: EditPredictionSettings::Disabled,
2503 edit_prediction_indent_conflict: false,
2504 edit_prediction_requires_modifier_in_indent_conflict: true,
2505 custom_context_menu: None,
2506 show_git_blame_gutter: false,
2507 show_git_blame_inline: false,
2508 show_selection_menu: None,
2509 show_git_blame_inline_delay_task: None,
2510 git_blame_inline_enabled: full_mode
2511 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2512 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2513 buffer_serialization: is_minimap.not().then(|| {
2514 BufferSerialization::new(
2515 ProjectSettings::get_global(cx)
2516 .session
2517 .restore_unsaved_buffers,
2518 )
2519 }),
2520 blame: None,
2521 blame_subscription: None,
2522 tasks: BTreeMap::default(),
2523
2524 breakpoint_store,
2525 gutter_breakpoint_indicator: (None, None),
2526 gutter_diff_review_indicator: (None, None),
2527 diff_review_drag_state: None,
2528 diff_review_overlays: Vec::new(),
2529 stored_review_comments: Vec::new(),
2530 next_review_comment_id: 0,
2531 hovered_diff_hunk_row: None,
2532 _subscriptions: (!is_minimap)
2533 .then(|| {
2534 vec![
2535 cx.observe(&multi_buffer, Self::on_buffer_changed),
2536 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2537 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2538 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2539 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2540 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2541 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2542 cx.observe_window_activation(window, |editor, window, cx| {
2543 let active = window.is_window_active();
2544 editor.blink_manager.update(cx, |blink_manager, cx| {
2545 if active {
2546 blink_manager.enable(cx);
2547 } else {
2548 blink_manager.disable(cx);
2549 }
2550 });
2551 if active {
2552 editor.show_mouse_cursor(cx);
2553 }
2554 }),
2555 ]
2556 })
2557 .unwrap_or_default(),
2558 tasks_update_task: None,
2559 pull_diagnostics_task: Task::ready(()),
2560 colors: None,
2561 refresh_colors_task: Task::ready(()),
2562 use_document_folding_ranges: false,
2563 refresh_folding_ranges_task: Task::ready(()),
2564 inlay_hints: None,
2565 next_color_inlay_id: 0,
2566 post_scroll_update: Task::ready(()),
2567 linked_edit_ranges: Default::default(),
2568 in_project_search: false,
2569 previous_search_ranges: None,
2570 breadcrumb_header: None,
2571 focused_block: None,
2572 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2573 addons: HashMap::default(),
2574 registered_buffers: HashMap::default(),
2575 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2576 selection_mark_mode: false,
2577 toggle_fold_multiple_buffers: Task::ready(()),
2578 serialize_selections: Task::ready(()),
2579 serialize_folds: Task::ready(()),
2580 text_style_refinement: None,
2581 load_diff_task: load_uncommitted_diff,
2582 temporary_diff_override: false,
2583 mouse_cursor_hidden: false,
2584 minimap: None,
2585 hide_mouse_mode: EditorSettings::get_global(cx)
2586 .hide_mouse
2587 .unwrap_or_default(),
2588 change_list: ChangeList::new(),
2589 mode,
2590 selection_drag_state: SelectionDragState::None,
2591 folding_newlines: Task::ready(()),
2592 lookup_key: None,
2593 select_next_is_case_sensitive: None,
2594 on_local_selections_changed: None,
2595 suppress_selection_callback: false,
2596 applicable_language_settings: HashMap::default(),
2597 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2598 accent_data: None,
2599 fetched_tree_sitter_chunks: HashMap::default(),
2600 number_deleted_lines: false,
2601 refresh_matching_bracket_highlights_task: Task::ready(()),
2602 refresh_document_symbols_task: Task::ready(()).shared(),
2603 lsp_document_symbols: HashMap::default(),
2604 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2605 outline_symbols_at_cursor: None,
2606 sticky_headers_task: Task::ready(()),
2607 sticky_headers: None,
2608 };
2609
2610 if is_minimap {
2611 return editor;
2612 }
2613
2614 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2615 editor.accent_data = editor.fetch_accent_data(cx);
2616
2617 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2618 editor
2619 ._subscriptions
2620 .push(cx.observe(breakpoints, |_, _, cx| {
2621 cx.notify();
2622 }));
2623 }
2624 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2625 editor._subscriptions.extend(project_subscriptions);
2626
2627 editor._subscriptions.push(cx.subscribe_in(
2628 &cx.entity(),
2629 window,
2630 |editor, _, e: &EditorEvent, window, cx| match e {
2631 EditorEvent::ScrollPositionChanged { local, .. } => {
2632 if *local {
2633 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2634 editor.inline_blame_popover.take();
2635 let snapshot = editor.snapshot(window, cx);
2636 let new_anchor = editor
2637 .scroll_manager
2638 .native_anchor(&snapshot.display_snapshot, cx);
2639 editor.update_restoration_data(cx, move |data| {
2640 data.scroll_position = (
2641 new_anchor.top_row(snapshot.buffer_snapshot()),
2642 new_anchor.offset,
2643 );
2644 });
2645
2646 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2647 cx.background_executor()
2648 .timer(Duration::from_millis(50))
2649 .await;
2650 editor
2651 .update_in(cx, |editor, window, cx| {
2652 editor.register_visible_buffers(cx);
2653 editor.colorize_brackets(false, cx);
2654 editor.refresh_inlay_hints(
2655 InlayHintRefreshReason::NewLinesShown,
2656 cx,
2657 );
2658 if !editor.buffer().read(cx).is_singleton() {
2659 editor.update_lsp_data(None, window, cx);
2660 }
2661 })
2662 .ok();
2663 });
2664 }
2665 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2666 }
2667 EditorEvent::Edited { .. } => {
2668 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2669 .map(|vim_mode| vim_mode.0)
2670 .unwrap_or(false);
2671 if !vim_mode {
2672 let display_map = editor.display_snapshot(cx);
2673 let selections = editor.selections.all_adjusted_display(&display_map);
2674 let pop_state = editor
2675 .change_list
2676 .last()
2677 .map(|previous| {
2678 previous.len() == selections.len()
2679 && previous.iter().enumerate().all(|(ix, p)| {
2680 p.to_display_point(&display_map).row()
2681 == selections[ix].head().row()
2682 })
2683 })
2684 .unwrap_or(false);
2685 let new_positions = selections
2686 .into_iter()
2687 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2688 .collect();
2689 editor
2690 .change_list
2691 .push_to_change_list(pop_state, new_positions);
2692 }
2693 }
2694 _ => (),
2695 },
2696 ));
2697
2698 if let Some(dap_store) = editor
2699 .project
2700 .as_ref()
2701 .map(|project| project.read(cx).dap_store())
2702 {
2703 let weak_editor = cx.weak_entity();
2704
2705 editor
2706 ._subscriptions
2707 .push(
2708 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2709 let session_entity = cx.entity();
2710 weak_editor
2711 .update(cx, |editor, cx| {
2712 editor._subscriptions.push(
2713 cx.subscribe(&session_entity, Self::on_debug_session_event),
2714 );
2715 })
2716 .ok();
2717 }),
2718 );
2719
2720 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2721 editor
2722 ._subscriptions
2723 .push(cx.subscribe(&session, Self::on_debug_session_event));
2724 }
2725 }
2726
2727 // skip adding the initial selection to selection history
2728 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2729 editor.end_selection(window, cx);
2730 editor.selection_history.mode = SelectionHistoryMode::Normal;
2731
2732 editor.scroll_manager.show_scrollbars(window, cx);
2733 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2734
2735 if full_mode {
2736 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2737 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2738
2739 if editor.git_blame_inline_enabled {
2740 editor.start_git_blame_inline(false, window, cx);
2741 }
2742
2743 editor.go_to_active_debug_line(window, cx);
2744
2745 editor.minimap =
2746 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2747 editor.colors = Some(LspColorData::new(cx));
2748 editor.use_document_folding_ranges = true;
2749 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2750
2751 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2752 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2753 }
2754 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2755 }
2756
2757 editor
2758 }
2759
2760 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2761 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2762 }
2763
2764 pub fn deploy_mouse_context_menu(
2765 &mut self,
2766 position: gpui::Point<Pixels>,
2767 context_menu: Entity<ContextMenu>,
2768 window: &mut Window,
2769 cx: &mut Context<Self>,
2770 ) {
2771 self.mouse_context_menu = Some(MouseContextMenu::new(
2772 self,
2773 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2774 context_menu,
2775 window,
2776 cx,
2777 ));
2778 }
2779
2780 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2781 self.mouse_context_menu
2782 .as_ref()
2783 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2784 }
2785
2786 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2787 if self
2788 .selections
2789 .pending_anchor()
2790 .is_some_and(|pending_selection| {
2791 let snapshot = self.buffer().read(cx).snapshot(cx);
2792 pending_selection.range().includes(range, &snapshot)
2793 })
2794 {
2795 return true;
2796 }
2797
2798 self.selections
2799 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2800 .into_iter()
2801 .any(|selection| {
2802 // This is needed to cover a corner case, if we just check for an existing
2803 // selection in the fold range, having a cursor at the start of the fold
2804 // marks it as selected. Non-empty selections don't cause this.
2805 let length = selection.end - selection.start;
2806 length > 0
2807 })
2808 }
2809
2810 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2811 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2812 }
2813
2814 fn key_context_internal(
2815 &self,
2816 has_active_edit_prediction: bool,
2817 window: &mut Window,
2818 cx: &mut App,
2819 ) -> KeyContext {
2820 let mut key_context = KeyContext::new_with_defaults();
2821 key_context.add("Editor");
2822 let mode = match self.mode {
2823 EditorMode::SingleLine => "single_line",
2824 EditorMode::AutoHeight { .. } => "auto_height",
2825 EditorMode::Minimap { .. } => "minimap",
2826 EditorMode::Full { .. } => "full",
2827 };
2828
2829 if EditorSettings::jupyter_enabled(cx) {
2830 key_context.add("jupyter");
2831 }
2832
2833 key_context.set("mode", mode);
2834 if self.pending_rename.is_some() {
2835 key_context.add("renaming");
2836 }
2837
2838 if let Some(snippet_stack) = self.snippet_stack.last() {
2839 key_context.add("in_snippet");
2840
2841 if snippet_stack.active_index > 0 {
2842 key_context.add("has_previous_tabstop");
2843 }
2844
2845 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2846 key_context.add("has_next_tabstop");
2847 }
2848 }
2849
2850 match self.context_menu.borrow().as_ref() {
2851 Some(CodeContextMenu::Completions(menu)) => {
2852 if menu.visible() {
2853 key_context.add("menu");
2854 key_context.add("showing_completions");
2855 }
2856 }
2857 Some(CodeContextMenu::CodeActions(menu)) => {
2858 if menu.visible() {
2859 key_context.add("menu");
2860 key_context.add("showing_code_actions")
2861 }
2862 }
2863 None => {}
2864 }
2865
2866 if self.signature_help_state.has_multiple_signatures() {
2867 key_context.add("showing_signature_help");
2868 }
2869
2870 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2871 if !self.focus_handle(cx).contains_focused(window, cx)
2872 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2873 {
2874 for addon in self.addons.values() {
2875 addon.extend_key_context(&mut key_context, cx)
2876 }
2877 }
2878
2879 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2880 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2881 Some(
2882 file.full_path(cx)
2883 .extension()?
2884 .to_string_lossy()
2885 .to_lowercase(),
2886 )
2887 }) {
2888 key_context.set("extension", extension);
2889 }
2890 } else {
2891 key_context.add("multibuffer");
2892 }
2893
2894 if has_active_edit_prediction {
2895 if self.edit_prediction_in_conflict() {
2896 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2897 } else {
2898 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2899 key_context.add("copilot_suggestion");
2900 }
2901 }
2902
2903 if self.selection_mark_mode {
2904 key_context.add("selection_mode");
2905 }
2906
2907 let disjoint = self.selections.disjoint_anchors();
2908 let snapshot = self.snapshot(window, cx);
2909 let snapshot = snapshot.buffer_snapshot();
2910 if self.mode == EditorMode::SingleLine
2911 && let [selection] = disjoint
2912 && selection.start == selection.end
2913 && selection.end.to_offset(snapshot) == snapshot.len()
2914 {
2915 key_context.add("end_of_input");
2916 }
2917
2918 if self.has_any_expanded_diff_hunks(cx) {
2919 key_context.add("diffs_expanded");
2920 }
2921
2922 key_context
2923 }
2924
2925 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2926 self.last_bounds.as_ref()
2927 }
2928
2929 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2930 if self.mouse_cursor_hidden {
2931 self.mouse_cursor_hidden = false;
2932 cx.notify();
2933 }
2934 }
2935
2936 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2937 let hide_mouse_cursor = match origin {
2938 HideMouseCursorOrigin::TypingAction => {
2939 matches!(
2940 self.hide_mouse_mode,
2941 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2942 )
2943 }
2944 HideMouseCursorOrigin::MovementAction => {
2945 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2946 }
2947 };
2948 if self.mouse_cursor_hidden != hide_mouse_cursor {
2949 self.mouse_cursor_hidden = hide_mouse_cursor;
2950 cx.notify();
2951 }
2952 }
2953
2954 pub fn edit_prediction_in_conflict(&self) -> bool {
2955 if !self.show_edit_predictions_in_menu() {
2956 return false;
2957 }
2958
2959 let showing_completions = self
2960 .context_menu
2961 .borrow()
2962 .as_ref()
2963 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2964
2965 showing_completions
2966 || self.edit_prediction_requires_modifier()
2967 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2968 // bindings to insert tab characters.
2969 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2970 }
2971
2972 pub fn accept_edit_prediction_keybind(
2973 &self,
2974 granularity: EditPredictionGranularity,
2975 window: &mut Window,
2976 cx: &mut App,
2977 ) -> AcceptEditPredictionBinding {
2978 let key_context = self.key_context_internal(true, window, cx);
2979 let in_conflict = self.edit_prediction_in_conflict();
2980
2981 let bindings =
2982 match granularity {
2983 EditPredictionGranularity::Word => window
2984 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2985 EditPredictionGranularity::Line => window
2986 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2987 EditPredictionGranularity::Full => {
2988 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2989 }
2990 };
2991
2992 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2993 !in_conflict
2994 || binding
2995 .keystrokes()
2996 .first()
2997 .is_some_and(|keystroke| keystroke.modifiers().modified())
2998 }))
2999 }
3000
3001 pub fn new_file(
3002 workspace: &mut Workspace,
3003 _: &workspace::NewFile,
3004 window: &mut Window,
3005 cx: &mut Context<Workspace>,
3006 ) {
3007 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3008 "Failed to create buffer",
3009 window,
3010 cx,
3011 |e, _, _| match e.error_code() {
3012 ErrorCode::RemoteUpgradeRequired => Some(format!(
3013 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3014 e.error_tag("required").unwrap_or("the latest version")
3015 )),
3016 _ => None,
3017 },
3018 );
3019 }
3020
3021 pub fn new_in_workspace(
3022 workspace: &mut Workspace,
3023 window: &mut Window,
3024 cx: &mut Context<Workspace>,
3025 ) -> Task<Result<Entity<Editor>>> {
3026 let project = workspace.project().clone();
3027 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3028
3029 cx.spawn_in(window, async move |workspace, cx| {
3030 let buffer = create.await?;
3031 workspace.update_in(cx, |workspace, window, cx| {
3032 let editor =
3033 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3034 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3035 editor
3036 })
3037 })
3038 }
3039
3040 fn new_file_vertical(
3041 workspace: &mut Workspace,
3042 _: &workspace::NewFileSplitVertical,
3043 window: &mut Window,
3044 cx: &mut Context<Workspace>,
3045 ) {
3046 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3047 }
3048
3049 fn new_file_horizontal(
3050 workspace: &mut Workspace,
3051 _: &workspace::NewFileSplitHorizontal,
3052 window: &mut Window,
3053 cx: &mut Context<Workspace>,
3054 ) {
3055 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3056 }
3057
3058 fn new_file_split(
3059 workspace: &mut Workspace,
3060 action: &workspace::NewFileSplit,
3061 window: &mut Window,
3062 cx: &mut Context<Workspace>,
3063 ) {
3064 Self::new_file_in_direction(workspace, action.0, window, cx)
3065 }
3066
3067 fn new_file_in_direction(
3068 workspace: &mut Workspace,
3069 direction: SplitDirection,
3070 window: &mut Window,
3071 cx: &mut Context<Workspace>,
3072 ) {
3073 let project = workspace.project().clone();
3074 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3075
3076 cx.spawn_in(window, async move |workspace, cx| {
3077 let buffer = create.await?;
3078 workspace.update_in(cx, move |workspace, window, cx| {
3079 workspace.split_item(
3080 direction,
3081 Box::new(
3082 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3083 ),
3084 window,
3085 cx,
3086 )
3087 })?;
3088 anyhow::Ok(())
3089 })
3090 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3091 match e.error_code() {
3092 ErrorCode::RemoteUpgradeRequired => Some(format!(
3093 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3094 e.error_tag("required").unwrap_or("the latest version")
3095 )),
3096 _ => None,
3097 }
3098 });
3099 }
3100
3101 pub fn leader_id(&self) -> Option<CollaboratorId> {
3102 self.leader_id
3103 }
3104
3105 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3106 &self.buffer
3107 }
3108
3109 pub fn project(&self) -> Option<&Entity<Project>> {
3110 self.project.as_ref()
3111 }
3112
3113 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3114 self.workspace.as_ref()?.0.upgrade()
3115 }
3116
3117 /// Detaches a task and shows an error notification in the workspace if available,
3118 /// otherwise just logs the error.
3119 pub fn detach_and_notify_err<R, E>(
3120 &self,
3121 task: Task<Result<R, E>>,
3122 window: &mut Window,
3123 cx: &mut App,
3124 ) where
3125 E: std::fmt::Debug + std::fmt::Display + 'static,
3126 R: 'static,
3127 {
3128 if let Some(workspace) = self.workspace() {
3129 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3130 } else {
3131 task.detach_and_log_err(cx);
3132 }
3133 }
3134
3135 /// Returns the workspace serialization ID if this editor should be serialized.
3136 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3137 self.workspace
3138 .as_ref()
3139 .filter(|_| self.should_serialize_buffer())
3140 .and_then(|workspace| workspace.1)
3141 }
3142
3143 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3144 self.buffer().read(cx).title(cx)
3145 }
3146
3147 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3148 let git_blame_gutter_max_author_length = self
3149 .render_git_blame_gutter(cx)
3150 .then(|| {
3151 if let Some(blame) = self.blame.as_ref() {
3152 let max_author_length =
3153 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3154 Some(max_author_length)
3155 } else {
3156 None
3157 }
3158 })
3159 .flatten();
3160
3161 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3162
3163 EditorSnapshot {
3164 mode: self.mode.clone(),
3165 show_gutter: self.show_gutter,
3166 offset_content: self.offset_content,
3167 show_line_numbers: self.show_line_numbers,
3168 number_deleted_lines: self.number_deleted_lines,
3169 show_git_diff_gutter: self.show_git_diff_gutter,
3170 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3171 show_code_actions: self.show_code_actions,
3172 show_runnables: self.show_runnables,
3173 show_breakpoints: self.show_breakpoints,
3174 git_blame_gutter_max_author_length,
3175 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3176 display_snapshot,
3177 placeholder_display_snapshot: self
3178 .placeholder_display_map
3179 .as_ref()
3180 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3181 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3182 is_focused: self.focus_handle.is_focused(window),
3183 current_line_highlight: self
3184 .current_line_highlight
3185 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3186 gutter_hovered: self.gutter_hovered,
3187 }
3188 }
3189
3190 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3191 self.buffer.read(cx).language_at(point, cx)
3192 }
3193
3194 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3195 self.buffer.read(cx).read(cx).file_at(point).cloned()
3196 }
3197
3198 pub fn active_excerpt(
3199 &self,
3200 cx: &App,
3201 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3202 self.buffer
3203 .read(cx)
3204 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3205 }
3206
3207 pub fn mode(&self) -> &EditorMode {
3208 &self.mode
3209 }
3210
3211 pub fn set_mode(&mut self, mode: EditorMode) {
3212 self.mode = mode;
3213 }
3214
3215 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3216 self.collaboration_hub.as_deref()
3217 }
3218
3219 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3220 self.collaboration_hub = Some(hub);
3221 }
3222
3223 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3224 self.in_project_search = in_project_search;
3225 }
3226
3227 pub fn set_custom_context_menu(
3228 &mut self,
3229 f: impl 'static
3230 + Fn(
3231 &mut Self,
3232 DisplayPoint,
3233 &mut Window,
3234 &mut Context<Self>,
3235 ) -> Option<Entity<ui::ContextMenu>>,
3236 ) {
3237 self.custom_context_menu = Some(Box::new(f))
3238 }
3239
3240 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3241 self.completion_provider = provider;
3242 }
3243
3244 #[cfg(any(test, feature = "test-support"))]
3245 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3246 self.completion_provider.clone()
3247 }
3248
3249 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3250 self.semantics_provider.clone()
3251 }
3252
3253 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3254 self.semantics_provider = provider;
3255 }
3256
3257 pub fn set_edit_prediction_provider<T>(
3258 &mut self,
3259 provider: Option<Entity<T>>,
3260 window: &mut Window,
3261 cx: &mut Context<Self>,
3262 ) where
3263 T: EditPredictionDelegate,
3264 {
3265 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3266 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3267 if this.focus_handle.is_focused(window) {
3268 this.update_visible_edit_prediction(window, cx);
3269 }
3270 }),
3271 provider: Arc::new(provider),
3272 });
3273 self.update_edit_prediction_settings(cx);
3274 self.refresh_edit_prediction(false, false, window, cx);
3275 }
3276
3277 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3278 self.placeholder_display_map
3279 .as_ref()
3280 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3281 }
3282
3283 pub fn set_placeholder_text(
3284 &mut self,
3285 placeholder_text: &str,
3286 window: &mut Window,
3287 cx: &mut Context<Self>,
3288 ) {
3289 let multibuffer = cx
3290 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3291
3292 let style = window.text_style();
3293
3294 self.placeholder_display_map = Some(cx.new(|cx| {
3295 DisplayMap::new(
3296 multibuffer,
3297 style.font(),
3298 style.font_size.to_pixels(window.rem_size()),
3299 None,
3300 FILE_HEADER_HEIGHT,
3301 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3302 Default::default(),
3303 DiagnosticSeverity::Off,
3304 cx,
3305 )
3306 }));
3307 cx.notify();
3308 }
3309
3310 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3311 self.cursor_shape = cursor_shape;
3312
3313 // Disrupt blink for immediate user feedback that the cursor shape has changed
3314 self.blink_manager.update(cx, BlinkManager::show_cursor);
3315
3316 cx.notify();
3317 }
3318
3319 pub fn cursor_shape(&self) -> CursorShape {
3320 self.cursor_shape
3321 }
3322
3323 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3324 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3325 }
3326
3327 pub fn set_current_line_highlight(
3328 &mut self,
3329 current_line_highlight: Option<CurrentLineHighlight>,
3330 ) {
3331 self.current_line_highlight = current_line_highlight;
3332 }
3333
3334 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3335 self.collapse_matches = collapse_matches;
3336 }
3337
3338 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3339 if self.collapse_matches {
3340 return range.start..range.start;
3341 }
3342 range.clone()
3343 }
3344
3345 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3346 self.display_map.read(cx).clip_at_line_ends
3347 }
3348
3349 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3350 if self.display_map.read(cx).clip_at_line_ends != clip {
3351 self.display_map
3352 .update(cx, |map, _| map.clip_at_line_ends = clip);
3353 }
3354 }
3355
3356 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3357 self.input_enabled = input_enabled;
3358 }
3359
3360 pub fn set_edit_predictions_hidden_for_vim_mode(
3361 &mut self,
3362 hidden: bool,
3363 window: &mut Window,
3364 cx: &mut Context<Self>,
3365 ) {
3366 if hidden != self.edit_predictions_hidden_for_vim_mode {
3367 self.edit_predictions_hidden_for_vim_mode = hidden;
3368 if hidden {
3369 self.update_visible_edit_prediction(window, cx);
3370 } else {
3371 self.refresh_edit_prediction(true, false, window, cx);
3372 }
3373 }
3374 }
3375
3376 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3377 self.menu_edit_predictions_policy = value;
3378 }
3379
3380 pub fn set_autoindent(&mut self, autoindent: bool) {
3381 if autoindent {
3382 self.autoindent_mode = Some(AutoindentMode::EachLine);
3383 } else {
3384 self.autoindent_mode = None;
3385 }
3386 }
3387
3388 pub fn capability(&self, cx: &App) -> Capability {
3389 if self.read_only {
3390 Capability::ReadOnly
3391 } else {
3392 self.buffer.read(cx).capability()
3393 }
3394 }
3395
3396 pub fn read_only(&self, cx: &App) -> bool {
3397 self.read_only || self.buffer.read(cx).read_only()
3398 }
3399
3400 pub fn set_read_only(&mut self, read_only: bool) {
3401 self.read_only = read_only;
3402 }
3403
3404 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3405 self.use_autoclose = autoclose;
3406 }
3407
3408 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3409 self.use_auto_surround = auto_surround;
3410 }
3411
3412 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3413 self.auto_replace_emoji_shortcode = auto_replace;
3414 }
3415
3416 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3417 self.buffer_serialization = should_serialize.then(|| {
3418 BufferSerialization::new(
3419 ProjectSettings::get_global(cx)
3420 .session
3421 .restore_unsaved_buffers,
3422 )
3423 })
3424 }
3425
3426 fn should_serialize_buffer(&self) -> bool {
3427 self.buffer_serialization.is_some()
3428 }
3429
3430 pub fn toggle_edit_predictions(
3431 &mut self,
3432 _: &ToggleEditPrediction,
3433 window: &mut Window,
3434 cx: &mut Context<Self>,
3435 ) {
3436 if self.show_edit_predictions_override.is_some() {
3437 self.set_show_edit_predictions(None, window, cx);
3438 } else {
3439 let show_edit_predictions = !self.edit_predictions_enabled();
3440 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3441 }
3442 }
3443
3444 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3445 self.show_completions_on_input_override = show_completions_on_input;
3446 }
3447
3448 pub fn set_show_edit_predictions(
3449 &mut self,
3450 show_edit_predictions: Option<bool>,
3451 window: &mut Window,
3452 cx: &mut Context<Self>,
3453 ) {
3454 self.show_edit_predictions_override = show_edit_predictions;
3455 self.update_edit_prediction_settings(cx);
3456
3457 if let Some(false) = show_edit_predictions {
3458 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3459 } else {
3460 self.refresh_edit_prediction(false, true, window, cx);
3461 }
3462 }
3463
3464 fn edit_predictions_disabled_in_scope(
3465 &self,
3466 buffer: &Entity<Buffer>,
3467 buffer_position: language::Anchor,
3468 cx: &App,
3469 ) -> bool {
3470 let snapshot = buffer.read(cx).snapshot();
3471 let settings = snapshot.settings_at(buffer_position, cx);
3472
3473 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3474 return false;
3475 };
3476
3477 scope.override_name().is_some_and(|scope_name| {
3478 settings
3479 .edit_predictions_disabled_in
3480 .iter()
3481 .any(|s| s == scope_name)
3482 })
3483 }
3484
3485 pub fn set_use_modal_editing(&mut self, to: bool) {
3486 self.use_modal_editing = to;
3487 }
3488
3489 pub fn use_modal_editing(&self) -> bool {
3490 self.use_modal_editing
3491 }
3492
3493 fn selections_did_change(
3494 &mut self,
3495 local: bool,
3496 old_cursor_position: &Anchor,
3497 effects: SelectionEffects,
3498 window: &mut Window,
3499 cx: &mut Context<Self>,
3500 ) {
3501 window.invalidate_character_coordinates();
3502
3503 // Copy selections to primary selection buffer
3504 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3505 if local {
3506 let selections = self
3507 .selections
3508 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3509 let buffer_handle = self.buffer.read(cx).read(cx);
3510
3511 let mut text = String::new();
3512 for (index, selection) in selections.iter().enumerate() {
3513 let text_for_selection = buffer_handle
3514 .text_for_range(selection.start..selection.end)
3515 .collect::<String>();
3516
3517 text.push_str(&text_for_selection);
3518 if index != selections.len() - 1 {
3519 text.push('\n');
3520 }
3521 }
3522
3523 if !text.is_empty() {
3524 cx.write_to_primary(ClipboardItem::new_string(text));
3525 }
3526 }
3527
3528 let selection_anchors = self.selections.disjoint_anchors_arc();
3529
3530 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3531 self.buffer.update(cx, |buffer, cx| {
3532 buffer.set_active_selections(
3533 &selection_anchors,
3534 self.selections.line_mode(),
3535 self.cursor_shape,
3536 cx,
3537 )
3538 });
3539 }
3540 let display_map = self
3541 .display_map
3542 .update(cx, |display_map, cx| display_map.snapshot(cx));
3543 let buffer = display_map.buffer_snapshot();
3544 if self.selections.count() == 1 {
3545 self.add_selections_state = None;
3546 }
3547 self.select_next_state = None;
3548 self.select_prev_state = None;
3549 self.select_syntax_node_history.try_clear();
3550 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3551 self.snippet_stack.invalidate(&selection_anchors, buffer);
3552 self.take_rename(false, window, cx);
3553
3554 let newest_selection = self.selections.newest_anchor();
3555 let new_cursor_position = newest_selection.head();
3556 let selection_start = newest_selection.start;
3557
3558 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3559 self.push_to_nav_history(
3560 *old_cursor_position,
3561 Some(new_cursor_position.to_point(buffer)),
3562 false,
3563 effects.nav_history == Some(true),
3564 cx,
3565 );
3566 }
3567
3568 if local {
3569 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3570 self.register_buffer(buffer_id, cx);
3571 }
3572
3573 let mut context_menu = self.context_menu.borrow_mut();
3574 let completion_menu = match context_menu.as_ref() {
3575 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3576 Some(CodeContextMenu::CodeActions(_)) => {
3577 *context_menu = None;
3578 None
3579 }
3580 None => None,
3581 };
3582 let completion_position = completion_menu.map(|menu| menu.initial_position);
3583 drop(context_menu);
3584
3585 if effects.completions
3586 && let Some(completion_position) = completion_position
3587 {
3588 let start_offset = selection_start.to_offset(buffer);
3589 let position_matches = start_offset == completion_position.to_offset(buffer);
3590 let continue_showing = if let Some((snap, ..)) =
3591 buffer.point_to_buffer_offset(completion_position)
3592 && !snap.capability.editable()
3593 {
3594 false
3595 } else if position_matches {
3596 if self.snippet_stack.is_empty() {
3597 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3598 == Some(CharKind::Word)
3599 } else {
3600 // Snippet choices can be shown even when the cursor is in whitespace.
3601 // Dismissing the menu with actions like backspace is handled by
3602 // invalidation regions.
3603 true
3604 }
3605 } else {
3606 false
3607 };
3608
3609 if continue_showing {
3610 self.open_or_update_completions_menu(None, None, false, window, cx);
3611 } else {
3612 self.hide_context_menu(window, cx);
3613 }
3614 }
3615
3616 hide_hover(self, cx);
3617
3618 if old_cursor_position.to_display_point(&display_map).row()
3619 != new_cursor_position.to_display_point(&display_map).row()
3620 {
3621 self.available_code_actions.take();
3622 }
3623 self.refresh_code_actions(window, cx);
3624 self.refresh_document_highlights(cx);
3625 refresh_linked_ranges(self, window, cx);
3626
3627 self.refresh_selected_text_highlights(false, window, cx);
3628 self.refresh_matching_bracket_highlights(&display_map, cx);
3629 self.refresh_outline_symbols_at_cursor(cx);
3630 self.update_visible_edit_prediction(window, cx);
3631 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3632 self.inline_blame_popover.take();
3633 if self.git_blame_inline_enabled {
3634 self.start_inline_blame_timer(window, cx);
3635 }
3636 }
3637
3638 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3639
3640 if local && !self.suppress_selection_callback {
3641 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3642 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3643 callback(cursor_position, window, cx);
3644 }
3645 }
3646
3647 cx.emit(EditorEvent::SelectionsChanged { local });
3648
3649 let selections = &self.selections.disjoint_anchors_arc();
3650 if selections.len() == 1 {
3651 cx.emit(SearchEvent::ActiveMatchChanged)
3652 }
3653 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3654 let inmemory_selections = selections
3655 .iter()
3656 .map(|s| {
3657 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3658 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3659 })
3660 .collect();
3661 self.update_restoration_data(cx, |data| {
3662 data.selections = inmemory_selections;
3663 });
3664
3665 if WorkspaceSettings::get(None, cx).restore_on_startup
3666 != RestoreOnStartupBehavior::EmptyTab
3667 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3668 {
3669 let snapshot = self.buffer().read(cx).snapshot(cx);
3670 let selections = selections.clone();
3671 let background_executor = cx.background_executor().clone();
3672 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3673 self.serialize_selections = cx.background_spawn(async move {
3674 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3675 let db_selections = selections
3676 .iter()
3677 .map(|selection| {
3678 (
3679 selection.start.to_offset(&snapshot).0,
3680 selection.end.to_offset(&snapshot).0,
3681 )
3682 })
3683 .collect();
3684
3685 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3686 .await
3687 .with_context(|| {
3688 format!(
3689 "persisting editor selections for editor {editor_id}, \
3690 workspace {workspace_id:?}"
3691 )
3692 })
3693 .log_err();
3694 });
3695 }
3696 }
3697
3698 cx.notify();
3699 }
3700
3701 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3702 use text::ToOffset as _;
3703 use text::ToPoint as _;
3704
3705 if self.mode.is_minimap()
3706 || WorkspaceSettings::get(None, cx).restore_on_startup
3707 == RestoreOnStartupBehavior::EmptyTab
3708 {
3709 return;
3710 }
3711
3712 if !self.buffer().read(cx).is_singleton() {
3713 return;
3714 }
3715
3716 let display_snapshot = self
3717 .display_map
3718 .update(cx, |display_map, cx| display_map.snapshot(cx));
3719 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3720 return;
3721 };
3722 let inmemory_folds = display_snapshot
3723 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3724 .map(|fold| {
3725 fold.range.start.text_anchor.to_point(&snapshot)
3726 ..fold.range.end.text_anchor.to_point(&snapshot)
3727 })
3728 .collect();
3729 self.update_restoration_data(cx, |data| {
3730 data.folds = inmemory_folds;
3731 });
3732
3733 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3734 return;
3735 };
3736 let background_executor = cx.background_executor().clone();
3737 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3738 const FINGERPRINT_LEN: usize = 32;
3739 let db_folds = display_snapshot
3740 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3741 .map(|fold| {
3742 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3743 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3744
3745 // Extract fingerprints - content at fold boundaries for validation on restore
3746 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3747 // content that might change independently.
3748 // start_fp: first min(32, fold_len) bytes of fold content
3749 // end_fp: last min(32, fold_len) bytes of fold content
3750 // Clip to character boundaries to handle multibyte UTF-8 characters.
3751 let fold_len = end - start;
3752 let start_fp_end = snapshot
3753 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3754 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3755 let end_fp_start = snapshot
3756 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3757 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3758
3759 (start, end, start_fp, end_fp)
3760 })
3761 .collect::<Vec<_>>();
3762 self.serialize_folds = cx.background_spawn(async move {
3763 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3764 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3765 .await
3766 .with_context(|| {
3767 format!(
3768 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3769 )
3770 })
3771 .log_err();
3772 });
3773 }
3774
3775 pub fn sync_selections(
3776 &mut self,
3777 other: Entity<Editor>,
3778 cx: &mut Context<Self>,
3779 ) -> gpui::Subscription {
3780 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3781 if !other_selections.is_empty() {
3782 self.selections
3783 .change_with(&self.display_snapshot(cx), |selections| {
3784 selections.select_anchors(other_selections);
3785 });
3786 }
3787
3788 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3789 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3790 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3791 if other_selections.is_empty() {
3792 return;
3793 }
3794 let snapshot = this.display_snapshot(cx);
3795 this.selections.change_with(&snapshot, |selections| {
3796 selections.select_anchors(other_selections);
3797 });
3798 }
3799 });
3800
3801 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3802 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3803 let these_selections = this.selections.disjoint_anchors().to_vec();
3804 if these_selections.is_empty() {
3805 return;
3806 }
3807 other.update(cx, |other_editor, cx| {
3808 let snapshot = other_editor.display_snapshot(cx);
3809 other_editor
3810 .selections
3811 .change_with(&snapshot, |selections| {
3812 selections.select_anchors(these_selections);
3813 })
3814 });
3815 }
3816 });
3817
3818 Subscription::join(other_subscription, this_subscription)
3819 }
3820
3821 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3822 if self.buffer().read(cx).is_singleton() {
3823 return;
3824 }
3825 let snapshot = self.buffer.read(cx).snapshot(cx);
3826 let buffer_ids: HashSet<BufferId> = self
3827 .selections
3828 .disjoint_anchor_ranges()
3829 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3830 .collect();
3831 for buffer_id in buffer_ids {
3832 self.unfold_buffer(buffer_id, cx);
3833 }
3834 }
3835
3836 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3837 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3838 /// effects of selection change occur at the end of the transaction.
3839 pub fn change_selections<R>(
3840 &mut self,
3841 effects: SelectionEffects,
3842 window: &mut Window,
3843 cx: &mut Context<Self>,
3844 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3845 ) -> R {
3846 let snapshot = self.display_snapshot(cx);
3847 if let Some(state) = &mut self.deferred_selection_effects_state {
3848 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3849 state.effects.completions = effects.completions;
3850 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3851 let (changed, result) = self.selections.change_with(&snapshot, change);
3852 state.changed |= changed;
3853 return result;
3854 }
3855 let mut state = DeferredSelectionEffectsState {
3856 changed: false,
3857 effects,
3858 old_cursor_position: self.selections.newest_anchor().head(),
3859 history_entry: SelectionHistoryEntry {
3860 selections: self.selections.disjoint_anchors_arc(),
3861 select_next_state: self.select_next_state.clone(),
3862 select_prev_state: self.select_prev_state.clone(),
3863 add_selections_state: self.add_selections_state.clone(),
3864 },
3865 };
3866 let (changed, result) = self.selections.change_with(&snapshot, change);
3867 state.changed = state.changed || changed;
3868 if self.defer_selection_effects {
3869 self.deferred_selection_effects_state = Some(state);
3870 } else {
3871 self.apply_selection_effects(state, window, cx);
3872 }
3873 result
3874 }
3875
3876 /// Defers the effects of selection change, so that the effects of multiple calls to
3877 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3878 /// to selection history and the state of popovers based on selection position aren't
3879 /// erroneously updated.
3880 pub fn with_selection_effects_deferred<R>(
3881 &mut self,
3882 window: &mut Window,
3883 cx: &mut Context<Self>,
3884 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3885 ) -> R {
3886 let already_deferred = self.defer_selection_effects;
3887 self.defer_selection_effects = true;
3888 let result = update(self, window, cx);
3889 if !already_deferred {
3890 self.defer_selection_effects = false;
3891 if let Some(state) = self.deferred_selection_effects_state.take() {
3892 self.apply_selection_effects(state, window, cx);
3893 }
3894 }
3895 result
3896 }
3897
3898 fn apply_selection_effects(
3899 &mut self,
3900 state: DeferredSelectionEffectsState,
3901 window: &mut Window,
3902 cx: &mut Context<Self>,
3903 ) {
3904 if state.changed {
3905 self.selection_history.push(state.history_entry);
3906
3907 if let Some(autoscroll) = state.effects.scroll {
3908 self.request_autoscroll(autoscroll, cx);
3909 }
3910
3911 let old_cursor_position = &state.old_cursor_position;
3912
3913 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3914
3915 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3916 self.show_signature_help_auto(window, cx);
3917 }
3918 }
3919 }
3920
3921 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3922 where
3923 I: IntoIterator<Item = (Range<S>, T)>,
3924 S: ToOffset,
3925 T: Into<Arc<str>>,
3926 {
3927 if self.read_only(cx) {
3928 return;
3929 }
3930
3931 self.buffer
3932 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3933 }
3934
3935 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3936 where
3937 I: IntoIterator<Item = (Range<S>, T)>,
3938 S: ToOffset,
3939 T: Into<Arc<str>>,
3940 {
3941 if self.read_only(cx) {
3942 return;
3943 }
3944
3945 self.buffer.update(cx, |buffer, cx| {
3946 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3947 });
3948 }
3949
3950 pub fn edit_with_block_indent<I, S, T>(
3951 &mut self,
3952 edits: I,
3953 original_indent_columns: Vec<Option<u32>>,
3954 cx: &mut Context<Self>,
3955 ) where
3956 I: IntoIterator<Item = (Range<S>, T)>,
3957 S: ToOffset,
3958 T: Into<Arc<str>>,
3959 {
3960 if self.read_only(cx) {
3961 return;
3962 }
3963
3964 self.buffer.update(cx, |buffer, cx| {
3965 buffer.edit(
3966 edits,
3967 Some(AutoindentMode::Block {
3968 original_indent_columns,
3969 }),
3970 cx,
3971 )
3972 });
3973 }
3974
3975 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3976 self.hide_context_menu(window, cx);
3977
3978 match phase {
3979 SelectPhase::Begin {
3980 position,
3981 add,
3982 click_count,
3983 } => self.begin_selection(position, add, click_count, window, cx),
3984 SelectPhase::BeginColumnar {
3985 position,
3986 goal_column,
3987 reset,
3988 mode,
3989 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3990 SelectPhase::Extend {
3991 position,
3992 click_count,
3993 } => self.extend_selection(position, click_count, window, cx),
3994 SelectPhase::Update {
3995 position,
3996 goal_column,
3997 scroll_delta,
3998 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3999 SelectPhase::End => self.end_selection(window, cx),
4000 }
4001 }
4002
4003 fn extend_selection(
4004 &mut self,
4005 position: DisplayPoint,
4006 click_count: usize,
4007 window: &mut Window,
4008 cx: &mut Context<Self>,
4009 ) {
4010 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4011 let tail = self
4012 .selections
4013 .newest::<MultiBufferOffset>(&display_map)
4014 .tail();
4015 let click_count = click_count.max(match self.selections.select_mode() {
4016 SelectMode::Character => 1,
4017 SelectMode::Word(_) => 2,
4018 SelectMode::Line(_) => 3,
4019 SelectMode::All => 4,
4020 });
4021 self.begin_selection(position, false, click_count, window, cx);
4022
4023 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4024
4025 let current_selection = match self.selections.select_mode() {
4026 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4027 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4028 };
4029
4030 let mut pending_selection = self
4031 .selections
4032 .pending_anchor()
4033 .cloned()
4034 .expect("extend_selection not called with pending selection");
4035
4036 if pending_selection
4037 .start
4038 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4039 == Ordering::Greater
4040 {
4041 pending_selection.start = current_selection.start;
4042 }
4043 if pending_selection
4044 .end
4045 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4046 == Ordering::Less
4047 {
4048 pending_selection.end = current_selection.end;
4049 pending_selection.reversed = true;
4050 }
4051
4052 let mut pending_mode = self.selections.pending_mode().unwrap();
4053 match &mut pending_mode {
4054 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4055 _ => {}
4056 }
4057
4058 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4059 SelectionEffects::scroll(Autoscroll::fit())
4060 } else {
4061 SelectionEffects::no_scroll()
4062 };
4063
4064 self.change_selections(effects, window, cx, |s| {
4065 s.set_pending(pending_selection.clone(), pending_mode);
4066 s.set_is_extending(true);
4067 });
4068 }
4069
4070 fn begin_selection(
4071 &mut self,
4072 position: DisplayPoint,
4073 add: bool,
4074 click_count: usize,
4075 window: &mut Window,
4076 cx: &mut Context<Self>,
4077 ) {
4078 if !self.focus_handle.is_focused(window) {
4079 self.last_focused_descendant = None;
4080 window.focus(&self.focus_handle, cx);
4081 }
4082
4083 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4084 let buffer = display_map.buffer_snapshot();
4085 let position = display_map.clip_point(position, Bias::Left);
4086
4087 let start;
4088 let end;
4089 let mode;
4090 let mut auto_scroll;
4091 match click_count {
4092 1 => {
4093 start = buffer.anchor_before(position.to_point(&display_map));
4094 end = start;
4095 mode = SelectMode::Character;
4096 auto_scroll = true;
4097 }
4098 2 => {
4099 let position = display_map
4100 .clip_point(position, Bias::Left)
4101 .to_offset(&display_map, Bias::Left);
4102 let (range, _) = buffer.surrounding_word(position, None);
4103 start = buffer.anchor_before(range.start);
4104 end = buffer.anchor_before(range.end);
4105 mode = SelectMode::Word(start..end);
4106 auto_scroll = true;
4107 }
4108 3 => {
4109 let position = display_map
4110 .clip_point(position, Bias::Left)
4111 .to_point(&display_map);
4112 let line_start = display_map.prev_line_boundary(position).0;
4113 let next_line_start = buffer.clip_point(
4114 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4115 Bias::Left,
4116 );
4117 start = buffer.anchor_before(line_start);
4118 end = buffer.anchor_before(next_line_start);
4119 mode = SelectMode::Line(start..end);
4120 auto_scroll = true;
4121 }
4122 _ => {
4123 start = buffer.anchor_before(MultiBufferOffset(0));
4124 end = buffer.anchor_before(buffer.len());
4125 mode = SelectMode::All;
4126 auto_scroll = false;
4127 }
4128 }
4129 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4130
4131 let point_to_delete: Option<usize> = {
4132 let selected_points: Vec<Selection<Point>> =
4133 self.selections.disjoint_in_range(start..end, &display_map);
4134
4135 if !add || click_count > 1 {
4136 None
4137 } else if !selected_points.is_empty() {
4138 Some(selected_points[0].id)
4139 } else {
4140 let clicked_point_already_selected =
4141 self.selections.disjoint_anchors().iter().find(|selection| {
4142 selection.start.to_point(buffer) == start.to_point(buffer)
4143 || selection.end.to_point(buffer) == end.to_point(buffer)
4144 });
4145
4146 clicked_point_already_selected.map(|selection| selection.id)
4147 }
4148 };
4149
4150 let selections_count = self.selections.count();
4151 let effects = if auto_scroll {
4152 SelectionEffects::default()
4153 } else {
4154 SelectionEffects::no_scroll()
4155 };
4156
4157 self.change_selections(effects, window, cx, |s| {
4158 if let Some(point_to_delete) = point_to_delete {
4159 s.delete(point_to_delete);
4160
4161 if selections_count == 1 {
4162 s.set_pending_anchor_range(start..end, mode);
4163 }
4164 } else {
4165 if !add {
4166 s.clear_disjoint();
4167 }
4168
4169 s.set_pending_anchor_range(start..end, mode);
4170 }
4171 });
4172 }
4173
4174 fn begin_columnar_selection(
4175 &mut self,
4176 position: DisplayPoint,
4177 goal_column: u32,
4178 reset: bool,
4179 mode: ColumnarMode,
4180 window: &mut Window,
4181 cx: &mut Context<Self>,
4182 ) {
4183 if !self.focus_handle.is_focused(window) {
4184 self.last_focused_descendant = None;
4185 window.focus(&self.focus_handle, cx);
4186 }
4187
4188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4189
4190 if reset {
4191 let pointer_position = display_map
4192 .buffer_snapshot()
4193 .anchor_before(position.to_point(&display_map));
4194
4195 self.change_selections(
4196 SelectionEffects::scroll(Autoscroll::newest()),
4197 window,
4198 cx,
4199 |s| {
4200 s.clear_disjoint();
4201 s.set_pending_anchor_range(
4202 pointer_position..pointer_position,
4203 SelectMode::Character,
4204 );
4205 },
4206 );
4207 };
4208
4209 let tail = self.selections.newest::<Point>(&display_map).tail();
4210 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4211 self.columnar_selection_state = match mode {
4212 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4213 selection_tail: selection_anchor,
4214 display_point: if reset {
4215 if position.column() != goal_column {
4216 Some(DisplayPoint::new(position.row(), goal_column))
4217 } else {
4218 None
4219 }
4220 } else {
4221 None
4222 },
4223 }),
4224 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4225 selection_tail: selection_anchor,
4226 }),
4227 };
4228
4229 if !reset {
4230 self.select_columns(position, goal_column, &display_map, window, cx);
4231 }
4232 }
4233
4234 fn update_selection(
4235 &mut self,
4236 position: DisplayPoint,
4237 goal_column: u32,
4238 scroll_delta: gpui::Point<f32>,
4239 window: &mut Window,
4240 cx: &mut Context<Self>,
4241 ) {
4242 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4243
4244 if self.columnar_selection_state.is_some() {
4245 self.select_columns(position, goal_column, &display_map, window, cx);
4246 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4247 let buffer = display_map.buffer_snapshot();
4248 let head;
4249 let tail;
4250 let mode = self.selections.pending_mode().unwrap();
4251 match &mode {
4252 SelectMode::Character => {
4253 head = position.to_point(&display_map);
4254 tail = pending.tail().to_point(buffer);
4255 }
4256 SelectMode::Word(original_range) => {
4257 let offset = display_map
4258 .clip_point(position, Bias::Left)
4259 .to_offset(&display_map, Bias::Left);
4260 let original_range = original_range.to_offset(buffer);
4261
4262 let head_offset = if buffer.is_inside_word(offset, None)
4263 || original_range.contains(&offset)
4264 {
4265 let (word_range, _) = buffer.surrounding_word(offset, None);
4266 if word_range.start < original_range.start {
4267 word_range.start
4268 } else {
4269 word_range.end
4270 }
4271 } else {
4272 offset
4273 };
4274
4275 head = head_offset.to_point(buffer);
4276 if head_offset <= original_range.start {
4277 tail = original_range.end.to_point(buffer);
4278 } else {
4279 tail = original_range.start.to_point(buffer);
4280 }
4281 }
4282 SelectMode::Line(original_range) => {
4283 let original_range = original_range.to_point(display_map.buffer_snapshot());
4284
4285 let position = display_map
4286 .clip_point(position, Bias::Left)
4287 .to_point(&display_map);
4288 let line_start = display_map.prev_line_boundary(position).0;
4289 let next_line_start = buffer.clip_point(
4290 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4291 Bias::Left,
4292 );
4293
4294 if line_start < original_range.start {
4295 head = line_start
4296 } else {
4297 head = next_line_start
4298 }
4299
4300 if head <= original_range.start {
4301 tail = original_range.end;
4302 } else {
4303 tail = original_range.start;
4304 }
4305 }
4306 SelectMode::All => {
4307 return;
4308 }
4309 };
4310
4311 if head < tail {
4312 pending.start = buffer.anchor_before(head);
4313 pending.end = buffer.anchor_before(tail);
4314 pending.reversed = true;
4315 } else {
4316 pending.start = buffer.anchor_before(tail);
4317 pending.end = buffer.anchor_before(head);
4318 pending.reversed = false;
4319 }
4320
4321 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4322 s.set_pending(pending.clone(), mode);
4323 });
4324 } else {
4325 log::error!("update_selection dispatched with no pending selection");
4326 return;
4327 }
4328
4329 self.apply_scroll_delta(scroll_delta, window, cx);
4330 cx.notify();
4331 }
4332
4333 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4334 self.columnar_selection_state.take();
4335 if let Some(pending_mode) = self.selections.pending_mode() {
4336 let selections = self
4337 .selections
4338 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4339 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4340 s.select(selections);
4341 s.clear_pending();
4342 if s.is_extending() {
4343 s.set_is_extending(false);
4344 } else {
4345 s.set_select_mode(pending_mode);
4346 }
4347 });
4348 }
4349 }
4350
4351 fn select_columns(
4352 &mut self,
4353 head: DisplayPoint,
4354 goal_column: u32,
4355 display_map: &DisplaySnapshot,
4356 window: &mut Window,
4357 cx: &mut Context<Self>,
4358 ) {
4359 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4360 return;
4361 };
4362
4363 let tail = match columnar_state {
4364 ColumnarSelectionState::FromMouse {
4365 selection_tail,
4366 display_point,
4367 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4368 ColumnarSelectionState::FromSelection { selection_tail } => {
4369 selection_tail.to_display_point(display_map)
4370 }
4371 };
4372
4373 let start_row = cmp::min(tail.row(), head.row());
4374 let end_row = cmp::max(tail.row(), head.row());
4375 let start_column = cmp::min(tail.column(), goal_column);
4376 let end_column = cmp::max(tail.column(), goal_column);
4377 let reversed = start_column < tail.column();
4378
4379 let selection_ranges = (start_row.0..=end_row.0)
4380 .map(DisplayRow)
4381 .filter_map(|row| {
4382 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4383 || start_column <= display_map.line_len(row))
4384 && !display_map.is_block_line(row)
4385 {
4386 let start = display_map
4387 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4388 .to_point(display_map);
4389 let end = display_map
4390 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4391 .to_point(display_map);
4392 if reversed {
4393 Some(end..start)
4394 } else {
4395 Some(start..end)
4396 }
4397 } else {
4398 None
4399 }
4400 })
4401 .collect::<Vec<_>>();
4402 if selection_ranges.is_empty() {
4403 return;
4404 }
4405
4406 let ranges = match columnar_state {
4407 ColumnarSelectionState::FromMouse { .. } => {
4408 let mut non_empty_ranges = selection_ranges
4409 .iter()
4410 .filter(|selection_range| selection_range.start != selection_range.end)
4411 .peekable();
4412 if non_empty_ranges.peek().is_some() {
4413 non_empty_ranges.cloned().collect()
4414 } else {
4415 selection_ranges
4416 }
4417 }
4418 _ => selection_ranges,
4419 };
4420
4421 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4422 s.select_ranges(ranges);
4423 });
4424 cx.notify();
4425 }
4426
4427 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4428 self.selections
4429 .all_adjusted(snapshot)
4430 .iter()
4431 .any(|selection| !selection.is_empty())
4432 }
4433
4434 pub fn has_pending_nonempty_selection(&self) -> bool {
4435 let pending_nonempty_selection = match self.selections.pending_anchor() {
4436 Some(Selection { start, end, .. }) => start != end,
4437 None => false,
4438 };
4439
4440 pending_nonempty_selection
4441 || (self.columnar_selection_state.is_some()
4442 && self.selections.disjoint_anchors().len() > 1)
4443 }
4444
4445 pub fn has_pending_selection(&self) -> bool {
4446 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4447 }
4448
4449 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4450 self.selection_mark_mode = false;
4451 self.selection_drag_state = SelectionDragState::None;
4452
4453 if self.dismiss_menus_and_popups(true, window, cx) {
4454 cx.notify();
4455 return;
4456 }
4457 if self.clear_expanded_diff_hunks(cx) {
4458 cx.notify();
4459 return;
4460 }
4461 if self.show_git_blame_gutter {
4462 self.show_git_blame_gutter = false;
4463 cx.notify();
4464 return;
4465 }
4466
4467 if self.mode.is_full()
4468 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4469 {
4470 cx.notify();
4471 return;
4472 }
4473
4474 cx.propagate();
4475 }
4476
4477 pub fn dismiss_menus_and_popups(
4478 &mut self,
4479 is_user_requested: bool,
4480 window: &mut Window,
4481 cx: &mut Context<Self>,
4482 ) -> bool {
4483 let mut dismissed = false;
4484
4485 dismissed |= self.take_rename(false, window, cx).is_some();
4486 dismissed |= self.hide_blame_popover(true, cx);
4487 dismissed |= hide_hover(self, cx);
4488 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4489 dismissed |= self.hide_context_menu(window, cx).is_some();
4490 dismissed |= self.mouse_context_menu.take().is_some();
4491 dismissed |= is_user_requested
4492 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4493 dismissed |= self.snippet_stack.pop().is_some();
4494 if self.diff_review_drag_state.is_some() {
4495 self.cancel_diff_review_drag(cx);
4496 dismissed = true;
4497 }
4498 if !self.diff_review_overlays.is_empty() {
4499 self.dismiss_all_diff_review_overlays(cx);
4500 dismissed = true;
4501 }
4502
4503 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4504 self.dismiss_diagnostics(cx);
4505 dismissed = true;
4506 }
4507
4508 dismissed
4509 }
4510
4511 fn linked_editing_ranges_for(
4512 &self,
4513 selection: Range<text::Anchor>,
4514 cx: &App,
4515 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4516 if self.linked_edit_ranges.is_empty() {
4517 return None;
4518 }
4519 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4520 selection.end.buffer_id.and_then(|end_buffer_id| {
4521 if selection.start.buffer_id != Some(end_buffer_id) {
4522 return None;
4523 }
4524 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4525 let snapshot = buffer.read(cx).snapshot();
4526 self.linked_edit_ranges
4527 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4528 .map(|ranges| (ranges, snapshot, buffer))
4529 })?;
4530 use text::ToOffset as TO;
4531 // find offset from the start of current range to current cursor position
4532 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4533
4534 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4535 let start_difference = start_offset - start_byte_offset;
4536 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4537 let end_difference = end_offset - start_byte_offset;
4538 // Current range has associated linked ranges.
4539 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4540 for range in linked_ranges.iter() {
4541 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4542 let end_offset = start_offset + end_difference;
4543 let start_offset = start_offset + start_difference;
4544 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4545 continue;
4546 }
4547 if self.selections.disjoint_anchor_ranges().any(|s| {
4548 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4549 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4550 {
4551 return false;
4552 }
4553 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4554 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4555 }) {
4556 continue;
4557 }
4558 let start = buffer_snapshot.anchor_after(start_offset);
4559 let end = buffer_snapshot.anchor_after(end_offset);
4560 linked_edits
4561 .entry(buffer.clone())
4562 .or_default()
4563 .push(start..end);
4564 }
4565 Some(linked_edits)
4566 }
4567
4568 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4569 let text: Arc<str> = text.into();
4570
4571 if self.read_only(cx) {
4572 return;
4573 }
4574
4575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4576
4577 self.unfold_buffers_with_selections(cx);
4578
4579 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4580 let mut bracket_inserted = false;
4581 let mut edits = Vec::new();
4582 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4583 let mut new_selections = Vec::with_capacity(selections.len());
4584 let mut new_autoclose_regions = Vec::new();
4585 let snapshot = self.buffer.read(cx).read(cx);
4586 let mut clear_linked_edit_ranges = false;
4587 let mut all_selections_read_only = true;
4588 let mut has_adjacent_edits = false;
4589 let mut in_adjacent_group = false;
4590
4591 let mut regions = self
4592 .selections_with_autoclose_regions(selections, &snapshot)
4593 .peekable();
4594
4595 while let Some((selection, autoclose_region)) = regions.next() {
4596 if snapshot
4597 .point_to_buffer_point(selection.head())
4598 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4599 {
4600 continue;
4601 }
4602 if snapshot
4603 .point_to_buffer_point(selection.tail())
4604 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4605 {
4606 // note, ideally we'd clip the tail to the closest writeable region towards the head
4607 continue;
4608 }
4609 all_selections_read_only = false;
4610
4611 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4612 // Determine if the inserted text matches the opening or closing
4613 // bracket of any of this language's bracket pairs.
4614 let mut bracket_pair = None;
4615 let mut is_bracket_pair_start = false;
4616 let mut is_bracket_pair_end = false;
4617 if !text.is_empty() {
4618 let mut bracket_pair_matching_end = None;
4619 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4620 // and they are removing the character that triggered IME popup.
4621 for (pair, enabled) in scope.brackets() {
4622 if !pair.close && !pair.surround {
4623 continue;
4624 }
4625
4626 if enabled && pair.start.ends_with(text.as_ref()) {
4627 let prefix_len = pair.start.len() - text.len();
4628 let preceding_text_matches_prefix = prefix_len == 0
4629 || (selection.start.column >= (prefix_len as u32)
4630 && snapshot.contains_str_at(
4631 Point::new(
4632 selection.start.row,
4633 selection.start.column - (prefix_len as u32),
4634 ),
4635 &pair.start[..prefix_len],
4636 ));
4637 if preceding_text_matches_prefix {
4638 bracket_pair = Some(pair.clone());
4639 is_bracket_pair_start = true;
4640 break;
4641 }
4642 }
4643 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4644 {
4645 // take first bracket pair matching end, but don't break in case a later bracket
4646 // pair matches start
4647 bracket_pair_matching_end = Some(pair.clone());
4648 }
4649 }
4650 if let Some(end) = bracket_pair_matching_end
4651 && bracket_pair.is_none()
4652 {
4653 bracket_pair = Some(end);
4654 is_bracket_pair_end = true;
4655 }
4656 }
4657
4658 if let Some(bracket_pair) = bracket_pair {
4659 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4660 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4661 let auto_surround =
4662 self.use_auto_surround && snapshot_settings.use_auto_surround;
4663 if selection.is_empty() {
4664 if is_bracket_pair_start {
4665 // If the inserted text is a suffix of an opening bracket and the
4666 // selection is preceded by the rest of the opening bracket, then
4667 // insert the closing bracket.
4668 let following_text_allows_autoclose = snapshot
4669 .chars_at(selection.start)
4670 .next()
4671 .is_none_or(|c| scope.should_autoclose_before(c));
4672
4673 let preceding_text_allows_autoclose = selection.start.column == 0
4674 || snapshot
4675 .reversed_chars_at(selection.start)
4676 .next()
4677 .is_none_or(|c| {
4678 bracket_pair.start != bracket_pair.end
4679 || !snapshot
4680 .char_classifier_at(selection.start)
4681 .is_word(c)
4682 });
4683
4684 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4685 && bracket_pair.start.len() == 1
4686 {
4687 let target = bracket_pair.start.chars().next().unwrap();
4688 let mut byte_offset = 0u32;
4689 let current_line_count = snapshot
4690 .reversed_chars_at(selection.start)
4691 .take_while(|&c| c != '\n')
4692 .filter(|c| {
4693 byte_offset += c.len_utf8() as u32;
4694 if *c != target {
4695 return false;
4696 }
4697
4698 let point = Point::new(
4699 selection.start.row,
4700 selection.start.column.saturating_sub(byte_offset),
4701 );
4702
4703 let is_enabled = snapshot
4704 .language_scope_at(point)
4705 .and_then(|scope| {
4706 scope
4707 .brackets()
4708 .find(|(pair, _)| {
4709 pair.start == bracket_pair.start
4710 })
4711 .map(|(_, enabled)| enabled)
4712 })
4713 .unwrap_or(true);
4714
4715 let is_delimiter = snapshot
4716 .language_scope_at(Point::new(
4717 point.row,
4718 point.column + 1,
4719 ))
4720 .and_then(|scope| {
4721 scope
4722 .brackets()
4723 .find(|(pair, _)| {
4724 pair.start == bracket_pair.start
4725 })
4726 .map(|(_, enabled)| !enabled)
4727 })
4728 .unwrap_or(false);
4729
4730 is_enabled && !is_delimiter
4731 })
4732 .count();
4733 current_line_count % 2 == 1
4734 } else {
4735 false
4736 };
4737
4738 if autoclose
4739 && bracket_pair.close
4740 && following_text_allows_autoclose
4741 && preceding_text_allows_autoclose
4742 && !is_closing_quote
4743 {
4744 let anchor = snapshot.anchor_before(selection.end);
4745 new_selections.push((selection.map(|_| anchor), text.len()));
4746 new_autoclose_regions.push((
4747 anchor,
4748 text.len(),
4749 selection.id,
4750 bracket_pair.clone(),
4751 ));
4752 edits.push((
4753 selection.range(),
4754 format!("{}{}", text, bracket_pair.end).into(),
4755 ));
4756 bracket_inserted = true;
4757 continue;
4758 }
4759 }
4760
4761 if let Some(region) = autoclose_region {
4762 // If the selection is followed by an auto-inserted closing bracket,
4763 // then don't insert that closing bracket again; just move the selection
4764 // past the closing bracket.
4765 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4766 && text.as_ref() == region.pair.end.as_str()
4767 && snapshot.contains_str_at(region.range.end, text.as_ref());
4768 if should_skip {
4769 let anchor = snapshot.anchor_after(selection.end);
4770 new_selections
4771 .push((selection.map(|_| anchor), region.pair.end.len()));
4772 continue;
4773 }
4774 }
4775
4776 let always_treat_brackets_as_autoclosed = snapshot
4777 .language_settings_at(selection.start, cx)
4778 .always_treat_brackets_as_autoclosed;
4779 if always_treat_brackets_as_autoclosed
4780 && is_bracket_pair_end
4781 && snapshot.contains_str_at(selection.end, text.as_ref())
4782 {
4783 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4784 // and the inserted text is a closing bracket and the selection is followed
4785 // by the closing bracket then move the selection past the closing bracket.
4786 let anchor = snapshot.anchor_after(selection.end);
4787 new_selections.push((selection.map(|_| anchor), text.len()));
4788 continue;
4789 }
4790 }
4791 // If an opening bracket is 1 character long and is typed while
4792 // text is selected, then surround that text with the bracket pair.
4793 else if auto_surround
4794 && bracket_pair.surround
4795 && is_bracket_pair_start
4796 && bracket_pair.start.chars().count() == 1
4797 {
4798 edits.push((selection.start..selection.start, text.clone()));
4799 edits.push((
4800 selection.end..selection.end,
4801 bracket_pair.end.as_str().into(),
4802 ));
4803 bracket_inserted = true;
4804 new_selections.push((
4805 Selection {
4806 id: selection.id,
4807 start: snapshot.anchor_after(selection.start),
4808 end: snapshot.anchor_before(selection.end),
4809 reversed: selection.reversed,
4810 goal: selection.goal,
4811 },
4812 0,
4813 ));
4814 continue;
4815 }
4816 }
4817 }
4818
4819 if self.auto_replace_emoji_shortcode
4820 && selection.is_empty()
4821 && text.as_ref().ends_with(':')
4822 && let Some(possible_emoji_short_code) =
4823 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4824 && !possible_emoji_short_code.is_empty()
4825 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4826 {
4827 let emoji_shortcode_start = Point::new(
4828 selection.start.row,
4829 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4830 );
4831
4832 // Remove shortcode from buffer
4833 edits.push((
4834 emoji_shortcode_start..selection.start,
4835 "".to_string().into(),
4836 ));
4837 new_selections.push((
4838 Selection {
4839 id: selection.id,
4840 start: snapshot.anchor_after(emoji_shortcode_start),
4841 end: snapshot.anchor_before(selection.start),
4842 reversed: selection.reversed,
4843 goal: selection.goal,
4844 },
4845 0,
4846 ));
4847
4848 // Insert emoji
4849 let selection_start_anchor = snapshot.anchor_after(selection.start);
4850 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4851 edits.push((selection.start..selection.end, emoji.to_string().into()));
4852
4853 continue;
4854 }
4855
4856 let next_is_adjacent = regions
4857 .peek()
4858 .is_some_and(|(next, _)| selection.end == next.start);
4859
4860 // If not handling any auto-close operation, then just replace the selected
4861 // text with the given input and move the selection to the end of the
4862 // newly inserted text.
4863 let anchor = if in_adjacent_group || next_is_adjacent {
4864 // After edits the right bias would shift those anchor to the next visible fragment
4865 // but we want to resolve to the previous one
4866 snapshot.anchor_before(selection.end)
4867 } else {
4868 snapshot.anchor_after(selection.end)
4869 };
4870
4871 if !self.linked_edit_ranges.is_empty() {
4872 let start_anchor = snapshot.anchor_before(selection.start);
4873
4874 let is_word_char = text.chars().next().is_none_or(|char| {
4875 let classifier = snapshot
4876 .char_classifier_at(start_anchor.to_offset(&snapshot))
4877 .scope_context(Some(CharScopeContext::LinkedEdit));
4878 classifier.is_word(char)
4879 });
4880
4881 if is_word_char {
4882 if let Some(ranges) = self
4883 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4884 {
4885 for (buffer, edits) in ranges {
4886 linked_edits
4887 .entry(buffer.clone())
4888 .or_default()
4889 .extend(edits.into_iter().map(|range| (range, text.clone())));
4890 }
4891 }
4892 } else {
4893 clear_linked_edit_ranges = true;
4894 }
4895 }
4896
4897 new_selections.push((selection.map(|_| anchor), 0));
4898 edits.push((selection.start..selection.end, text.clone()));
4899
4900 has_adjacent_edits |= next_is_adjacent;
4901 in_adjacent_group = next_is_adjacent;
4902 }
4903
4904 if all_selections_read_only {
4905 return;
4906 }
4907
4908 drop(regions);
4909 drop(snapshot);
4910
4911 self.transact(window, cx, |this, window, cx| {
4912 if clear_linked_edit_ranges {
4913 this.linked_edit_ranges.clear();
4914 }
4915 let initial_buffer_versions =
4916 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4917
4918 this.buffer.update(cx, |buffer, cx| {
4919 if has_adjacent_edits {
4920 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4921 } else {
4922 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4923 }
4924 });
4925 for (buffer, edits) in linked_edits {
4926 buffer.update(cx, |buffer, cx| {
4927 let snapshot = buffer.snapshot();
4928 let edits = edits
4929 .into_iter()
4930 .map(|(range, text)| {
4931 use text::ToPoint as TP;
4932 let end_point = TP::to_point(&range.end, &snapshot);
4933 let start_point = TP::to_point(&range.start, &snapshot);
4934 (start_point..end_point, text)
4935 })
4936 .sorted_by_key(|(range, _)| range.start);
4937 buffer.edit(edits, None, cx);
4938 })
4939 }
4940 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4941 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4942 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4943 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4944 new_anchor_selections,
4945 &map,
4946 )
4947 .zip(new_selection_deltas)
4948 .map(|(selection, delta)| Selection {
4949 id: selection.id,
4950 start: selection.start + delta,
4951 end: selection.end + delta,
4952 reversed: selection.reversed,
4953 goal: SelectionGoal::None,
4954 })
4955 .collect::<Vec<_>>();
4956
4957 let mut i = 0;
4958 for (position, delta, selection_id, pair) in new_autoclose_regions {
4959 let position = position.to_offset(map.buffer_snapshot()) + delta;
4960 let start = map.buffer_snapshot().anchor_before(position);
4961 let end = map.buffer_snapshot().anchor_after(position);
4962 while let Some(existing_state) = this.autoclose_regions.get(i) {
4963 match existing_state
4964 .range
4965 .start
4966 .cmp(&start, map.buffer_snapshot())
4967 {
4968 Ordering::Less => i += 1,
4969 Ordering::Greater => break,
4970 Ordering::Equal => {
4971 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4972 Ordering::Less => i += 1,
4973 Ordering::Equal => break,
4974 Ordering::Greater => break,
4975 }
4976 }
4977 }
4978 }
4979 this.autoclose_regions.insert(
4980 i,
4981 AutocloseRegion {
4982 selection_id,
4983 range: start..end,
4984 pair,
4985 },
4986 );
4987 }
4988
4989 let had_active_edit_prediction = this.has_active_edit_prediction();
4990 this.change_selections(
4991 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4992 window,
4993 cx,
4994 |s| s.select(new_selections),
4995 );
4996
4997 if !bracket_inserted
4998 && let Some(on_type_format_task) =
4999 this.trigger_on_type_formatting(text.to_string(), window, cx)
5000 {
5001 on_type_format_task.detach_and_log_err(cx);
5002 }
5003
5004 let editor_settings = EditorSettings::get_global(cx);
5005 if bracket_inserted
5006 && (editor_settings.auto_signature_help
5007 || editor_settings.show_signature_help_after_edits)
5008 {
5009 this.show_signature_help(&ShowSignatureHelp, window, cx);
5010 }
5011
5012 let trigger_in_words =
5013 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5014 if this.hard_wrap.is_some() {
5015 let latest: Range<Point> = this.selections.newest(&map).range();
5016 if latest.is_empty()
5017 && this
5018 .buffer()
5019 .read(cx)
5020 .snapshot(cx)
5021 .line_len(MultiBufferRow(latest.start.row))
5022 == latest.start.column
5023 {
5024 this.rewrap_impl(
5025 RewrapOptions {
5026 override_language_settings: true,
5027 preserve_existing_whitespace: true,
5028 },
5029 cx,
5030 )
5031 }
5032 }
5033 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5034 refresh_linked_ranges(this, window, cx);
5035 this.refresh_edit_prediction(true, false, window, cx);
5036 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5037 });
5038 }
5039
5040 fn find_possible_emoji_shortcode_at_position(
5041 snapshot: &MultiBufferSnapshot,
5042 position: Point,
5043 ) -> Option<String> {
5044 let mut chars = Vec::new();
5045 let mut found_colon = false;
5046 for char in snapshot.reversed_chars_at(position).take(100) {
5047 // Found a possible emoji shortcode in the middle of the buffer
5048 if found_colon {
5049 if char.is_whitespace() {
5050 chars.reverse();
5051 return Some(chars.iter().collect());
5052 }
5053 // If the previous character is not a whitespace, we are in the middle of a word
5054 // and we only want to complete the shortcode if the word is made up of other emojis
5055 let mut containing_word = String::new();
5056 for ch in snapshot
5057 .reversed_chars_at(position)
5058 .skip(chars.len() + 1)
5059 .take(100)
5060 {
5061 if ch.is_whitespace() {
5062 break;
5063 }
5064 containing_word.push(ch);
5065 }
5066 let containing_word = containing_word.chars().rev().collect::<String>();
5067 if util::word_consists_of_emojis(containing_word.as_str()) {
5068 chars.reverse();
5069 return Some(chars.iter().collect());
5070 }
5071 }
5072
5073 if char.is_whitespace() || !char.is_ascii() {
5074 return None;
5075 }
5076 if char == ':' {
5077 found_colon = true;
5078 } else {
5079 chars.push(char);
5080 }
5081 }
5082 // Found a possible emoji shortcode at the beginning of the buffer
5083 chars.reverse();
5084 Some(chars.iter().collect())
5085 }
5086
5087 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5088 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5089 self.transact(window, cx, |this, window, cx| {
5090 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5091 let selections = this
5092 .selections
5093 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5094 let multi_buffer = this.buffer.read(cx);
5095 let buffer = multi_buffer.snapshot(cx);
5096 selections
5097 .iter()
5098 .map(|selection| {
5099 let start_point = selection.start.to_point(&buffer);
5100 let mut existing_indent =
5101 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5102 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5103 let start = selection.start;
5104 let end = selection.end;
5105 let selection_is_empty = start == end;
5106 let language_scope = buffer.language_scope_at(start);
5107 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5108 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5109 &buffer,
5110 start..end,
5111 language,
5112 )
5113 || NewlineConfig::insert_extra_newline_tree_sitter(
5114 &buffer,
5115 start..end,
5116 );
5117
5118 let mut newline_config = NewlineConfig::Newline {
5119 additional_indent: IndentSize::spaces(0),
5120 extra_line_additional_indent: if needs_extra_newline {
5121 Some(IndentSize::spaces(0))
5122 } else {
5123 None
5124 },
5125 prevent_auto_indent: false,
5126 };
5127
5128 let comment_delimiter = maybe!({
5129 if !selection_is_empty {
5130 return None;
5131 }
5132
5133 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5134 return None;
5135 }
5136
5137 return comment_delimiter_for_newline(
5138 &start_point,
5139 &buffer,
5140 language,
5141 );
5142 });
5143
5144 let doc_delimiter = maybe!({
5145 if !selection_is_empty {
5146 return None;
5147 }
5148
5149 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5150 return None;
5151 }
5152
5153 return documentation_delimiter_for_newline(
5154 &start_point,
5155 &buffer,
5156 language,
5157 &mut newline_config,
5158 );
5159 });
5160
5161 let list_delimiter = maybe!({
5162 if !selection_is_empty {
5163 return None;
5164 }
5165
5166 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5167 return None;
5168 }
5169
5170 return list_delimiter_for_newline(
5171 &start_point,
5172 &buffer,
5173 language,
5174 &mut newline_config,
5175 );
5176 });
5177
5178 (
5179 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5180 newline_config,
5181 )
5182 } else {
5183 (
5184 None,
5185 NewlineConfig::Newline {
5186 additional_indent: IndentSize::spaces(0),
5187 extra_line_additional_indent: None,
5188 prevent_auto_indent: false,
5189 },
5190 )
5191 };
5192
5193 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5194 NewlineConfig::ClearCurrentLine => {
5195 let row_start =
5196 buffer.point_to_offset(Point::new(start_point.row, 0));
5197 (row_start, String::new(), false)
5198 }
5199 NewlineConfig::UnindentCurrentLine { continuation } => {
5200 let row_start =
5201 buffer.point_to_offset(Point::new(start_point.row, 0));
5202 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5203 let tab_size_indent = IndentSize::spaces(tab_size.get());
5204 let reduced_indent =
5205 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5206 let mut new_text = String::new();
5207 new_text.extend(reduced_indent.chars());
5208 new_text.push_str(continuation);
5209 (row_start, new_text, true)
5210 }
5211 NewlineConfig::Newline {
5212 additional_indent,
5213 extra_line_additional_indent,
5214 prevent_auto_indent,
5215 } => {
5216 let capacity_for_delimiter =
5217 delimiter.as_deref().map(str::len).unwrap_or_default();
5218 let extra_line_len = extra_line_additional_indent
5219 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5220 .unwrap_or(0);
5221 let mut new_text = String::with_capacity(
5222 1 + capacity_for_delimiter
5223 + existing_indent.len as usize
5224 + additional_indent.len as usize
5225 + extra_line_len,
5226 );
5227 new_text.push('\n');
5228 new_text.extend(existing_indent.chars());
5229 new_text.extend(additional_indent.chars());
5230 if let Some(delimiter) = &delimiter {
5231 new_text.push_str(delimiter);
5232 }
5233 if let Some(extra_indent) = extra_line_additional_indent {
5234 new_text.push('\n');
5235 new_text.extend(existing_indent.chars());
5236 new_text.extend(extra_indent.chars());
5237 }
5238 (start, new_text, *prevent_auto_indent)
5239 }
5240 };
5241
5242 let anchor = buffer.anchor_after(end);
5243 let new_selection = selection.map(|_| anchor);
5244 (
5245 ((edit_start..end, new_text), prevent_auto_indent),
5246 (newline_config.has_extra_line(), new_selection),
5247 )
5248 })
5249 .unzip()
5250 };
5251
5252 let mut auto_indent_edits = Vec::new();
5253 let mut edits = Vec::new();
5254 for (edit, prevent_auto_indent) in edits_with_flags {
5255 if prevent_auto_indent {
5256 edits.push(edit);
5257 } else {
5258 auto_indent_edits.push(edit);
5259 }
5260 }
5261 if !edits.is_empty() {
5262 this.edit(edits, cx);
5263 }
5264 if !auto_indent_edits.is_empty() {
5265 this.edit_with_autoindent(auto_indent_edits, cx);
5266 }
5267
5268 let buffer = this.buffer.read(cx).snapshot(cx);
5269 let new_selections = selection_info
5270 .into_iter()
5271 .map(|(extra_newline_inserted, new_selection)| {
5272 let mut cursor = new_selection.end.to_point(&buffer);
5273 if extra_newline_inserted {
5274 cursor.row -= 1;
5275 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5276 }
5277 new_selection.map(|_| cursor)
5278 })
5279 .collect();
5280
5281 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5282 this.refresh_edit_prediction(true, false, window, cx);
5283 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5284 task.detach_and_log_err(cx);
5285 }
5286 });
5287 }
5288
5289 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5291
5292 let buffer = self.buffer.read(cx);
5293 let snapshot = buffer.snapshot(cx);
5294
5295 let mut edits = Vec::new();
5296 let mut rows = Vec::new();
5297
5298 for (rows_inserted, selection) in self
5299 .selections
5300 .all_adjusted(&self.display_snapshot(cx))
5301 .into_iter()
5302 .enumerate()
5303 {
5304 let cursor = selection.head();
5305 let row = cursor.row;
5306
5307 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5308
5309 let newline = "\n".to_string();
5310 edits.push((start_of_line..start_of_line, newline));
5311
5312 rows.push(row + rows_inserted as u32);
5313 }
5314
5315 self.transact(window, cx, |editor, window, cx| {
5316 editor.edit(edits, cx);
5317
5318 editor.change_selections(Default::default(), window, cx, |s| {
5319 let mut index = 0;
5320 s.move_cursors_with(&mut |map, _, _| {
5321 let row = rows[index];
5322 index += 1;
5323
5324 let point = Point::new(row, 0);
5325 let boundary = map.next_line_boundary(point).1;
5326 let clipped = map.clip_point(boundary, Bias::Left);
5327
5328 (clipped, SelectionGoal::None)
5329 });
5330 });
5331
5332 let mut indent_edits = Vec::new();
5333 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5334 for row in rows {
5335 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5336 for (row, indent) in indents {
5337 if indent.len == 0 {
5338 continue;
5339 }
5340
5341 let text = match indent.kind {
5342 IndentKind::Space => " ".repeat(indent.len as usize),
5343 IndentKind::Tab => "\t".repeat(indent.len as usize),
5344 };
5345 let point = Point::new(row.0, 0);
5346 indent_edits.push((point..point, text));
5347 }
5348 }
5349 editor.edit(indent_edits, cx);
5350 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5351 format.detach_and_log_err(cx);
5352 }
5353 });
5354 }
5355
5356 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5357 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5358
5359 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5360 let mut rows = Vec::new();
5361 let mut rows_inserted = 0;
5362
5363 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5364 let cursor = selection.head();
5365 let row = cursor.row;
5366
5367 let point = Point::new(row, 0);
5368 let Some((buffer_handle, buffer_point, _)) =
5369 self.buffer.read(cx).point_to_buffer_point(point, cx)
5370 else {
5371 continue;
5372 };
5373
5374 buffer_edits
5375 .entry(buffer_handle.entity_id())
5376 .or_insert_with(|| (buffer_handle, Vec::new()))
5377 .1
5378 .push(buffer_point);
5379
5380 rows_inserted += 1;
5381 rows.push(row + rows_inserted);
5382 }
5383
5384 self.transact(window, cx, |editor, window, cx| {
5385 for (_, (buffer_handle, points)) in &buffer_edits {
5386 buffer_handle.update(cx, |buffer, cx| {
5387 let edits: Vec<_> = points
5388 .iter()
5389 .map(|point| {
5390 let target = Point::new(point.row + 1, 0);
5391 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5392 (start_of_line..start_of_line, "\n")
5393 })
5394 .collect();
5395 buffer.edit(edits, None, cx);
5396 });
5397 }
5398
5399 editor.change_selections(Default::default(), window, cx, |s| {
5400 let mut index = 0;
5401 s.move_cursors_with(&mut |map, _, _| {
5402 let row = rows[index];
5403 index += 1;
5404
5405 let point = Point::new(row, 0);
5406 let boundary = map.next_line_boundary(point).1;
5407 let clipped = map.clip_point(boundary, Bias::Left);
5408
5409 (clipped, SelectionGoal::None)
5410 });
5411 });
5412
5413 let mut indent_edits = Vec::new();
5414 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5415 for row in rows {
5416 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5417 for (row, indent) in indents {
5418 if indent.len == 0 {
5419 continue;
5420 }
5421
5422 let text = match indent.kind {
5423 IndentKind::Space => " ".repeat(indent.len as usize),
5424 IndentKind::Tab => "\t".repeat(indent.len as usize),
5425 };
5426 let point = Point::new(row.0, 0);
5427 indent_edits.push((point..point, text));
5428 }
5429 }
5430 editor.edit(indent_edits, cx);
5431 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5432 format.detach_and_log_err(cx);
5433 }
5434 });
5435 }
5436
5437 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5438 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5439 original_indent_columns: Vec::new(),
5440 });
5441 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5442 }
5443
5444 fn insert_with_autoindent_mode(
5445 &mut self,
5446 text: &str,
5447 autoindent_mode: Option<AutoindentMode>,
5448 window: &mut Window,
5449 cx: &mut Context<Self>,
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 selection_anchors = this.buffer.update(cx, |buffer, cx| {
5459 let anchors = {
5460 let snapshot = buffer.read(cx);
5461 old_selections
5462 .iter()
5463 .map(|s| {
5464 let anchor = snapshot.anchor_after(s.head());
5465 s.map(|_| anchor)
5466 })
5467 .collect::<Vec<_>>()
5468 };
5469 buffer.edit(
5470 old_selections
5471 .iter()
5472 .map(|s| (s.start..s.end, text.clone())),
5473 autoindent_mode,
5474 cx,
5475 );
5476 anchors
5477 });
5478
5479 this.change_selections(Default::default(), window, cx, |s| {
5480 s.select_anchors(selection_anchors);
5481 });
5482
5483 cx.notify();
5484 });
5485 }
5486
5487 fn trigger_completion_on_input(
5488 &mut self,
5489 text: &str,
5490 trigger_in_words: bool,
5491 window: &mut Window,
5492 cx: &mut Context<Self>,
5493 ) {
5494 let completions_source = self
5495 .context_menu
5496 .borrow()
5497 .as_ref()
5498 .and_then(|menu| match menu {
5499 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5500 CodeContextMenu::CodeActions(_) => None,
5501 });
5502
5503 match completions_source {
5504 Some(CompletionsMenuSource::Words { .. }) => {
5505 self.open_or_update_completions_menu(
5506 Some(CompletionsMenuSource::Words {
5507 ignore_threshold: false,
5508 }),
5509 None,
5510 trigger_in_words,
5511 window,
5512 cx,
5513 );
5514 }
5515 _ => self.open_or_update_completions_menu(
5516 None,
5517 Some(text.to_owned()).filter(|x| !x.is_empty()),
5518 true,
5519 window,
5520 cx,
5521 ),
5522 }
5523 }
5524
5525 /// If any empty selections is touching the start of its innermost containing autoclose
5526 /// region, expand it to select the brackets.
5527 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5528 let selections = self
5529 .selections
5530 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5531 let buffer = self.buffer.read(cx).read(cx);
5532 let new_selections = self
5533 .selections_with_autoclose_regions(selections, &buffer)
5534 .map(|(mut selection, region)| {
5535 if !selection.is_empty() {
5536 return selection;
5537 }
5538
5539 if let Some(region) = region {
5540 let mut range = region.range.to_offset(&buffer);
5541 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5542 range.start -= region.pair.start.len();
5543 if buffer.contains_str_at(range.start, ®ion.pair.start)
5544 && buffer.contains_str_at(range.end, ®ion.pair.end)
5545 {
5546 range.end += region.pair.end.len();
5547 selection.start = range.start;
5548 selection.end = range.end;
5549
5550 return selection;
5551 }
5552 }
5553 }
5554
5555 let always_treat_brackets_as_autoclosed = buffer
5556 .language_settings_at(selection.start, cx)
5557 .always_treat_brackets_as_autoclosed;
5558
5559 if !always_treat_brackets_as_autoclosed {
5560 return selection;
5561 }
5562
5563 if let Some(scope) = buffer.language_scope_at(selection.start) {
5564 for (pair, enabled) in scope.brackets() {
5565 if !enabled || !pair.close {
5566 continue;
5567 }
5568
5569 if buffer.contains_str_at(selection.start, &pair.end) {
5570 let pair_start_len = pair.start.len();
5571 if buffer.contains_str_at(
5572 selection.start.saturating_sub_usize(pair_start_len),
5573 &pair.start,
5574 ) {
5575 selection.start -= pair_start_len;
5576 selection.end += pair.end.len();
5577
5578 return selection;
5579 }
5580 }
5581 }
5582 }
5583
5584 selection
5585 })
5586 .collect();
5587
5588 drop(buffer);
5589 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5590 selections.select(new_selections)
5591 });
5592 }
5593
5594 /// Iterate the given selections, and for each one, find the smallest surrounding
5595 /// autoclose region. This uses the ordering of the selections and the autoclose
5596 /// regions to avoid repeated comparisons.
5597 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5598 &'a self,
5599 selections: impl IntoIterator<Item = Selection<D>>,
5600 buffer: &'a MultiBufferSnapshot,
5601 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5602 let mut i = 0;
5603 let mut regions = self.autoclose_regions.as_slice();
5604 selections.into_iter().map(move |selection| {
5605 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5606
5607 let mut enclosing = None;
5608 while let Some(pair_state) = regions.get(i) {
5609 if pair_state.range.end.to_offset(buffer) < range.start {
5610 regions = ®ions[i + 1..];
5611 i = 0;
5612 } else if pair_state.range.start.to_offset(buffer) > range.end {
5613 break;
5614 } else {
5615 if pair_state.selection_id == selection.id {
5616 enclosing = Some(pair_state);
5617 }
5618 i += 1;
5619 }
5620 }
5621
5622 (selection, enclosing)
5623 })
5624 }
5625
5626 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5627 fn invalidate_autoclose_regions(
5628 &mut self,
5629 mut selections: &[Selection<Anchor>],
5630 buffer: &MultiBufferSnapshot,
5631 ) {
5632 self.autoclose_regions.retain(|state| {
5633 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5634 return false;
5635 }
5636
5637 let mut i = 0;
5638 while let Some(selection) = selections.get(i) {
5639 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5640 selections = &selections[1..];
5641 continue;
5642 }
5643 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5644 break;
5645 }
5646 if selection.id == state.selection_id {
5647 return true;
5648 } else {
5649 i += 1;
5650 }
5651 }
5652 false
5653 });
5654 }
5655
5656 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5657 let offset = position.to_offset(buffer);
5658 let (word_range, kind) =
5659 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5660 if offset > word_range.start && kind == Some(CharKind::Word) {
5661 Some(
5662 buffer
5663 .text_for_range(word_range.start..offset)
5664 .collect::<String>(),
5665 )
5666 } else {
5667 None
5668 }
5669 }
5670
5671 pub fn visible_excerpts(
5672 &self,
5673 lsp_related_only: bool,
5674 cx: &mut Context<Editor>,
5675 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5676 let project = self.project().cloned();
5677 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5678 let multi_buffer = self.buffer().read(cx);
5679 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5680 let multi_buffer_visible_start = self
5681 .scroll_manager
5682 .native_anchor(&display_snapshot, cx)
5683 .anchor
5684 .to_point(&multi_buffer_snapshot);
5685 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5686 multi_buffer_visible_start
5687 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5688 Bias::Left,
5689 );
5690 multi_buffer_snapshot
5691 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5692 .into_iter()
5693 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5694 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5695 if !lsp_related_only {
5696 return Some((
5697 excerpt_id,
5698 (
5699 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5700 buffer.version().clone(),
5701 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5702 ),
5703 ));
5704 }
5705
5706 let project = project.as_ref()?.read(cx);
5707 let buffer_file = project::File::from_dyn(buffer.file())?;
5708 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5709 let worktree_entry = buffer_worktree
5710 .read(cx)
5711 .entry_for_id(buffer_file.project_entry_id()?)?;
5712 if worktree_entry.is_ignored {
5713 None
5714 } else {
5715 Some((
5716 excerpt_id,
5717 (
5718 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5719 buffer.version().clone(),
5720 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5721 ),
5722 ))
5723 }
5724 })
5725 .collect()
5726 }
5727
5728 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5729 TextLayoutDetails {
5730 text_system: window.text_system().clone(),
5731 editor_style: self.style.clone().unwrap(),
5732 rem_size: window.rem_size(),
5733 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5734 visible_rows: self.visible_line_count(),
5735 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5736 }
5737 }
5738
5739 fn trigger_on_type_formatting(
5740 &self,
5741 input: String,
5742 window: &mut Window,
5743 cx: &mut Context<Self>,
5744 ) -> Option<Task<Result<()>>> {
5745 if input.chars().count() != 1 {
5746 return None;
5747 }
5748
5749 let project = self.project()?;
5750 let position = self.selections.newest_anchor().head();
5751 let (buffer, buffer_position) = self
5752 .buffer
5753 .read(cx)
5754 .text_anchor_for_position(position, cx)?;
5755
5756 let settings = language_settings::language_settings(
5757 buffer
5758 .read(cx)
5759 .language_at(buffer_position)
5760 .map(|l| l.name()),
5761 buffer.read(cx).file(),
5762 cx,
5763 );
5764 if !settings.use_on_type_format {
5765 return None;
5766 }
5767
5768 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5769 // hence we do LSP request & edit on host side only — add formats to host's history.
5770 let push_to_lsp_host_history = true;
5771 // If this is not the host, append its history with new edits.
5772 let push_to_client_history = project.read(cx).is_via_collab();
5773
5774 let on_type_formatting = project.update(cx, |project, cx| {
5775 project.on_type_format(
5776 buffer.clone(),
5777 buffer_position,
5778 input,
5779 push_to_lsp_host_history,
5780 cx,
5781 )
5782 });
5783 Some(cx.spawn_in(window, async move |editor, cx| {
5784 if let Some(transaction) = on_type_formatting.await? {
5785 if push_to_client_history {
5786 buffer.update(cx, |buffer, _| {
5787 buffer.push_transaction(transaction, Instant::now());
5788 buffer.finalize_last_transaction();
5789 });
5790 }
5791 editor.update(cx, |editor, cx| {
5792 editor.refresh_document_highlights(cx);
5793 })?;
5794 }
5795 Ok(())
5796 }))
5797 }
5798
5799 pub fn show_word_completions(
5800 &mut self,
5801 _: &ShowWordCompletions,
5802 window: &mut Window,
5803 cx: &mut Context<Self>,
5804 ) {
5805 self.open_or_update_completions_menu(
5806 Some(CompletionsMenuSource::Words {
5807 ignore_threshold: true,
5808 }),
5809 None,
5810 false,
5811 window,
5812 cx,
5813 );
5814 }
5815
5816 pub fn show_completions(
5817 &mut self,
5818 _: &ShowCompletions,
5819 window: &mut Window,
5820 cx: &mut Context<Self>,
5821 ) {
5822 self.open_or_update_completions_menu(None, None, false, window, cx);
5823 }
5824
5825 fn open_or_update_completions_menu(
5826 &mut self,
5827 requested_source: Option<CompletionsMenuSource>,
5828 trigger: Option<String>,
5829 trigger_in_words: bool,
5830 window: &mut Window,
5831 cx: &mut Context<Self>,
5832 ) {
5833 if self.pending_rename.is_some() {
5834 return;
5835 }
5836
5837 let completions_source = self
5838 .context_menu
5839 .borrow()
5840 .as_ref()
5841 .and_then(|menu| match menu {
5842 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5843 CodeContextMenu::CodeActions(_) => None,
5844 });
5845
5846 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5847
5848 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5849 // inserted and selected. To handle that case, the start of the selection is used so that
5850 // the menu starts with all choices.
5851 let position = self
5852 .selections
5853 .newest_anchor()
5854 .start
5855 .bias_right(&multibuffer_snapshot);
5856 if position.diff_base_anchor.is_some() {
5857 return;
5858 }
5859 let buffer_position = multibuffer_snapshot.anchor_before(position);
5860 let Some(buffer) = buffer_position
5861 .text_anchor
5862 .buffer_id
5863 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5864 else {
5865 return;
5866 };
5867 let buffer_snapshot = buffer.read(cx).snapshot();
5868
5869 let menu_is_open = matches!(
5870 self.context_menu.borrow().as_ref(),
5871 Some(CodeContextMenu::Completions(_))
5872 );
5873
5874 let language = buffer_snapshot
5875 .language_at(buffer_position.text_anchor)
5876 .map(|language| language.name());
5877
5878 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5879 let completion_settings = language_settings.completions.clone();
5880
5881 let show_completions_on_input = self
5882 .show_completions_on_input_override
5883 .unwrap_or(language_settings.show_completions_on_input);
5884 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5885 return;
5886 }
5887
5888 let query: Option<Arc<String>> =
5889 Self::completion_query(&multibuffer_snapshot, buffer_position)
5890 .map(|query| query.into());
5891
5892 drop(multibuffer_snapshot);
5893
5894 // Hide the current completions menu when query is empty. Without this, cached
5895 // completions from before the trigger char may be reused (#32774).
5896 if query.is_none() && menu_is_open {
5897 self.hide_context_menu(window, cx);
5898 }
5899
5900 let mut ignore_word_threshold = false;
5901 let provider = match requested_source {
5902 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5903 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5904 ignore_word_threshold = ignore_threshold;
5905 None
5906 }
5907 Some(CompletionsMenuSource::SnippetChoices)
5908 | Some(CompletionsMenuSource::SnippetsOnly) => {
5909 log::error!("bug: SnippetChoices requested_source is not handled");
5910 None
5911 }
5912 };
5913
5914 let sort_completions = provider
5915 .as_ref()
5916 .is_some_and(|provider| provider.sort_completions());
5917
5918 let filter_completions = provider
5919 .as_ref()
5920 .is_none_or(|provider| provider.filter_completions());
5921
5922 let was_snippets_only = matches!(
5923 completions_source,
5924 Some(CompletionsMenuSource::SnippetsOnly)
5925 );
5926
5927 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5928 if filter_completions {
5929 menu.filter(
5930 query.clone().unwrap_or_default(),
5931 buffer_position.text_anchor,
5932 &buffer,
5933 provider.clone(),
5934 window,
5935 cx,
5936 );
5937 }
5938 // When `is_incomplete` is false, no need to re-query completions when the current query
5939 // is a suffix of the initial query.
5940 let was_complete = !menu.is_incomplete;
5941 if was_complete && !was_snippets_only {
5942 // If the new query is a suffix of the old query (typing more characters) and
5943 // the previous result was complete, the existing completions can be filtered.
5944 //
5945 // Note that snippet completions are always complete.
5946 let query_matches = match (&menu.initial_query, &query) {
5947 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5948 (None, _) => true,
5949 _ => false,
5950 };
5951 if query_matches {
5952 let position_matches = if menu.initial_position == position {
5953 true
5954 } else {
5955 let snapshot = self.buffer.read(cx).read(cx);
5956 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5957 };
5958 if position_matches {
5959 return;
5960 }
5961 }
5962 }
5963 };
5964
5965 let Anchor {
5966 excerpt_id: buffer_excerpt_id,
5967 text_anchor: buffer_position,
5968 ..
5969 } = buffer_position;
5970
5971 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5972 buffer_snapshot.surrounding_word(buffer_position, None)
5973 {
5974 let word_to_exclude = buffer_snapshot
5975 .text_for_range(word_range.clone())
5976 .collect::<String>();
5977 (
5978 buffer_snapshot.anchor_before(word_range.start)
5979 ..buffer_snapshot.anchor_after(buffer_position),
5980 Some(word_to_exclude),
5981 )
5982 } else {
5983 (buffer_position..buffer_position, None)
5984 };
5985
5986 let show_completion_documentation = buffer_snapshot
5987 .settings_at(buffer_position, cx)
5988 .show_completion_documentation;
5989
5990 // The document can be large, so stay in reasonable bounds when searching for words,
5991 // otherwise completion pop-up might be slow to appear.
5992 const WORD_LOOKUP_ROWS: u32 = 5_000;
5993 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5994 let min_word_search = buffer_snapshot.clip_point(
5995 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5996 Bias::Left,
5997 );
5998 let max_word_search = buffer_snapshot.clip_point(
5999 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6000 Bias::Right,
6001 );
6002 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6003 ..buffer_snapshot.point_to_offset(max_word_search);
6004
6005 let skip_digits = query
6006 .as_ref()
6007 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6008
6009 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6010 trigger.as_ref().is_none_or(|trigger| {
6011 provider.is_completion_trigger(
6012 &buffer,
6013 position.text_anchor,
6014 trigger,
6015 trigger_in_words,
6016 cx,
6017 )
6018 })
6019 });
6020
6021 let provider_responses = if let Some(provider) = &provider
6022 && load_provider_completions
6023 {
6024 let trigger_character =
6025 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6026 let completion_context = CompletionContext {
6027 trigger_kind: match &trigger_character {
6028 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6029 None => CompletionTriggerKind::INVOKED,
6030 },
6031 trigger_character,
6032 };
6033
6034 provider.completions(
6035 buffer_excerpt_id,
6036 &buffer,
6037 buffer_position,
6038 completion_context,
6039 window,
6040 cx,
6041 )
6042 } else {
6043 Task::ready(Ok(Vec::new()))
6044 };
6045
6046 let load_word_completions = if !self.word_completions_enabled {
6047 false
6048 } else if requested_source
6049 == Some(CompletionsMenuSource::Words {
6050 ignore_threshold: true,
6051 })
6052 {
6053 true
6054 } else {
6055 load_provider_completions
6056 && completion_settings.words != WordsCompletionMode::Disabled
6057 && (ignore_word_threshold || {
6058 let words_min_length = completion_settings.words_min_length;
6059 // check whether word has at least `words_min_length` characters
6060 let query_chars = query.iter().flat_map(|q| q.chars());
6061 query_chars.take(words_min_length).count() == words_min_length
6062 })
6063 };
6064
6065 let mut words = if load_word_completions {
6066 cx.background_spawn({
6067 let buffer_snapshot = buffer_snapshot.clone();
6068 async move {
6069 buffer_snapshot.words_in_range(WordsQuery {
6070 fuzzy_contents: None,
6071 range: word_search_range,
6072 skip_digits,
6073 })
6074 }
6075 })
6076 } else {
6077 Task::ready(BTreeMap::default())
6078 };
6079
6080 let snippets = if let Some(provider) = &provider
6081 && provider.show_snippets()
6082 && let Some(project) = self.project()
6083 {
6084 let char_classifier = buffer_snapshot
6085 .char_classifier_at(buffer_position)
6086 .scope_context(Some(CharScopeContext::Completion));
6087 project.update(cx, |project, cx| {
6088 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6089 })
6090 } else {
6091 Task::ready(Ok(CompletionResponse {
6092 completions: Vec::new(),
6093 display_options: Default::default(),
6094 is_incomplete: false,
6095 }))
6096 };
6097
6098 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6099
6100 let id = post_inc(&mut self.next_completion_id);
6101 let task = cx.spawn_in(window, async move |editor, cx| {
6102 let Ok(()) = editor.update(cx, |this, _| {
6103 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6104 }) else {
6105 return;
6106 };
6107
6108 // TODO: Ideally completions from different sources would be selectively re-queried, so
6109 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6110 let mut completions = Vec::new();
6111 let mut is_incomplete = false;
6112 let mut display_options: Option<CompletionDisplayOptions> = None;
6113 if let Some(provider_responses) = provider_responses.await.log_err()
6114 && !provider_responses.is_empty()
6115 {
6116 for response in provider_responses {
6117 completions.extend(response.completions);
6118 is_incomplete = is_incomplete || response.is_incomplete;
6119 match display_options.as_mut() {
6120 None => {
6121 display_options = Some(response.display_options);
6122 }
6123 Some(options) => options.merge(&response.display_options),
6124 }
6125 }
6126 if completion_settings.words == WordsCompletionMode::Fallback {
6127 words = Task::ready(BTreeMap::default());
6128 }
6129 }
6130 let display_options = display_options.unwrap_or_default();
6131
6132 let mut words = words.await;
6133 if let Some(word_to_exclude) = &word_to_exclude {
6134 words.remove(word_to_exclude);
6135 }
6136 for lsp_completion in &completions {
6137 words.remove(&lsp_completion.new_text);
6138 }
6139 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6140 replace_range: word_replace_range.clone(),
6141 new_text: word.clone(),
6142 label: CodeLabel::plain(word, None),
6143 match_start: None,
6144 snippet_deduplication_key: None,
6145 icon_path: None,
6146 documentation: None,
6147 source: CompletionSource::BufferWord {
6148 word_range,
6149 resolved: false,
6150 },
6151 insert_text_mode: Some(InsertTextMode::AS_IS),
6152 confirm: None,
6153 }));
6154
6155 completions.extend(
6156 snippets
6157 .await
6158 .into_iter()
6159 .flat_map(|response| response.completions),
6160 );
6161
6162 let menu = if completions.is_empty() {
6163 None
6164 } else {
6165 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6166 let languages = editor
6167 .workspace
6168 .as_ref()
6169 .and_then(|(workspace, _)| workspace.upgrade())
6170 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6171 let menu = CompletionsMenu::new(
6172 id,
6173 requested_source.unwrap_or(if load_provider_completions {
6174 CompletionsMenuSource::Normal
6175 } else {
6176 CompletionsMenuSource::SnippetsOnly
6177 }),
6178 sort_completions,
6179 show_completion_documentation,
6180 position,
6181 query.clone(),
6182 is_incomplete,
6183 buffer.clone(),
6184 completions.into(),
6185 editor
6186 .context_menu()
6187 .borrow_mut()
6188 .as_ref()
6189 .map(|menu| menu.primary_scroll_handle()),
6190 display_options,
6191 snippet_sort_order,
6192 languages,
6193 language,
6194 cx,
6195 );
6196
6197 let query = if filter_completions { query } else { None };
6198 let matches_task = menu.do_async_filtering(
6199 query.unwrap_or_default(),
6200 buffer_position,
6201 &buffer,
6202 cx,
6203 );
6204 (menu, matches_task)
6205 }) else {
6206 return;
6207 };
6208
6209 let matches = matches_task.await;
6210
6211 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6212 // Newer menu already set, so exit.
6213 if let Some(CodeContextMenu::Completions(prev_menu)) =
6214 editor.context_menu.borrow().as_ref()
6215 && prev_menu.id > id
6216 {
6217 return;
6218 };
6219
6220 // Only valid to take prev_menu because either the new menu is immediately set
6221 // below, or the menu is hidden.
6222 if let Some(CodeContextMenu::Completions(prev_menu)) =
6223 editor.context_menu.borrow_mut().take()
6224 {
6225 let position_matches =
6226 if prev_menu.initial_position == menu.initial_position {
6227 true
6228 } else {
6229 let snapshot = editor.buffer.read(cx).read(cx);
6230 prev_menu.initial_position.to_offset(&snapshot)
6231 == menu.initial_position.to_offset(&snapshot)
6232 };
6233 if position_matches {
6234 // Preserve markdown cache before `set_filter_results` because it will
6235 // try to populate the documentation cache.
6236 menu.preserve_markdown_cache(prev_menu);
6237 }
6238 };
6239
6240 menu.set_filter_results(matches, provider, window, cx);
6241 }) else {
6242 return;
6243 };
6244
6245 menu.visible().then_some(menu)
6246 };
6247
6248 editor
6249 .update_in(cx, |editor, window, cx| {
6250 if editor.focus_handle.is_focused(window)
6251 && let Some(menu) = menu
6252 {
6253 *editor.context_menu.borrow_mut() =
6254 Some(CodeContextMenu::Completions(menu));
6255
6256 crate::hover_popover::hide_hover(editor, cx);
6257 if editor.show_edit_predictions_in_menu() {
6258 editor.update_visible_edit_prediction(window, cx);
6259 } else {
6260 editor
6261 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6262 }
6263
6264 cx.notify();
6265 return;
6266 }
6267
6268 if editor.completion_tasks.len() <= 1 {
6269 // If there are no more completion tasks and the last menu was empty, we should hide it.
6270 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6271 // If it was already hidden and we don't show edit predictions in the menu,
6272 // we should also show the edit prediction when available.
6273 if was_hidden && editor.show_edit_predictions_in_menu() {
6274 editor.update_visible_edit_prediction(window, cx);
6275 }
6276 }
6277 })
6278 .ok();
6279 });
6280
6281 self.completion_tasks.push((id, task));
6282 }
6283
6284 #[cfg(feature = "test-support")]
6285 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6286 let menu = self.context_menu.borrow();
6287 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6288 let completions = menu.completions.borrow();
6289 Some(completions.to_vec())
6290 } else {
6291 None
6292 }
6293 }
6294
6295 pub fn with_completions_menu_matching_id<R>(
6296 &self,
6297 id: CompletionId,
6298 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6299 ) -> R {
6300 let mut context_menu = self.context_menu.borrow_mut();
6301 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6302 return f(None);
6303 };
6304 if completions_menu.id != id {
6305 return f(None);
6306 }
6307 f(Some(completions_menu))
6308 }
6309
6310 pub fn confirm_completion(
6311 &mut self,
6312 action: &ConfirmCompletion,
6313 window: &mut Window,
6314 cx: &mut Context<Self>,
6315 ) -> Option<Task<Result<()>>> {
6316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6317 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6318 }
6319
6320 pub fn confirm_completion_insert(
6321 &mut self,
6322 _: &ConfirmCompletionInsert,
6323 window: &mut Window,
6324 cx: &mut Context<Self>,
6325 ) -> Option<Task<Result<()>>> {
6326 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6327 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6328 }
6329
6330 pub fn confirm_completion_replace(
6331 &mut self,
6332 _: &ConfirmCompletionReplace,
6333 window: &mut Window,
6334 cx: &mut Context<Self>,
6335 ) -> Option<Task<Result<()>>> {
6336 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6337 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6338 }
6339
6340 pub fn compose_completion(
6341 &mut self,
6342 action: &ComposeCompletion,
6343 window: &mut Window,
6344 cx: &mut Context<Self>,
6345 ) -> Option<Task<Result<()>>> {
6346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6347 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6348 }
6349
6350 fn do_completion(
6351 &mut self,
6352 item_ix: Option<usize>,
6353 intent: CompletionIntent,
6354 window: &mut Window,
6355 cx: &mut Context<Editor>,
6356 ) -> Option<Task<Result<()>>> {
6357 use language::ToOffset as _;
6358
6359 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6360 else {
6361 return None;
6362 };
6363
6364 let candidate_id = {
6365 let entries = completions_menu.entries.borrow();
6366 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6367 if self.show_edit_predictions_in_menu() {
6368 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6369 }
6370 mat.candidate_id
6371 };
6372
6373 let completion = completions_menu
6374 .completions
6375 .borrow()
6376 .get(candidate_id)?
6377 .clone();
6378 cx.stop_propagation();
6379
6380 let buffer_handle = completions_menu.buffer.clone();
6381
6382 let CompletionEdit {
6383 new_text,
6384 snippet,
6385 replace_range,
6386 } = process_completion_for_edit(
6387 &completion,
6388 intent,
6389 &buffer_handle,
6390 &completions_menu.initial_position.text_anchor,
6391 cx,
6392 );
6393
6394 let buffer = buffer_handle.read(cx);
6395 let snapshot = self.buffer.read(cx).snapshot(cx);
6396 let newest_anchor = self.selections.newest_anchor();
6397 let replace_range_multibuffer = {
6398 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6399 excerpt.map_range_from_buffer(replace_range.clone())
6400 };
6401 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6402 return None;
6403 }
6404
6405 let old_text = buffer
6406 .text_for_range(replace_range.clone())
6407 .collect::<String>();
6408 let lookbehind = newest_anchor
6409 .start
6410 .text_anchor
6411 .to_offset(buffer)
6412 .saturating_sub(replace_range.start.0);
6413 let lookahead = replace_range
6414 .end
6415 .0
6416 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6417 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6418 let suffix = &old_text[lookbehind.min(old_text.len())..];
6419
6420 let selections = self
6421 .selections
6422 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6423 let mut ranges = Vec::new();
6424 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6425
6426 for selection in &selections {
6427 let range = if selection.id == newest_anchor.id {
6428 replace_range_multibuffer.clone()
6429 } else {
6430 let mut range = selection.range();
6431
6432 // if prefix is present, don't duplicate it
6433 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6434 range.start = range.start.saturating_sub_usize(lookbehind);
6435
6436 // if suffix is also present, mimic the newest cursor and replace it
6437 if selection.id != newest_anchor.id
6438 && snapshot.contains_str_at(range.end, suffix)
6439 {
6440 range.end += lookahead;
6441 }
6442 }
6443 range
6444 };
6445
6446 ranges.push(range.clone());
6447
6448 if !self.linked_edit_ranges.is_empty() {
6449 let start_anchor = snapshot.anchor_before(range.start);
6450 let end_anchor = snapshot.anchor_after(range.end);
6451 if let Some(ranges) = self
6452 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6453 {
6454 for (buffer, edits) in ranges {
6455 linked_edits
6456 .entry(buffer.clone())
6457 .or_default()
6458 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6459 }
6460 }
6461 }
6462 }
6463
6464 let common_prefix_len = old_text
6465 .chars()
6466 .zip(new_text.chars())
6467 .take_while(|(a, b)| a == b)
6468 .map(|(a, _)| a.len_utf8())
6469 .sum::<usize>();
6470
6471 cx.emit(EditorEvent::InputHandled {
6472 utf16_range_to_replace: None,
6473 text: new_text[common_prefix_len..].into(),
6474 });
6475
6476 self.transact(window, cx, |editor, window, cx| {
6477 if let Some(mut snippet) = snippet {
6478 snippet.text = new_text.to_string();
6479 editor
6480 .insert_snippet(&ranges, snippet, window, cx)
6481 .log_err();
6482 } else {
6483 editor.buffer.update(cx, |multi_buffer, cx| {
6484 let auto_indent = match completion.insert_text_mode {
6485 Some(InsertTextMode::AS_IS) => None,
6486 _ => editor.autoindent_mode.clone(),
6487 };
6488 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6489 multi_buffer.edit(edits, auto_indent, cx);
6490 });
6491 }
6492 for (buffer, edits) in linked_edits {
6493 buffer.update(cx, |buffer, cx| {
6494 let snapshot = buffer.snapshot();
6495 let edits = edits
6496 .into_iter()
6497 .map(|(range, text)| {
6498 use text::ToPoint as TP;
6499 let end_point = TP::to_point(&range.end, &snapshot);
6500 let start_point = TP::to_point(&range.start, &snapshot);
6501 (start_point..end_point, text)
6502 })
6503 .sorted_by_key(|(range, _)| range.start);
6504 buffer.edit(edits, None, cx);
6505 })
6506 }
6507
6508 editor.refresh_edit_prediction(true, false, window, cx);
6509 });
6510 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6511
6512 let show_new_completions_on_confirm = completion
6513 .confirm
6514 .as_ref()
6515 .is_some_and(|confirm| confirm(intent, window, cx));
6516 if show_new_completions_on_confirm {
6517 self.open_or_update_completions_menu(None, None, false, window, cx);
6518 }
6519
6520 let provider = self.completion_provider.as_ref()?;
6521
6522 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6523 let command = lsp_store.as_ref().and_then(|lsp_store| {
6524 let CompletionSource::Lsp {
6525 lsp_completion,
6526 server_id,
6527 ..
6528 } = &completion.source
6529 else {
6530 return None;
6531 };
6532 let lsp_command = lsp_completion.command.as_ref()?;
6533 let available_commands = lsp_store
6534 .read(cx)
6535 .lsp_server_capabilities
6536 .get(server_id)
6537 .and_then(|server_capabilities| {
6538 server_capabilities
6539 .execute_command_provider
6540 .as_ref()
6541 .map(|options| options.commands.as_slice())
6542 })?;
6543 if available_commands.contains(&lsp_command.command) {
6544 Some(CodeAction {
6545 server_id: *server_id,
6546 range: language::Anchor::MIN..language::Anchor::MIN,
6547 lsp_action: LspAction::Command(lsp_command.clone()),
6548 resolved: false,
6549 })
6550 } else {
6551 None
6552 }
6553 });
6554
6555 drop(completion);
6556 let apply_edits = provider.apply_additional_edits_for_completion(
6557 buffer_handle.clone(),
6558 completions_menu.completions.clone(),
6559 candidate_id,
6560 true,
6561 cx,
6562 );
6563
6564 let editor_settings = EditorSettings::get_global(cx);
6565 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6566 // After the code completion is finished, users often want to know what signatures are needed.
6567 // so we should automatically call signature_help
6568 self.show_signature_help(&ShowSignatureHelp, window, cx);
6569 }
6570
6571 Some(cx.spawn_in(window, async move |editor, cx| {
6572 apply_edits.await?;
6573
6574 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6575 let title = command.lsp_action.title().to_owned();
6576 let project_transaction = lsp_store
6577 .update(cx, |lsp_store, cx| {
6578 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6579 })
6580 .await
6581 .context("applying post-completion command")?;
6582 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6583 Self::open_project_transaction(
6584 &editor,
6585 workspace.downgrade(),
6586 project_transaction,
6587 title,
6588 cx,
6589 )
6590 .await?;
6591 }
6592 }
6593
6594 Ok(())
6595 }))
6596 }
6597
6598 pub fn toggle_code_actions(
6599 &mut self,
6600 action: &ToggleCodeActions,
6601 window: &mut Window,
6602 cx: &mut Context<Self>,
6603 ) {
6604 let quick_launch = action.quick_launch;
6605 let mut context_menu = self.context_menu.borrow_mut();
6606 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6607 if code_actions.deployed_from == action.deployed_from {
6608 // Toggle if we're selecting the same one
6609 *context_menu = None;
6610 cx.notify();
6611 return;
6612 } else {
6613 // Otherwise, clear it and start a new one
6614 *context_menu = None;
6615 cx.notify();
6616 }
6617 }
6618 drop(context_menu);
6619 let snapshot = self.snapshot(window, cx);
6620 let deployed_from = action.deployed_from.clone();
6621 let action = action.clone();
6622 self.completion_tasks.clear();
6623 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6624
6625 let multibuffer_point = match &action.deployed_from {
6626 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6627 DisplayPoint::new(*row, 0).to_point(&snapshot)
6628 }
6629 _ => self
6630 .selections
6631 .newest::<Point>(&snapshot.display_snapshot)
6632 .head(),
6633 };
6634 let Some((buffer, buffer_row)) = snapshot
6635 .buffer_snapshot()
6636 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6637 .and_then(|(buffer_snapshot, range)| {
6638 self.buffer()
6639 .read(cx)
6640 .buffer(buffer_snapshot.remote_id())
6641 .map(|buffer| (buffer, range.start.row))
6642 })
6643 else {
6644 return;
6645 };
6646 let buffer_id = buffer.read(cx).remote_id();
6647 let tasks = self
6648 .tasks
6649 .get(&(buffer_id, buffer_row))
6650 .map(|t| Arc::new(t.to_owned()));
6651
6652 if !self.focus_handle.is_focused(window) {
6653 return;
6654 }
6655 let project = self.project.clone();
6656
6657 let code_actions_task = match deployed_from {
6658 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6659 _ => self.code_actions(buffer_row, window, cx),
6660 };
6661
6662 let runnable_task = match deployed_from {
6663 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6664 _ => {
6665 let mut task_context_task = Task::ready(None);
6666 if let Some(tasks) = &tasks
6667 && let Some(project) = project
6668 {
6669 task_context_task =
6670 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6671 }
6672
6673 cx.spawn_in(window, {
6674 let buffer = buffer.clone();
6675 async move |editor, cx| {
6676 let task_context = task_context_task.await;
6677
6678 let resolved_tasks =
6679 tasks
6680 .zip(task_context.clone())
6681 .map(|(tasks, task_context)| ResolvedTasks {
6682 templates: tasks.resolve(&task_context).collect(),
6683 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6684 multibuffer_point.row,
6685 tasks.column,
6686 )),
6687 });
6688 let debug_scenarios = editor
6689 .update(cx, |editor, cx| {
6690 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6691 })?
6692 .await;
6693 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6694 }
6695 })
6696 }
6697 };
6698
6699 cx.spawn_in(window, async move |editor, cx| {
6700 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6701 let code_actions = code_actions_task.await;
6702 let spawn_straight_away = quick_launch
6703 && resolved_tasks
6704 .as_ref()
6705 .is_some_and(|tasks| tasks.templates.len() == 1)
6706 && code_actions
6707 .as_ref()
6708 .is_none_or(|actions| actions.is_empty())
6709 && debug_scenarios.is_empty();
6710
6711 editor.update_in(cx, |editor, window, cx| {
6712 crate::hover_popover::hide_hover(editor, cx);
6713 let actions = CodeActionContents::new(
6714 resolved_tasks,
6715 code_actions,
6716 debug_scenarios,
6717 task_context.unwrap_or_default(),
6718 );
6719
6720 // Don't show the menu if there are no actions available
6721 if actions.is_empty() {
6722 cx.notify();
6723 return Task::ready(Ok(()));
6724 }
6725
6726 *editor.context_menu.borrow_mut() =
6727 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6728 buffer,
6729 actions,
6730 selected_item: Default::default(),
6731 scroll_handle: UniformListScrollHandle::default(),
6732 deployed_from,
6733 }));
6734 cx.notify();
6735 if spawn_straight_away
6736 && let Some(task) = editor.confirm_code_action(
6737 &ConfirmCodeAction { item_ix: Some(0) },
6738 window,
6739 cx,
6740 )
6741 {
6742 return task;
6743 }
6744
6745 Task::ready(Ok(()))
6746 })
6747 })
6748 .detach_and_log_err(cx);
6749 }
6750
6751 fn debug_scenarios(
6752 &mut self,
6753 resolved_tasks: &Option<ResolvedTasks>,
6754 buffer: &Entity<Buffer>,
6755 cx: &mut App,
6756 ) -> Task<Vec<task::DebugScenario>> {
6757 maybe!({
6758 let project = self.project()?;
6759 let dap_store = project.read(cx).dap_store();
6760 let mut scenarios = vec![];
6761 let resolved_tasks = resolved_tasks.as_ref()?;
6762 let buffer = buffer.read(cx);
6763 let language = buffer.language()?;
6764 let file = buffer.file();
6765 let debug_adapter = language_settings(language.name().into(), file, cx)
6766 .debuggers
6767 .first()
6768 .map(SharedString::from)
6769 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6770
6771 dap_store.update(cx, |dap_store, cx| {
6772 for (_, task) in &resolved_tasks.templates {
6773 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6774 task.original_task().clone(),
6775 debug_adapter.clone().into(),
6776 task.display_label().to_owned().into(),
6777 cx,
6778 );
6779 scenarios.push(maybe_scenario);
6780 }
6781 });
6782 Some(cx.background_spawn(async move {
6783 futures::future::join_all(scenarios)
6784 .await
6785 .into_iter()
6786 .flatten()
6787 .collect::<Vec<_>>()
6788 }))
6789 })
6790 .unwrap_or_else(|| Task::ready(vec![]))
6791 }
6792
6793 fn code_actions(
6794 &mut self,
6795 buffer_row: u32,
6796 window: &mut Window,
6797 cx: &mut Context<Self>,
6798 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6799 let mut task = self.code_actions_task.take();
6800 cx.spawn_in(window, async move |editor, cx| {
6801 while let Some(prev_task) = task {
6802 prev_task.await.log_err();
6803 task = editor
6804 .update(cx, |this, _| this.code_actions_task.take())
6805 .ok()?;
6806 }
6807
6808 editor
6809 .update(cx, |editor, cx| {
6810 editor
6811 .available_code_actions
6812 .clone()
6813 .and_then(|(location, code_actions)| {
6814 let snapshot = location.buffer.read(cx).snapshot();
6815 let point_range = location.range.to_point(&snapshot);
6816 let point_range = point_range.start.row..=point_range.end.row;
6817 if point_range.contains(&buffer_row) {
6818 Some(code_actions)
6819 } else {
6820 None
6821 }
6822 })
6823 })
6824 .ok()
6825 .flatten()
6826 })
6827 }
6828
6829 pub fn confirm_code_action(
6830 &mut self,
6831 action: &ConfirmCodeAction,
6832 window: &mut Window,
6833 cx: &mut Context<Self>,
6834 ) -> Option<Task<Result<()>>> {
6835 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6836
6837 let actions_menu =
6838 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6839 menu
6840 } else {
6841 return None;
6842 };
6843
6844 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6845 let action = actions_menu.actions.get(action_ix)?;
6846 let title = action.label();
6847 let buffer = actions_menu.buffer;
6848 let workspace = self.workspace()?;
6849
6850 match action {
6851 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6852 workspace.update(cx, |workspace, cx| {
6853 workspace.schedule_resolved_task(
6854 task_source_kind,
6855 resolved_task,
6856 false,
6857 window,
6858 cx,
6859 );
6860
6861 Some(Task::ready(Ok(())))
6862 })
6863 }
6864 CodeActionsItem::CodeAction {
6865 excerpt_id,
6866 action,
6867 provider,
6868 } => {
6869 let apply_code_action =
6870 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6871 let workspace = workspace.downgrade();
6872 Some(cx.spawn_in(window, async move |editor, cx| {
6873 let project_transaction = apply_code_action.await?;
6874 Self::open_project_transaction(
6875 &editor,
6876 workspace,
6877 project_transaction,
6878 title,
6879 cx,
6880 )
6881 .await
6882 }))
6883 }
6884 CodeActionsItem::DebugScenario(scenario) => {
6885 let context = actions_menu.actions.context.into();
6886
6887 workspace.update(cx, |workspace, cx| {
6888 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6889 workspace.start_debug_session(
6890 scenario,
6891 context,
6892 Some(buffer),
6893 None,
6894 window,
6895 cx,
6896 );
6897 });
6898 Some(Task::ready(Ok(())))
6899 }
6900 }
6901 }
6902
6903 fn open_transaction_for_hidden_buffers(
6904 workspace: Entity<Workspace>,
6905 transaction: ProjectTransaction,
6906 title: String,
6907 window: &mut Window,
6908 cx: &mut Context<Self>,
6909 ) {
6910 if transaction.0.is_empty() {
6911 return;
6912 }
6913
6914 let edited_buffers_already_open = {
6915 let other_editors: Vec<Entity<Editor>> = workspace
6916 .read(cx)
6917 .panes()
6918 .iter()
6919 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6920 .filter(|editor| editor.entity_id() != cx.entity_id())
6921 .collect();
6922
6923 transaction.0.keys().all(|buffer| {
6924 other_editors.iter().any(|editor| {
6925 let multi_buffer = editor.read(cx).buffer();
6926 multi_buffer.read(cx).is_singleton()
6927 && multi_buffer
6928 .read(cx)
6929 .as_singleton()
6930 .map_or(false, |singleton| {
6931 singleton.entity_id() == buffer.entity_id()
6932 })
6933 })
6934 })
6935 };
6936 if !edited_buffers_already_open {
6937 let workspace = workspace.downgrade();
6938 cx.defer_in(window, move |_, window, cx| {
6939 cx.spawn_in(window, async move |editor, cx| {
6940 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6941 .await
6942 .ok()
6943 })
6944 .detach();
6945 });
6946 }
6947 }
6948
6949 pub async fn open_project_transaction(
6950 editor: &WeakEntity<Editor>,
6951 workspace: WeakEntity<Workspace>,
6952 transaction: ProjectTransaction,
6953 title: String,
6954 cx: &mut AsyncWindowContext,
6955 ) -> Result<()> {
6956 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6957 cx.update(|_, cx| {
6958 entries.sort_unstable_by_key(|(buffer, _)| {
6959 buffer.read(cx).file().map(|f| f.path().clone())
6960 });
6961 })?;
6962 if entries.is_empty() {
6963 return Ok(());
6964 }
6965
6966 // If the project transaction's edits are all contained within this editor, then
6967 // avoid opening a new editor to display them.
6968
6969 if let [(buffer, transaction)] = &*entries {
6970 let excerpt = editor.update(cx, |editor, cx| {
6971 editor
6972 .buffer()
6973 .read(cx)
6974 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6975 })?;
6976 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6977 && excerpted_buffer == *buffer
6978 {
6979 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6980 let excerpt_range = excerpt_range.to_offset(buffer);
6981 buffer
6982 .edited_ranges_for_transaction::<usize>(transaction)
6983 .all(|range| {
6984 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6985 })
6986 });
6987
6988 if all_edits_within_excerpt {
6989 return Ok(());
6990 }
6991 }
6992 }
6993
6994 let mut ranges_to_highlight = Vec::new();
6995 let excerpt_buffer = cx.new(|cx| {
6996 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6997 for (buffer_handle, transaction) in &entries {
6998 let edited_ranges = buffer_handle
6999 .read(cx)
7000 .edited_ranges_for_transaction::<Point>(transaction)
7001 .collect::<Vec<_>>();
7002 let (ranges, _) = multibuffer.set_excerpts_for_path(
7003 PathKey::for_buffer(buffer_handle, cx),
7004 buffer_handle.clone(),
7005 edited_ranges,
7006 multibuffer_context_lines(cx),
7007 cx,
7008 );
7009
7010 ranges_to_highlight.extend(ranges);
7011 }
7012 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7013 multibuffer
7014 });
7015
7016 workspace.update_in(cx, |workspace, window, cx| {
7017 let project = workspace.project().clone();
7018 let editor =
7019 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7020 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7021 editor.update(cx, |editor, cx| {
7022 editor.highlight_background(
7023 HighlightKey::Editor,
7024 &ranges_to_highlight,
7025 |_, theme| theme.colors().editor_highlighted_line_background,
7026 cx,
7027 );
7028 });
7029 })?;
7030
7031 Ok(())
7032 }
7033
7034 pub fn clear_code_action_providers(&mut self) {
7035 self.code_action_providers.clear();
7036 self.available_code_actions.take();
7037 }
7038
7039 pub fn add_code_action_provider(
7040 &mut self,
7041 provider: Rc<dyn CodeActionProvider>,
7042 window: &mut Window,
7043 cx: &mut Context<Self>,
7044 ) {
7045 if self
7046 .code_action_providers
7047 .iter()
7048 .any(|existing_provider| existing_provider.id() == provider.id())
7049 {
7050 return;
7051 }
7052
7053 self.code_action_providers.push(provider);
7054 self.refresh_code_actions(window, cx);
7055 }
7056
7057 pub fn remove_code_action_provider(
7058 &mut self,
7059 id: Arc<str>,
7060 window: &mut Window,
7061 cx: &mut Context<Self>,
7062 ) {
7063 self.code_action_providers
7064 .retain(|provider| provider.id() != id);
7065 self.refresh_code_actions(window, cx);
7066 }
7067
7068 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7069 !self.code_action_providers.is_empty()
7070 && EditorSettings::get_global(cx).toolbar.code_actions
7071 }
7072
7073 pub fn has_available_code_actions(&self) -> bool {
7074 self.available_code_actions
7075 .as_ref()
7076 .is_some_and(|(_, actions)| !actions.is_empty())
7077 }
7078
7079 fn render_inline_code_actions(
7080 &self,
7081 icon_size: ui::IconSize,
7082 display_row: DisplayRow,
7083 is_active: bool,
7084 cx: &mut Context<Self>,
7085 ) -> AnyElement {
7086 let show_tooltip = !self.context_menu_visible();
7087 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7088 .icon_size(icon_size)
7089 .shape(ui::IconButtonShape::Square)
7090 .icon_color(ui::Color::Hidden)
7091 .toggle_state(is_active)
7092 .when(show_tooltip, |this| {
7093 this.tooltip({
7094 let focus_handle = self.focus_handle.clone();
7095 move |_window, cx| {
7096 Tooltip::for_action_in(
7097 "Toggle Code Actions",
7098 &ToggleCodeActions {
7099 deployed_from: None,
7100 quick_launch: false,
7101 },
7102 &focus_handle,
7103 cx,
7104 )
7105 }
7106 })
7107 })
7108 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7109 window.focus(&editor.focus_handle(cx), cx);
7110 editor.toggle_code_actions(
7111 &crate::actions::ToggleCodeActions {
7112 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7113 display_row,
7114 )),
7115 quick_launch: false,
7116 },
7117 window,
7118 cx,
7119 );
7120 }))
7121 .into_any_element()
7122 }
7123
7124 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7125 &self.context_menu
7126 }
7127
7128 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7129 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7130 cx.background_executor()
7131 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7132 .await;
7133
7134 let (start_buffer, start, _, end, newest_selection) = this
7135 .update(cx, |this, cx| {
7136 let newest_selection = this.selections.newest_anchor().clone();
7137 if newest_selection.head().diff_base_anchor.is_some() {
7138 return None;
7139 }
7140 let display_snapshot = this.display_snapshot(cx);
7141 let newest_selection_adjusted =
7142 this.selections.newest_adjusted(&display_snapshot);
7143 let buffer = this.buffer.read(cx);
7144
7145 let (start_buffer, start) =
7146 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7147 let (end_buffer, end) =
7148 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7149
7150 Some((start_buffer, start, end_buffer, end, newest_selection))
7151 })?
7152 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7153 .context(
7154 "Expected selection to lie in a single buffer when refreshing code actions",
7155 )?;
7156 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7157 let providers = this.code_action_providers.clone();
7158 let tasks = this
7159 .code_action_providers
7160 .iter()
7161 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7162 .collect::<Vec<_>>();
7163 (providers, tasks)
7164 })?;
7165
7166 let mut actions = Vec::new();
7167 for (provider, provider_actions) in
7168 providers.into_iter().zip(future::join_all(tasks).await)
7169 {
7170 if let Some(provider_actions) = provider_actions.log_err() {
7171 actions.extend(provider_actions.into_iter().map(|action| {
7172 AvailableCodeAction {
7173 excerpt_id: newest_selection.start.excerpt_id,
7174 action,
7175 provider: provider.clone(),
7176 }
7177 }));
7178 }
7179 }
7180
7181 this.update(cx, |this, cx| {
7182 this.available_code_actions = if actions.is_empty() {
7183 None
7184 } else {
7185 Some((
7186 Location {
7187 buffer: start_buffer,
7188 range: start..end,
7189 },
7190 actions.into(),
7191 ))
7192 };
7193 cx.notify();
7194 })
7195 }));
7196 }
7197
7198 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7199 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7200 self.show_git_blame_inline = false;
7201
7202 self.show_git_blame_inline_delay_task =
7203 Some(cx.spawn_in(window, async move |this, cx| {
7204 cx.background_executor().timer(delay).await;
7205
7206 this.update(cx, |this, cx| {
7207 this.show_git_blame_inline = true;
7208 cx.notify();
7209 })
7210 .log_err();
7211 }));
7212 }
7213 }
7214
7215 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7216 let snapshot = self.snapshot(window, cx);
7217 let cursor = self
7218 .selections
7219 .newest::<Point>(&snapshot.display_snapshot)
7220 .head();
7221 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7222 else {
7223 return;
7224 };
7225
7226 if self.blame.is_none() {
7227 self.start_git_blame(true, window, cx);
7228 }
7229 let Some(blame) = self.blame.as_ref() else {
7230 return;
7231 };
7232
7233 let row_info = RowInfo {
7234 buffer_id: Some(buffer.remote_id()),
7235 buffer_row: Some(point.row),
7236 ..Default::default()
7237 };
7238 let Some((buffer, blame_entry)) = blame
7239 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7240 .flatten()
7241 else {
7242 return;
7243 };
7244
7245 let anchor = self.selections.newest_anchor().head();
7246 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7247 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7248 self.show_blame_popover(
7249 buffer,
7250 &blame_entry,
7251 position + last_bounds.origin,
7252 true,
7253 cx,
7254 );
7255 };
7256 }
7257
7258 fn show_blame_popover(
7259 &mut self,
7260 buffer: BufferId,
7261 blame_entry: &BlameEntry,
7262 position: gpui::Point<Pixels>,
7263 ignore_timeout: bool,
7264 cx: &mut Context<Self>,
7265 ) {
7266 if let Some(state) = &mut self.inline_blame_popover {
7267 state.hide_task.take();
7268 } else {
7269 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7270 let blame_entry = blame_entry.clone();
7271 let show_task = cx.spawn(async move |editor, cx| {
7272 if !ignore_timeout {
7273 cx.background_executor()
7274 .timer(std::time::Duration::from_millis(blame_popover_delay))
7275 .await;
7276 }
7277 editor
7278 .update(cx, |editor, cx| {
7279 editor.inline_blame_popover_show_task.take();
7280 let Some(blame) = editor.blame.as_ref() else {
7281 return;
7282 };
7283 let blame = blame.read(cx);
7284 let details = blame.details_for_entry(buffer, &blame_entry);
7285 let markdown = cx.new(|cx| {
7286 Markdown::new(
7287 details
7288 .as_ref()
7289 .map(|message| message.message.clone())
7290 .unwrap_or_default(),
7291 None,
7292 None,
7293 cx,
7294 )
7295 });
7296 editor.inline_blame_popover = Some(InlineBlamePopover {
7297 position,
7298 hide_task: None,
7299 popover_bounds: None,
7300 popover_state: InlineBlamePopoverState {
7301 scroll_handle: ScrollHandle::new(),
7302 commit_message: details,
7303 markdown,
7304 },
7305 keyboard_grace: ignore_timeout,
7306 });
7307 cx.notify();
7308 })
7309 .ok();
7310 });
7311 self.inline_blame_popover_show_task = Some(show_task);
7312 }
7313 }
7314
7315 pub fn has_mouse_context_menu(&self) -> bool {
7316 self.mouse_context_menu.is_some()
7317 }
7318
7319 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7320 self.inline_blame_popover_show_task.take();
7321 if let Some(state) = &mut self.inline_blame_popover {
7322 let hide_task = cx.spawn(async move |editor, cx| {
7323 if !ignore_timeout {
7324 cx.background_executor()
7325 .timer(std::time::Duration::from_millis(100))
7326 .await;
7327 }
7328 editor
7329 .update(cx, |editor, cx| {
7330 editor.inline_blame_popover.take();
7331 cx.notify();
7332 })
7333 .ok();
7334 });
7335 state.hide_task = Some(hide_task);
7336 true
7337 } else {
7338 false
7339 }
7340 }
7341
7342 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7343 if self.pending_rename.is_some() {
7344 return None;
7345 }
7346
7347 let provider = self.semantics_provider.clone()?;
7348 let buffer = self.buffer.read(cx);
7349 let newest_selection = self.selections.newest_anchor().clone();
7350 let cursor_position = newest_selection.head();
7351 let (cursor_buffer, cursor_buffer_position) =
7352 buffer.text_anchor_for_position(cursor_position, cx)?;
7353 let (tail_buffer, tail_buffer_position) =
7354 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7355 if cursor_buffer != tail_buffer {
7356 return None;
7357 }
7358
7359 let snapshot = cursor_buffer.read(cx).snapshot();
7360 let word_ranges = cx.background_spawn(async move {
7361 // this might look odd to put on the background thread, but
7362 // `surrounding_word` can be quite expensive as it calls into
7363 // tree-sitter language scopes
7364 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7365 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7366 (start_word_range, end_word_range)
7367 });
7368
7369 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7370 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7371 let (start_word_range, end_word_range) = word_ranges.await;
7372 if start_word_range != end_word_range {
7373 this.update(cx, |this, cx| {
7374 this.document_highlights_task.take();
7375 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7376 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7377 })
7378 .ok();
7379 return;
7380 }
7381 cx.background_executor()
7382 .timer(Duration::from_millis(debounce))
7383 .await;
7384
7385 let highlights = if let Some(highlights) = cx.update(|cx| {
7386 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7387 }) {
7388 highlights.await.log_err()
7389 } else {
7390 None
7391 };
7392
7393 if let Some(highlights) = highlights {
7394 this.update(cx, |this, cx| {
7395 if this.pending_rename.is_some() {
7396 return;
7397 }
7398
7399 let buffer = this.buffer.read(cx);
7400 if buffer
7401 .text_anchor_for_position(cursor_position, cx)
7402 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7403 {
7404 return;
7405 }
7406
7407 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7408 let mut write_ranges = Vec::new();
7409 let mut read_ranges = Vec::new();
7410 for highlight in highlights {
7411 let buffer_id = cursor_buffer.read(cx).remote_id();
7412 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7413 {
7414 let start = highlight
7415 .range
7416 .start
7417 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7418 let end = highlight
7419 .range
7420 .end
7421 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7422 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7423 continue;
7424 }
7425
7426 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7427 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7428 write_ranges.push(range);
7429 } else {
7430 read_ranges.push(range);
7431 }
7432 }
7433 }
7434
7435 this.highlight_background(
7436 HighlightKey::DocumentHighlightRead,
7437 &read_ranges,
7438 |_, theme| theme.colors().editor_document_highlight_read_background,
7439 cx,
7440 );
7441 this.highlight_background(
7442 HighlightKey::DocumentHighlightWrite,
7443 &write_ranges,
7444 |_, theme| theme.colors().editor_document_highlight_write_background,
7445 cx,
7446 );
7447 cx.notify();
7448 })
7449 .log_err();
7450 }
7451 }));
7452 None
7453 }
7454
7455 fn prepare_highlight_query_from_selection(
7456 &mut self,
7457 window: &Window,
7458 cx: &mut Context<Editor>,
7459 ) -> Option<(String, Range<Anchor>)> {
7460 if matches!(self.mode, EditorMode::SingleLine) {
7461 return None;
7462 }
7463 if !EditorSettings::get_global(cx).selection_highlight {
7464 return None;
7465 }
7466 if self.selections.count() != 1 || self.selections.line_mode() {
7467 return None;
7468 }
7469 let snapshot = self.snapshot(window, cx);
7470 let selection = self.selections.newest::<Point>(&snapshot);
7471 // If the selection spans multiple rows OR it is empty
7472 if selection.start.row != selection.end.row
7473 || selection.start.column == selection.end.column
7474 {
7475 return None;
7476 }
7477 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7478 let query = snapshot
7479 .buffer_snapshot()
7480 .text_for_range(selection_anchor_range.clone())
7481 .collect::<String>();
7482 if query.trim().is_empty() {
7483 return None;
7484 }
7485 Some((query, selection_anchor_range))
7486 }
7487
7488 #[ztracing::instrument(skip_all)]
7489 fn update_selection_occurrence_highlights(
7490 &mut self,
7491 query_text: String,
7492 query_range: Range<Anchor>,
7493 multi_buffer_range_to_query: Range<Point>,
7494 use_debounce: bool,
7495 window: &mut Window,
7496 cx: &mut Context<Editor>,
7497 ) -> Task<()> {
7498 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7499 cx.spawn_in(window, async move |editor, cx| {
7500 if use_debounce {
7501 cx.background_executor()
7502 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7503 .await;
7504 }
7505 let match_task = cx.background_spawn(async move {
7506 let buffer_ranges = multi_buffer_snapshot
7507 .range_to_buffer_ranges(
7508 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7509 )
7510 .into_iter()
7511 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7512 let mut match_ranges = Vec::new();
7513 let Ok(regex) = project::search::SearchQuery::text(
7514 query_text.clone(),
7515 false,
7516 false,
7517 false,
7518 Default::default(),
7519 Default::default(),
7520 false,
7521 None,
7522 ) else {
7523 return Vec::default();
7524 };
7525 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7526 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7527 match_ranges.extend(
7528 regex
7529 .search(
7530 buffer_snapshot,
7531 Some(search_range.start.0..search_range.end.0),
7532 )
7533 .await
7534 .into_iter()
7535 .filter_map(|match_range| {
7536 let match_start = buffer_snapshot
7537 .anchor_after(search_range.start + match_range.start);
7538 let match_end = buffer_snapshot
7539 .anchor_before(search_range.start + match_range.end);
7540 let match_anchor_range =
7541 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7542 (match_anchor_range != query_range).then_some(match_anchor_range)
7543 }),
7544 );
7545 }
7546 match_ranges
7547 });
7548 let match_ranges = match_task.await;
7549 editor
7550 .update_in(cx, |editor, _, cx| {
7551 if use_debounce {
7552 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7553 editor.debounced_selection_highlight_complete = true;
7554 } else if editor.debounced_selection_highlight_complete {
7555 return;
7556 }
7557 if !match_ranges.is_empty() {
7558 editor.highlight_background(
7559 HighlightKey::SelectedTextHighlight,
7560 &match_ranges,
7561 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7562 cx,
7563 )
7564 }
7565 })
7566 .log_err();
7567 })
7568 }
7569
7570 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7571 struct NewlineFold;
7572 let type_id = std::any::TypeId::of::<NewlineFold>();
7573 if !self.mode.is_single_line() {
7574 return;
7575 }
7576 let snapshot = self.snapshot(window, cx);
7577 if snapshot.buffer_snapshot().max_point().row == 0 {
7578 return;
7579 }
7580 let task = cx.background_spawn(async move {
7581 let new_newlines = snapshot
7582 .buffer_chars_at(MultiBufferOffset(0))
7583 .filter_map(|(c, i)| {
7584 if c == '\n' {
7585 Some(
7586 snapshot.buffer_snapshot().anchor_after(i)
7587 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7588 )
7589 } else {
7590 None
7591 }
7592 })
7593 .collect::<Vec<_>>();
7594 let existing_newlines = snapshot
7595 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7596 .filter_map(|fold| {
7597 if fold.placeholder.type_tag == Some(type_id) {
7598 Some(fold.range.start..fold.range.end)
7599 } else {
7600 None
7601 }
7602 })
7603 .collect::<Vec<_>>();
7604
7605 (new_newlines, existing_newlines)
7606 });
7607 self.folding_newlines = cx.spawn(async move |this, cx| {
7608 let (new_newlines, existing_newlines) = task.await;
7609 if new_newlines == existing_newlines {
7610 return;
7611 }
7612 let placeholder = FoldPlaceholder {
7613 render: Arc::new(move |_, _, cx| {
7614 div()
7615 .bg(cx.theme().status().hint_background)
7616 .border_b_1()
7617 .size_full()
7618 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7619 .border_color(cx.theme().status().hint)
7620 .child("\\n")
7621 .into_any()
7622 }),
7623 constrain_width: false,
7624 merge_adjacent: false,
7625 type_tag: Some(type_id),
7626 collapsed_text: None,
7627 };
7628 let creases = new_newlines
7629 .into_iter()
7630 .map(|range| Crease::simple(range, placeholder.clone()))
7631 .collect();
7632 this.update(cx, |this, cx| {
7633 this.display_map.update(cx, |display_map, cx| {
7634 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7635 display_map.fold(creases, cx);
7636 });
7637 })
7638 .ok();
7639 });
7640 }
7641
7642 #[ztracing::instrument(skip_all)]
7643 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7644 if !self.mode.is_full() {
7645 return;
7646 }
7647 let cursor = self.selections.newest_anchor().head();
7648 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7649
7650 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7651 self.outline_symbols_at_cursor =
7652 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7653 cx.emit(EditorEvent::OutlineSymbolsChanged);
7654 cx.notify();
7655 } else {
7656 let syntax = cx.theme().syntax().clone();
7657 let background_task = cx.background_spawn(async move {
7658 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7659 });
7660 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7661 cx.spawn(async move |this, cx| {
7662 let symbols = background_task.await;
7663 this.update(cx, |this, cx| {
7664 this.outline_symbols_at_cursor = symbols;
7665 cx.emit(EditorEvent::OutlineSymbolsChanged);
7666 cx.notify();
7667 })
7668 .ok();
7669 });
7670 }
7671 }
7672
7673 #[ztracing::instrument(skip_all)]
7674 fn refresh_selected_text_highlights(
7675 &mut self,
7676 on_buffer_edit: bool,
7677 window: &mut Window,
7678 cx: &mut Context<Editor>,
7679 ) {
7680 let Some((query_text, query_range)) =
7681 self.prepare_highlight_query_from_selection(window, cx)
7682 else {
7683 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7684 self.quick_selection_highlight_task.take();
7685 self.debounced_selection_highlight_task.take();
7686 self.debounced_selection_highlight_complete = false;
7687 return;
7688 };
7689 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7690 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7691 let query_changed = self
7692 .quick_selection_highlight_task
7693 .as_ref()
7694 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7695 if query_changed {
7696 self.debounced_selection_highlight_complete = false;
7697 }
7698 if on_buffer_edit || query_changed {
7699 let multi_buffer_visible_start = self
7700 .scroll_manager
7701 .native_anchor(&display_snapshot, cx)
7702 .anchor
7703 .to_point(&multi_buffer_snapshot);
7704 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7705 multi_buffer_visible_start
7706 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7707 Bias::Left,
7708 );
7709 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7710 self.quick_selection_highlight_task = Some((
7711 query_range.clone(),
7712 self.update_selection_occurrence_highlights(
7713 query_text.clone(),
7714 query_range.clone(),
7715 multi_buffer_visible_range,
7716 false,
7717 window,
7718 cx,
7719 ),
7720 ));
7721 }
7722 if on_buffer_edit
7723 || self
7724 .debounced_selection_highlight_task
7725 .as_ref()
7726 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7727 {
7728 let multi_buffer_start = multi_buffer_snapshot
7729 .anchor_before(MultiBufferOffset(0))
7730 .to_point(&multi_buffer_snapshot);
7731 let multi_buffer_end = multi_buffer_snapshot
7732 .anchor_after(multi_buffer_snapshot.len())
7733 .to_point(&multi_buffer_snapshot);
7734 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7735 self.debounced_selection_highlight_task = Some((
7736 query_range.clone(),
7737 self.update_selection_occurrence_highlights(
7738 query_text,
7739 query_range,
7740 multi_buffer_full_range,
7741 true,
7742 window,
7743 cx,
7744 ),
7745 ));
7746 }
7747 }
7748
7749 pub fn refresh_edit_prediction(
7750 &mut self,
7751 debounce: bool,
7752 user_requested: bool,
7753 window: &mut Window,
7754 cx: &mut Context<Self>,
7755 ) -> Option<()> {
7756 let provider = self.edit_prediction_provider()?;
7757 let cursor = self.selections.newest_anchor().head();
7758 let (buffer, cursor_buffer_position) =
7759 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7760
7761 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7762 return None;
7763 }
7764
7765 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7766 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7767 return None;
7768 }
7769
7770 self.update_visible_edit_prediction(window, cx);
7771
7772 if !user_requested
7773 && (!self.should_show_edit_predictions()
7774 || !self.is_focused(window)
7775 || buffer.read(cx).is_empty())
7776 {
7777 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7778 return None;
7779 }
7780
7781 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7782 Some(())
7783 }
7784
7785 fn show_edit_predictions_in_menu(&self) -> bool {
7786 match self.edit_prediction_settings {
7787 EditPredictionSettings::Disabled => false,
7788 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7789 }
7790 }
7791
7792 pub fn edit_predictions_enabled(&self) -> bool {
7793 match self.edit_prediction_settings {
7794 EditPredictionSettings::Disabled => false,
7795 EditPredictionSettings::Enabled { .. } => true,
7796 }
7797 }
7798
7799 fn edit_prediction_requires_modifier(&self) -> bool {
7800 match self.edit_prediction_settings {
7801 EditPredictionSettings::Disabled => false,
7802 EditPredictionSettings::Enabled {
7803 preview_requires_modifier,
7804 ..
7805 } => preview_requires_modifier,
7806 }
7807 }
7808
7809 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7810 if self.edit_prediction_provider.is_none() {
7811 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7812 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7813 return;
7814 }
7815
7816 let selection = self.selections.newest_anchor();
7817 let cursor = selection.head();
7818
7819 if let Some((buffer, cursor_buffer_position)) =
7820 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7821 {
7822 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7823 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7824 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7825 return;
7826 }
7827 self.edit_prediction_settings =
7828 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7829 }
7830 }
7831
7832 fn edit_prediction_settings_at_position(
7833 &self,
7834 buffer: &Entity<Buffer>,
7835 buffer_position: language::Anchor,
7836 cx: &App,
7837 ) -> EditPredictionSettings {
7838 if !self.mode.is_full()
7839 || !self.show_edit_predictions_override.unwrap_or(true)
7840 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7841 {
7842 return EditPredictionSettings::Disabled;
7843 }
7844
7845 let buffer = buffer.read(cx);
7846
7847 let file = buffer.file();
7848
7849 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7850 return EditPredictionSettings::Disabled;
7851 };
7852
7853 let by_provider = matches!(
7854 self.menu_edit_predictions_policy,
7855 MenuEditPredictionsPolicy::ByProvider
7856 );
7857
7858 let show_in_menu = by_provider
7859 && self
7860 .edit_prediction_provider
7861 .as_ref()
7862 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7863
7864 let preview_requires_modifier =
7865 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7866
7867 EditPredictionSettings::Enabled {
7868 show_in_menu,
7869 preview_requires_modifier,
7870 }
7871 }
7872
7873 fn should_show_edit_predictions(&self) -> bool {
7874 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7875 }
7876
7877 pub fn edit_prediction_preview_is_active(&self) -> bool {
7878 matches!(
7879 self.edit_prediction_preview,
7880 EditPredictionPreview::Active { .. }
7881 )
7882 }
7883
7884 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7885 let cursor = self.selections.newest_anchor().head();
7886 if let Some((buffer, cursor_position)) =
7887 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7888 {
7889 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7890 } else {
7891 false
7892 }
7893 }
7894
7895 pub fn supports_minimap(&self, cx: &App) -> bool {
7896 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7897 }
7898
7899 fn edit_predictions_enabled_in_buffer(
7900 &self,
7901 buffer: &Entity<Buffer>,
7902 buffer_position: language::Anchor,
7903 cx: &App,
7904 ) -> bool {
7905 maybe!({
7906 if self.read_only(cx) {
7907 return Some(false);
7908 }
7909 let provider = self.edit_prediction_provider()?;
7910 if !provider.is_enabled(buffer, buffer_position, cx) {
7911 return Some(false);
7912 }
7913 let buffer = buffer.read(cx);
7914 let Some(file) = buffer.file() else {
7915 return Some(true);
7916 };
7917 let settings = all_language_settings(Some(file), cx);
7918 Some(settings.edit_predictions_enabled_for_file(file, cx))
7919 })
7920 .unwrap_or(false)
7921 }
7922
7923 pub fn show_edit_prediction(
7924 &mut self,
7925 _: &ShowEditPrediction,
7926 window: &mut Window,
7927 cx: &mut Context<Self>,
7928 ) {
7929 if !self.has_active_edit_prediction() {
7930 self.refresh_edit_prediction(false, true, window, cx);
7931 return;
7932 }
7933
7934 self.update_visible_edit_prediction(window, cx);
7935 }
7936
7937 pub fn display_cursor_names(
7938 &mut self,
7939 _: &DisplayCursorNames,
7940 window: &mut Window,
7941 cx: &mut Context<Self>,
7942 ) {
7943 self.show_cursor_names(window, cx);
7944 }
7945
7946 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7947 self.show_cursor_names = true;
7948 cx.notify();
7949 cx.spawn_in(window, async move |this, cx| {
7950 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7951 this.update(cx, |this, cx| {
7952 this.show_cursor_names = false;
7953 cx.notify()
7954 })
7955 .ok()
7956 })
7957 .detach();
7958 }
7959
7960 pub fn accept_partial_edit_prediction(
7961 &mut self,
7962 granularity: EditPredictionGranularity,
7963 window: &mut Window,
7964 cx: &mut Context<Self>,
7965 ) {
7966 if self.show_edit_predictions_in_menu() {
7967 self.hide_context_menu(window, cx);
7968 }
7969
7970 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7971 return;
7972 };
7973
7974 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7975 return;
7976 }
7977
7978 match &active_edit_prediction.completion {
7979 EditPrediction::MoveWithin { target, .. } => {
7980 let target = *target;
7981
7982 if matches!(granularity, EditPredictionGranularity::Full) {
7983 if let Some(position_map) = &self.last_position_map {
7984 let target_row = target.to_display_point(&position_map.snapshot).row();
7985 let is_visible = position_map.visible_row_range.contains(&target_row);
7986
7987 if is_visible || !self.edit_prediction_requires_modifier() {
7988 self.unfold_ranges(&[target..target], true, false, cx);
7989 self.change_selections(
7990 SelectionEffects::scroll(Autoscroll::newest()),
7991 window,
7992 cx,
7993 |selections| {
7994 selections.select_anchor_ranges([target..target]);
7995 },
7996 );
7997 self.clear_row_highlights::<EditPredictionPreview>();
7998 self.edit_prediction_preview
7999 .set_previous_scroll_position(None);
8000 } else {
8001 // Highlight and request scroll
8002 self.edit_prediction_preview
8003 .set_previous_scroll_position(Some(
8004 position_map.snapshot.scroll_anchor,
8005 ));
8006 self.highlight_rows::<EditPredictionPreview>(
8007 target..target,
8008 cx.theme().colors().editor_highlighted_line_background,
8009 RowHighlightOptions {
8010 autoscroll: true,
8011 ..Default::default()
8012 },
8013 cx,
8014 );
8015 self.request_autoscroll(Autoscroll::fit(), cx);
8016 }
8017 }
8018 } else {
8019 self.change_selections(
8020 SelectionEffects::scroll(Autoscroll::newest()),
8021 window,
8022 cx,
8023 |selections| {
8024 selections.select_anchor_ranges([target..target]);
8025 },
8026 );
8027 }
8028 }
8029 EditPrediction::MoveOutside { snapshot, target } => {
8030 if let Some(workspace) = self.workspace() {
8031 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8032 .detach_and_log_err(cx);
8033 }
8034 }
8035 EditPrediction::Edit {
8036 edits,
8037 cursor_position,
8038 ..
8039 } => {
8040 self.report_edit_prediction_event(
8041 active_edit_prediction.completion_id.clone(),
8042 true,
8043 cx,
8044 );
8045
8046 match granularity {
8047 EditPredictionGranularity::Full => {
8048 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8049
8050 // Compute fallback cursor position BEFORE applying the edit,
8051 // so the anchor tracks through the edit correctly
8052 let fallback_cursor_target = {
8053 let snapshot = self.buffer.read(cx).snapshot(cx);
8054 edits.last().unwrap().0.end.bias_right(&snapshot)
8055 };
8056
8057 self.buffer.update(cx, |buffer, cx| {
8058 buffer.edit(edits.iter().cloned(), None, cx)
8059 });
8060
8061 if let Some(provider) = self.edit_prediction_provider() {
8062 provider.accept(cx);
8063 }
8064
8065 // Resolve cursor position after the edit is applied
8066 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8067 // The anchor tracks through the edit, then we add the offset
8068 let snapshot = self.buffer.read(cx).snapshot(cx);
8069 let base_offset = anchor.to_offset(&snapshot).0;
8070 let target_offset =
8071 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8072 snapshot.anchor_after(target_offset)
8073 } else {
8074 fallback_cursor_target
8075 };
8076
8077 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8078 s.select_anchor_ranges([cursor_target..cursor_target]);
8079 });
8080
8081 let selections = self.selections.disjoint_anchors_arc();
8082 if let Some(transaction_id_now) =
8083 self.buffer.read(cx).last_transaction_id(cx)
8084 {
8085 if transaction_id_prev != Some(transaction_id_now) {
8086 self.selection_history
8087 .insert_transaction(transaction_id_now, selections);
8088 }
8089 }
8090
8091 self.update_visible_edit_prediction(window, cx);
8092 if self.active_edit_prediction.is_none() {
8093 self.refresh_edit_prediction(true, true, window, cx);
8094 }
8095 cx.notify();
8096 }
8097 _ => {
8098 let snapshot = self.buffer.read(cx).snapshot(cx);
8099 let cursor_offset = self
8100 .selections
8101 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8102 .head();
8103
8104 let insertion = edits.iter().find_map(|(range, text)| {
8105 let range = range.to_offset(&snapshot);
8106 if range.is_empty() && range.start == cursor_offset {
8107 Some(text)
8108 } else {
8109 None
8110 }
8111 });
8112
8113 if let Some(text) = insertion {
8114 let text_to_insert = match granularity {
8115 EditPredictionGranularity::Word => {
8116 let mut partial = text
8117 .chars()
8118 .by_ref()
8119 .take_while(|c| c.is_alphabetic())
8120 .collect::<String>();
8121 if partial.is_empty() {
8122 partial = text
8123 .chars()
8124 .by_ref()
8125 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8126 .collect::<String>();
8127 }
8128 partial
8129 }
8130 EditPredictionGranularity::Line => {
8131 if let Some(line) = text.split_inclusive('\n').next() {
8132 line.to_string()
8133 } else {
8134 text.to_string()
8135 }
8136 }
8137 EditPredictionGranularity::Full => unreachable!(),
8138 };
8139
8140 cx.emit(EditorEvent::InputHandled {
8141 utf16_range_to_replace: None,
8142 text: text_to_insert.clone().into(),
8143 });
8144
8145 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
8146 self.refresh_edit_prediction(true, true, window, cx);
8147 cx.notify();
8148 } else {
8149 self.accept_partial_edit_prediction(
8150 EditPredictionGranularity::Full,
8151 window,
8152 cx,
8153 );
8154 }
8155 }
8156 }
8157 }
8158 }
8159
8160 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8161 }
8162
8163 pub fn accept_next_word_edit_prediction(
8164 &mut self,
8165 _: &AcceptNextWordEditPrediction,
8166 window: &mut Window,
8167 cx: &mut Context<Self>,
8168 ) {
8169 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8170 }
8171
8172 pub fn accept_next_line_edit_prediction(
8173 &mut self,
8174 _: &AcceptNextLineEditPrediction,
8175 window: &mut Window,
8176 cx: &mut Context<Self>,
8177 ) {
8178 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8179 }
8180
8181 pub fn accept_edit_prediction(
8182 &mut self,
8183 _: &AcceptEditPrediction,
8184 window: &mut Window,
8185 cx: &mut Context<Self>,
8186 ) {
8187 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8188 }
8189
8190 fn discard_edit_prediction(
8191 &mut self,
8192 reason: EditPredictionDiscardReason,
8193 cx: &mut Context<Self>,
8194 ) -> bool {
8195 if reason == EditPredictionDiscardReason::Rejected {
8196 let completion_id = self
8197 .active_edit_prediction
8198 .as_ref()
8199 .and_then(|active_completion| active_completion.completion_id.clone());
8200
8201 self.report_edit_prediction_event(completion_id, false, cx);
8202 }
8203
8204 if let Some(provider) = self.edit_prediction_provider() {
8205 provider.discard(reason, cx);
8206 }
8207
8208 self.take_active_edit_prediction(cx)
8209 }
8210
8211 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8212 let Some(provider) = self.edit_prediction_provider() else {
8213 return;
8214 };
8215
8216 let Some((_, buffer, _)) = self
8217 .buffer
8218 .read(cx)
8219 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8220 else {
8221 return;
8222 };
8223
8224 let extension = buffer
8225 .read(cx)
8226 .file()
8227 .and_then(|file| Some(file.path().extension()?.to_string()));
8228
8229 let event_type = match accepted {
8230 true => "Edit Prediction Accepted",
8231 false => "Edit Prediction Discarded",
8232 };
8233 telemetry::event!(
8234 event_type,
8235 provider = provider.name(),
8236 prediction_id = id,
8237 suggestion_accepted = accepted,
8238 file_extension = extension,
8239 );
8240 }
8241
8242 fn open_editor_at_anchor(
8243 snapshot: &language::BufferSnapshot,
8244 target: language::Anchor,
8245 workspace: &Entity<Workspace>,
8246 window: &mut Window,
8247 cx: &mut App,
8248 ) -> Task<Result<()>> {
8249 workspace.update(cx, |workspace, cx| {
8250 let path = snapshot.file().map(|file| file.full_path(cx));
8251 let Some(path) =
8252 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8253 else {
8254 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8255 };
8256 let target = text::ToPoint::to_point(&target, snapshot);
8257 let item = workspace.open_path(path, None, true, window, cx);
8258 window.spawn(cx, async move |cx| {
8259 let Some(editor) = item.await?.downcast::<Editor>() else {
8260 return Ok(());
8261 };
8262 editor
8263 .update_in(cx, |editor, window, cx| {
8264 editor.go_to_singleton_buffer_point(target, window, cx);
8265 })
8266 .ok();
8267 anyhow::Ok(())
8268 })
8269 })
8270 }
8271
8272 pub fn has_active_edit_prediction(&self) -> bool {
8273 self.active_edit_prediction.is_some()
8274 }
8275
8276 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8277 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8278 return false;
8279 };
8280
8281 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8282 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8283 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8284 true
8285 }
8286
8287 /// Returns true when we're displaying the edit prediction popover below the cursor
8288 /// like we are not previewing and the LSP autocomplete menu is visible
8289 /// or we are in `when_holding_modifier` mode.
8290 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8291 if self.edit_prediction_preview_is_active()
8292 || !self.show_edit_predictions_in_menu()
8293 || !self.edit_predictions_enabled()
8294 {
8295 return false;
8296 }
8297
8298 if self.has_visible_completions_menu() {
8299 return true;
8300 }
8301
8302 has_completion && self.edit_prediction_requires_modifier()
8303 }
8304
8305 fn handle_modifiers_changed(
8306 &mut self,
8307 modifiers: Modifiers,
8308 position_map: &PositionMap,
8309 window: &mut Window,
8310 cx: &mut Context<Self>,
8311 ) {
8312 // Ensure that the edit prediction preview is updated, even when not
8313 // enabled, if there's an active edit prediction preview.
8314 if self.show_edit_predictions_in_menu()
8315 || matches!(
8316 self.edit_prediction_preview,
8317 EditPredictionPreview::Active { .. }
8318 )
8319 {
8320 self.update_edit_prediction_preview(&modifiers, window, cx);
8321 }
8322
8323 self.update_selection_mode(&modifiers, position_map, window, cx);
8324
8325 let mouse_position = window.mouse_position();
8326 if !position_map.text_hitbox.is_hovered(window) {
8327 return;
8328 }
8329
8330 self.update_hovered_link(
8331 position_map.point_for_position(mouse_position),
8332 &position_map.snapshot,
8333 modifiers,
8334 window,
8335 cx,
8336 )
8337 }
8338
8339 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8340 match EditorSettings::get_global(cx).multi_cursor_modifier {
8341 MultiCursorModifier::Alt => modifiers.secondary(),
8342 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8343 }
8344 }
8345
8346 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8347 match EditorSettings::get_global(cx).multi_cursor_modifier {
8348 MultiCursorModifier::Alt => modifiers.alt,
8349 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8350 }
8351 }
8352
8353 fn columnar_selection_mode(
8354 modifiers: &Modifiers,
8355 cx: &mut Context<Self>,
8356 ) -> Option<ColumnarMode> {
8357 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8358 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8359 Some(ColumnarMode::FromMouse)
8360 } else if Self::is_alt_pressed(modifiers, cx) {
8361 Some(ColumnarMode::FromSelection)
8362 } else {
8363 None
8364 }
8365 } else {
8366 None
8367 }
8368 }
8369
8370 fn update_selection_mode(
8371 &mut self,
8372 modifiers: &Modifiers,
8373 position_map: &PositionMap,
8374 window: &mut Window,
8375 cx: &mut Context<Self>,
8376 ) {
8377 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8378 return;
8379 };
8380 if self.selections.pending_anchor().is_none() {
8381 return;
8382 }
8383
8384 let mouse_position = window.mouse_position();
8385 let point_for_position = position_map.point_for_position(mouse_position);
8386 let position = point_for_position.previous_valid;
8387
8388 self.select(
8389 SelectPhase::BeginColumnar {
8390 position,
8391 reset: false,
8392 mode,
8393 goal_column: point_for_position.exact_unclipped.column(),
8394 },
8395 window,
8396 cx,
8397 );
8398 }
8399
8400 fn update_edit_prediction_preview(
8401 &mut self,
8402 modifiers: &Modifiers,
8403 window: &mut Window,
8404 cx: &mut Context<Self>,
8405 ) {
8406 let mut modifiers_held = false;
8407
8408 // Check bindings for all granularities.
8409 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8410 let granularities = [
8411 EditPredictionGranularity::Full,
8412 EditPredictionGranularity::Line,
8413 EditPredictionGranularity::Word,
8414 ];
8415
8416 for granularity in granularities {
8417 if let Some(keystroke) = self
8418 .accept_edit_prediction_keybind(granularity, window, cx)
8419 .keystroke()
8420 {
8421 modifiers_held = modifiers_held
8422 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8423 }
8424 }
8425
8426 if modifiers_held {
8427 if matches!(
8428 self.edit_prediction_preview,
8429 EditPredictionPreview::Inactive { .. }
8430 ) {
8431 self.edit_prediction_preview = EditPredictionPreview::Active {
8432 previous_scroll_position: None,
8433 since: Instant::now(),
8434 };
8435
8436 self.update_visible_edit_prediction(window, cx);
8437 cx.notify();
8438 }
8439 } else if let EditPredictionPreview::Active {
8440 previous_scroll_position,
8441 since,
8442 } = self.edit_prediction_preview
8443 {
8444 if let (Some(previous_scroll_position), Some(position_map)) =
8445 (previous_scroll_position, self.last_position_map.as_ref())
8446 {
8447 self.set_scroll_position(
8448 previous_scroll_position
8449 .scroll_position(&position_map.snapshot.display_snapshot),
8450 window,
8451 cx,
8452 );
8453 }
8454
8455 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8456 released_too_fast: since.elapsed() < Duration::from_millis(200),
8457 };
8458 self.clear_row_highlights::<EditPredictionPreview>();
8459 self.update_visible_edit_prediction(window, cx);
8460 cx.notify();
8461 }
8462 }
8463
8464 fn update_visible_edit_prediction(
8465 &mut self,
8466 _window: &mut Window,
8467 cx: &mut Context<Self>,
8468 ) -> Option<()> {
8469 if self.ime_transaction.is_some() {
8470 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8471 return None;
8472 }
8473
8474 let selection = self.selections.newest_anchor();
8475 let cursor = selection.head();
8476 let multibuffer = self.buffer.read(cx).snapshot(cx);
8477
8478 // Check project-level disable_ai setting for the current buffer
8479 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8480 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8481 return None;
8482 }
8483 }
8484 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8485 let excerpt_id = cursor.excerpt_id;
8486
8487 let show_in_menu = self.show_edit_predictions_in_menu();
8488 let completions_menu_has_precedence = !show_in_menu
8489 && (self.context_menu.borrow().is_some()
8490 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8491
8492 if completions_menu_has_precedence
8493 || !offset_selection.is_empty()
8494 || self
8495 .active_edit_prediction
8496 .as_ref()
8497 .is_some_and(|completion| {
8498 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8499 return false;
8500 };
8501 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8502 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8503 !invalidation_range.contains(&offset_selection.head())
8504 })
8505 {
8506 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8507 return None;
8508 }
8509
8510 self.take_active_edit_prediction(cx);
8511 let Some(provider) = self.edit_prediction_provider() else {
8512 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8513 return None;
8514 };
8515
8516 let (buffer, cursor_buffer_position) =
8517 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8518
8519 self.edit_prediction_settings =
8520 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8521
8522 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8523
8524 if self.edit_prediction_indent_conflict {
8525 let cursor_point = cursor.to_point(&multibuffer);
8526 let mut suggested_indent = None;
8527 multibuffer.suggested_indents_callback(
8528 cursor_point.row..cursor_point.row + 1,
8529 &mut |_, indent| {
8530 suggested_indent = Some(indent);
8531 ControlFlow::Break(())
8532 },
8533 cx,
8534 );
8535
8536 if let Some(indent) = suggested_indent
8537 && indent.len == cursor_point.column
8538 {
8539 self.edit_prediction_indent_conflict = false;
8540 }
8541 }
8542
8543 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8544
8545 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8546 {
8547 edit_prediction_types::EditPrediction::Local {
8548 id,
8549 edits,
8550 cursor_position,
8551 edit_preview,
8552 } => (id, edits, cursor_position, edit_preview),
8553 edit_prediction_types::EditPrediction::Jump {
8554 id,
8555 snapshot,
8556 target,
8557 } => {
8558 if let Some(provider) = &self.edit_prediction_provider {
8559 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8560 }
8561 self.stale_edit_prediction_in_menu = None;
8562 self.active_edit_prediction = Some(EditPredictionState {
8563 inlay_ids: vec![],
8564 completion: EditPrediction::MoveOutside { snapshot, target },
8565 completion_id: id,
8566 invalidation_range: None,
8567 });
8568 cx.notify();
8569 return Some(());
8570 }
8571 };
8572
8573 let edits = edits
8574 .into_iter()
8575 .flat_map(|(range, new_text)| {
8576 Some((
8577 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8578 new_text,
8579 ))
8580 })
8581 .collect::<Vec<_>>();
8582 if edits.is_empty() {
8583 return None;
8584 }
8585
8586 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8587 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8588 Some((anchor, predicted.offset))
8589 });
8590
8591 let first_edit_start = edits.first().unwrap().0.start;
8592 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8593 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8594
8595 let last_edit_end = edits.last().unwrap().0.end;
8596 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8597 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8598
8599 let cursor_row = cursor.to_point(&multibuffer).row;
8600
8601 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8602
8603 let mut inlay_ids = Vec::new();
8604 let invalidation_row_range;
8605 let move_invalidation_row_range = if cursor_row < edit_start_row {
8606 Some(cursor_row..edit_end_row)
8607 } else if cursor_row > edit_end_row {
8608 Some(edit_start_row..cursor_row)
8609 } else {
8610 None
8611 };
8612 let supports_jump = self
8613 .edit_prediction_provider
8614 .as_ref()
8615 .map(|provider| provider.provider.supports_jump_to_edit())
8616 .unwrap_or(true);
8617
8618 let is_move = supports_jump
8619 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8620 let completion = if is_move {
8621 if let Some(provider) = &self.edit_prediction_provider {
8622 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8623 }
8624 invalidation_row_range =
8625 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8626 let target = first_edit_start;
8627 EditPrediction::MoveWithin { target, snapshot }
8628 } else {
8629 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8630 && !self.edit_predictions_hidden_for_vim_mode;
8631
8632 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8633 if provider.show_tab_accept_marker() {
8634 EditDisplayMode::TabAccept
8635 } else {
8636 EditDisplayMode::Inline
8637 }
8638 } else {
8639 EditDisplayMode::DiffPopover
8640 };
8641
8642 if show_completions_in_buffer {
8643 if let Some(provider) = &self.edit_prediction_provider {
8644 let suggestion_display_type = match display_mode {
8645 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8646 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8647 SuggestionDisplayType::GhostText
8648 }
8649 };
8650 provider.provider.did_show(suggestion_display_type, cx);
8651 }
8652 if edits
8653 .iter()
8654 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8655 {
8656 let mut inlays = Vec::new();
8657 for (range, new_text) in &edits {
8658 let inlay = Inlay::edit_prediction(
8659 post_inc(&mut self.next_inlay_id),
8660 range.start,
8661 new_text.as_ref(),
8662 );
8663 inlay_ids.push(inlay.id);
8664 inlays.push(inlay);
8665 }
8666
8667 self.splice_inlays(&[], inlays, cx);
8668 } else {
8669 let background_color = cx.theme().status().deleted_background;
8670 self.highlight_text(
8671 HighlightKey::EditPredictionHighlight,
8672 edits.iter().map(|(range, _)| range.clone()).collect(),
8673 HighlightStyle {
8674 background_color: Some(background_color),
8675 ..Default::default()
8676 },
8677 cx,
8678 );
8679 }
8680 }
8681
8682 invalidation_row_range = edit_start_row..edit_end_row;
8683
8684 EditPrediction::Edit {
8685 edits,
8686 cursor_position,
8687 edit_preview,
8688 display_mode,
8689 snapshot,
8690 }
8691 };
8692
8693 let invalidation_range = multibuffer
8694 .anchor_before(Point::new(invalidation_row_range.start, 0))
8695 ..multibuffer.anchor_after(Point::new(
8696 invalidation_row_range.end,
8697 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8698 ));
8699
8700 self.stale_edit_prediction_in_menu = None;
8701 self.active_edit_prediction = Some(EditPredictionState {
8702 inlay_ids,
8703 completion,
8704 completion_id,
8705 invalidation_range: Some(invalidation_range),
8706 });
8707
8708 cx.notify();
8709
8710 Some(())
8711 }
8712
8713 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8714 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8715 }
8716
8717 fn clear_tasks(&mut self) {
8718 self.tasks.clear()
8719 }
8720
8721 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8722 if self.tasks.insert(key, value).is_some() {
8723 // This case should hopefully be rare, but just in case...
8724 log::error!(
8725 "multiple different run targets found on a single line, only the last target will be rendered"
8726 )
8727 }
8728 }
8729
8730 /// Get all display points of breakpoints that will be rendered within editor
8731 ///
8732 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8733 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8734 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8735 fn active_breakpoints(
8736 &self,
8737 range: Range<DisplayRow>,
8738 window: &mut Window,
8739 cx: &mut Context<Self>,
8740 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8741 let mut breakpoint_display_points = HashMap::default();
8742
8743 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8744 return breakpoint_display_points;
8745 };
8746
8747 let snapshot = self.snapshot(window, cx);
8748
8749 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8750 let Some(project) = self.project() else {
8751 return breakpoint_display_points;
8752 };
8753
8754 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8755 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8756
8757 for (buffer_snapshot, range, excerpt_id) in
8758 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8759 {
8760 let Some(buffer) = project
8761 .read(cx)
8762 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8763 else {
8764 continue;
8765 };
8766 let breakpoints = breakpoint_store.read(cx).breakpoints(
8767 &buffer,
8768 Some(
8769 buffer_snapshot.anchor_before(range.start)
8770 ..buffer_snapshot.anchor_after(range.end),
8771 ),
8772 buffer_snapshot,
8773 cx,
8774 );
8775 for (breakpoint, state) in breakpoints {
8776 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8777 let position = multi_buffer_anchor
8778 .to_point(&multi_buffer_snapshot)
8779 .to_display_point(&snapshot);
8780
8781 breakpoint_display_points.insert(
8782 position.row(),
8783 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8784 );
8785 }
8786 }
8787
8788 breakpoint_display_points
8789 }
8790
8791 fn breakpoint_context_menu(
8792 &self,
8793 anchor: Anchor,
8794 window: &mut Window,
8795 cx: &mut Context<Self>,
8796 ) -> Entity<ui::ContextMenu> {
8797 let weak_editor = cx.weak_entity();
8798 let focus_handle = self.focus_handle(cx);
8799
8800 let row = self
8801 .buffer
8802 .read(cx)
8803 .snapshot(cx)
8804 .summary_for_anchor::<Point>(&anchor)
8805 .row;
8806
8807 let breakpoint = self
8808 .breakpoint_at_row(row, window, cx)
8809 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8810
8811 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8812 "Edit Log Breakpoint"
8813 } else {
8814 "Set Log Breakpoint"
8815 };
8816
8817 let condition_breakpoint_msg = if breakpoint
8818 .as_ref()
8819 .is_some_and(|bp| bp.1.condition.is_some())
8820 {
8821 "Edit Condition Breakpoint"
8822 } else {
8823 "Set Condition Breakpoint"
8824 };
8825
8826 let hit_condition_breakpoint_msg = if breakpoint
8827 .as_ref()
8828 .is_some_and(|bp| bp.1.hit_condition.is_some())
8829 {
8830 "Edit Hit Condition Breakpoint"
8831 } else {
8832 "Set Hit Condition Breakpoint"
8833 };
8834
8835 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8836 "Unset Breakpoint"
8837 } else {
8838 "Set Breakpoint"
8839 };
8840
8841 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8842
8843 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8844 BreakpointState::Enabled => Some("Disable"),
8845 BreakpointState::Disabled => Some("Enable"),
8846 });
8847
8848 let (anchor, breakpoint) =
8849 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8850
8851 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8852 menu.on_blur_subscription(Subscription::new(|| {}))
8853 .context(focus_handle)
8854 .when(run_to_cursor, |this| {
8855 let weak_editor = weak_editor.clone();
8856 this.entry("Run to cursor", None, move |window, cx| {
8857 weak_editor
8858 .update(cx, |editor, cx| {
8859 editor.change_selections(
8860 SelectionEffects::no_scroll(),
8861 window,
8862 cx,
8863 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8864 );
8865 })
8866 .ok();
8867
8868 window.dispatch_action(Box::new(RunToCursor), cx);
8869 })
8870 .separator()
8871 })
8872 .when_some(toggle_state_msg, |this, msg| {
8873 this.entry(msg, None, {
8874 let weak_editor = weak_editor.clone();
8875 let breakpoint = breakpoint.clone();
8876 move |_window, cx| {
8877 weak_editor
8878 .update(cx, |this, cx| {
8879 this.edit_breakpoint_at_anchor(
8880 anchor,
8881 breakpoint.as_ref().clone(),
8882 BreakpointEditAction::InvertState,
8883 cx,
8884 );
8885 })
8886 .log_err();
8887 }
8888 })
8889 })
8890 .entry(set_breakpoint_msg, None, {
8891 let weak_editor = weak_editor.clone();
8892 let breakpoint = breakpoint.clone();
8893 move |_window, cx| {
8894 weak_editor
8895 .update(cx, |this, cx| {
8896 this.edit_breakpoint_at_anchor(
8897 anchor,
8898 breakpoint.as_ref().clone(),
8899 BreakpointEditAction::Toggle,
8900 cx,
8901 );
8902 })
8903 .log_err();
8904 }
8905 })
8906 .entry(log_breakpoint_msg, None, {
8907 let breakpoint = breakpoint.clone();
8908 let weak_editor = weak_editor.clone();
8909 move |window, cx| {
8910 weak_editor
8911 .update(cx, |this, cx| {
8912 this.add_edit_breakpoint_block(
8913 anchor,
8914 breakpoint.as_ref(),
8915 BreakpointPromptEditAction::Log,
8916 window,
8917 cx,
8918 );
8919 })
8920 .log_err();
8921 }
8922 })
8923 .entry(condition_breakpoint_msg, None, {
8924 let breakpoint = breakpoint.clone();
8925 let weak_editor = weak_editor.clone();
8926 move |window, cx| {
8927 weak_editor
8928 .update(cx, |this, cx| {
8929 this.add_edit_breakpoint_block(
8930 anchor,
8931 breakpoint.as_ref(),
8932 BreakpointPromptEditAction::Condition,
8933 window,
8934 cx,
8935 );
8936 })
8937 .log_err();
8938 }
8939 })
8940 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8941 weak_editor
8942 .update(cx, |this, cx| {
8943 this.add_edit_breakpoint_block(
8944 anchor,
8945 breakpoint.as_ref(),
8946 BreakpointPromptEditAction::HitCondition,
8947 window,
8948 cx,
8949 );
8950 })
8951 .log_err();
8952 })
8953 })
8954 }
8955
8956 fn render_breakpoint(
8957 &self,
8958 position: Anchor,
8959 row: DisplayRow,
8960 breakpoint: &Breakpoint,
8961 state: Option<BreakpointSessionState>,
8962 cx: &mut Context<Self>,
8963 ) -> IconButton {
8964 let is_rejected = state.is_some_and(|s| !s.verified);
8965 // Is it a breakpoint that shows up when hovering over gutter?
8966 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8967 (false, false),
8968 |PhantomBreakpointIndicator {
8969 is_active,
8970 display_row,
8971 collides_with_existing_breakpoint,
8972 }| {
8973 (
8974 is_active && display_row == row,
8975 collides_with_existing_breakpoint,
8976 )
8977 },
8978 );
8979
8980 let (color, icon) = {
8981 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8982 (false, false) => ui::IconName::DebugBreakpoint,
8983 (true, false) => ui::IconName::DebugLogBreakpoint,
8984 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8985 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8986 };
8987
8988 let theme_colors = cx.theme().colors();
8989
8990 let color = if is_phantom {
8991 if collides_with_existing {
8992 Color::Custom(
8993 theme_colors
8994 .debugger_accent
8995 .blend(theme_colors.text.opacity(0.6)),
8996 )
8997 } else {
8998 Color::Hint
8999 }
9000 } else if is_rejected {
9001 Color::Disabled
9002 } else {
9003 Color::Debugger
9004 };
9005
9006 (color, icon)
9007 };
9008
9009 let breakpoint = Arc::from(breakpoint.clone());
9010
9011 let alt_as_text = gpui::Keystroke {
9012 modifiers: Modifiers::secondary_key(),
9013 ..Default::default()
9014 };
9015 let primary_action_text = if breakpoint.is_disabled() {
9016 "Enable breakpoint"
9017 } else if is_phantom && !collides_with_existing {
9018 "Set breakpoint"
9019 } else {
9020 "Unset breakpoint"
9021 };
9022 let focus_handle = self.focus_handle.clone();
9023
9024 let meta = if is_rejected {
9025 SharedString::from("No executable code is associated with this line.")
9026 } else if collides_with_existing && !breakpoint.is_disabled() {
9027 SharedString::from(format!(
9028 "{alt_as_text}-click to disable,\nright-click for more options."
9029 ))
9030 } else {
9031 SharedString::from("Right-click for more options.")
9032 };
9033 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9034 .icon_size(IconSize::XSmall)
9035 .size(ui::ButtonSize::None)
9036 .when(is_rejected, |this| {
9037 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9038 })
9039 .icon_color(color)
9040 .style(ButtonStyle::Transparent)
9041 .on_click(cx.listener({
9042 move |editor, event: &ClickEvent, window, cx| {
9043 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9044 BreakpointEditAction::InvertState
9045 } else {
9046 BreakpointEditAction::Toggle
9047 };
9048
9049 window.focus(&editor.focus_handle(cx), cx);
9050 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9051 editor.edit_breakpoint_at_anchor(
9052 position,
9053 breakpoint.as_ref().clone(),
9054 edit_action,
9055 cx,
9056 );
9057 }
9058 }))
9059 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9060 editor.set_breakpoint_context_menu(
9061 row,
9062 Some(position),
9063 event.position(),
9064 window,
9065 cx,
9066 );
9067 }))
9068 .tooltip(move |_window, cx| {
9069 Tooltip::with_meta_in(
9070 primary_action_text,
9071 Some(&ToggleBreakpoint),
9072 meta.clone(),
9073 &focus_handle,
9074 cx,
9075 )
9076 })
9077 }
9078
9079 fn build_tasks_context(
9080 project: &Entity<Project>,
9081 buffer: &Entity<Buffer>,
9082 buffer_row: u32,
9083 tasks: &Arc<RunnableTasks>,
9084 cx: &mut Context<Self>,
9085 ) -> Task<Option<task::TaskContext>> {
9086 let position = Point::new(buffer_row, tasks.column);
9087 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9088 let location = Location {
9089 buffer: buffer.clone(),
9090 range: range_start..range_start,
9091 };
9092 // Fill in the environmental variables from the tree-sitter captures
9093 let mut captured_task_variables = TaskVariables::default();
9094 for (capture_name, value) in tasks.extra_variables.clone() {
9095 captured_task_variables.insert(
9096 task::VariableName::Custom(capture_name.into()),
9097 value.clone(),
9098 );
9099 }
9100 project.update(cx, |project, cx| {
9101 project.task_store().update(cx, |task_store, cx| {
9102 task_store.task_context_for_location(captured_task_variables, location, cx)
9103 })
9104 })
9105 }
9106
9107 pub fn spawn_nearest_task(
9108 &mut self,
9109 action: &SpawnNearestTask,
9110 window: &mut Window,
9111 cx: &mut Context<Self>,
9112 ) {
9113 let Some((workspace, _)) = self.workspace.clone() else {
9114 return;
9115 };
9116 let Some(project) = self.project.clone() else {
9117 return;
9118 };
9119
9120 // Try to find a closest, enclosing node using tree-sitter that has a task
9121 let Some((buffer, buffer_row, tasks)) = self
9122 .find_enclosing_node_task(cx)
9123 // Or find the task that's closest in row-distance.
9124 .or_else(|| self.find_closest_task(cx))
9125 else {
9126 return;
9127 };
9128
9129 let reveal_strategy = action.reveal;
9130 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
9131 cx.spawn_in(window, async move |_, cx| {
9132 let context = task_context.await?;
9133 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
9134
9135 let resolved = &mut resolved_task.resolved;
9136 resolved.reveal = reveal_strategy;
9137
9138 workspace
9139 .update_in(cx, |workspace, window, cx| {
9140 workspace.schedule_resolved_task(
9141 task_source_kind,
9142 resolved_task,
9143 false,
9144 window,
9145 cx,
9146 );
9147 })
9148 .ok()
9149 })
9150 .detach();
9151 }
9152
9153 fn find_closest_task(
9154 &mut self,
9155 cx: &mut Context<Self>,
9156 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9157 let cursor_row = self
9158 .selections
9159 .newest_adjusted(&self.display_snapshot(cx))
9160 .head()
9161 .row;
9162
9163 let ((buffer_id, row), tasks) = self
9164 .tasks
9165 .iter()
9166 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
9167
9168 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
9169 let tasks = Arc::new(tasks.to_owned());
9170 Some((buffer, *row, tasks))
9171 }
9172
9173 fn find_enclosing_node_task(
9174 &mut self,
9175 cx: &mut Context<Self>,
9176 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9177 let snapshot = self.buffer.read(cx).snapshot(cx);
9178 let offset = self
9179 .selections
9180 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
9181 .head();
9182 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
9183 let offset = excerpt.map_offset_to_buffer(offset);
9184 let buffer_id = excerpt.buffer().remote_id();
9185
9186 let layer = excerpt.buffer().syntax_layer_at(offset)?;
9187 let mut cursor = layer.node().walk();
9188
9189 while cursor.goto_first_child_for_byte(offset.0).is_some() {
9190 if cursor.node().end_byte() == offset.0 {
9191 cursor.goto_next_sibling();
9192 }
9193 }
9194
9195 // Ascend to the smallest ancestor that contains the range and has a task.
9196 loop {
9197 let node = cursor.node();
9198 let node_range = node.byte_range();
9199 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9200
9201 // Check if this node contains our offset
9202 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9203 // If it contains offset, check for task
9204 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9205 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9206 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9207 }
9208 }
9209
9210 if !cursor.goto_parent() {
9211 break;
9212 }
9213 }
9214 None
9215 }
9216
9217 fn render_run_indicator(
9218 &self,
9219 _style: &EditorStyle,
9220 is_active: bool,
9221 row: DisplayRow,
9222 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9223 cx: &mut Context<Self>,
9224 ) -> IconButton {
9225 let color = Color::Muted;
9226 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9227
9228 IconButton::new(
9229 ("run_indicator", row.0 as usize),
9230 ui::IconName::PlayOutlined,
9231 )
9232 .shape(ui::IconButtonShape::Square)
9233 .icon_size(IconSize::XSmall)
9234 .icon_color(color)
9235 .toggle_state(is_active)
9236 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9237 let quick_launch = match e {
9238 ClickEvent::Keyboard(_) => true,
9239 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9240 };
9241
9242 window.focus(&editor.focus_handle(cx), cx);
9243 editor.toggle_code_actions(
9244 &ToggleCodeActions {
9245 deployed_from: Some(CodeActionSource::RunMenu(row)),
9246 quick_launch,
9247 },
9248 window,
9249 cx,
9250 );
9251 }))
9252 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9253 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9254 }))
9255 }
9256
9257 pub fn context_menu_visible(&self) -> bool {
9258 !self.edit_prediction_preview_is_active()
9259 && self
9260 .context_menu
9261 .borrow()
9262 .as_ref()
9263 .is_some_and(|menu| menu.visible())
9264 }
9265
9266 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9267 self.context_menu
9268 .borrow()
9269 .as_ref()
9270 .map(|menu| menu.origin())
9271 }
9272
9273 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9274 self.context_menu_options = Some(options);
9275 }
9276
9277 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9278 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9279
9280 fn render_edit_prediction_popover(
9281 &mut self,
9282 text_bounds: &Bounds<Pixels>,
9283 content_origin: gpui::Point<Pixels>,
9284 right_margin: Pixels,
9285 editor_snapshot: &EditorSnapshot,
9286 visible_row_range: Range<DisplayRow>,
9287 scroll_top: ScrollOffset,
9288 scroll_bottom: ScrollOffset,
9289 line_layouts: &[LineWithInvisibles],
9290 line_height: Pixels,
9291 scroll_position: gpui::Point<ScrollOffset>,
9292 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9293 newest_selection_head: Option<DisplayPoint>,
9294 editor_width: Pixels,
9295 style: &EditorStyle,
9296 window: &mut Window,
9297 cx: &mut App,
9298 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9299 if self.mode().is_minimap() {
9300 return None;
9301 }
9302 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9303
9304 if self.edit_prediction_visible_in_cursor_popover(true) {
9305 return None;
9306 }
9307
9308 match &active_edit_prediction.completion {
9309 EditPrediction::MoveWithin { target, .. } => {
9310 let target_display_point = target.to_display_point(editor_snapshot);
9311
9312 if self.edit_prediction_requires_modifier() {
9313 if !self.edit_prediction_preview_is_active() {
9314 return None;
9315 }
9316
9317 self.render_edit_prediction_modifier_jump_popover(
9318 text_bounds,
9319 content_origin,
9320 visible_row_range,
9321 line_layouts,
9322 line_height,
9323 scroll_pixel_position,
9324 newest_selection_head,
9325 target_display_point,
9326 window,
9327 cx,
9328 )
9329 } else {
9330 self.render_edit_prediction_eager_jump_popover(
9331 text_bounds,
9332 content_origin,
9333 editor_snapshot,
9334 visible_row_range,
9335 scroll_top,
9336 scroll_bottom,
9337 line_height,
9338 scroll_pixel_position,
9339 target_display_point,
9340 editor_width,
9341 window,
9342 cx,
9343 )
9344 }
9345 }
9346 EditPrediction::Edit {
9347 display_mode: EditDisplayMode::Inline,
9348 ..
9349 } => None,
9350 EditPrediction::Edit {
9351 display_mode: EditDisplayMode::TabAccept,
9352 edits,
9353 ..
9354 } => {
9355 let range = &edits.first()?.0;
9356 let target_display_point = range.end.to_display_point(editor_snapshot);
9357
9358 self.render_edit_prediction_end_of_line_popover(
9359 "Accept",
9360 editor_snapshot,
9361 visible_row_range,
9362 target_display_point,
9363 line_height,
9364 scroll_pixel_position,
9365 content_origin,
9366 editor_width,
9367 window,
9368 cx,
9369 )
9370 }
9371 EditPrediction::Edit {
9372 edits,
9373 edit_preview,
9374 display_mode: EditDisplayMode::DiffPopover,
9375 snapshot,
9376 ..
9377 } => self.render_edit_prediction_diff_popover(
9378 text_bounds,
9379 content_origin,
9380 right_margin,
9381 editor_snapshot,
9382 visible_row_range,
9383 line_layouts,
9384 line_height,
9385 scroll_position,
9386 scroll_pixel_position,
9387 newest_selection_head,
9388 editor_width,
9389 style,
9390 edits,
9391 edit_preview,
9392 snapshot,
9393 window,
9394 cx,
9395 ),
9396 EditPrediction::MoveOutside { snapshot, .. } => {
9397 let mut element = self
9398 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9399 .into_any();
9400
9401 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9402 let origin_x = text_bounds.size.width - size.width - px(30.);
9403 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9404 element.prepaint_at(origin, window, cx);
9405
9406 Some((element, origin))
9407 }
9408 }
9409 }
9410
9411 fn render_edit_prediction_modifier_jump_popover(
9412 &mut self,
9413 text_bounds: &Bounds<Pixels>,
9414 content_origin: gpui::Point<Pixels>,
9415 visible_row_range: Range<DisplayRow>,
9416 line_layouts: &[LineWithInvisibles],
9417 line_height: Pixels,
9418 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9419 newest_selection_head: Option<DisplayPoint>,
9420 target_display_point: DisplayPoint,
9421 window: &mut Window,
9422 cx: &mut App,
9423 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9424 let scrolled_content_origin =
9425 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9426
9427 const SCROLL_PADDING_Y: Pixels = px(12.);
9428
9429 if target_display_point.row() < visible_row_range.start {
9430 return self.render_edit_prediction_scroll_popover(
9431 |_| SCROLL_PADDING_Y,
9432 IconName::ArrowUp,
9433 visible_row_range,
9434 line_layouts,
9435 newest_selection_head,
9436 scrolled_content_origin,
9437 window,
9438 cx,
9439 );
9440 } else if target_display_point.row() >= visible_row_range.end {
9441 return self.render_edit_prediction_scroll_popover(
9442 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9443 IconName::ArrowDown,
9444 visible_row_range,
9445 line_layouts,
9446 newest_selection_head,
9447 scrolled_content_origin,
9448 window,
9449 cx,
9450 );
9451 }
9452
9453 const POLE_WIDTH: Pixels = px(2.);
9454
9455 let line_layout =
9456 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9457 let target_column = target_display_point.column() as usize;
9458
9459 let target_x = line_layout.x_for_index(target_column);
9460 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9461 - scroll_pixel_position.y;
9462
9463 let flag_on_right = target_x < text_bounds.size.width / 2.;
9464
9465 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9466 border_color.l += 0.001;
9467
9468 let mut element = v_flex()
9469 .items_end()
9470 .when(flag_on_right, |el| el.items_start())
9471 .child(if flag_on_right {
9472 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9473 .rounded_bl(px(0.))
9474 .rounded_tl(px(0.))
9475 .border_l_2()
9476 .border_color(border_color)
9477 } else {
9478 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9479 .rounded_br(px(0.))
9480 .rounded_tr(px(0.))
9481 .border_r_2()
9482 .border_color(border_color)
9483 })
9484 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9485 .into_any();
9486
9487 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9488
9489 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9490 - point(
9491 if flag_on_right {
9492 POLE_WIDTH
9493 } else {
9494 size.width - POLE_WIDTH
9495 },
9496 size.height - line_height,
9497 );
9498
9499 origin.x = origin.x.max(content_origin.x);
9500
9501 element.prepaint_at(origin, window, cx);
9502
9503 Some((element, origin))
9504 }
9505
9506 fn render_edit_prediction_scroll_popover(
9507 &mut self,
9508 to_y: impl Fn(Size<Pixels>) -> Pixels,
9509 scroll_icon: IconName,
9510 visible_row_range: Range<DisplayRow>,
9511 line_layouts: &[LineWithInvisibles],
9512 newest_selection_head: Option<DisplayPoint>,
9513 scrolled_content_origin: gpui::Point<Pixels>,
9514 window: &mut Window,
9515 cx: &mut App,
9516 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9517 let mut element = self
9518 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9519 .into_any();
9520
9521 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9522
9523 let cursor = newest_selection_head?;
9524 let cursor_row_layout =
9525 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9526 let cursor_column = cursor.column() as usize;
9527
9528 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9529
9530 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9531
9532 element.prepaint_at(origin, window, cx);
9533 Some((element, origin))
9534 }
9535
9536 fn render_edit_prediction_eager_jump_popover(
9537 &mut self,
9538 text_bounds: &Bounds<Pixels>,
9539 content_origin: gpui::Point<Pixels>,
9540 editor_snapshot: &EditorSnapshot,
9541 visible_row_range: Range<DisplayRow>,
9542 scroll_top: ScrollOffset,
9543 scroll_bottom: ScrollOffset,
9544 line_height: Pixels,
9545 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9546 target_display_point: DisplayPoint,
9547 editor_width: Pixels,
9548 window: &mut Window,
9549 cx: &mut App,
9550 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9551 if target_display_point.row().as_f64() < scroll_top {
9552 let mut element = self
9553 .render_edit_prediction_line_popover(
9554 "Jump to Edit",
9555 Some(IconName::ArrowUp),
9556 window,
9557 cx,
9558 )
9559 .into_any();
9560
9561 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9562 let offset = point(
9563 (text_bounds.size.width - size.width) / 2.,
9564 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9565 );
9566
9567 let origin = text_bounds.origin + offset;
9568 element.prepaint_at(origin, window, cx);
9569 Some((element, origin))
9570 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9571 let mut element = self
9572 .render_edit_prediction_line_popover(
9573 "Jump to Edit",
9574 Some(IconName::ArrowDown),
9575 window,
9576 cx,
9577 )
9578 .into_any();
9579
9580 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9581 let offset = point(
9582 (text_bounds.size.width - size.width) / 2.,
9583 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9584 );
9585
9586 let origin = text_bounds.origin + offset;
9587 element.prepaint_at(origin, window, cx);
9588 Some((element, origin))
9589 } else {
9590 self.render_edit_prediction_end_of_line_popover(
9591 "Jump to Edit",
9592 editor_snapshot,
9593 visible_row_range,
9594 target_display_point,
9595 line_height,
9596 scroll_pixel_position,
9597 content_origin,
9598 editor_width,
9599 window,
9600 cx,
9601 )
9602 }
9603 }
9604
9605 fn render_edit_prediction_end_of_line_popover(
9606 self: &mut Editor,
9607 label: &'static str,
9608 editor_snapshot: &EditorSnapshot,
9609 visible_row_range: Range<DisplayRow>,
9610 target_display_point: DisplayPoint,
9611 line_height: Pixels,
9612 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9613 content_origin: gpui::Point<Pixels>,
9614 editor_width: Pixels,
9615 window: &mut Window,
9616 cx: &mut App,
9617 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9618 let target_line_end = DisplayPoint::new(
9619 target_display_point.row(),
9620 editor_snapshot.line_len(target_display_point.row()),
9621 );
9622
9623 let mut element = self
9624 .render_edit_prediction_line_popover(label, None, window, cx)
9625 .into_any();
9626
9627 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9628
9629 let line_origin =
9630 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9631
9632 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9633 let mut origin = start_point
9634 + line_origin
9635 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9636 origin.x = origin.x.max(content_origin.x);
9637
9638 let max_x = content_origin.x + editor_width - size.width;
9639
9640 if origin.x > max_x {
9641 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9642
9643 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9644 origin.y += offset;
9645 IconName::ArrowUp
9646 } else {
9647 origin.y -= offset;
9648 IconName::ArrowDown
9649 };
9650
9651 element = self
9652 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9653 .into_any();
9654
9655 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9656
9657 origin.x = content_origin.x + editor_width - size.width - px(2.);
9658 }
9659
9660 element.prepaint_at(origin, window, cx);
9661 Some((element, origin))
9662 }
9663
9664 fn render_edit_prediction_diff_popover(
9665 self: &Editor,
9666 text_bounds: &Bounds<Pixels>,
9667 content_origin: gpui::Point<Pixels>,
9668 right_margin: Pixels,
9669 editor_snapshot: &EditorSnapshot,
9670 visible_row_range: Range<DisplayRow>,
9671 line_layouts: &[LineWithInvisibles],
9672 line_height: Pixels,
9673 scroll_position: gpui::Point<ScrollOffset>,
9674 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9675 newest_selection_head: Option<DisplayPoint>,
9676 editor_width: Pixels,
9677 style: &EditorStyle,
9678 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9679 edit_preview: &Option<language::EditPreview>,
9680 snapshot: &language::BufferSnapshot,
9681 window: &mut Window,
9682 cx: &mut App,
9683 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9684 let edit_start = edits
9685 .first()
9686 .unwrap()
9687 .0
9688 .start
9689 .to_display_point(editor_snapshot);
9690 let edit_end = edits
9691 .last()
9692 .unwrap()
9693 .0
9694 .end
9695 .to_display_point(editor_snapshot);
9696
9697 let is_visible = visible_row_range.contains(&edit_start.row())
9698 || visible_row_range.contains(&edit_end.row());
9699 if !is_visible {
9700 return None;
9701 }
9702
9703 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9704 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9705 } else {
9706 // Fallback for providers without edit_preview
9707 crate::edit_prediction_fallback_text(edits, cx)
9708 };
9709
9710 let styled_text = highlighted_edits.to_styled_text(&style.text);
9711 let line_count = highlighted_edits.text.lines().count();
9712
9713 const BORDER_WIDTH: Pixels = px(1.);
9714
9715 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9716 let has_keybind = keybind.is_some();
9717
9718 let mut element = h_flex()
9719 .items_start()
9720 .child(
9721 h_flex()
9722 .bg(cx.theme().colors().editor_background)
9723 .border(BORDER_WIDTH)
9724 .shadow_xs()
9725 .border_color(cx.theme().colors().border)
9726 .rounded_l_lg()
9727 .when(line_count > 1, |el| el.rounded_br_lg())
9728 .pr_1()
9729 .child(styled_text),
9730 )
9731 .child(
9732 h_flex()
9733 .h(line_height + BORDER_WIDTH * 2.)
9734 .px_1p5()
9735 .gap_1()
9736 // Workaround: For some reason, there's a gap if we don't do this
9737 .ml(-BORDER_WIDTH)
9738 .shadow(vec![gpui::BoxShadow {
9739 color: gpui::black().opacity(0.05),
9740 offset: point(px(1.), px(1.)),
9741 blur_radius: px(2.),
9742 spread_radius: px(0.),
9743 }])
9744 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9745 .border(BORDER_WIDTH)
9746 .border_color(cx.theme().colors().border)
9747 .rounded_r_lg()
9748 .id("edit_prediction_diff_popover_keybind")
9749 .when(!has_keybind, |el| {
9750 let status_colors = cx.theme().status();
9751
9752 el.bg(status_colors.error_background)
9753 .border_color(status_colors.error.opacity(0.6))
9754 .child(Icon::new(IconName::Info).color(Color::Error))
9755 .cursor_default()
9756 .hoverable_tooltip(move |_window, cx| {
9757 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9758 })
9759 })
9760 .children(keybind),
9761 )
9762 .into_any();
9763
9764 let longest_row =
9765 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9766 let longest_line_width = if visible_row_range.contains(&longest_row) {
9767 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9768 } else {
9769 layout_line(
9770 longest_row,
9771 editor_snapshot,
9772 style,
9773 editor_width,
9774 |_| false,
9775 window,
9776 cx,
9777 )
9778 .width
9779 };
9780
9781 let viewport_bounds =
9782 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9783 right: -right_margin,
9784 ..Default::default()
9785 });
9786
9787 let x_after_longest = Pixels::from(
9788 ScrollPixelOffset::from(
9789 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9790 ) - scroll_pixel_position.x,
9791 );
9792
9793 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9794
9795 // Fully visible if it can be displayed within the window (allow overlapping other
9796 // panes). However, this is only allowed if the popover starts within text_bounds.
9797 let can_position_to_the_right = x_after_longest < text_bounds.right()
9798 && x_after_longest + element_bounds.width < viewport_bounds.right();
9799
9800 let mut origin = if can_position_to_the_right {
9801 point(
9802 x_after_longest,
9803 text_bounds.origin.y
9804 + Pixels::from(
9805 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9806 - scroll_pixel_position.y,
9807 ),
9808 )
9809 } else {
9810 let cursor_row = newest_selection_head.map(|head| head.row());
9811 let above_edit = edit_start
9812 .row()
9813 .0
9814 .checked_sub(line_count as u32)
9815 .map(DisplayRow);
9816 let below_edit = Some(edit_end.row() + 1);
9817 let above_cursor =
9818 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9819 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9820
9821 // Place the edit popover adjacent to the edit if there is a location
9822 // available that is onscreen and does not obscure the cursor. Otherwise,
9823 // place it adjacent to the cursor.
9824 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9825 .into_iter()
9826 .flatten()
9827 .find(|&start_row| {
9828 let end_row = start_row + line_count as u32;
9829 visible_row_range.contains(&start_row)
9830 && visible_row_range.contains(&end_row)
9831 && cursor_row
9832 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9833 })?;
9834
9835 content_origin
9836 + point(
9837 Pixels::from(-scroll_pixel_position.x),
9838 Pixels::from(
9839 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9840 ),
9841 )
9842 };
9843
9844 origin.x -= BORDER_WIDTH;
9845
9846 window.defer_draw(element, origin, 1);
9847
9848 // Do not return an element, since it will already be drawn due to defer_draw.
9849 None
9850 }
9851
9852 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9853 px(30.)
9854 }
9855
9856 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9857 if self.read_only(cx) {
9858 cx.theme().players().read_only()
9859 } else {
9860 self.style.as_ref().unwrap().local_player
9861 }
9862 }
9863
9864 fn render_edit_prediction_accept_keybind(
9865 &self,
9866 window: &mut Window,
9867 cx: &mut App,
9868 ) -> Option<AnyElement> {
9869 let accept_binding =
9870 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9871 let accept_keystroke = accept_binding.keystroke()?;
9872
9873 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9874
9875 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9876 Color::Accent
9877 } else {
9878 Color::Muted
9879 };
9880
9881 h_flex()
9882 .px_0p5()
9883 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9884 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9885 .text_size(TextSize::XSmall.rems(cx))
9886 .child(h_flex().children(ui::render_modifiers(
9887 accept_keystroke.modifiers(),
9888 PlatformStyle::platform(),
9889 Some(modifiers_color),
9890 Some(IconSize::XSmall.rems().into()),
9891 true,
9892 )))
9893 .when(is_platform_style_mac, |parent| {
9894 parent.child(accept_keystroke.key().to_string())
9895 })
9896 .when(!is_platform_style_mac, |parent| {
9897 parent.child(
9898 Key::new(
9899 util::capitalize(accept_keystroke.key()),
9900 Some(Color::Default),
9901 )
9902 .size(Some(IconSize::XSmall.rems().into())),
9903 )
9904 })
9905 .into_any()
9906 .into()
9907 }
9908
9909 fn render_edit_prediction_line_popover(
9910 &self,
9911 label: impl Into<SharedString>,
9912 icon: Option<IconName>,
9913 window: &mut Window,
9914 cx: &mut App,
9915 ) -> Stateful<Div> {
9916 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9917
9918 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9919 let has_keybind = keybind.is_some();
9920 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9921
9922 h_flex()
9923 .id("ep-line-popover")
9924 .py_0p5()
9925 .pl_1()
9926 .pr(padding_right)
9927 .gap_1()
9928 .rounded_md()
9929 .border_1()
9930 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9931 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9932 .shadow_xs()
9933 .when(!has_keybind, |el| {
9934 let status_colors = cx.theme().status();
9935
9936 el.bg(status_colors.error_background)
9937 .border_color(status_colors.error.opacity(0.6))
9938 .pl_2()
9939 .child(Icon::new(icons.error).color(Color::Error))
9940 .cursor_default()
9941 .hoverable_tooltip(move |_window, cx| {
9942 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9943 })
9944 })
9945 .children(keybind)
9946 .child(
9947 Label::new(label)
9948 .size(LabelSize::Small)
9949 .when(!has_keybind, |el| {
9950 el.color(cx.theme().status().error.into()).strikethrough()
9951 }),
9952 )
9953 .when(!has_keybind, |el| {
9954 el.child(
9955 h_flex().ml_1().child(
9956 Icon::new(IconName::Info)
9957 .size(IconSize::Small)
9958 .color(cx.theme().status().error.into()),
9959 ),
9960 )
9961 })
9962 .when_some(icon, |element, icon| {
9963 element.child(
9964 div()
9965 .mt(px(1.5))
9966 .child(Icon::new(icon).size(IconSize::Small)),
9967 )
9968 })
9969 }
9970
9971 fn render_edit_prediction_jump_outside_popover(
9972 &self,
9973 snapshot: &BufferSnapshot,
9974 window: &mut Window,
9975 cx: &mut App,
9976 ) -> Stateful<Div> {
9977 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9978 let has_keybind = keybind.is_some();
9979 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9980
9981 let file_name = snapshot
9982 .file()
9983 .map(|file| SharedString::new(file.file_name(cx)))
9984 .unwrap_or(SharedString::new_static("untitled"));
9985
9986 h_flex()
9987 .id("ep-jump-outside-popover")
9988 .py_1()
9989 .px_2()
9990 .gap_1()
9991 .rounded_md()
9992 .border_1()
9993 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9994 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9995 .shadow_xs()
9996 .when(!has_keybind, |el| {
9997 let status_colors = cx.theme().status();
9998
9999 el.bg(status_colors.error_background)
10000 .border_color(status_colors.error.opacity(0.6))
10001 .pl_2()
10002 .child(Icon::new(icons.error).color(Color::Error))
10003 .cursor_default()
10004 .hoverable_tooltip(move |_window, cx| {
10005 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10006 })
10007 })
10008 .children(keybind)
10009 .child(
10010 Label::new(file_name)
10011 .size(LabelSize::Small)
10012 .buffer_font(cx)
10013 .when(!has_keybind, |el| {
10014 el.color(cx.theme().status().error.into()).strikethrough()
10015 }),
10016 )
10017 .when(!has_keybind, |el| {
10018 el.child(
10019 h_flex().ml_1().child(
10020 Icon::new(IconName::Info)
10021 .size(IconSize::Small)
10022 .color(cx.theme().status().error.into()),
10023 ),
10024 )
10025 })
10026 .child(
10027 div()
10028 .mt(px(1.5))
10029 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10030 )
10031 }
10032
10033 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10034 let accent_color = cx.theme().colors().text_accent;
10035 let editor_bg_color = cx.theme().colors().editor_background;
10036 editor_bg_color.blend(accent_color.opacity(0.1))
10037 }
10038
10039 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10040 let accent_color = cx.theme().colors().text_accent;
10041 let editor_bg_color = cx.theme().colors().editor_background;
10042 editor_bg_color.blend(accent_color.opacity(0.6))
10043 }
10044 fn get_prediction_provider_icons(
10045 provider: &Option<RegisteredEditPredictionDelegate>,
10046 cx: &App,
10047 ) -> edit_prediction_types::EditPredictionIconSet {
10048 match provider {
10049 Some(provider) => provider.provider.icons(cx),
10050 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10051 }
10052 }
10053
10054 fn render_edit_prediction_cursor_popover(
10055 &self,
10056 min_width: Pixels,
10057 max_width: Pixels,
10058 cursor_point: Point,
10059 style: &EditorStyle,
10060 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
10061 _window: &Window,
10062 cx: &mut Context<Editor>,
10063 ) -> Option<AnyElement> {
10064 let provider = self.edit_prediction_provider.as_ref()?;
10065 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10066
10067 let is_refreshing = provider.provider.is_refreshing(cx);
10068
10069 fn pending_completion_container(icon: IconName) -> Div {
10070 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10071 }
10072
10073 let completion = match &self.active_edit_prediction {
10074 Some(prediction) => {
10075 if !self.has_visible_completions_menu() {
10076 const RADIUS: Pixels = px(6.);
10077 const BORDER_WIDTH: Pixels = px(1.);
10078
10079 return Some(
10080 h_flex()
10081 .elevation_2(cx)
10082 .border(BORDER_WIDTH)
10083 .border_color(cx.theme().colors().border)
10084 .when(accept_keystroke.is_none(), |el| {
10085 el.border_color(cx.theme().status().error)
10086 })
10087 .rounded(RADIUS)
10088 .rounded_tl(px(0.))
10089 .overflow_hidden()
10090 .child(div().px_1p5().child(match &prediction.completion {
10091 EditPrediction::MoveWithin { target, snapshot } => {
10092 use text::ToPoint as _;
10093 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10094 {
10095 Icon::new(icons.down)
10096 } else {
10097 Icon::new(icons.up)
10098 }
10099 }
10100 EditPrediction::MoveOutside { .. } => {
10101 // TODO [zeta2] custom icon for external jump?
10102 Icon::new(icons.base)
10103 }
10104 EditPrediction::Edit { .. } => Icon::new(icons.base),
10105 }))
10106 .child(
10107 h_flex()
10108 .gap_1()
10109 .py_1()
10110 .px_2()
10111 .rounded_r(RADIUS - BORDER_WIDTH)
10112 .border_l_1()
10113 .border_color(cx.theme().colors().border)
10114 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10115 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10116 el.child(
10117 Label::new("Hold")
10118 .size(LabelSize::Small)
10119 .when(accept_keystroke.is_none(), |el| {
10120 el.strikethrough()
10121 })
10122 .line_height_style(LineHeightStyle::UiLabel),
10123 )
10124 })
10125 .id("edit_prediction_cursor_popover_keybind")
10126 .when(accept_keystroke.is_none(), |el| {
10127 let status_colors = cx.theme().status();
10128
10129 el.bg(status_colors.error_background)
10130 .border_color(status_colors.error.opacity(0.6))
10131 .child(Icon::new(IconName::Info).color(Color::Error))
10132 .cursor_default()
10133 .hoverable_tooltip(move |_window, cx| {
10134 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10135 .into()
10136 })
10137 })
10138 .when_some(
10139 accept_keystroke.as_ref(),
10140 |el, accept_keystroke| {
10141 el.child(h_flex().children(ui::render_modifiers(
10142 accept_keystroke.modifiers(),
10143 PlatformStyle::platform(),
10144 Some(Color::Default),
10145 Some(IconSize::XSmall.rems().into()),
10146 false,
10147 )))
10148 },
10149 ),
10150 )
10151 .into_any(),
10152 );
10153 }
10154
10155 self.render_edit_prediction_cursor_popover_preview(
10156 prediction,
10157 cursor_point,
10158 style,
10159 cx,
10160 )?
10161 }
10162
10163 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10164 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10165 stale_completion,
10166 cursor_point,
10167 style,
10168 cx,
10169 )?,
10170
10171 None => pending_completion_container(icons.base)
10172 .child(Label::new("...").size(LabelSize::Small)),
10173 },
10174
10175 None => pending_completion_container(icons.base)
10176 .child(Label::new("...").size(LabelSize::Small)),
10177 };
10178
10179 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10180 completion
10181 .with_animation(
10182 "loading-completion",
10183 Animation::new(Duration::from_secs(2))
10184 .repeat()
10185 .with_easing(pulsating_between(0.4, 0.8)),
10186 |label, delta| label.opacity(delta),
10187 )
10188 .into_any_element()
10189 } else {
10190 completion.into_any_element()
10191 };
10192
10193 let has_completion = self.active_edit_prediction.is_some();
10194
10195 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10196 Some(
10197 h_flex()
10198 .min_w(min_width)
10199 .max_w(max_width)
10200 .flex_1()
10201 .elevation_2(cx)
10202 .border_color(cx.theme().colors().border)
10203 .child(
10204 div()
10205 .flex_1()
10206 .py_1()
10207 .px_2()
10208 .overflow_hidden()
10209 .child(completion),
10210 )
10211 .when_some(accept_keystroke, |el, accept_keystroke| {
10212 if !accept_keystroke.modifiers().modified() {
10213 return el;
10214 }
10215
10216 el.child(
10217 h_flex()
10218 .h_full()
10219 .border_l_1()
10220 .rounded_r_lg()
10221 .border_color(cx.theme().colors().border)
10222 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10223 .gap_1()
10224 .py_1()
10225 .px_2()
10226 .child(
10227 h_flex()
10228 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10229 .when(is_platform_style_mac, |parent| parent.gap_1())
10230 .child(h_flex().children(ui::render_modifiers(
10231 accept_keystroke.modifiers(),
10232 PlatformStyle::platform(),
10233 Some(if !has_completion {
10234 Color::Muted
10235 } else {
10236 Color::Default
10237 }),
10238 None,
10239 false,
10240 ))),
10241 )
10242 .child(Label::new("Preview").into_any_element())
10243 .opacity(if has_completion { 1.0 } else { 0.4 }),
10244 )
10245 })
10246 .into_any(),
10247 )
10248 }
10249
10250 fn render_edit_prediction_cursor_popover_preview(
10251 &self,
10252 completion: &EditPredictionState,
10253 cursor_point: Point,
10254 style: &EditorStyle,
10255 cx: &mut Context<Editor>,
10256 ) -> Option<Div> {
10257 use text::ToPoint as _;
10258
10259 fn render_relative_row_jump(
10260 prefix: impl Into<String>,
10261 current_row: u32,
10262 target_row: u32,
10263 ) -> Div {
10264 let (row_diff, arrow) = if target_row < current_row {
10265 (current_row - target_row, IconName::ArrowUp)
10266 } else {
10267 (target_row - current_row, IconName::ArrowDown)
10268 };
10269
10270 h_flex()
10271 .child(
10272 Label::new(format!("{}{}", prefix.into(), row_diff))
10273 .color(Color::Muted)
10274 .size(LabelSize::Small),
10275 )
10276 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10277 }
10278
10279 let supports_jump = self
10280 .edit_prediction_provider
10281 .as_ref()
10282 .map(|provider| provider.provider.supports_jump_to_edit())
10283 .unwrap_or(true);
10284
10285 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10286
10287 match &completion.completion {
10288 EditPrediction::MoveWithin {
10289 target, snapshot, ..
10290 } => {
10291 if !supports_jump {
10292 return None;
10293 }
10294
10295 Some(
10296 h_flex()
10297 .px_2()
10298 .gap_2()
10299 .flex_1()
10300 .child(
10301 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10302 Icon::new(icons.down)
10303 } else {
10304 Icon::new(icons.up)
10305 },
10306 )
10307 .child(Label::new("Jump to Edit")),
10308 )
10309 }
10310 EditPrediction::MoveOutside { snapshot, .. } => {
10311 let file_name = snapshot
10312 .file()
10313 .map(|file| file.file_name(cx))
10314 .unwrap_or("untitled");
10315 Some(
10316 h_flex()
10317 .px_2()
10318 .gap_2()
10319 .flex_1()
10320 .child(Icon::new(icons.base))
10321 .child(Label::new(format!("Jump to {file_name}"))),
10322 )
10323 }
10324 EditPrediction::Edit {
10325 edits,
10326 edit_preview,
10327 snapshot,
10328 ..
10329 } => {
10330 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10331
10332 let (highlighted_edits, has_more_lines) =
10333 if let Some(edit_preview) = edit_preview.as_ref() {
10334 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10335 .first_line_preview()
10336 } else {
10337 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10338 };
10339
10340 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10341 .with_default_highlights(&style.text, highlighted_edits.highlights);
10342
10343 let preview = h_flex()
10344 .gap_1()
10345 .min_w_16()
10346 .child(styled_text)
10347 .when(has_more_lines, |parent| parent.child("…"));
10348
10349 let left = if supports_jump && first_edit_row != cursor_point.row {
10350 render_relative_row_jump("", cursor_point.row, first_edit_row)
10351 .into_any_element()
10352 } else {
10353 Icon::new(icons.base).into_any_element()
10354 };
10355
10356 Some(
10357 h_flex()
10358 .h_full()
10359 .flex_1()
10360 .gap_2()
10361 .pr_1()
10362 .overflow_x_hidden()
10363 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10364 .child(left)
10365 .child(preview),
10366 )
10367 }
10368 }
10369 }
10370
10371 pub fn render_context_menu(
10372 &mut self,
10373 max_height_in_lines: u32,
10374 window: &mut Window,
10375 cx: &mut Context<Editor>,
10376 ) -> Option<AnyElement> {
10377 let menu = self.context_menu.borrow();
10378 let menu = menu.as_ref()?;
10379 if !menu.visible() {
10380 return None;
10381 };
10382 self.style
10383 .as_ref()
10384 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10385 }
10386
10387 fn render_context_menu_aside(
10388 &mut self,
10389 max_size: Size<Pixels>,
10390 window: &mut Window,
10391 cx: &mut Context<Editor>,
10392 ) -> Option<AnyElement> {
10393 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10394 if menu.visible() {
10395 menu.render_aside(max_size, window, cx)
10396 } else {
10397 None
10398 }
10399 })
10400 }
10401
10402 fn hide_context_menu(
10403 &mut self,
10404 window: &mut Window,
10405 cx: &mut Context<Self>,
10406 ) -> Option<CodeContextMenu> {
10407 cx.notify();
10408 self.completion_tasks.clear();
10409 let context_menu = self.context_menu.borrow_mut().take();
10410 self.stale_edit_prediction_in_menu.take();
10411 self.update_visible_edit_prediction(window, cx);
10412 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10413 && let Some(completion_provider) = &self.completion_provider
10414 {
10415 completion_provider.selection_changed(None, window, cx);
10416 }
10417 context_menu
10418 }
10419
10420 fn show_snippet_choices(
10421 &mut self,
10422 choices: &Vec<String>,
10423 selection: Range<Anchor>,
10424 cx: &mut Context<Self>,
10425 ) {
10426 let Some((_, buffer, _)) = self
10427 .buffer()
10428 .read(cx)
10429 .excerpt_containing(selection.start, cx)
10430 else {
10431 return;
10432 };
10433 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10434 else {
10435 return;
10436 };
10437 if buffer != end_buffer {
10438 log::error!("expected anchor range to have matching buffer IDs");
10439 return;
10440 }
10441
10442 let id = post_inc(&mut self.next_completion_id);
10443 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10444 let mut context_menu = self.context_menu.borrow_mut();
10445 let old_menu = context_menu.take();
10446 *context_menu = Some(CodeContextMenu::Completions(
10447 CompletionsMenu::new_snippet_choices(
10448 id,
10449 true,
10450 choices,
10451 selection,
10452 buffer,
10453 old_menu.map(|menu| menu.primary_scroll_handle()),
10454 snippet_sort_order,
10455 ),
10456 ));
10457 }
10458
10459 pub fn insert_snippet(
10460 &mut self,
10461 insertion_ranges: &[Range<MultiBufferOffset>],
10462 snippet: Snippet,
10463 window: &mut Window,
10464 cx: &mut Context<Self>,
10465 ) -> Result<()> {
10466 struct Tabstop<T> {
10467 is_end_tabstop: bool,
10468 ranges: Vec<Range<T>>,
10469 choices: Option<Vec<String>>,
10470 }
10471
10472 let tabstops = self.buffer.update(cx, |buffer, cx| {
10473 let snippet_text: Arc<str> = snippet.text.clone().into();
10474 let edits = insertion_ranges
10475 .iter()
10476 .cloned()
10477 .map(|range| (range, snippet_text.clone()));
10478 let autoindent_mode = AutoindentMode::Block {
10479 original_indent_columns: Vec::new(),
10480 };
10481 buffer.edit(edits, Some(autoindent_mode), cx);
10482
10483 let snapshot = &*buffer.read(cx);
10484 let snippet = &snippet;
10485 snippet
10486 .tabstops
10487 .iter()
10488 .map(|tabstop| {
10489 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10490 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10491 });
10492 let mut tabstop_ranges = tabstop
10493 .ranges
10494 .iter()
10495 .flat_map(|tabstop_range| {
10496 let mut delta = 0_isize;
10497 insertion_ranges.iter().map(move |insertion_range| {
10498 let insertion_start = insertion_range.start + delta;
10499 delta += snippet.text.len() as isize
10500 - (insertion_range.end - insertion_range.start) as isize;
10501
10502 let start =
10503 (insertion_start + tabstop_range.start).min(snapshot.len());
10504 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10505 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10506 })
10507 })
10508 .collect::<Vec<_>>();
10509 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10510
10511 Tabstop {
10512 is_end_tabstop,
10513 ranges: tabstop_ranges,
10514 choices: tabstop.choices.clone(),
10515 }
10516 })
10517 .collect::<Vec<_>>()
10518 });
10519 if let Some(tabstop) = tabstops.first() {
10520 self.change_selections(Default::default(), window, cx, |s| {
10521 // Reverse order so that the first range is the newest created selection.
10522 // Completions will use it and autoscroll will prioritize it.
10523 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10524 });
10525
10526 if let Some(choices) = &tabstop.choices
10527 && let Some(selection) = tabstop.ranges.first()
10528 {
10529 self.show_snippet_choices(choices, selection.clone(), cx)
10530 }
10531
10532 // If we're already at the last tabstop and it's at the end of the snippet,
10533 // we're done, we don't need to keep the state around.
10534 if !tabstop.is_end_tabstop {
10535 let choices = tabstops
10536 .iter()
10537 .map(|tabstop| tabstop.choices.clone())
10538 .collect();
10539
10540 let ranges = tabstops
10541 .into_iter()
10542 .map(|tabstop| tabstop.ranges)
10543 .collect::<Vec<_>>();
10544
10545 self.snippet_stack.push(SnippetState {
10546 active_index: 0,
10547 ranges,
10548 choices,
10549 });
10550 }
10551
10552 // Check whether the just-entered snippet ends with an auto-closable bracket.
10553 if self.autoclose_regions.is_empty() {
10554 let snapshot = self.buffer.read(cx).snapshot(cx);
10555 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10556 let selection_head = selection.head();
10557 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10558 continue;
10559 };
10560
10561 let mut bracket_pair = None;
10562 let max_lookup_length = scope
10563 .brackets()
10564 .map(|(pair, _)| {
10565 pair.start
10566 .as_str()
10567 .chars()
10568 .count()
10569 .max(pair.end.as_str().chars().count())
10570 })
10571 .max();
10572 if let Some(max_lookup_length) = max_lookup_length {
10573 let next_text = snapshot
10574 .chars_at(selection_head)
10575 .take(max_lookup_length)
10576 .collect::<String>();
10577 let prev_text = snapshot
10578 .reversed_chars_at(selection_head)
10579 .take(max_lookup_length)
10580 .collect::<String>();
10581
10582 for (pair, enabled) in scope.brackets() {
10583 if enabled
10584 && pair.close
10585 && prev_text.starts_with(pair.start.as_str())
10586 && next_text.starts_with(pair.end.as_str())
10587 {
10588 bracket_pair = Some(pair.clone());
10589 break;
10590 }
10591 }
10592 }
10593
10594 if let Some(pair) = bracket_pair {
10595 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10596 let autoclose_enabled =
10597 self.use_autoclose && snapshot_settings.use_autoclose;
10598 if autoclose_enabled {
10599 let start = snapshot.anchor_after(selection_head);
10600 let end = snapshot.anchor_after(selection_head);
10601 self.autoclose_regions.push(AutocloseRegion {
10602 selection_id: selection.id,
10603 range: start..end,
10604 pair,
10605 });
10606 }
10607 }
10608 }
10609 }
10610 }
10611 Ok(())
10612 }
10613
10614 pub fn move_to_next_snippet_tabstop(
10615 &mut self,
10616 window: &mut Window,
10617 cx: &mut Context<Self>,
10618 ) -> bool {
10619 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10620 }
10621
10622 pub fn move_to_prev_snippet_tabstop(
10623 &mut self,
10624 window: &mut Window,
10625 cx: &mut Context<Self>,
10626 ) -> bool {
10627 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10628 }
10629
10630 pub fn move_to_snippet_tabstop(
10631 &mut self,
10632 bias: Bias,
10633 window: &mut Window,
10634 cx: &mut Context<Self>,
10635 ) -> bool {
10636 if let Some(mut snippet) = self.snippet_stack.pop() {
10637 match bias {
10638 Bias::Left => {
10639 if snippet.active_index > 0 {
10640 snippet.active_index -= 1;
10641 } else {
10642 self.snippet_stack.push(snippet);
10643 return false;
10644 }
10645 }
10646 Bias::Right => {
10647 if snippet.active_index + 1 < snippet.ranges.len() {
10648 snippet.active_index += 1;
10649 } else {
10650 self.snippet_stack.push(snippet);
10651 return false;
10652 }
10653 }
10654 }
10655 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10656 self.change_selections(Default::default(), window, cx, |s| {
10657 // Reverse order so that the first range is the newest created selection.
10658 // Completions will use it and autoscroll will prioritize it.
10659 s.select_ranges(current_ranges.iter().rev().cloned())
10660 });
10661
10662 if let Some(choices) = &snippet.choices[snippet.active_index]
10663 && let Some(selection) = current_ranges.first()
10664 {
10665 self.show_snippet_choices(choices, selection.clone(), cx);
10666 }
10667
10668 // If snippet state is not at the last tabstop, push it back on the stack
10669 if snippet.active_index + 1 < snippet.ranges.len() {
10670 self.snippet_stack.push(snippet);
10671 }
10672 return true;
10673 }
10674 }
10675
10676 false
10677 }
10678
10679 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10680 self.transact(window, cx, |this, window, cx| {
10681 this.select_all(&SelectAll, window, cx);
10682 this.insert("", window, cx);
10683 });
10684 }
10685
10686 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10687 if self.read_only(cx) {
10688 return;
10689 }
10690 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10691 self.transact(window, cx, |this, window, cx| {
10692 this.select_autoclose_pair(window, cx);
10693
10694 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10695
10696 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10697 if !this.linked_edit_ranges.is_empty() {
10698 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10699 let snapshot = this.buffer.read(cx).snapshot(cx);
10700
10701 for selection in selections.iter() {
10702 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10703 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10704 if selection_start.buffer_id != selection_end.buffer_id {
10705 continue;
10706 }
10707 if let Some(ranges) =
10708 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10709 {
10710 for (buffer, entries) in ranges {
10711 linked_ranges.entry(buffer).or_default().extend(entries);
10712 }
10713 }
10714 }
10715 }
10716
10717 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10718 for selection in &mut selections {
10719 if selection.is_empty() {
10720 let old_head = selection.head();
10721 let mut new_head =
10722 movement::left(&display_map, old_head.to_display_point(&display_map))
10723 .to_point(&display_map);
10724 if let Some((buffer, line_buffer_range)) = display_map
10725 .buffer_snapshot()
10726 .buffer_line_for_row(MultiBufferRow(old_head.row))
10727 {
10728 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10729 let indent_len = match indent_size.kind {
10730 IndentKind::Space => {
10731 buffer.settings_at(line_buffer_range.start, cx).tab_size
10732 }
10733 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10734 };
10735 if old_head.column <= indent_size.len && old_head.column > 0 {
10736 let indent_len = indent_len.get();
10737 new_head = cmp::min(
10738 new_head,
10739 MultiBufferPoint::new(
10740 old_head.row,
10741 ((old_head.column - 1) / indent_len) * indent_len,
10742 ),
10743 );
10744 }
10745 }
10746
10747 selection.set_head(new_head, SelectionGoal::None);
10748 }
10749 }
10750
10751 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10752 this.insert("", window, cx);
10753 let empty_str: Arc<str> = Arc::from("");
10754 for (buffer, edits) in linked_ranges {
10755 let snapshot = buffer.read(cx).snapshot();
10756 use text::ToPoint as TP;
10757
10758 let edits = edits
10759 .into_iter()
10760 .map(|range| {
10761 let end_point = TP::to_point(&range.end, &snapshot);
10762 let mut start_point = TP::to_point(&range.start, &snapshot);
10763
10764 if end_point == start_point {
10765 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10766 .saturating_sub(1);
10767 start_point =
10768 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10769 };
10770
10771 (start_point..end_point, empty_str.clone())
10772 })
10773 .sorted_by_key(|(range, _)| range.start)
10774 .collect::<Vec<_>>();
10775 buffer.update(cx, |this, cx| {
10776 this.edit(edits, None, cx);
10777 })
10778 }
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 this.insert("", window, cx);
10801 this.refresh_edit_prediction(true, false, window, cx);
10802 });
10803 }
10804
10805 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10806 if self.mode.is_single_line() {
10807 cx.propagate();
10808 return;
10809 }
10810
10811 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10812 if self.move_to_prev_snippet_tabstop(window, cx) {
10813 return;
10814 }
10815 self.outdent(&Outdent, window, cx);
10816 }
10817
10818 pub fn next_snippet_tabstop(
10819 &mut self,
10820 _: &NextSnippetTabstop,
10821 window: &mut Window,
10822 cx: &mut Context<Self>,
10823 ) {
10824 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10825 cx.propagate();
10826 return;
10827 }
10828
10829 if self.move_to_next_snippet_tabstop(window, cx) {
10830 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10831 return;
10832 }
10833 cx.propagate();
10834 }
10835
10836 pub fn previous_snippet_tabstop(
10837 &mut self,
10838 _: &PreviousSnippetTabstop,
10839 window: &mut Window,
10840 cx: &mut Context<Self>,
10841 ) {
10842 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10843 cx.propagate();
10844 return;
10845 }
10846
10847 if self.move_to_prev_snippet_tabstop(window, cx) {
10848 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10849 return;
10850 }
10851 cx.propagate();
10852 }
10853
10854 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10855 if self.mode.is_single_line() {
10856 cx.propagate();
10857 return;
10858 }
10859
10860 if self.move_to_next_snippet_tabstop(window, cx) {
10861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10862 return;
10863 }
10864 if self.read_only(cx) {
10865 return;
10866 }
10867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10868 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10869 let buffer = self.buffer.read(cx);
10870 let snapshot = buffer.snapshot(cx);
10871 let rows_iter = selections.iter().map(|s| s.head().row);
10872 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10873
10874 let has_some_cursor_in_whitespace = selections
10875 .iter()
10876 .filter(|selection| selection.is_empty())
10877 .any(|selection| {
10878 let cursor = selection.head();
10879 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10880 cursor.column < current_indent.len
10881 });
10882
10883 let mut edits = Vec::new();
10884 let mut prev_edited_row = 0;
10885 let mut row_delta = 0;
10886 for selection in &mut selections {
10887 if selection.start.row != prev_edited_row {
10888 row_delta = 0;
10889 }
10890 prev_edited_row = selection.end.row;
10891
10892 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10893 if selection.is_empty() {
10894 let cursor = selection.head();
10895 let settings = buffer.language_settings_at(cursor, cx);
10896 if settings.indent_list_on_tab {
10897 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10898 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10899 row_delta = Self::indent_selection(
10900 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10901 );
10902 continue;
10903 }
10904 }
10905 }
10906 }
10907
10908 // If the selection is non-empty, then increase the indentation of the selected lines.
10909 if !selection.is_empty() {
10910 row_delta =
10911 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10912 continue;
10913 }
10914
10915 let cursor = selection.head();
10916 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10917 if let Some(suggested_indent) =
10918 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10919 {
10920 // Don't do anything if already at suggested indent
10921 // and there is any other cursor which is not
10922 if has_some_cursor_in_whitespace
10923 && cursor.column == current_indent.len
10924 && current_indent.len == suggested_indent.len
10925 {
10926 continue;
10927 }
10928
10929 // Adjust line and move cursor to suggested indent
10930 // if cursor is not at suggested indent
10931 if cursor.column < suggested_indent.len
10932 && cursor.column <= current_indent.len
10933 && current_indent.len <= suggested_indent.len
10934 {
10935 selection.start = Point::new(cursor.row, suggested_indent.len);
10936 selection.end = selection.start;
10937 if row_delta == 0 {
10938 edits.extend(Buffer::edit_for_indent_size_adjustment(
10939 cursor.row,
10940 current_indent,
10941 suggested_indent,
10942 ));
10943 row_delta = suggested_indent.len - current_indent.len;
10944 }
10945 continue;
10946 }
10947
10948 // If current indent is more than suggested indent
10949 // only move cursor to current indent and skip indent
10950 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10951 selection.start = Point::new(cursor.row, current_indent.len);
10952 selection.end = selection.start;
10953 continue;
10954 }
10955 }
10956
10957 // Otherwise, insert a hard or soft tab.
10958 let settings = buffer.language_settings_at(cursor, cx);
10959 let tab_size = if settings.hard_tabs {
10960 IndentSize::tab()
10961 } else {
10962 let tab_size = settings.tab_size.get();
10963 let indent_remainder = snapshot
10964 .text_for_range(Point::new(cursor.row, 0)..cursor)
10965 .flat_map(str::chars)
10966 .fold(row_delta % tab_size, |counter: u32, c| {
10967 if c == '\t' {
10968 0
10969 } else {
10970 (counter + 1) % tab_size
10971 }
10972 });
10973
10974 let chars_to_next_tab_stop = tab_size - indent_remainder;
10975 IndentSize::spaces(chars_to_next_tab_stop)
10976 };
10977 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10978 selection.end = selection.start;
10979 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10980 row_delta += tab_size.len;
10981 }
10982
10983 self.transact(window, cx, |this, window, cx| {
10984 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10985 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10986 this.refresh_edit_prediction(true, false, window, cx);
10987 });
10988 }
10989
10990 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10991 if self.read_only(cx) {
10992 return;
10993 }
10994 if self.mode.is_single_line() {
10995 cx.propagate();
10996 return;
10997 }
10998
10999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11000 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11001 let mut prev_edited_row = 0;
11002 let mut row_delta = 0;
11003 let mut edits = Vec::new();
11004 let buffer = self.buffer.read(cx);
11005 let snapshot = buffer.snapshot(cx);
11006 for selection in &mut selections {
11007 if selection.start.row != prev_edited_row {
11008 row_delta = 0;
11009 }
11010 prev_edited_row = selection.end.row;
11011
11012 row_delta =
11013 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11014 }
11015
11016 self.transact(window, cx, |this, window, cx| {
11017 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11018 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11019 });
11020 }
11021
11022 fn indent_selection(
11023 buffer: &MultiBuffer,
11024 snapshot: &MultiBufferSnapshot,
11025 selection: &mut Selection<Point>,
11026 edits: &mut Vec<(Range<Point>, String)>,
11027 delta_for_start_row: u32,
11028 cx: &App,
11029 ) -> u32 {
11030 let settings = buffer.language_settings_at(selection.start, cx);
11031 let tab_size = settings.tab_size.get();
11032 let indent_kind = if settings.hard_tabs {
11033 IndentKind::Tab
11034 } else {
11035 IndentKind::Space
11036 };
11037 let mut start_row = selection.start.row;
11038 let mut end_row = selection.end.row + 1;
11039
11040 // If a selection ends at the beginning of a line, don't indent
11041 // that last line.
11042 if selection.end.column == 0 && selection.end.row > selection.start.row {
11043 end_row -= 1;
11044 }
11045
11046 // Avoid re-indenting a row that has already been indented by a
11047 // previous selection, but still update this selection's column
11048 // to reflect that indentation.
11049 if delta_for_start_row > 0 {
11050 start_row += 1;
11051 selection.start.column += delta_for_start_row;
11052 if selection.end.row == selection.start.row {
11053 selection.end.column += delta_for_start_row;
11054 }
11055 }
11056
11057 let mut delta_for_end_row = 0;
11058 let has_multiple_rows = start_row + 1 != end_row;
11059 for row in start_row..end_row {
11060 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11061 let indent_delta = match (current_indent.kind, indent_kind) {
11062 (IndentKind::Space, IndentKind::Space) => {
11063 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11064 IndentSize::spaces(columns_to_next_tab_stop)
11065 }
11066 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11067 (_, IndentKind::Tab) => IndentSize::tab(),
11068 };
11069
11070 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11071 0
11072 } else {
11073 selection.start.column
11074 };
11075 let row_start = Point::new(row, start);
11076 edits.push((
11077 row_start..row_start,
11078 indent_delta.chars().collect::<String>(),
11079 ));
11080
11081 // Update this selection's endpoints to reflect the indentation.
11082 if row == selection.start.row {
11083 selection.start.column += indent_delta.len;
11084 }
11085 if row == selection.end.row {
11086 selection.end.column += indent_delta.len;
11087 delta_for_end_row = indent_delta.len;
11088 }
11089 }
11090
11091 if selection.start.row == selection.end.row {
11092 delta_for_start_row + delta_for_end_row
11093 } else {
11094 delta_for_end_row
11095 }
11096 }
11097
11098 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11099 if self.read_only(cx) {
11100 return;
11101 }
11102 if self.mode.is_single_line() {
11103 cx.propagate();
11104 return;
11105 }
11106
11107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11108 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11109 let selections = self.selections.all::<Point>(&display_map);
11110 let mut deletion_ranges = Vec::new();
11111 let mut last_outdent = None;
11112 {
11113 let buffer = self.buffer.read(cx);
11114 let snapshot = buffer.snapshot(cx);
11115 for selection in &selections {
11116 let settings = buffer.language_settings_at(selection.start, cx);
11117 let tab_size = settings.tab_size.get();
11118 let mut rows = selection.spanned_rows(false, &display_map);
11119
11120 // Avoid re-outdenting a row that has already been outdented by a
11121 // previous selection.
11122 if let Some(last_row) = last_outdent
11123 && last_row == rows.start
11124 {
11125 rows.start = rows.start.next_row();
11126 }
11127 let has_multiple_rows = rows.len() > 1;
11128 for row in rows.iter_rows() {
11129 let indent_size = snapshot.indent_size_for_line(row);
11130 if indent_size.len > 0 {
11131 let deletion_len = match indent_size.kind {
11132 IndentKind::Space => {
11133 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11134 if columns_to_prev_tab_stop == 0 {
11135 tab_size
11136 } else {
11137 columns_to_prev_tab_stop
11138 }
11139 }
11140 IndentKind::Tab => 1,
11141 };
11142 let start = if has_multiple_rows
11143 || deletion_len > selection.start.column
11144 || indent_size.len < selection.start.column
11145 {
11146 0
11147 } else {
11148 selection.start.column - deletion_len
11149 };
11150 deletion_ranges.push(
11151 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11152 );
11153 last_outdent = Some(row);
11154 }
11155 }
11156 }
11157 }
11158
11159 self.transact(window, cx, |this, window, cx| {
11160 this.buffer.update(cx, |buffer, cx| {
11161 let empty_str: Arc<str> = Arc::default();
11162 buffer.edit(
11163 deletion_ranges
11164 .into_iter()
11165 .map(|range| (range, empty_str.clone())),
11166 None,
11167 cx,
11168 );
11169 });
11170 let selections = this
11171 .selections
11172 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11173 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11174 });
11175 }
11176
11177 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11178 if self.read_only(cx) {
11179 return;
11180 }
11181 if self.mode.is_single_line() {
11182 cx.propagate();
11183 return;
11184 }
11185
11186 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11187 let selections = self
11188 .selections
11189 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11190 .into_iter()
11191 .map(|s| s.range());
11192
11193 self.transact(window, cx, |this, window, cx| {
11194 this.buffer.update(cx, |buffer, cx| {
11195 buffer.autoindent_ranges(selections, cx);
11196 });
11197 let selections = this
11198 .selections
11199 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11200 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11201 });
11202 }
11203
11204 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11205 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11207 let selections = self.selections.all::<Point>(&display_map);
11208
11209 let mut new_cursors = Vec::new();
11210 let mut edit_ranges = Vec::new();
11211 let mut selections = selections.iter().peekable();
11212 while let Some(selection) = selections.next() {
11213 let mut rows = selection.spanned_rows(false, &display_map);
11214
11215 // Accumulate contiguous regions of rows that we want to delete.
11216 while let Some(next_selection) = selections.peek() {
11217 let next_rows = next_selection.spanned_rows(false, &display_map);
11218 if next_rows.start <= rows.end {
11219 rows.end = next_rows.end;
11220 selections.next().unwrap();
11221 } else {
11222 break;
11223 }
11224 }
11225
11226 let buffer = display_map.buffer_snapshot();
11227 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11228 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11229 // If there's a line after the range, delete the \n from the end of the row range
11230 (
11231 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11232 rows.end,
11233 )
11234 } else {
11235 // If there isn't a line after the range, delete the \n from the line before the
11236 // start of the row range
11237 edit_start = edit_start.saturating_sub_usize(1);
11238 (buffer.len(), rows.start.previous_row())
11239 };
11240
11241 let text_layout_details = self.text_layout_details(window, cx);
11242 let x = display_map.x_for_display_point(
11243 selection.head().to_display_point(&display_map),
11244 &text_layout_details,
11245 );
11246 let row = Point::new(target_row.0, 0)
11247 .to_display_point(&display_map)
11248 .row();
11249 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11250
11251 new_cursors.push((
11252 selection.id,
11253 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11254 SelectionGoal::None,
11255 ));
11256 edit_ranges.push(edit_start..edit_end);
11257 }
11258
11259 self.transact(window, cx, |this, window, cx| {
11260 let buffer = this.buffer.update(cx, |buffer, cx| {
11261 let empty_str: Arc<str> = Arc::default();
11262 buffer.edit(
11263 edit_ranges
11264 .into_iter()
11265 .map(|range| (range, empty_str.clone())),
11266 None,
11267 cx,
11268 );
11269 buffer.snapshot(cx)
11270 });
11271 let new_selections = new_cursors
11272 .into_iter()
11273 .map(|(id, cursor, goal)| {
11274 let cursor = cursor.to_point(&buffer);
11275 Selection {
11276 id,
11277 start: cursor,
11278 end: cursor,
11279 reversed: false,
11280 goal,
11281 }
11282 })
11283 .collect();
11284
11285 this.change_selections(Default::default(), window, cx, |s| {
11286 s.select(new_selections);
11287 });
11288 });
11289 }
11290
11291 pub fn join_lines_impl(
11292 &mut self,
11293 insert_whitespace: bool,
11294 window: &mut Window,
11295 cx: &mut Context<Self>,
11296 ) {
11297 if self.read_only(cx) {
11298 return;
11299 }
11300 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11301 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11302 let start = MultiBufferRow(selection.start.row);
11303 // Treat single line selections as if they include the next line. Otherwise this action
11304 // would do nothing for single line selections individual cursors.
11305 let end = if selection.start.row == selection.end.row {
11306 MultiBufferRow(selection.start.row + 1)
11307 } else {
11308 MultiBufferRow(selection.end.row)
11309 };
11310
11311 if let Some(last_row_range) = row_ranges.last_mut()
11312 && start <= last_row_range.end
11313 {
11314 last_row_range.end = end;
11315 continue;
11316 }
11317 row_ranges.push(start..end);
11318 }
11319
11320 let snapshot = self.buffer.read(cx).snapshot(cx);
11321 let mut cursor_positions = Vec::new();
11322 for row_range in &row_ranges {
11323 let anchor = snapshot.anchor_before(Point::new(
11324 row_range.end.previous_row().0,
11325 snapshot.line_len(row_range.end.previous_row()),
11326 ));
11327 cursor_positions.push(anchor..anchor);
11328 }
11329
11330 self.transact(window, cx, |this, window, cx| {
11331 for row_range in row_ranges.into_iter().rev() {
11332 for row in row_range.iter_rows().rev() {
11333 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11334 let next_line_row = row.next_row();
11335 let indent = snapshot.indent_size_for_line(next_line_row);
11336 let mut join_start_column = indent.len;
11337
11338 if let Some(language_scope) =
11339 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11340 {
11341 let line_end =
11342 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11343 let line_text_after_indent = snapshot
11344 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11345 .collect::<String>();
11346
11347 if !line_text_after_indent.is_empty() {
11348 let block_prefix = language_scope
11349 .block_comment()
11350 .map(|c| c.prefix.as_ref())
11351 .filter(|p| !p.is_empty());
11352 let doc_prefix = language_scope
11353 .documentation_comment()
11354 .map(|c| c.prefix.as_ref())
11355 .filter(|p| !p.is_empty());
11356 let all_prefixes = language_scope
11357 .line_comment_prefixes()
11358 .iter()
11359 .map(|p| p.as_ref())
11360 .chain(block_prefix)
11361 .chain(doc_prefix)
11362 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11363
11364 let mut longest_prefix_len = None;
11365 for prefix in all_prefixes {
11366 let trimmed = prefix.trim_end();
11367 if line_text_after_indent.starts_with(trimmed) {
11368 let candidate_len =
11369 if line_text_after_indent.starts_with(prefix) {
11370 prefix.len()
11371 } else {
11372 trimmed.len()
11373 };
11374 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11375 longest_prefix_len = Some(candidate_len);
11376 }
11377 }
11378 }
11379
11380 if let Some(prefix_len) = longest_prefix_len {
11381 join_start_column =
11382 join_start_column.saturating_add(prefix_len as u32);
11383 }
11384 }
11385 }
11386
11387 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11388
11389 let replace = if snapshot.line_len(next_line_row) > join_start_column
11390 && insert_whitespace
11391 {
11392 " "
11393 } else {
11394 ""
11395 };
11396
11397 this.buffer.update(cx, |buffer, cx| {
11398 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11399 });
11400 }
11401 }
11402
11403 this.change_selections(Default::default(), window, cx, |s| {
11404 s.select_anchor_ranges(cursor_positions)
11405 });
11406 });
11407 }
11408
11409 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11411 self.join_lines_impl(true, window, cx);
11412 }
11413
11414 pub fn sort_lines_case_sensitive(
11415 &mut self,
11416 _: &SortLinesCaseSensitive,
11417 window: &mut Window,
11418 cx: &mut Context<Self>,
11419 ) {
11420 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11421 }
11422
11423 pub fn sort_lines_by_length(
11424 &mut self,
11425 _: &SortLinesByLength,
11426 window: &mut Window,
11427 cx: &mut Context<Self>,
11428 ) {
11429 self.manipulate_immutable_lines(window, cx, |lines| {
11430 lines.sort_by_key(|&line| line.chars().count())
11431 })
11432 }
11433
11434 pub fn sort_lines_case_insensitive(
11435 &mut self,
11436 _: &SortLinesCaseInsensitive,
11437 window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 self.manipulate_immutable_lines(window, cx, |lines| {
11441 lines.sort_by_key(|line| line.to_lowercase())
11442 })
11443 }
11444
11445 pub fn unique_lines_case_insensitive(
11446 &mut self,
11447 _: &UniqueLinesCaseInsensitive,
11448 window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 self.manipulate_immutable_lines(window, cx, |lines| {
11452 let mut seen = HashSet::default();
11453 lines.retain(|line| seen.insert(line.to_lowercase()));
11454 })
11455 }
11456
11457 pub fn unique_lines_case_sensitive(
11458 &mut self,
11459 _: &UniqueLinesCaseSensitive,
11460 window: &mut Window,
11461 cx: &mut Context<Self>,
11462 ) {
11463 self.manipulate_immutable_lines(window, cx, |lines| {
11464 let mut seen = HashSet::default();
11465 lines.retain(|line| seen.insert(*line));
11466 })
11467 }
11468
11469 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11470 let snapshot = self.buffer.read(cx).snapshot(cx);
11471 for selection in self.selections.disjoint_anchors_arc().iter() {
11472 if snapshot
11473 .language_at(selection.start)
11474 .and_then(|lang| lang.config().wrap_characters.as_ref())
11475 .is_some()
11476 {
11477 return true;
11478 }
11479 }
11480 false
11481 }
11482
11483 fn wrap_selections_in_tag(
11484 &mut self,
11485 _: &WrapSelectionsInTag,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11490
11491 let snapshot = self.buffer.read(cx).snapshot(cx);
11492
11493 let mut edits = Vec::new();
11494 let mut boundaries = Vec::new();
11495
11496 for selection in self
11497 .selections
11498 .all_adjusted(&self.display_snapshot(cx))
11499 .iter()
11500 {
11501 let Some(wrap_config) = snapshot
11502 .language_at(selection.start)
11503 .and_then(|lang| lang.config().wrap_characters.clone())
11504 else {
11505 continue;
11506 };
11507
11508 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11509 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11510
11511 let start_before = snapshot.anchor_before(selection.start);
11512 let end_after = snapshot.anchor_after(selection.end);
11513
11514 edits.push((start_before..start_before, open_tag));
11515 edits.push((end_after..end_after, close_tag));
11516
11517 boundaries.push((
11518 start_before,
11519 end_after,
11520 wrap_config.start_prefix.len(),
11521 wrap_config.end_suffix.len(),
11522 ));
11523 }
11524
11525 if edits.is_empty() {
11526 return;
11527 }
11528
11529 self.transact(window, cx, |this, window, cx| {
11530 let buffer = this.buffer.update(cx, |buffer, cx| {
11531 buffer.edit(edits, None, cx);
11532 buffer.snapshot(cx)
11533 });
11534
11535 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11536 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11537 boundaries.into_iter()
11538 {
11539 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11540 let close_offset = end_after
11541 .to_offset(&buffer)
11542 .saturating_sub_usize(end_suffix_len);
11543 new_selections.push(open_offset..open_offset);
11544 new_selections.push(close_offset..close_offset);
11545 }
11546
11547 this.change_selections(Default::default(), window, cx, |s| {
11548 s.select_ranges(new_selections);
11549 });
11550
11551 this.request_autoscroll(Autoscroll::fit(), cx);
11552 });
11553 }
11554
11555 pub fn toggle_read_only(
11556 &mut self,
11557 _: &workspace::ToggleReadOnlyFile,
11558 _: &mut Window,
11559 cx: &mut Context<Self>,
11560 ) {
11561 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11562 buffer.update(cx, |buffer, cx| {
11563 buffer.set_capability(
11564 match buffer.capability() {
11565 Capability::ReadWrite => Capability::Read,
11566 Capability::Read => Capability::ReadWrite,
11567 Capability::ReadOnly => Capability::ReadOnly,
11568 },
11569 cx,
11570 );
11571 })
11572 }
11573 }
11574
11575 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11576 let Some(project) = self.project.clone() else {
11577 return;
11578 };
11579 let task = self.reload(project, window, cx);
11580 self.detach_and_notify_err(task, window, cx);
11581 }
11582
11583 pub fn restore_file(
11584 &mut self,
11585 _: &::git::RestoreFile,
11586 window: &mut Window,
11587 cx: &mut Context<Self>,
11588 ) {
11589 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11590 let mut buffer_ids = HashSet::default();
11591 let snapshot = self.buffer().read(cx).snapshot(cx);
11592 for selection in self
11593 .selections
11594 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11595 {
11596 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11597 }
11598
11599 let buffer = self.buffer().read(cx);
11600 let ranges = buffer_ids
11601 .into_iter()
11602 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11603 .collect::<Vec<_>>();
11604
11605 self.restore_hunks_in_ranges(ranges, window, cx);
11606 }
11607
11608 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11609 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11610 let selections = self
11611 .selections
11612 .all(&self.display_snapshot(cx))
11613 .into_iter()
11614 .map(|s| s.range())
11615 .collect();
11616 self.restore_hunks_in_ranges(selections, window, cx);
11617 }
11618
11619 pub fn restore_hunks_in_ranges(
11620 &mut self,
11621 ranges: Vec<Range<Point>>,
11622 window: &mut Window,
11623 cx: &mut Context<Editor>,
11624 ) {
11625 if self.delegate_stage_and_restore {
11626 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11627 if !hunks.is_empty() {
11628 cx.emit(EditorEvent::RestoreRequested { hunks });
11629 }
11630 return;
11631 }
11632 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11633 self.transact(window, cx, |editor, window, cx| {
11634 editor.restore_diff_hunks(hunks, cx);
11635 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11636 selections.refresh()
11637 });
11638 });
11639 }
11640
11641 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11642 let mut revert_changes = HashMap::default();
11643 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11644 for (buffer_id, hunks) in &chunk_by {
11645 let hunks = hunks.collect::<Vec<_>>();
11646 for hunk in &hunks {
11647 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11648 }
11649 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11650 }
11651 if !revert_changes.is_empty() {
11652 self.buffer().update(cx, |multi_buffer, cx| {
11653 for (buffer_id, changes) in revert_changes {
11654 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11655 buffer.update(cx, |buffer, cx| {
11656 buffer.edit(
11657 changes
11658 .into_iter()
11659 .map(|(range, text)| (range, text.to_string())),
11660 None,
11661 cx,
11662 );
11663 });
11664 }
11665 }
11666 });
11667 }
11668 }
11669
11670 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11671 if let Some(status) = self
11672 .addons
11673 .iter()
11674 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11675 {
11676 return Some(status);
11677 }
11678 self.project
11679 .as_ref()?
11680 .read(cx)
11681 .status_for_buffer_id(buffer_id, cx)
11682 }
11683
11684 pub fn open_active_item_in_terminal(
11685 &mut self,
11686 _: &OpenInTerminal,
11687 window: &mut Window,
11688 cx: &mut Context<Self>,
11689 ) {
11690 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11691 let project_path = buffer.read(cx).project_path(cx)?;
11692 let project = self.project()?.read(cx);
11693 let entry = project.entry_for_path(&project_path, cx)?;
11694 let parent = match &entry.canonical_path {
11695 Some(canonical_path) => canonical_path.to_path_buf(),
11696 None => project.absolute_path(&project_path, cx)?,
11697 }
11698 .parent()?
11699 .to_path_buf();
11700 Some(parent)
11701 }) {
11702 window.dispatch_action(
11703 OpenTerminal {
11704 working_directory,
11705 local: false,
11706 }
11707 .boxed_clone(),
11708 cx,
11709 );
11710 }
11711 }
11712
11713 fn set_breakpoint_context_menu(
11714 &mut self,
11715 display_row: DisplayRow,
11716 position: Option<Anchor>,
11717 clicked_point: gpui::Point<Pixels>,
11718 window: &mut Window,
11719 cx: &mut Context<Self>,
11720 ) {
11721 let source = self
11722 .buffer
11723 .read(cx)
11724 .snapshot(cx)
11725 .anchor_before(Point::new(display_row.0, 0u32));
11726
11727 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11728
11729 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11730 self,
11731 source,
11732 clicked_point,
11733 context_menu,
11734 window,
11735 cx,
11736 );
11737 }
11738
11739 fn add_edit_breakpoint_block(
11740 &mut self,
11741 anchor: Anchor,
11742 breakpoint: &Breakpoint,
11743 edit_action: BreakpointPromptEditAction,
11744 window: &mut Window,
11745 cx: &mut Context<Self>,
11746 ) {
11747 let weak_editor = cx.weak_entity();
11748 let bp_prompt = cx.new(|cx| {
11749 BreakpointPromptEditor::new(
11750 weak_editor,
11751 anchor,
11752 breakpoint.clone(),
11753 edit_action,
11754 window,
11755 cx,
11756 )
11757 });
11758
11759 let height = bp_prompt.update(cx, |this, cx| {
11760 this.prompt
11761 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11762 });
11763 let cloned_prompt = bp_prompt.clone();
11764 let blocks = vec![BlockProperties {
11765 style: BlockStyle::Sticky,
11766 placement: BlockPlacement::Above(anchor),
11767 height: Some(height),
11768 render: Arc::new(move |cx| {
11769 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11770 cloned_prompt.clone().into_any_element()
11771 }),
11772 priority: 0,
11773 }];
11774
11775 let focus_handle = bp_prompt.focus_handle(cx);
11776 window.focus(&focus_handle, cx);
11777
11778 let block_ids = self.insert_blocks(blocks, None, cx);
11779 bp_prompt.update(cx, |prompt, _| {
11780 prompt.add_block_ids(block_ids);
11781 });
11782 }
11783
11784 pub(crate) fn breakpoint_at_row(
11785 &self,
11786 row: u32,
11787 window: &mut Window,
11788 cx: &mut Context<Self>,
11789 ) -> Option<(Anchor, Breakpoint)> {
11790 let snapshot = self.snapshot(window, cx);
11791 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11792
11793 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11794 }
11795
11796 pub(crate) fn breakpoint_at_anchor(
11797 &self,
11798 breakpoint_position: Anchor,
11799 snapshot: &EditorSnapshot,
11800 cx: &mut Context<Self>,
11801 ) -> Option<(Anchor, Breakpoint)> {
11802 let buffer = self
11803 .buffer
11804 .read(cx)
11805 .buffer_for_anchor(breakpoint_position, cx)?;
11806
11807 let enclosing_excerpt = breakpoint_position.excerpt_id;
11808 let buffer_snapshot = buffer.read(cx).snapshot();
11809
11810 let row = buffer_snapshot
11811 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11812 .row;
11813
11814 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11815 let anchor_end = snapshot
11816 .buffer_snapshot()
11817 .anchor_after(Point::new(row, line_len));
11818
11819 self.breakpoint_store
11820 .as_ref()?
11821 .read_with(cx, |breakpoint_store, cx| {
11822 breakpoint_store
11823 .breakpoints(
11824 &buffer,
11825 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11826 &buffer_snapshot,
11827 cx,
11828 )
11829 .next()
11830 .and_then(|(bp, _)| {
11831 let breakpoint_row = buffer_snapshot
11832 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11833 .row;
11834
11835 if breakpoint_row == row {
11836 snapshot
11837 .buffer_snapshot()
11838 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11839 .map(|position| (position, bp.bp.clone()))
11840 } else {
11841 None
11842 }
11843 })
11844 })
11845 }
11846
11847 pub fn edit_log_breakpoint(
11848 &mut self,
11849 _: &EditLogBreakpoint,
11850 window: &mut Window,
11851 cx: &mut Context<Self>,
11852 ) {
11853 if self.breakpoint_store.is_none() {
11854 return;
11855 }
11856
11857 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11858 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11859 message: None,
11860 state: BreakpointState::Enabled,
11861 condition: None,
11862 hit_condition: None,
11863 });
11864
11865 self.add_edit_breakpoint_block(
11866 anchor,
11867 &breakpoint,
11868 BreakpointPromptEditAction::Log,
11869 window,
11870 cx,
11871 );
11872 }
11873 }
11874
11875 fn breakpoints_at_cursors(
11876 &self,
11877 window: &mut Window,
11878 cx: &mut Context<Self>,
11879 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11880 let snapshot = self.snapshot(window, cx);
11881 let cursors = self
11882 .selections
11883 .disjoint_anchors_arc()
11884 .iter()
11885 .map(|selection| {
11886 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11887
11888 let breakpoint_position = self
11889 .breakpoint_at_row(cursor_position.row, window, cx)
11890 .map(|bp| bp.0)
11891 .unwrap_or_else(|| {
11892 snapshot
11893 .display_snapshot
11894 .buffer_snapshot()
11895 .anchor_after(Point::new(cursor_position.row, 0))
11896 });
11897
11898 let breakpoint = self
11899 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11900 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11901
11902 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11903 })
11904 // 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.
11905 .collect::<HashMap<Anchor, _>>();
11906
11907 cursors.into_iter().collect()
11908 }
11909
11910 pub fn enable_breakpoint(
11911 &mut self,
11912 _: &crate::actions::EnableBreakpoint,
11913 window: &mut Window,
11914 cx: &mut Context<Self>,
11915 ) {
11916 if self.breakpoint_store.is_none() {
11917 return;
11918 }
11919
11920 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11921 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11922 continue;
11923 };
11924 self.edit_breakpoint_at_anchor(
11925 anchor,
11926 breakpoint,
11927 BreakpointEditAction::InvertState,
11928 cx,
11929 );
11930 }
11931 }
11932
11933 pub fn disable_breakpoint(
11934 &mut self,
11935 _: &crate::actions::DisableBreakpoint,
11936 window: &mut Window,
11937 cx: &mut Context<Self>,
11938 ) {
11939 if self.breakpoint_store.is_none() {
11940 return;
11941 }
11942
11943 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11944 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11945 continue;
11946 };
11947 self.edit_breakpoint_at_anchor(
11948 anchor,
11949 breakpoint,
11950 BreakpointEditAction::InvertState,
11951 cx,
11952 );
11953 }
11954 }
11955
11956 pub fn toggle_breakpoint(
11957 &mut self,
11958 _: &crate::actions::ToggleBreakpoint,
11959 window: &mut Window,
11960 cx: &mut Context<Self>,
11961 ) {
11962 if self.breakpoint_store.is_none() {
11963 return;
11964 }
11965
11966 let snapshot = self.snapshot(window, cx);
11967 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11968 if self.gutter_breakpoint_indicator.0.is_some() {
11969 let display_row = anchor
11970 .to_point(snapshot.buffer_snapshot())
11971 .to_display_point(&snapshot.display_snapshot)
11972 .row();
11973 self.update_breakpoint_collision_on_toggle(
11974 display_row,
11975 &BreakpointEditAction::Toggle,
11976 );
11977 }
11978
11979 if let Some(breakpoint) = breakpoint {
11980 self.edit_breakpoint_at_anchor(
11981 anchor,
11982 breakpoint,
11983 BreakpointEditAction::Toggle,
11984 cx,
11985 );
11986 } else {
11987 self.edit_breakpoint_at_anchor(
11988 anchor,
11989 Breakpoint::new_standard(),
11990 BreakpointEditAction::Toggle,
11991 cx,
11992 );
11993 }
11994 }
11995 }
11996
11997 fn update_breakpoint_collision_on_toggle(
11998 &mut self,
11999 display_row: DisplayRow,
12000 edit_action: &BreakpointEditAction,
12001 ) {
12002 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12003 if breakpoint_indicator.display_row == display_row
12004 && matches!(edit_action, BreakpointEditAction::Toggle)
12005 {
12006 breakpoint_indicator.collides_with_existing_breakpoint =
12007 !breakpoint_indicator.collides_with_existing_breakpoint;
12008 }
12009 }
12010 }
12011
12012 pub fn edit_breakpoint_at_anchor(
12013 &mut self,
12014 breakpoint_position: Anchor,
12015 breakpoint: Breakpoint,
12016 edit_action: BreakpointEditAction,
12017 cx: &mut Context<Self>,
12018 ) {
12019 let Some(breakpoint_store) = &self.breakpoint_store else {
12020 return;
12021 };
12022
12023 let Some(buffer) = self
12024 .buffer
12025 .read(cx)
12026 .buffer_for_anchor(breakpoint_position, cx)
12027 else {
12028 return;
12029 };
12030
12031 breakpoint_store.update(cx, |breakpoint_store, cx| {
12032 breakpoint_store.toggle_breakpoint(
12033 buffer,
12034 BreakpointWithPosition {
12035 position: breakpoint_position.text_anchor,
12036 bp: breakpoint,
12037 },
12038 edit_action,
12039 cx,
12040 );
12041 });
12042
12043 cx.notify();
12044 }
12045
12046 #[cfg(any(test, feature = "test-support"))]
12047 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12048 self.breakpoint_store.clone()
12049 }
12050
12051 pub fn prepare_restore_change(
12052 &self,
12053 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12054 hunk: &MultiBufferDiffHunk,
12055 cx: &mut App,
12056 ) -> Option<()> {
12057 if hunk.is_created_file() {
12058 return None;
12059 }
12060 let buffer = self.buffer.read(cx);
12061 let diff = buffer.diff_for(hunk.buffer_id)?;
12062 let buffer = buffer.buffer(hunk.buffer_id)?;
12063 let buffer = buffer.read(cx);
12064 let original_text = diff
12065 .read(cx)
12066 .base_text(cx)
12067 .as_rope()
12068 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12069 let buffer_snapshot = buffer.snapshot();
12070 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12071 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12072 probe
12073 .0
12074 .start
12075 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12076 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12077 }) {
12078 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12079 Some(())
12080 } else {
12081 None
12082 }
12083 }
12084
12085 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12086 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12087 }
12088
12089 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12090 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12091 }
12092
12093 pub fn rotate_selections_forward(
12094 &mut self,
12095 _: &RotateSelectionsForward,
12096 window: &mut Window,
12097 cx: &mut Context<Self>,
12098 ) {
12099 self.rotate_selections(window, cx, false)
12100 }
12101
12102 pub fn rotate_selections_backward(
12103 &mut self,
12104 _: &RotateSelectionsBackward,
12105 window: &mut Window,
12106 cx: &mut Context<Self>,
12107 ) {
12108 self.rotate_selections(window, cx, true)
12109 }
12110
12111 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12112 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12113 let display_snapshot = self.display_snapshot(cx);
12114 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12115
12116 if selections.len() < 2 {
12117 return;
12118 }
12119
12120 let (edits, new_selections) = {
12121 let buffer = self.buffer.read(cx).read(cx);
12122 let has_selections = selections.iter().any(|s| !s.is_empty());
12123 if has_selections {
12124 let mut selected_texts: Vec<String> = selections
12125 .iter()
12126 .map(|selection| {
12127 buffer
12128 .text_for_range(selection.start..selection.end)
12129 .collect()
12130 })
12131 .collect();
12132
12133 if reverse {
12134 selected_texts.rotate_left(1);
12135 } else {
12136 selected_texts.rotate_right(1);
12137 }
12138
12139 let mut offset_delta: i64 = 0;
12140 let mut new_selections = Vec::new();
12141 let edits: Vec<_> = selections
12142 .iter()
12143 .zip(selected_texts.iter())
12144 .map(|(selection, new_text)| {
12145 let old_len = (selection.end.0 - selection.start.0) as i64;
12146 let new_len = new_text.len() as i64;
12147 let adjusted_start =
12148 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12149 let adjusted_end =
12150 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12151
12152 new_selections.push(Selection {
12153 id: selection.id,
12154 start: adjusted_start,
12155 end: adjusted_end,
12156 reversed: selection.reversed,
12157 goal: selection.goal,
12158 });
12159
12160 offset_delta += new_len - old_len;
12161 (selection.start..selection.end, new_text.clone())
12162 })
12163 .collect();
12164 (edits, new_selections)
12165 } else {
12166 let mut all_rows: Vec<u32> = selections
12167 .iter()
12168 .map(|selection| buffer.offset_to_point(selection.start).row)
12169 .collect();
12170 all_rows.sort_unstable();
12171 all_rows.dedup();
12172
12173 if all_rows.len() < 2 {
12174 return;
12175 }
12176
12177 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12178 .iter()
12179 .map(|&row| {
12180 let start = Point::new(row, 0);
12181 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12182 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12183 })
12184 .collect();
12185
12186 let mut line_texts: Vec<String> = line_ranges
12187 .iter()
12188 .map(|range| buffer.text_for_range(range.clone()).collect())
12189 .collect();
12190
12191 if reverse {
12192 line_texts.rotate_left(1);
12193 } else {
12194 line_texts.rotate_right(1);
12195 }
12196
12197 let edits = line_ranges
12198 .iter()
12199 .zip(line_texts.iter())
12200 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12201 .collect();
12202
12203 let num_rows = all_rows.len();
12204 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12205 .iter()
12206 .enumerate()
12207 .map(|(i, &row)| (row, i))
12208 .collect();
12209
12210 // Compute new line start offsets after rotation (handles CRLF)
12211 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12212 let first_line_start = line_ranges[0].start.0;
12213 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12214 for text in line_texts.iter().take(num_rows - 1) {
12215 let prev_start = *new_line_starts.last().unwrap();
12216 new_line_starts.push(prev_start + text.len() + newline_len);
12217 }
12218
12219 let new_selections = selections
12220 .iter()
12221 .map(|selection| {
12222 let point = buffer.offset_to_point(selection.start);
12223 let old_index = row_to_index[&point.row];
12224 let new_index = if reverse {
12225 (old_index + num_rows - 1) % num_rows
12226 } else {
12227 (old_index + 1) % num_rows
12228 };
12229 let new_offset =
12230 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12231 Selection {
12232 id: selection.id,
12233 start: new_offset,
12234 end: new_offset,
12235 reversed: selection.reversed,
12236 goal: selection.goal,
12237 }
12238 })
12239 .collect();
12240
12241 (edits, new_selections)
12242 }
12243 };
12244
12245 self.transact(window, cx, |this, window, cx| {
12246 this.buffer.update(cx, |buffer, cx| {
12247 buffer.edit(edits, None, cx);
12248 });
12249 this.change_selections(Default::default(), window, cx, |s| {
12250 s.select(new_selections);
12251 });
12252 });
12253 }
12254
12255 fn manipulate_lines<M>(
12256 &mut self,
12257 window: &mut Window,
12258 cx: &mut Context<Self>,
12259 mut manipulate: M,
12260 ) where
12261 M: FnMut(&str) -> LineManipulationResult,
12262 {
12263 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12264
12265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12266 let buffer = self.buffer.read(cx).snapshot(cx);
12267
12268 let mut edits = Vec::new();
12269
12270 let selections = self.selections.all::<Point>(&display_map);
12271 let mut selections = selections.iter().peekable();
12272 let mut contiguous_row_selections = Vec::new();
12273 let mut new_selections = Vec::new();
12274 let mut added_lines = 0;
12275 let mut removed_lines = 0;
12276
12277 while let Some(selection) = selections.next() {
12278 let (start_row, end_row) = consume_contiguous_rows(
12279 &mut contiguous_row_selections,
12280 selection,
12281 &display_map,
12282 &mut selections,
12283 );
12284
12285 let start_point = Point::new(start_row.0, 0);
12286 let end_point = Point::new(
12287 end_row.previous_row().0,
12288 buffer.line_len(end_row.previous_row()),
12289 );
12290 let text = buffer
12291 .text_for_range(start_point..end_point)
12292 .collect::<String>();
12293
12294 let LineManipulationResult {
12295 new_text,
12296 line_count_before,
12297 line_count_after,
12298 } = manipulate(&text);
12299
12300 edits.push((start_point..end_point, new_text));
12301
12302 // Selections must change based on added and removed line count
12303 let start_row =
12304 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12305 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12306 new_selections.push(Selection {
12307 id: selection.id,
12308 start: start_row,
12309 end: end_row,
12310 goal: SelectionGoal::None,
12311 reversed: selection.reversed,
12312 });
12313
12314 if line_count_after > line_count_before {
12315 added_lines += line_count_after - line_count_before;
12316 } else if line_count_before > line_count_after {
12317 removed_lines += line_count_before - line_count_after;
12318 }
12319 }
12320
12321 self.transact(window, cx, |this, window, cx| {
12322 let buffer = this.buffer.update(cx, |buffer, cx| {
12323 buffer.edit(edits, None, cx);
12324 buffer.snapshot(cx)
12325 });
12326
12327 // Recalculate offsets on newly edited buffer
12328 let new_selections = new_selections
12329 .iter()
12330 .map(|s| {
12331 let start_point = Point::new(s.start.0, 0);
12332 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12333 Selection {
12334 id: s.id,
12335 start: buffer.point_to_offset(start_point),
12336 end: buffer.point_to_offset(end_point),
12337 goal: s.goal,
12338 reversed: s.reversed,
12339 }
12340 })
12341 .collect();
12342
12343 this.change_selections(Default::default(), window, cx, |s| {
12344 s.select(new_selections);
12345 });
12346
12347 this.request_autoscroll(Autoscroll::fit(), cx);
12348 });
12349 }
12350
12351 fn manipulate_immutable_lines<Fn>(
12352 &mut self,
12353 window: &mut Window,
12354 cx: &mut Context<Self>,
12355 mut callback: Fn,
12356 ) where
12357 Fn: FnMut(&mut Vec<&str>),
12358 {
12359 self.manipulate_lines(window, cx, |text| {
12360 let mut lines: Vec<&str> = text.split('\n').collect();
12361 let line_count_before = lines.len();
12362
12363 callback(&mut lines);
12364
12365 LineManipulationResult {
12366 new_text: lines.join("\n"),
12367 line_count_before,
12368 line_count_after: lines.len(),
12369 }
12370 });
12371 }
12372
12373 fn manipulate_mutable_lines<Fn>(
12374 &mut self,
12375 window: &mut Window,
12376 cx: &mut Context<Self>,
12377 mut callback: Fn,
12378 ) where
12379 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12380 {
12381 self.manipulate_lines(window, cx, |text| {
12382 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12383 let line_count_before = lines.len();
12384
12385 callback(&mut lines);
12386
12387 LineManipulationResult {
12388 new_text: lines.join("\n"),
12389 line_count_before,
12390 line_count_after: lines.len(),
12391 }
12392 });
12393 }
12394
12395 pub fn convert_indentation_to_spaces(
12396 &mut self,
12397 _: &ConvertIndentationToSpaces,
12398 window: &mut Window,
12399 cx: &mut Context<Self>,
12400 ) {
12401 let settings = self.buffer.read(cx).language_settings(cx);
12402 let tab_size = settings.tab_size.get() as usize;
12403
12404 self.manipulate_mutable_lines(window, cx, |lines| {
12405 // Allocates a reasonably sized scratch buffer once for the whole loop
12406 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12407 // Avoids recomputing spaces that could be inserted many times
12408 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12409 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12410 .collect();
12411
12412 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12413 let mut chars = line.as_ref().chars();
12414 let mut col = 0;
12415 let mut changed = false;
12416
12417 for ch in chars.by_ref() {
12418 match ch {
12419 ' ' => {
12420 reindented_line.push(' ');
12421 col += 1;
12422 }
12423 '\t' => {
12424 // \t are converted to spaces depending on the current column
12425 let spaces_len = tab_size - (col % tab_size);
12426 reindented_line.extend(&space_cache[spaces_len - 1]);
12427 col += spaces_len;
12428 changed = true;
12429 }
12430 _ => {
12431 // If we dont append before break, the character is consumed
12432 reindented_line.push(ch);
12433 break;
12434 }
12435 }
12436 }
12437
12438 if !changed {
12439 reindented_line.clear();
12440 continue;
12441 }
12442 // Append the rest of the line and replace old reference with new one
12443 reindented_line.extend(chars);
12444 *line = Cow::Owned(reindented_line.clone());
12445 reindented_line.clear();
12446 }
12447 });
12448 }
12449
12450 pub fn convert_indentation_to_tabs(
12451 &mut self,
12452 _: &ConvertIndentationToTabs,
12453 window: &mut Window,
12454 cx: &mut Context<Self>,
12455 ) {
12456 let settings = self.buffer.read(cx).language_settings(cx);
12457 let tab_size = settings.tab_size.get() as usize;
12458
12459 self.manipulate_mutable_lines(window, cx, |lines| {
12460 // Allocates a reasonably sized buffer once for the whole loop
12461 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12462 // Avoids recomputing spaces that could be inserted many times
12463 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12464 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12465 .collect();
12466
12467 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12468 let mut chars = line.chars();
12469 let mut spaces_count = 0;
12470 let mut first_non_indent_char = None;
12471 let mut changed = false;
12472
12473 for ch in chars.by_ref() {
12474 match ch {
12475 ' ' => {
12476 // Keep track of spaces. Append \t when we reach tab_size
12477 spaces_count += 1;
12478 changed = true;
12479 if spaces_count == tab_size {
12480 reindented_line.push('\t');
12481 spaces_count = 0;
12482 }
12483 }
12484 '\t' => {
12485 reindented_line.push('\t');
12486 spaces_count = 0;
12487 }
12488 _ => {
12489 // Dont append it yet, we might have remaining spaces
12490 first_non_indent_char = Some(ch);
12491 break;
12492 }
12493 }
12494 }
12495
12496 if !changed {
12497 reindented_line.clear();
12498 continue;
12499 }
12500 // Remaining spaces that didn't make a full tab stop
12501 if spaces_count > 0 {
12502 reindented_line.extend(&space_cache[spaces_count - 1]);
12503 }
12504 // If we consume an extra character that was not indentation, add it back
12505 if let Some(extra_char) = first_non_indent_char {
12506 reindented_line.push(extra_char);
12507 }
12508 // Append the rest of the line and replace old reference with new one
12509 reindented_line.extend(chars);
12510 *line = Cow::Owned(reindented_line.clone());
12511 reindented_line.clear();
12512 }
12513 });
12514 }
12515
12516 pub fn convert_to_upper_case(
12517 &mut self,
12518 _: &ConvertToUpperCase,
12519 window: &mut Window,
12520 cx: &mut Context<Self>,
12521 ) {
12522 self.manipulate_text(window, cx, |text| text.to_uppercase())
12523 }
12524
12525 pub fn convert_to_lower_case(
12526 &mut self,
12527 _: &ConvertToLowerCase,
12528 window: &mut Window,
12529 cx: &mut Context<Self>,
12530 ) {
12531 self.manipulate_text(window, cx, |text| text.to_lowercase())
12532 }
12533
12534 pub fn convert_to_title_case(
12535 &mut self,
12536 _: &ConvertToTitleCase,
12537 window: &mut Window,
12538 cx: &mut Context<Self>,
12539 ) {
12540 self.manipulate_text(window, cx, |text| {
12541 text.split('\n')
12542 .map(|line| line.to_case(Case::Title))
12543 .join("\n")
12544 })
12545 }
12546
12547 pub fn convert_to_snake_case(
12548 &mut self,
12549 _: &ConvertToSnakeCase,
12550 window: &mut Window,
12551 cx: &mut Context<Self>,
12552 ) {
12553 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12554 }
12555
12556 pub fn convert_to_kebab_case(
12557 &mut self,
12558 _: &ConvertToKebabCase,
12559 window: &mut Window,
12560 cx: &mut Context<Self>,
12561 ) {
12562 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12563 }
12564
12565 pub fn convert_to_upper_camel_case(
12566 &mut self,
12567 _: &ConvertToUpperCamelCase,
12568 window: &mut Window,
12569 cx: &mut Context<Self>,
12570 ) {
12571 self.manipulate_text(window, cx, |text| {
12572 text.split('\n')
12573 .map(|line| line.to_case(Case::UpperCamel))
12574 .join("\n")
12575 })
12576 }
12577
12578 pub fn convert_to_lower_camel_case(
12579 &mut self,
12580 _: &ConvertToLowerCamelCase,
12581 window: &mut Window,
12582 cx: &mut Context<Self>,
12583 ) {
12584 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12585 }
12586
12587 pub fn convert_to_opposite_case(
12588 &mut self,
12589 _: &ConvertToOppositeCase,
12590 window: &mut Window,
12591 cx: &mut Context<Self>,
12592 ) {
12593 self.manipulate_text(window, cx, |text| {
12594 text.chars()
12595 .fold(String::with_capacity(text.len()), |mut t, c| {
12596 if c.is_uppercase() {
12597 t.extend(c.to_lowercase());
12598 } else {
12599 t.extend(c.to_uppercase());
12600 }
12601 t
12602 })
12603 })
12604 }
12605
12606 pub fn convert_to_sentence_case(
12607 &mut self,
12608 _: &ConvertToSentenceCase,
12609 window: &mut Window,
12610 cx: &mut Context<Self>,
12611 ) {
12612 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12613 }
12614
12615 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12616 self.manipulate_text(window, cx, |text| {
12617 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12618 if has_upper_case_characters {
12619 text.to_lowercase()
12620 } else {
12621 text.to_uppercase()
12622 }
12623 })
12624 }
12625
12626 pub fn convert_to_rot13(
12627 &mut self,
12628 _: &ConvertToRot13,
12629 window: &mut Window,
12630 cx: &mut Context<Self>,
12631 ) {
12632 self.manipulate_text(window, cx, |text| {
12633 text.chars()
12634 .map(|c| match c {
12635 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12636 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12637 _ => c,
12638 })
12639 .collect()
12640 })
12641 }
12642
12643 pub fn convert_to_rot47(
12644 &mut self,
12645 _: &ConvertToRot47,
12646 window: &mut Window,
12647 cx: &mut Context<Self>,
12648 ) {
12649 self.manipulate_text(window, cx, |text| {
12650 text.chars()
12651 .map(|c| {
12652 let code_point = c as u32;
12653 if code_point >= 33 && code_point <= 126 {
12654 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12655 }
12656 c
12657 })
12658 .collect()
12659 })
12660 }
12661
12662 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12663 where
12664 Fn: FnMut(&str) -> String,
12665 {
12666 let buffer = self.buffer.read(cx).snapshot(cx);
12667
12668 let mut new_selections = Vec::new();
12669 let mut edits = Vec::new();
12670 let mut selection_adjustment = 0isize;
12671
12672 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12673 let selection_is_empty = selection.is_empty();
12674
12675 let (start, end) = if selection_is_empty {
12676 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12677 (word_range.start, word_range.end)
12678 } else {
12679 (
12680 buffer.point_to_offset(selection.start),
12681 buffer.point_to_offset(selection.end),
12682 )
12683 };
12684
12685 let text = buffer.text_for_range(start..end).collect::<String>();
12686 let old_length = text.len() as isize;
12687 let text = callback(&text);
12688
12689 new_selections.push(Selection {
12690 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12691 end: MultiBufferOffset(
12692 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12693 ),
12694 goal: SelectionGoal::None,
12695 id: selection.id,
12696 reversed: selection.reversed,
12697 });
12698
12699 selection_adjustment += old_length - text.len() as isize;
12700
12701 edits.push((start..end, text));
12702 }
12703
12704 self.transact(window, cx, |this, window, cx| {
12705 this.buffer.update(cx, |buffer, cx| {
12706 buffer.edit(edits, None, cx);
12707 });
12708
12709 this.change_selections(Default::default(), window, cx, |s| {
12710 s.select(new_selections);
12711 });
12712
12713 this.request_autoscroll(Autoscroll::fit(), cx);
12714 });
12715 }
12716
12717 pub fn move_selection_on_drop(
12718 &mut self,
12719 selection: &Selection<Anchor>,
12720 target: DisplayPoint,
12721 is_cut: bool,
12722 window: &mut Window,
12723 cx: &mut Context<Self>,
12724 ) {
12725 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12726 let buffer = display_map.buffer_snapshot();
12727 let mut edits = Vec::new();
12728 let insert_point = display_map
12729 .clip_point(target, Bias::Left)
12730 .to_point(&display_map);
12731 let text = buffer
12732 .text_for_range(selection.start..selection.end)
12733 .collect::<String>();
12734 if is_cut {
12735 edits.push(((selection.start..selection.end), String::new()));
12736 }
12737 let insert_anchor = buffer.anchor_before(insert_point);
12738 edits.push(((insert_anchor..insert_anchor), text));
12739 let last_edit_start = insert_anchor.bias_left(buffer);
12740 let last_edit_end = insert_anchor.bias_right(buffer);
12741 self.transact(window, cx, |this, window, cx| {
12742 this.buffer.update(cx, |buffer, cx| {
12743 buffer.edit(edits, None, cx);
12744 });
12745 this.change_selections(Default::default(), window, cx, |s| {
12746 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12747 });
12748 });
12749 }
12750
12751 pub fn clear_selection_drag_state(&mut self) {
12752 self.selection_drag_state = SelectionDragState::None;
12753 }
12754
12755 pub fn duplicate(
12756 &mut self,
12757 upwards: bool,
12758 whole_lines: bool,
12759 window: &mut Window,
12760 cx: &mut Context<Self>,
12761 ) {
12762 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12763
12764 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12765 let buffer = display_map.buffer_snapshot();
12766 let selections = self.selections.all::<Point>(&display_map);
12767
12768 let mut edits = Vec::new();
12769 let mut selections_iter = selections.iter().peekable();
12770 while let Some(selection) = selections_iter.next() {
12771 let mut rows = selection.spanned_rows(false, &display_map);
12772 // duplicate line-wise
12773 if whole_lines || selection.start == selection.end {
12774 // Avoid duplicating the same lines twice.
12775 while let Some(next_selection) = selections_iter.peek() {
12776 let next_rows = next_selection.spanned_rows(false, &display_map);
12777 if next_rows.start < rows.end {
12778 rows.end = next_rows.end;
12779 selections_iter.next().unwrap();
12780 } else {
12781 break;
12782 }
12783 }
12784
12785 // Copy the text from the selected row region and splice it either at the start
12786 // or end of the region.
12787 let start = Point::new(rows.start.0, 0);
12788 let end = Point::new(
12789 rows.end.previous_row().0,
12790 buffer.line_len(rows.end.previous_row()),
12791 );
12792
12793 let mut text = buffer.text_for_range(start..end).collect::<String>();
12794
12795 let insert_location = if upwards {
12796 // When duplicating upward, we need to insert before the current line.
12797 // If we're on the last line and it doesn't end with a newline,
12798 // we need to add a newline before the duplicated content.
12799 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12800 && buffer.max_point().column > 0
12801 && !text.ends_with('\n');
12802
12803 if needs_leading_newline {
12804 text.insert(0, '\n');
12805 end
12806 } else {
12807 text.push('\n');
12808 Point::new(rows.start.0, 0)
12809 }
12810 } else {
12811 text.push('\n');
12812 start
12813 };
12814 edits.push((insert_location..insert_location, text));
12815 } else {
12816 // duplicate character-wise
12817 let start = selection.start;
12818 let end = selection.end;
12819 let text = buffer.text_for_range(start..end).collect::<String>();
12820 edits.push((selection.end..selection.end, text));
12821 }
12822 }
12823
12824 self.transact(window, cx, |this, window, cx| {
12825 this.buffer.update(cx, |buffer, cx| {
12826 buffer.edit(edits, None, cx);
12827 });
12828
12829 // When duplicating upward with whole lines, move the cursor to the duplicated line
12830 if upwards && whole_lines {
12831 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12832
12833 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12834 let mut new_ranges = Vec::new();
12835 let selections = s.all::<Point>(&display_map);
12836 let mut selections_iter = selections.iter().peekable();
12837
12838 while let Some(first_selection) = selections_iter.next() {
12839 // Group contiguous selections together to find the total row span
12840 let mut group_selections = vec![first_selection];
12841 let mut rows = first_selection.spanned_rows(false, &display_map);
12842
12843 while let Some(next_selection) = selections_iter.peek() {
12844 let next_rows = next_selection.spanned_rows(false, &display_map);
12845 if next_rows.start < rows.end {
12846 rows.end = next_rows.end;
12847 group_selections.push(selections_iter.next().unwrap());
12848 } else {
12849 break;
12850 }
12851 }
12852
12853 let row_count = rows.end.0 - rows.start.0;
12854
12855 // Move all selections in this group up by the total number of duplicated rows
12856 for selection in group_selections {
12857 let new_start = Point::new(
12858 selection.start.row.saturating_sub(row_count),
12859 selection.start.column,
12860 );
12861
12862 let new_end = Point::new(
12863 selection.end.row.saturating_sub(row_count),
12864 selection.end.column,
12865 );
12866
12867 new_ranges.push(new_start..new_end);
12868 }
12869 }
12870
12871 s.select_ranges(new_ranges);
12872 });
12873 }
12874
12875 this.request_autoscroll(Autoscroll::fit(), cx);
12876 });
12877 }
12878
12879 pub fn duplicate_line_up(
12880 &mut self,
12881 _: &DuplicateLineUp,
12882 window: &mut Window,
12883 cx: &mut Context<Self>,
12884 ) {
12885 self.duplicate(true, true, window, cx);
12886 }
12887
12888 pub fn duplicate_line_down(
12889 &mut self,
12890 _: &DuplicateLineDown,
12891 window: &mut Window,
12892 cx: &mut Context<Self>,
12893 ) {
12894 self.duplicate(false, true, window, cx);
12895 }
12896
12897 pub fn duplicate_selection(
12898 &mut self,
12899 _: &DuplicateSelection,
12900 window: &mut Window,
12901 cx: &mut Context<Self>,
12902 ) {
12903 self.duplicate(false, false, window, cx);
12904 }
12905
12906 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12907 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12908 if self.mode.is_single_line() {
12909 cx.propagate();
12910 return;
12911 }
12912
12913 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12914 let buffer = self.buffer.read(cx).snapshot(cx);
12915
12916 let mut edits = Vec::new();
12917 let mut unfold_ranges = Vec::new();
12918 let mut refold_creases = Vec::new();
12919
12920 let selections = self.selections.all::<Point>(&display_map);
12921 let mut selections = selections.iter().peekable();
12922 let mut contiguous_row_selections = Vec::new();
12923 let mut new_selections = Vec::new();
12924
12925 while let Some(selection) = selections.next() {
12926 // Find all the selections that span a contiguous row range
12927 let (start_row, end_row) = consume_contiguous_rows(
12928 &mut contiguous_row_selections,
12929 selection,
12930 &display_map,
12931 &mut selections,
12932 );
12933
12934 // Move the text spanned by the row range to be before the line preceding the row range
12935 if start_row.0 > 0 {
12936 let range_to_move = Point::new(
12937 start_row.previous_row().0,
12938 buffer.line_len(start_row.previous_row()),
12939 )
12940 ..Point::new(
12941 end_row.previous_row().0,
12942 buffer.line_len(end_row.previous_row()),
12943 );
12944 let insertion_point = display_map
12945 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12946 .0;
12947
12948 // Don't move lines across excerpts
12949 if buffer
12950 .excerpt_containing(insertion_point..range_to_move.end)
12951 .is_some()
12952 {
12953 let text = buffer
12954 .text_for_range(range_to_move.clone())
12955 .flat_map(|s| s.chars())
12956 .skip(1)
12957 .chain(['\n'])
12958 .collect::<String>();
12959
12960 edits.push((
12961 buffer.anchor_after(range_to_move.start)
12962 ..buffer.anchor_before(range_to_move.end),
12963 String::new(),
12964 ));
12965 let insertion_anchor = buffer.anchor_after(insertion_point);
12966 edits.push((insertion_anchor..insertion_anchor, text));
12967
12968 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12969
12970 // Move selections up
12971 new_selections.extend(contiguous_row_selections.drain(..).map(
12972 |mut selection| {
12973 selection.start.row -= row_delta;
12974 selection.end.row -= row_delta;
12975 selection
12976 },
12977 ));
12978
12979 // Move folds up
12980 unfold_ranges.push(range_to_move.clone());
12981 for fold in display_map.folds_in_range(
12982 buffer.anchor_before(range_to_move.start)
12983 ..buffer.anchor_after(range_to_move.end),
12984 ) {
12985 let mut start = fold.range.start.to_point(&buffer);
12986 let mut end = fold.range.end.to_point(&buffer);
12987 start.row -= row_delta;
12988 end.row -= row_delta;
12989 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12990 }
12991 }
12992 }
12993
12994 // If we didn't move line(s), preserve the existing selections
12995 new_selections.append(&mut contiguous_row_selections);
12996 }
12997
12998 self.transact(window, cx, |this, window, cx| {
12999 this.unfold_ranges(&unfold_ranges, true, true, cx);
13000 this.buffer.update(cx, |buffer, cx| {
13001 for (range, text) in edits {
13002 buffer.edit([(range, text)], None, cx);
13003 }
13004 });
13005 this.fold_creases(refold_creases, true, window, cx);
13006 this.change_selections(Default::default(), window, cx, |s| {
13007 s.select(new_selections);
13008 })
13009 });
13010 }
13011
13012 pub fn move_line_down(
13013 &mut self,
13014 _: &MoveLineDown,
13015 window: &mut Window,
13016 cx: &mut Context<Self>,
13017 ) {
13018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13019 if self.mode.is_single_line() {
13020 cx.propagate();
13021 return;
13022 }
13023
13024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13025 let buffer = self.buffer.read(cx).snapshot(cx);
13026
13027 let mut edits = Vec::new();
13028 let mut unfold_ranges = Vec::new();
13029 let mut refold_creases = Vec::new();
13030
13031 let selections = self.selections.all::<Point>(&display_map);
13032 let mut selections = selections.iter().peekable();
13033 let mut contiguous_row_selections = Vec::new();
13034 let mut new_selections = Vec::new();
13035
13036 while let Some(selection) = selections.next() {
13037 // Find all the selections that span a contiguous row range
13038 let (start_row, end_row) = consume_contiguous_rows(
13039 &mut contiguous_row_selections,
13040 selection,
13041 &display_map,
13042 &mut selections,
13043 );
13044
13045 // Move the text spanned by the row range to be after the last line of the row range
13046 if end_row.0 <= buffer.max_point().row {
13047 let range_to_move =
13048 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13049 let insertion_point = display_map
13050 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13051 .0;
13052
13053 // Don't move lines across excerpt boundaries
13054 if buffer
13055 .excerpt_containing(range_to_move.start..insertion_point)
13056 .is_some()
13057 {
13058 let mut text = String::from("\n");
13059 text.extend(buffer.text_for_range(range_to_move.clone()));
13060 text.pop(); // Drop trailing newline
13061 edits.push((
13062 buffer.anchor_after(range_to_move.start)
13063 ..buffer.anchor_before(range_to_move.end),
13064 String::new(),
13065 ));
13066 let insertion_anchor = buffer.anchor_after(insertion_point);
13067 edits.push((insertion_anchor..insertion_anchor, text));
13068
13069 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13070
13071 // Move selections down
13072 new_selections.extend(contiguous_row_selections.drain(..).map(
13073 |mut selection| {
13074 selection.start.row += row_delta;
13075 selection.end.row += row_delta;
13076 selection
13077 },
13078 ));
13079
13080 // Move folds down
13081 unfold_ranges.push(range_to_move.clone());
13082 for fold in display_map.folds_in_range(
13083 buffer.anchor_before(range_to_move.start)
13084 ..buffer.anchor_after(range_to_move.end),
13085 ) {
13086 let mut start = fold.range.start.to_point(&buffer);
13087 let mut end = fold.range.end.to_point(&buffer);
13088 start.row += row_delta;
13089 end.row += row_delta;
13090 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13091 }
13092 }
13093 }
13094
13095 // If we didn't move line(s), preserve the existing selections
13096 new_selections.append(&mut contiguous_row_selections);
13097 }
13098
13099 self.transact(window, cx, |this, window, cx| {
13100 this.unfold_ranges(&unfold_ranges, true, true, cx);
13101 this.buffer.update(cx, |buffer, cx| {
13102 for (range, text) in edits {
13103 buffer.edit([(range, text)], None, cx);
13104 }
13105 });
13106 this.fold_creases(refold_creases, true, window, cx);
13107 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13108 });
13109 }
13110
13111 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13112 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13113 let text_layout_details = &self.text_layout_details(window, cx);
13114 self.transact(window, cx, |this, window, cx| {
13115 let edits = this.change_selections(Default::default(), window, cx, |s| {
13116 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13117 s.move_with(&mut |display_map, selection| {
13118 if !selection.is_empty() {
13119 return;
13120 }
13121
13122 let mut head = selection.head();
13123 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13124 if head.column() == display_map.line_len(head.row()) {
13125 transpose_offset = display_map
13126 .buffer_snapshot()
13127 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13128 }
13129
13130 if transpose_offset == MultiBufferOffset(0) {
13131 return;
13132 }
13133
13134 *head.column_mut() += 1;
13135 head = display_map.clip_point(head, Bias::Right);
13136 let goal = SelectionGoal::HorizontalPosition(
13137 display_map
13138 .x_for_display_point(head, text_layout_details)
13139 .into(),
13140 );
13141 selection.collapse_to(head, goal);
13142
13143 let transpose_start = display_map
13144 .buffer_snapshot()
13145 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13146 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13147 let transpose_end = display_map
13148 .buffer_snapshot()
13149 .clip_offset(transpose_offset + 1usize, Bias::Right);
13150 if let Some(ch) = display_map
13151 .buffer_snapshot()
13152 .chars_at(transpose_start)
13153 .next()
13154 {
13155 edits.push((transpose_start..transpose_offset, String::new()));
13156 edits.push((transpose_end..transpose_end, ch.to_string()));
13157 }
13158 }
13159 });
13160 edits
13161 });
13162 this.buffer
13163 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13164 let selections = this
13165 .selections
13166 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13167 this.change_selections(Default::default(), window, cx, |s| {
13168 s.select(selections);
13169 });
13170 });
13171 }
13172
13173 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13175 if self.mode.is_single_line() {
13176 cx.propagate();
13177 return;
13178 }
13179
13180 self.rewrap_impl(RewrapOptions::default(), cx)
13181 }
13182
13183 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13184 let buffer = self.buffer.read(cx).snapshot(cx);
13185 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13186
13187 #[derive(Clone, Debug, PartialEq)]
13188 enum CommentFormat {
13189 /// single line comment, with prefix for line
13190 Line(String),
13191 /// single line within a block comment, with prefix for line
13192 BlockLine(String),
13193 /// a single line of a block comment that includes the initial delimiter
13194 BlockCommentWithStart(BlockCommentConfig),
13195 /// a single line of a block comment that includes the ending delimiter
13196 BlockCommentWithEnd(BlockCommentConfig),
13197 }
13198
13199 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13200 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13201 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13202 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13203 .peekable();
13204
13205 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13206 row
13207 } else {
13208 return Vec::new();
13209 };
13210
13211 let language_settings = buffer.language_settings_at(selection.head(), cx);
13212 let language_scope = buffer.language_scope_at(selection.head());
13213
13214 let indent_and_prefix_for_row =
13215 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13216 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13217 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13218 &language_scope
13219 {
13220 let indent_end = Point::new(row, indent.len);
13221 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13222 let line_text_after_indent = buffer
13223 .text_for_range(indent_end..line_end)
13224 .collect::<String>();
13225
13226 let is_within_comment_override = buffer
13227 .language_scope_at(indent_end)
13228 .is_some_and(|scope| scope.override_name() == Some("comment"));
13229 let comment_delimiters = if is_within_comment_override {
13230 // we are within a comment syntax node, but we don't
13231 // yet know what kind of comment: block, doc or line
13232 match (
13233 language_scope.documentation_comment(),
13234 language_scope.block_comment(),
13235 ) {
13236 (Some(config), _) | (_, Some(config))
13237 if buffer.contains_str_at(indent_end, &config.start) =>
13238 {
13239 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13240 }
13241 (Some(config), _) | (_, Some(config))
13242 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13243 {
13244 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13245 }
13246 (Some(config), _) | (_, Some(config))
13247 if buffer.contains_str_at(indent_end, &config.prefix) =>
13248 {
13249 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13250 }
13251 (_, _) => language_scope
13252 .line_comment_prefixes()
13253 .iter()
13254 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13255 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13256 }
13257 } else {
13258 // we not in an overridden comment node, but we may
13259 // be within a non-overridden line comment node
13260 language_scope
13261 .line_comment_prefixes()
13262 .iter()
13263 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13264 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13265 };
13266
13267 let rewrap_prefix = language_scope
13268 .rewrap_prefixes()
13269 .iter()
13270 .find_map(|prefix_regex| {
13271 prefix_regex.find(&line_text_after_indent).map(|mat| {
13272 if mat.start() == 0 {
13273 Some(mat.as_str().to_string())
13274 } else {
13275 None
13276 }
13277 })
13278 })
13279 .flatten();
13280 (comment_delimiters, rewrap_prefix)
13281 } else {
13282 (None, None)
13283 };
13284 (indent, comment_prefix, rewrap_prefix)
13285 };
13286
13287 let mut ranges = Vec::new();
13288 let from_empty_selection = selection.is_empty();
13289
13290 let mut current_range_start = first_row;
13291 let mut prev_row = first_row;
13292 let (
13293 mut current_range_indent,
13294 mut current_range_comment_delimiters,
13295 mut current_range_rewrap_prefix,
13296 ) = indent_and_prefix_for_row(first_row);
13297
13298 for row in non_blank_rows_iter.skip(1) {
13299 let has_paragraph_break = row > prev_row + 1;
13300
13301 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13302 indent_and_prefix_for_row(row);
13303
13304 let has_indent_change = row_indent != current_range_indent;
13305 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13306
13307 let has_boundary_change = has_comment_change
13308 || row_rewrap_prefix.is_some()
13309 || (has_indent_change && current_range_comment_delimiters.is_some());
13310
13311 if has_paragraph_break || has_boundary_change {
13312 ranges.push((
13313 language_settings.clone(),
13314 Point::new(current_range_start, 0)
13315 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13316 current_range_indent,
13317 current_range_comment_delimiters.clone(),
13318 current_range_rewrap_prefix.clone(),
13319 from_empty_selection,
13320 ));
13321 current_range_start = row;
13322 current_range_indent = row_indent;
13323 current_range_comment_delimiters = row_comment_delimiters;
13324 current_range_rewrap_prefix = row_rewrap_prefix;
13325 }
13326 prev_row = row;
13327 }
13328
13329 ranges.push((
13330 language_settings.clone(),
13331 Point::new(current_range_start, 0)
13332 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13333 current_range_indent,
13334 current_range_comment_delimiters,
13335 current_range_rewrap_prefix,
13336 from_empty_selection,
13337 ));
13338
13339 ranges
13340 });
13341
13342 let mut edits = Vec::new();
13343 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13344
13345 for (
13346 language_settings,
13347 wrap_range,
13348 mut indent_size,
13349 comment_prefix,
13350 rewrap_prefix,
13351 from_empty_selection,
13352 ) in wrap_ranges
13353 {
13354 let mut start_row = wrap_range.start.row;
13355 let mut end_row = wrap_range.end.row;
13356
13357 // Skip selections that overlap with a range that has already been rewrapped.
13358 let selection_range = start_row..end_row;
13359 if rewrapped_row_ranges
13360 .iter()
13361 .any(|range| range.overlaps(&selection_range))
13362 {
13363 continue;
13364 }
13365
13366 let tab_size = language_settings.tab_size;
13367
13368 let (line_prefix, inside_comment) = match &comment_prefix {
13369 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13370 (Some(prefix.as_str()), true)
13371 }
13372 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13373 (Some(prefix.as_ref()), true)
13374 }
13375 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13376 start: _,
13377 end: _,
13378 prefix,
13379 tab_size,
13380 })) => {
13381 indent_size.len += tab_size;
13382 (Some(prefix.as_ref()), true)
13383 }
13384 None => (None, false),
13385 };
13386 let indent_prefix = indent_size.chars().collect::<String>();
13387 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13388
13389 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13390 RewrapBehavior::InComments => inside_comment,
13391 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13392 RewrapBehavior::Anywhere => true,
13393 };
13394
13395 let should_rewrap = options.override_language_settings
13396 || allow_rewrap_based_on_language
13397 || self.hard_wrap.is_some();
13398 if !should_rewrap {
13399 continue;
13400 }
13401
13402 if from_empty_selection {
13403 'expand_upwards: while start_row > 0 {
13404 let prev_row = start_row - 1;
13405 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13406 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13407 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13408 {
13409 start_row = prev_row;
13410 } else {
13411 break 'expand_upwards;
13412 }
13413 }
13414
13415 'expand_downwards: while end_row < buffer.max_point().row {
13416 let next_row = end_row + 1;
13417 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13418 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13419 && !buffer.is_line_blank(MultiBufferRow(next_row))
13420 {
13421 end_row = next_row;
13422 } else {
13423 break 'expand_downwards;
13424 }
13425 }
13426 }
13427
13428 let start = Point::new(start_row, 0);
13429 let start_offset = ToOffset::to_offset(&start, &buffer);
13430 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13431 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13432 let mut first_line_delimiter = None;
13433 let mut last_line_delimiter = None;
13434 let Some(lines_without_prefixes) = selection_text
13435 .lines()
13436 .enumerate()
13437 .map(|(ix, line)| {
13438 let line_trimmed = line.trim_start();
13439 if rewrap_prefix.is_some() && ix > 0 {
13440 Ok(line_trimmed)
13441 } else if let Some(
13442 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13443 start,
13444 prefix,
13445 end,
13446 tab_size,
13447 })
13448 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13449 start,
13450 prefix,
13451 end,
13452 tab_size,
13453 }),
13454 ) = &comment_prefix
13455 {
13456 let line_trimmed = line_trimmed
13457 .strip_prefix(start.as_ref())
13458 .map(|s| {
13459 let mut indent_size = indent_size;
13460 indent_size.len -= tab_size;
13461 let indent_prefix: String = indent_size.chars().collect();
13462 first_line_delimiter = Some((indent_prefix, start));
13463 s.trim_start()
13464 })
13465 .unwrap_or(line_trimmed);
13466 let line_trimmed = line_trimmed
13467 .strip_suffix(end.as_ref())
13468 .map(|s| {
13469 last_line_delimiter = Some(end);
13470 s.trim_end()
13471 })
13472 .unwrap_or(line_trimmed);
13473 let line_trimmed = line_trimmed
13474 .strip_prefix(prefix.as_ref())
13475 .unwrap_or(line_trimmed);
13476 Ok(line_trimmed)
13477 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13478 line_trimmed.strip_prefix(prefix).with_context(|| {
13479 format!("line did not start with prefix {prefix:?}: {line:?}")
13480 })
13481 } else {
13482 line_trimmed
13483 .strip_prefix(&line_prefix.trim_start())
13484 .with_context(|| {
13485 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13486 })
13487 }
13488 })
13489 .collect::<Result<Vec<_>, _>>()
13490 .log_err()
13491 else {
13492 continue;
13493 };
13494
13495 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13496 buffer
13497 .language_settings_at(Point::new(start_row, 0), cx)
13498 .preferred_line_length as usize
13499 });
13500
13501 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13502 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13503 } else {
13504 line_prefix.clone()
13505 };
13506
13507 let wrapped_text = {
13508 let mut wrapped_text = wrap_with_prefix(
13509 line_prefix,
13510 subsequent_lines_prefix,
13511 lines_without_prefixes.join("\n"),
13512 wrap_column,
13513 tab_size,
13514 options.preserve_existing_whitespace,
13515 );
13516
13517 if let Some((indent, delimiter)) = first_line_delimiter {
13518 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13519 }
13520 if let Some(last_line) = last_line_delimiter {
13521 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13522 }
13523
13524 wrapped_text
13525 };
13526
13527 // TODO: should always use char-based diff while still supporting cursor behavior that
13528 // matches vim.
13529 let mut diff_options = DiffOptions::default();
13530 if options.override_language_settings {
13531 diff_options.max_word_diff_len = 0;
13532 diff_options.max_word_diff_line_count = 0;
13533 } else {
13534 diff_options.max_word_diff_len = usize::MAX;
13535 diff_options.max_word_diff_line_count = usize::MAX;
13536 }
13537
13538 for (old_range, new_text) in
13539 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13540 {
13541 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13542 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13543 edits.push((edit_start..edit_end, new_text));
13544 }
13545
13546 rewrapped_row_ranges.push(start_row..=end_row);
13547 }
13548
13549 self.buffer
13550 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13551 }
13552
13553 pub fn cut_common(
13554 &mut self,
13555 cut_no_selection_line: bool,
13556 window: &mut Window,
13557 cx: &mut Context<Self>,
13558 ) -> ClipboardItem {
13559 let mut text = String::new();
13560 let buffer = self.buffer.read(cx).snapshot(cx);
13561 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13562 let mut clipboard_selections = Vec::with_capacity(selections.len());
13563 {
13564 let max_point = buffer.max_point();
13565 let mut is_first = true;
13566 let mut prev_selection_was_entire_line = false;
13567 for selection in &mut selections {
13568 let is_entire_line =
13569 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13570 if is_entire_line {
13571 selection.start = Point::new(selection.start.row, 0);
13572 if !selection.is_empty() && selection.end.column == 0 {
13573 selection.end = cmp::min(max_point, selection.end);
13574 } else {
13575 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13576 }
13577 selection.goal = SelectionGoal::None;
13578 }
13579 if is_first {
13580 is_first = false;
13581 } else if !prev_selection_was_entire_line {
13582 text += "\n";
13583 }
13584 prev_selection_was_entire_line = is_entire_line;
13585 let mut len = 0;
13586 for chunk in buffer.text_for_range(selection.start..selection.end) {
13587 text.push_str(chunk);
13588 len += chunk.len();
13589 }
13590
13591 clipboard_selections.push(ClipboardSelection::for_buffer(
13592 len,
13593 is_entire_line,
13594 selection.range(),
13595 &buffer,
13596 self.project.as_ref(),
13597 cx,
13598 ));
13599 }
13600 }
13601
13602 self.transact(window, cx, |this, window, cx| {
13603 this.change_selections(Default::default(), window, cx, |s| {
13604 s.select(selections);
13605 });
13606 this.insert("", window, cx);
13607 });
13608 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13609 }
13610
13611 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13612 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13613 let item = self.cut_common(true, window, cx);
13614 cx.write_to_clipboard(item);
13615 }
13616
13617 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13618 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13619 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13620 s.move_with(&mut |snapshot, sel| {
13621 if sel.is_empty() {
13622 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13623 }
13624 if sel.is_empty() {
13625 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13626 }
13627 });
13628 });
13629 let item = self.cut_common(false, window, cx);
13630 cx.set_global(KillRing(item))
13631 }
13632
13633 pub fn kill_ring_yank(
13634 &mut self,
13635 _: &KillRingYank,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13640 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13641 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13642 (kill_ring.text().to_string(), kill_ring.metadata_json())
13643 } else {
13644 return;
13645 }
13646 } else {
13647 return;
13648 };
13649 self.do_paste(&text, metadata, false, window, cx);
13650 }
13651
13652 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13653 self.do_copy(true, cx);
13654 }
13655
13656 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13657 self.do_copy(false, cx);
13658 }
13659
13660 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13661 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13662 let buffer = self.buffer.read(cx).read(cx);
13663 let mut text = String::new();
13664
13665 let mut clipboard_selections = Vec::with_capacity(selections.len());
13666 {
13667 let max_point = buffer.max_point();
13668 let mut is_first = true;
13669 let mut prev_selection_was_entire_line = false;
13670 for selection in &selections {
13671 let mut start = selection.start;
13672 let mut end = selection.end;
13673 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13674 let mut add_trailing_newline = false;
13675 if is_entire_line {
13676 start = Point::new(start.row, 0);
13677 let next_line_start = Point::new(end.row + 1, 0);
13678 if next_line_start <= max_point {
13679 end = next_line_start;
13680 } else {
13681 // We're on the last line without a trailing newline.
13682 // Copy to the end of the line and add a newline afterwards.
13683 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13684 add_trailing_newline = true;
13685 }
13686 }
13687
13688 let mut trimmed_selections = Vec::new();
13689 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13690 let row = MultiBufferRow(start.row);
13691 let first_indent = buffer.indent_size_for_line(row);
13692 if first_indent.len == 0 || start.column > first_indent.len {
13693 trimmed_selections.push(start..end);
13694 } else {
13695 trimmed_selections.push(
13696 Point::new(row.0, first_indent.len)
13697 ..Point::new(row.0, buffer.line_len(row)),
13698 );
13699 for row in start.row + 1..=end.row {
13700 let mut line_len = buffer.line_len(MultiBufferRow(row));
13701 if row == end.row {
13702 line_len = end.column;
13703 }
13704 if line_len == 0 {
13705 trimmed_selections
13706 .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.push(
13712 Point::new(row, first_indent.len)..Point::new(row, line_len),
13713 );
13714 } else {
13715 trimmed_selections.clear();
13716 trimmed_selections.push(start..end);
13717 break;
13718 }
13719 }
13720 }
13721 } else {
13722 trimmed_selections.push(start..end);
13723 }
13724
13725 let is_multiline_trim = trimmed_selections.len() > 1;
13726 for trimmed_range in trimmed_selections {
13727 if is_first {
13728 is_first = false;
13729 } else if is_multiline_trim || !prev_selection_was_entire_line {
13730 text += "\n";
13731 }
13732 prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13733 let mut len = 0;
13734 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13735 text.push_str(chunk);
13736 len += chunk.len();
13737 }
13738 if add_trailing_newline {
13739 text.push('\n');
13740 len += 1;
13741 }
13742 clipboard_selections.push(ClipboardSelection::for_buffer(
13743 len,
13744 is_entire_line,
13745 trimmed_range,
13746 &buffer,
13747 self.project.as_ref(),
13748 cx,
13749 ));
13750 }
13751 }
13752 }
13753
13754 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13755 text,
13756 clipboard_selections,
13757 ));
13758 }
13759
13760 pub fn do_paste(
13761 &mut self,
13762 text: &String,
13763 clipboard_selections: Option<Vec<ClipboardSelection>>,
13764 handle_entire_lines: bool,
13765 window: &mut Window,
13766 cx: &mut Context<Self>,
13767 ) {
13768 if self.read_only(cx) {
13769 return;
13770 }
13771
13772 let clipboard_text = Cow::Borrowed(text.as_str());
13773
13774 self.transact(window, cx, |this, window, cx| {
13775 let had_active_edit_prediction = this.has_active_edit_prediction();
13776 let display_map = this.display_snapshot(cx);
13777 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13778 let cursor_offset = this
13779 .selections
13780 .last::<MultiBufferOffset>(&display_map)
13781 .head();
13782
13783 if let Some(mut clipboard_selections) = clipboard_selections {
13784 let all_selections_were_entire_line =
13785 clipboard_selections.iter().all(|s| s.is_entire_line);
13786 let first_selection_indent_column =
13787 clipboard_selections.first().map(|s| s.first_line_indent);
13788 if clipboard_selections.len() != old_selections.len() {
13789 clipboard_selections.drain(..);
13790 }
13791 let mut auto_indent_on_paste = true;
13792
13793 this.buffer.update(cx, |buffer, cx| {
13794 let snapshot = buffer.read(cx);
13795 auto_indent_on_paste = snapshot
13796 .language_settings_at(cursor_offset, cx)
13797 .auto_indent_on_paste;
13798
13799 let mut start_offset = 0;
13800 let mut edits = Vec::new();
13801 let mut original_indent_columns = Vec::new();
13802 for (ix, selection) in old_selections.iter().enumerate() {
13803 let to_insert;
13804 let entire_line;
13805 let original_indent_column;
13806 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13807 let end_offset = start_offset + clipboard_selection.len;
13808 to_insert = &clipboard_text[start_offset..end_offset];
13809 entire_line = clipboard_selection.is_entire_line;
13810 start_offset = if entire_line {
13811 end_offset
13812 } else {
13813 end_offset + 1
13814 };
13815 original_indent_column = Some(clipboard_selection.first_line_indent);
13816 } else {
13817 to_insert = &*clipboard_text;
13818 entire_line = all_selections_were_entire_line;
13819 original_indent_column = first_selection_indent_column
13820 }
13821
13822 let (range, to_insert) =
13823 if selection.is_empty() && handle_entire_lines && entire_line {
13824 // If the corresponding selection was empty when this slice of the
13825 // clipboard text was written, then the entire line containing the
13826 // selection was copied. If this selection is also currently empty,
13827 // then paste the line before the current line of the buffer.
13828 let column = selection.start.to_point(&snapshot).column as usize;
13829 let line_start = selection.start - column;
13830 (line_start..line_start, Cow::Borrowed(to_insert))
13831 } else {
13832 let language = snapshot.language_at(selection.head());
13833 let range = selection.range();
13834 if let Some(language) = language
13835 && language.name() == "Markdown".into()
13836 {
13837 edit_for_markdown_paste(
13838 &snapshot,
13839 range,
13840 to_insert,
13841 url::Url::parse(to_insert).ok(),
13842 )
13843 } else {
13844 (range, Cow::Borrowed(to_insert))
13845 }
13846 };
13847
13848 edits.push((range, to_insert));
13849 original_indent_columns.push(original_indent_column);
13850 }
13851 drop(snapshot);
13852
13853 buffer.edit(
13854 edits,
13855 if auto_indent_on_paste {
13856 Some(AutoindentMode::Block {
13857 original_indent_columns,
13858 })
13859 } else {
13860 None
13861 },
13862 cx,
13863 );
13864 });
13865
13866 let selections = this
13867 .selections
13868 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13869 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13870 } else {
13871 let url = url::Url::parse(&clipboard_text).ok();
13872
13873 let auto_indent_mode = if !clipboard_text.is_empty() {
13874 Some(AutoindentMode::Block {
13875 original_indent_columns: Vec::new(),
13876 })
13877 } else {
13878 None
13879 };
13880
13881 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13882 let snapshot = buffer.snapshot(cx);
13883
13884 let anchors = old_selections
13885 .iter()
13886 .map(|s| {
13887 let anchor = snapshot.anchor_after(s.head());
13888 s.map(|_| anchor)
13889 })
13890 .collect::<Vec<_>>();
13891
13892 let mut edits = Vec::new();
13893
13894 for selection in old_selections.iter() {
13895 let language = snapshot.language_at(selection.head());
13896 let range = selection.range();
13897
13898 let (edit_range, edit_text) = if let Some(language) = language
13899 && language.name() == "Markdown".into()
13900 {
13901 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13902 } else {
13903 (range, clipboard_text.clone())
13904 };
13905
13906 edits.push((edit_range, edit_text));
13907 }
13908
13909 drop(snapshot);
13910 buffer.edit(edits, auto_indent_mode, cx);
13911
13912 anchors
13913 });
13914
13915 this.change_selections(Default::default(), window, cx, |s| {
13916 s.select_anchors(selection_anchors);
13917 });
13918 }
13919
13920 // 🤔 | .. | show_in_menu |
13921 // | .. | true true
13922 // | had_edit_prediction | false true
13923
13924 let trigger_in_words =
13925 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13926
13927 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13928 });
13929 }
13930
13931 pub fn diff_clipboard_with_selection(
13932 &mut self,
13933 _: &DiffClipboardWithSelection,
13934 window: &mut Window,
13935 cx: &mut Context<Self>,
13936 ) {
13937 let selections = self
13938 .selections
13939 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13940
13941 if selections.is_empty() {
13942 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13943 return;
13944 };
13945
13946 let clipboard_text = match cx.read_from_clipboard() {
13947 Some(item) => match item.entries().first() {
13948 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13949 _ => None,
13950 },
13951 None => None,
13952 };
13953
13954 let Some(clipboard_text) = clipboard_text else {
13955 log::warn!("Clipboard doesn't contain text.");
13956 return;
13957 };
13958
13959 window.dispatch_action(
13960 Box::new(DiffClipboardWithSelectionData {
13961 clipboard_text,
13962 editor: cx.entity(),
13963 }),
13964 cx,
13965 );
13966 }
13967
13968 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13970 if let Some(item) = cx.read_from_clipboard() {
13971 let entries = item.entries();
13972
13973 match entries.first() {
13974 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13975 // of all the pasted entries.
13976 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13977 .do_paste(
13978 clipboard_string.text(),
13979 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13980 true,
13981 window,
13982 cx,
13983 ),
13984 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13985 }
13986 }
13987 }
13988
13989 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13990 if self.read_only(cx) {
13991 return;
13992 }
13993
13994 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13995
13996 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13997 if let Some((selections, _)) =
13998 self.selection_history.transaction(transaction_id).cloned()
13999 {
14000 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14001 s.select_anchors(selections.to_vec());
14002 });
14003 } else {
14004 log::error!(
14005 "No entry in selection_history found for undo. \
14006 This may correspond to a bug where undo does not update the selection. \
14007 If this is occurring, please add details to \
14008 https://github.com/zed-industries/zed/issues/22692"
14009 );
14010 }
14011 self.request_autoscroll(Autoscroll::fit(), cx);
14012 self.unmark_text(window, cx);
14013 self.refresh_edit_prediction(true, false, window, cx);
14014 cx.emit(EditorEvent::Edited { transaction_id });
14015 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14016 }
14017 }
14018
14019 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14020 if self.read_only(cx) {
14021 return;
14022 }
14023
14024 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14025
14026 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14027 if let Some((_, Some(selections))) =
14028 self.selection_history.transaction(transaction_id).cloned()
14029 {
14030 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14031 s.select_anchors(selections.to_vec());
14032 });
14033 } else {
14034 log::error!(
14035 "No entry in selection_history found for redo. \
14036 This may correspond to a bug where undo does not update the selection. \
14037 If this is occurring, please add details to \
14038 https://github.com/zed-industries/zed/issues/22692"
14039 );
14040 }
14041 self.request_autoscroll(Autoscroll::fit(), cx);
14042 self.unmark_text(window, cx);
14043 self.refresh_edit_prediction(true, false, window, cx);
14044 cx.emit(EditorEvent::Edited { transaction_id });
14045 }
14046 }
14047
14048 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14049 self.buffer
14050 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14051 }
14052
14053 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14054 self.buffer
14055 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14056 }
14057
14058 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14060 self.change_selections(Default::default(), window, cx, |s| {
14061 s.move_with(&mut |map, selection| {
14062 let cursor = if selection.is_empty() {
14063 movement::left(map, selection.start)
14064 } else {
14065 selection.start
14066 };
14067 selection.collapse_to(cursor, SelectionGoal::None);
14068 });
14069 })
14070 }
14071
14072 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14074 self.change_selections(Default::default(), window, cx, |s| {
14075 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14076 })
14077 }
14078
14079 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14081 self.change_selections(Default::default(), window, cx, |s| {
14082 s.move_with(&mut |map, selection| {
14083 let cursor = if selection.is_empty() {
14084 movement::right(map, selection.end)
14085 } else {
14086 selection.end
14087 };
14088 selection.collapse_to(cursor, SelectionGoal::None)
14089 });
14090 })
14091 }
14092
14093 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14095 self.change_selections(Default::default(), window, cx, |s| {
14096 s.move_heads_with(&mut |map, head, _| {
14097 (movement::right(map, head), SelectionGoal::None)
14098 });
14099 });
14100 }
14101
14102 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14103 if self.take_rename(true, window, cx).is_some() {
14104 return;
14105 }
14106
14107 if self.mode.is_single_line() {
14108 cx.propagate();
14109 return;
14110 }
14111
14112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14113
14114 let text_layout_details = &self.text_layout_details(window, cx);
14115 let selection_count = self.selections.count();
14116 let first_selection = self.selections.first_anchor();
14117
14118 self.change_selections(Default::default(), window, cx, |s| {
14119 s.move_with(&mut |map, selection| {
14120 if !selection.is_empty() {
14121 selection.goal = SelectionGoal::None;
14122 }
14123 let (cursor, goal) = movement::up(
14124 map,
14125 selection.start,
14126 selection.goal,
14127 false,
14128 text_layout_details,
14129 );
14130 selection.collapse_to(cursor, goal);
14131 });
14132 });
14133
14134 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14135 {
14136 cx.propagate();
14137 }
14138 }
14139
14140 pub fn move_up_by_lines(
14141 &mut self,
14142 action: &MoveUpByLines,
14143 window: &mut Window,
14144 cx: &mut Context<Self>,
14145 ) {
14146 if self.take_rename(true, window, cx).is_some() {
14147 return;
14148 }
14149
14150 if self.mode.is_single_line() {
14151 cx.propagate();
14152 return;
14153 }
14154
14155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14156
14157 let text_layout_details = &self.text_layout_details(window, cx);
14158
14159 self.change_selections(Default::default(), window, cx, |s| {
14160 s.move_with(&mut |map, selection| {
14161 if !selection.is_empty() {
14162 selection.goal = SelectionGoal::None;
14163 }
14164 let (cursor, goal) = movement::up_by_rows(
14165 map,
14166 selection.start,
14167 action.lines,
14168 selection.goal,
14169 false,
14170 text_layout_details,
14171 );
14172 selection.collapse_to(cursor, goal);
14173 });
14174 })
14175 }
14176
14177 pub fn move_down_by_lines(
14178 &mut self,
14179 action: &MoveDownByLines,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) {
14183 if self.take_rename(true, window, cx).is_some() {
14184 return;
14185 }
14186
14187 if self.mode.is_single_line() {
14188 cx.propagate();
14189 return;
14190 }
14191
14192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14193
14194 let text_layout_details = &self.text_layout_details(window, cx);
14195
14196 self.change_selections(Default::default(), window, cx, |s| {
14197 s.move_with(&mut |map, selection| {
14198 if !selection.is_empty() {
14199 selection.goal = SelectionGoal::None;
14200 }
14201 let (cursor, goal) = movement::down_by_rows(
14202 map,
14203 selection.start,
14204 action.lines,
14205 selection.goal,
14206 false,
14207 text_layout_details,
14208 );
14209 selection.collapse_to(cursor, goal);
14210 });
14211 })
14212 }
14213
14214 pub fn select_down_by_lines(
14215 &mut self,
14216 action: &SelectDownByLines,
14217 window: &mut Window,
14218 cx: &mut Context<Self>,
14219 ) {
14220 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14221 let text_layout_details = &self.text_layout_details(window, cx);
14222 self.change_selections(Default::default(), window, cx, |s| {
14223 s.move_heads_with(&mut |map, head, goal| {
14224 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14225 })
14226 })
14227 }
14228
14229 pub fn select_up_by_lines(
14230 &mut self,
14231 action: &SelectUpByLines,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) {
14235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14236 let text_layout_details = &self.text_layout_details(window, cx);
14237 self.change_selections(Default::default(), window, cx, |s| {
14238 s.move_heads_with(&mut |map, head, goal| {
14239 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14240 })
14241 })
14242 }
14243
14244 pub fn select_page_up(
14245 &mut self,
14246 _: &SelectPageUp,
14247 window: &mut Window,
14248 cx: &mut Context<Self>,
14249 ) {
14250 let Some(row_count) = self.visible_row_count() else {
14251 return;
14252 };
14253
14254 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14255
14256 let text_layout_details = &self.text_layout_details(window, cx);
14257
14258 self.change_selections(Default::default(), window, cx, |s| {
14259 s.move_heads_with(&mut |map, head, goal| {
14260 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14261 })
14262 })
14263 }
14264
14265 pub fn move_page_up(
14266 &mut self,
14267 action: &MovePageUp,
14268 window: &mut Window,
14269 cx: &mut Context<Self>,
14270 ) {
14271 if self.take_rename(true, window, cx).is_some() {
14272 return;
14273 }
14274
14275 if self
14276 .context_menu
14277 .borrow_mut()
14278 .as_mut()
14279 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14280 .unwrap_or(false)
14281 {
14282 return;
14283 }
14284
14285 if matches!(self.mode, EditorMode::SingleLine) {
14286 cx.propagate();
14287 return;
14288 }
14289
14290 let Some(row_count) = self.visible_row_count() else {
14291 return;
14292 };
14293
14294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14295
14296 let effects = if action.center_cursor {
14297 SelectionEffects::scroll(Autoscroll::center())
14298 } else {
14299 SelectionEffects::default()
14300 };
14301
14302 let text_layout_details = &self.text_layout_details(window, cx);
14303
14304 self.change_selections(effects, window, cx, |s| {
14305 s.move_with(&mut |map, selection| {
14306 if !selection.is_empty() {
14307 selection.goal = SelectionGoal::None;
14308 }
14309 let (cursor, goal) = movement::up_by_rows(
14310 map,
14311 selection.end,
14312 row_count,
14313 selection.goal,
14314 false,
14315 text_layout_details,
14316 );
14317 selection.collapse_to(cursor, goal);
14318 });
14319 });
14320 }
14321
14322 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14323 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14324 let text_layout_details = &self.text_layout_details(window, cx);
14325 self.change_selections(Default::default(), window, cx, |s| {
14326 s.move_heads_with(&mut |map, head, goal| {
14327 movement::up(map, head, goal, false, text_layout_details)
14328 })
14329 })
14330 }
14331
14332 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14333 self.take_rename(true, window, cx);
14334
14335 if self.mode.is_single_line() {
14336 cx.propagate();
14337 return;
14338 }
14339
14340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14341
14342 let text_layout_details = &self.text_layout_details(window, cx);
14343 let selection_count = self.selections.count();
14344 let first_selection = self.selections.first_anchor();
14345
14346 self.change_selections(Default::default(), window, cx, |s| {
14347 s.move_with(&mut |map, selection| {
14348 if !selection.is_empty() {
14349 selection.goal = SelectionGoal::None;
14350 }
14351 let (cursor, goal) = movement::down(
14352 map,
14353 selection.end,
14354 selection.goal,
14355 false,
14356 text_layout_details,
14357 );
14358 selection.collapse_to(cursor, goal);
14359 });
14360 });
14361
14362 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14363 {
14364 cx.propagate();
14365 }
14366 }
14367
14368 pub fn select_page_down(
14369 &mut self,
14370 _: &SelectPageDown,
14371 window: &mut Window,
14372 cx: &mut Context<Self>,
14373 ) {
14374 let Some(row_count) = self.visible_row_count() else {
14375 return;
14376 };
14377
14378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14379
14380 let text_layout_details = &self.text_layout_details(window, cx);
14381
14382 self.change_selections(Default::default(), window, cx, |s| {
14383 s.move_heads_with(&mut |map, head, goal| {
14384 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14385 })
14386 })
14387 }
14388
14389 pub fn move_page_down(
14390 &mut self,
14391 action: &MovePageDown,
14392 window: &mut Window,
14393 cx: &mut Context<Self>,
14394 ) {
14395 if self.take_rename(true, window, cx).is_some() {
14396 return;
14397 }
14398
14399 if self
14400 .context_menu
14401 .borrow_mut()
14402 .as_mut()
14403 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14404 .unwrap_or(false)
14405 {
14406 return;
14407 }
14408
14409 if matches!(self.mode, EditorMode::SingleLine) {
14410 cx.propagate();
14411 return;
14412 }
14413
14414 let Some(row_count) = self.visible_row_count() else {
14415 return;
14416 };
14417
14418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14419
14420 let effects = if action.center_cursor {
14421 SelectionEffects::scroll(Autoscroll::center())
14422 } else {
14423 SelectionEffects::default()
14424 };
14425
14426 let text_layout_details = &self.text_layout_details(window, cx);
14427 self.change_selections(effects, window, cx, |s| {
14428 s.move_with(&mut |map, selection| {
14429 if !selection.is_empty() {
14430 selection.goal = SelectionGoal::None;
14431 }
14432 let (cursor, goal) = movement::down_by_rows(
14433 map,
14434 selection.end,
14435 row_count,
14436 selection.goal,
14437 false,
14438 text_layout_details,
14439 );
14440 selection.collapse_to(cursor, goal);
14441 });
14442 });
14443 }
14444
14445 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14446 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14447 let text_layout_details = &self.text_layout_details(window, cx);
14448 self.change_selections(Default::default(), window, cx, |s| {
14449 s.move_heads_with(&mut |map, head, goal| {
14450 movement::down(map, head, goal, false, text_layout_details)
14451 })
14452 });
14453 }
14454
14455 pub fn context_menu_first(
14456 &mut self,
14457 _: &ContextMenuFirst,
14458 window: &mut Window,
14459 cx: &mut Context<Self>,
14460 ) {
14461 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14462 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14463 }
14464 }
14465
14466 pub fn context_menu_prev(
14467 &mut self,
14468 _: &ContextMenuPrevious,
14469 window: &mut Window,
14470 cx: &mut Context<Self>,
14471 ) {
14472 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14473 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14474 }
14475 }
14476
14477 pub fn context_menu_next(
14478 &mut self,
14479 _: &ContextMenuNext,
14480 window: &mut Window,
14481 cx: &mut Context<Self>,
14482 ) {
14483 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14484 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14485 }
14486 }
14487
14488 pub fn context_menu_last(
14489 &mut self,
14490 _: &ContextMenuLast,
14491 window: &mut Window,
14492 cx: &mut Context<Self>,
14493 ) {
14494 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14495 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14496 }
14497 }
14498
14499 pub fn signature_help_prev(
14500 &mut self,
14501 _: &SignatureHelpPrevious,
14502 _: &mut Window,
14503 cx: &mut Context<Self>,
14504 ) {
14505 if let Some(popover) = self.signature_help_state.popover_mut() {
14506 if popover.current_signature == 0 {
14507 popover.current_signature = popover.signatures.len() - 1;
14508 } else {
14509 popover.current_signature -= 1;
14510 }
14511 cx.notify();
14512 }
14513 }
14514
14515 pub fn signature_help_next(
14516 &mut self,
14517 _: &SignatureHelpNext,
14518 _: &mut Window,
14519 cx: &mut Context<Self>,
14520 ) {
14521 if let Some(popover) = self.signature_help_state.popover_mut() {
14522 if popover.current_signature + 1 == popover.signatures.len() {
14523 popover.current_signature = 0;
14524 } else {
14525 popover.current_signature += 1;
14526 }
14527 cx.notify();
14528 }
14529 }
14530
14531 pub fn move_to_previous_word_start(
14532 &mut self,
14533 _: &MoveToPreviousWordStart,
14534 window: &mut Window,
14535 cx: &mut Context<Self>,
14536 ) {
14537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14538 self.change_selections(Default::default(), window, cx, |s| {
14539 s.move_cursors_with(&mut |map, head, _| {
14540 (
14541 movement::previous_word_start(map, head),
14542 SelectionGoal::None,
14543 )
14544 });
14545 })
14546 }
14547
14548 pub fn move_to_previous_subword_start(
14549 &mut self,
14550 _: &MoveToPreviousSubwordStart,
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_subword_start(map, head),
14559 SelectionGoal::None,
14560 )
14561 });
14562 })
14563 }
14564
14565 pub fn select_to_previous_word_start(
14566 &mut self,
14567 _: &SelectToPreviousWordStart,
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_heads_with(&mut |map, head, _| {
14574 (
14575 movement::previous_word_start(map, head),
14576 SelectionGoal::None,
14577 )
14578 });
14579 })
14580 }
14581
14582 pub fn select_to_previous_subword_start(
14583 &mut self,
14584 _: &SelectToPreviousSubwordStart,
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_subword_start(map, head),
14593 SelectionGoal::None,
14594 )
14595 });
14596 })
14597 }
14598
14599 pub fn delete_to_previous_word_start(
14600 &mut self,
14601 action: &DeleteToPreviousWordStart,
14602 window: &mut Window,
14603 cx: &mut Context<Self>,
14604 ) {
14605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14606 self.transact(window, cx, |this, window, cx| {
14607 this.select_autoclose_pair(window, cx);
14608 this.change_selections(Default::default(), window, cx, |s| {
14609 s.move_with(&mut |map, selection| {
14610 if selection.is_empty() {
14611 let mut cursor = if action.ignore_newlines {
14612 movement::previous_word_start(map, selection.head())
14613 } else {
14614 movement::previous_word_start_or_newline(map, selection.head())
14615 };
14616 cursor = movement::adjust_greedy_deletion(
14617 map,
14618 selection.head(),
14619 cursor,
14620 action.ignore_brackets,
14621 );
14622 selection.set_head(cursor, SelectionGoal::None);
14623 }
14624 });
14625 });
14626 this.insert("", window, cx);
14627 });
14628 }
14629
14630 pub fn delete_to_previous_subword_start(
14631 &mut self,
14632 action: &DeleteToPreviousSubwordStart,
14633 window: &mut Window,
14634 cx: &mut Context<Self>,
14635 ) {
14636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14637 self.transact(window, cx, |this, window, cx| {
14638 this.select_autoclose_pair(window, cx);
14639 this.change_selections(Default::default(), window, cx, |s| {
14640 s.move_with(&mut |map, selection| {
14641 if selection.is_empty() {
14642 let mut cursor = if action.ignore_newlines {
14643 movement::previous_subword_start(map, selection.head())
14644 } else {
14645 movement::previous_subword_start_or_newline(map, selection.head())
14646 };
14647 cursor = movement::adjust_greedy_deletion(
14648 map,
14649 selection.head(),
14650 cursor,
14651 action.ignore_brackets,
14652 );
14653 selection.set_head(cursor, SelectionGoal::None);
14654 }
14655 });
14656 });
14657 this.insert("", window, cx);
14658 });
14659 }
14660
14661 pub fn move_to_next_word_end(
14662 &mut self,
14663 _: &MoveToNextWordEnd,
14664 window: &mut Window,
14665 cx: &mut Context<Self>,
14666 ) {
14667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14668 self.change_selections(Default::default(), window, cx, |s| {
14669 s.move_cursors_with(&mut |map, head, _| {
14670 (movement::next_word_end(map, head), SelectionGoal::None)
14671 });
14672 })
14673 }
14674
14675 pub fn move_to_next_subword_end(
14676 &mut self,
14677 _: &MoveToNextSubwordEnd,
14678 window: &mut Window,
14679 cx: &mut Context<Self>,
14680 ) {
14681 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14682 self.change_selections(Default::default(), window, cx, |s| {
14683 s.move_cursors_with(&mut |map, head, _| {
14684 (movement::next_subword_end(map, head), SelectionGoal::None)
14685 });
14686 })
14687 }
14688
14689 pub fn select_to_next_word_end(
14690 &mut self,
14691 _: &SelectToNextWordEnd,
14692 window: &mut Window,
14693 cx: &mut Context<Self>,
14694 ) {
14695 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14696 self.change_selections(Default::default(), window, cx, |s| {
14697 s.move_heads_with(&mut |map, head, _| {
14698 (movement::next_word_end(map, head), SelectionGoal::None)
14699 });
14700 })
14701 }
14702
14703 pub fn select_to_next_subword_end(
14704 &mut self,
14705 _: &SelectToNextSubwordEnd,
14706 window: &mut Window,
14707 cx: &mut Context<Self>,
14708 ) {
14709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14710 self.change_selections(Default::default(), window, cx, |s| {
14711 s.move_heads_with(&mut |map, head, _| {
14712 (movement::next_subword_end(map, head), SelectionGoal::None)
14713 });
14714 })
14715 }
14716
14717 pub fn delete_to_next_word_end(
14718 &mut self,
14719 action: &DeleteToNextWordEnd,
14720 window: &mut Window,
14721 cx: &mut Context<Self>,
14722 ) {
14723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14724 self.transact(window, cx, |this, window, cx| {
14725 this.change_selections(Default::default(), window, cx, |s| {
14726 s.move_with(&mut |map, selection| {
14727 if selection.is_empty() {
14728 let mut cursor = if action.ignore_newlines {
14729 movement::next_word_end(map, selection.head())
14730 } else {
14731 movement::next_word_end_or_newline(map, selection.head())
14732 };
14733 cursor = movement::adjust_greedy_deletion(
14734 map,
14735 selection.head(),
14736 cursor,
14737 action.ignore_brackets,
14738 );
14739 selection.set_head(cursor, SelectionGoal::None);
14740 }
14741 });
14742 });
14743 this.insert("", window, cx);
14744 });
14745 }
14746
14747 pub fn delete_to_next_subword_end(
14748 &mut self,
14749 action: &DeleteToNextSubwordEnd,
14750 window: &mut Window,
14751 cx: &mut Context<Self>,
14752 ) {
14753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14754 self.transact(window, cx, |this, window, cx| {
14755 this.change_selections(Default::default(), window, cx, |s| {
14756 s.move_with(&mut |map, selection| {
14757 if selection.is_empty() {
14758 let mut cursor = if action.ignore_newlines {
14759 movement::next_subword_end(map, selection.head())
14760 } else {
14761 movement::next_subword_end_or_newline(map, selection.head())
14762 };
14763 cursor = movement::adjust_greedy_deletion(
14764 map,
14765 selection.head(),
14766 cursor,
14767 action.ignore_brackets,
14768 );
14769 selection.set_head(cursor, SelectionGoal::None);
14770 }
14771 });
14772 });
14773 this.insert("", window, cx);
14774 });
14775 }
14776
14777 pub fn move_to_beginning_of_line(
14778 &mut self,
14779 action: &MoveToBeginningOfLine,
14780 window: &mut Window,
14781 cx: &mut Context<Self>,
14782 ) {
14783 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14784 self.change_selections(Default::default(), window, cx, |s| {
14785 s.move_cursors_with(&mut |map, head, _| {
14786 (
14787 movement::indented_line_beginning(
14788 map,
14789 head,
14790 action.stop_at_soft_wraps,
14791 action.stop_at_indent,
14792 ),
14793 SelectionGoal::None,
14794 )
14795 });
14796 })
14797 }
14798
14799 pub fn select_to_beginning_of_line(
14800 &mut self,
14801 action: &SelectToBeginningOfLine,
14802 window: &mut Window,
14803 cx: &mut Context<Self>,
14804 ) {
14805 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14806 self.change_selections(Default::default(), window, cx, |s| {
14807 s.move_heads_with(&mut |map, head, _| {
14808 (
14809 movement::indented_line_beginning(
14810 map,
14811 head,
14812 action.stop_at_soft_wraps,
14813 action.stop_at_indent,
14814 ),
14815 SelectionGoal::None,
14816 )
14817 });
14818 });
14819 }
14820
14821 pub fn delete_to_beginning_of_line(
14822 &mut self,
14823 action: &DeleteToBeginningOfLine,
14824 window: &mut Window,
14825 cx: &mut Context<Self>,
14826 ) {
14827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14828 self.transact(window, cx, |this, window, cx| {
14829 this.change_selections(Default::default(), window, cx, |s| {
14830 s.move_with(&mut |_, selection| {
14831 selection.reversed = true;
14832 });
14833 });
14834
14835 this.select_to_beginning_of_line(
14836 &SelectToBeginningOfLine {
14837 stop_at_soft_wraps: false,
14838 stop_at_indent: action.stop_at_indent,
14839 },
14840 window,
14841 cx,
14842 );
14843 this.backspace(&Backspace, window, cx);
14844 });
14845 }
14846
14847 pub fn move_to_end_of_line(
14848 &mut self,
14849 action: &MoveToEndOfLine,
14850 window: &mut Window,
14851 cx: &mut Context<Self>,
14852 ) {
14853 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14854 self.change_selections(Default::default(), window, cx, |s| {
14855 s.move_cursors_with(&mut |map, head, _| {
14856 (
14857 movement::line_end(map, head, action.stop_at_soft_wraps),
14858 SelectionGoal::None,
14859 )
14860 });
14861 })
14862 }
14863
14864 pub fn select_to_end_of_line(
14865 &mut self,
14866 action: &SelectToEndOfLine,
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_heads_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 delete_to_end_of_line(
14882 &mut self,
14883 _: &DeleteToEndOfLine,
14884 window: &mut Window,
14885 cx: &mut Context<Self>,
14886 ) {
14887 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14888 self.transact(window, cx, |this, window, cx| {
14889 this.select_to_end_of_line(
14890 &SelectToEndOfLine {
14891 stop_at_soft_wraps: false,
14892 },
14893 window,
14894 cx,
14895 );
14896 this.delete(&Delete, window, cx);
14897 });
14898 }
14899
14900 pub fn cut_to_end_of_line(
14901 &mut self,
14902 action: &CutToEndOfLine,
14903 window: &mut Window,
14904 cx: &mut Context<Self>,
14905 ) {
14906 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14907 self.transact(window, cx, |this, window, cx| {
14908 this.select_to_end_of_line(
14909 &SelectToEndOfLine {
14910 stop_at_soft_wraps: false,
14911 },
14912 window,
14913 cx,
14914 );
14915 if !action.stop_at_newlines {
14916 this.change_selections(Default::default(), window, cx, |s| {
14917 s.move_with(&mut |_, sel| {
14918 if sel.is_empty() {
14919 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14920 }
14921 });
14922 });
14923 }
14924 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14925 let item = this.cut_common(false, window, cx);
14926 cx.write_to_clipboard(item);
14927 });
14928 }
14929
14930 pub fn move_to_start_of_paragraph(
14931 &mut self,
14932 _: &MoveToStartOfParagraph,
14933 window: &mut Window,
14934 cx: &mut Context<Self>,
14935 ) {
14936 if matches!(self.mode, EditorMode::SingleLine) {
14937 cx.propagate();
14938 return;
14939 }
14940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14941 self.change_selections(Default::default(), window, cx, |s| {
14942 s.move_with(&mut |map, selection| {
14943 selection.collapse_to(
14944 movement::start_of_paragraph(map, selection.head(), 1),
14945 SelectionGoal::None,
14946 )
14947 });
14948 })
14949 }
14950
14951 pub fn move_to_end_of_paragraph(
14952 &mut self,
14953 _: &MoveToEndOfParagraph,
14954 window: &mut Window,
14955 cx: &mut Context<Self>,
14956 ) {
14957 if matches!(self.mode, EditorMode::SingleLine) {
14958 cx.propagate();
14959 return;
14960 }
14961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14962 self.change_selections(Default::default(), window, cx, |s| {
14963 s.move_with(&mut |map, selection| {
14964 selection.collapse_to(
14965 movement::end_of_paragraph(map, selection.head(), 1),
14966 SelectionGoal::None,
14967 )
14968 });
14969 })
14970 }
14971
14972 pub fn select_to_start_of_paragraph(
14973 &mut self,
14974 _: &SelectToStartOfParagraph,
14975 window: &mut Window,
14976 cx: &mut Context<Self>,
14977 ) {
14978 if matches!(self.mode, EditorMode::SingleLine) {
14979 cx.propagate();
14980 return;
14981 }
14982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14983 self.change_selections(Default::default(), window, cx, |s| {
14984 s.move_heads_with(&mut |map, head, _| {
14985 (
14986 movement::start_of_paragraph(map, head, 1),
14987 SelectionGoal::None,
14988 )
14989 });
14990 })
14991 }
14992
14993 pub fn select_to_end_of_paragraph(
14994 &mut self,
14995 _: &SelectToEndOfParagraph,
14996 window: &mut Window,
14997 cx: &mut Context<Self>,
14998 ) {
14999 if matches!(self.mode, EditorMode::SingleLine) {
15000 cx.propagate();
15001 return;
15002 }
15003 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15004 self.change_selections(Default::default(), window, cx, |s| {
15005 s.move_heads_with(&mut |map, head, _| {
15006 (
15007 movement::end_of_paragraph(map, head, 1),
15008 SelectionGoal::None,
15009 )
15010 });
15011 })
15012 }
15013
15014 pub fn move_to_start_of_excerpt(
15015 &mut self,
15016 _: &MoveToStartOfExcerpt,
15017 window: &mut Window,
15018 cx: &mut Context<Self>,
15019 ) {
15020 if matches!(self.mode, EditorMode::SingleLine) {
15021 cx.propagate();
15022 return;
15023 }
15024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15025 self.change_selections(Default::default(), window, cx, |s| {
15026 s.move_with(&mut |map, selection| {
15027 selection.collapse_to(
15028 movement::start_of_excerpt(
15029 map,
15030 selection.head(),
15031 workspace::searchable::Direction::Prev,
15032 ),
15033 SelectionGoal::None,
15034 )
15035 });
15036 })
15037 }
15038
15039 pub fn move_to_start_of_next_excerpt(
15040 &mut self,
15041 _: &MoveToStartOfNextExcerpt,
15042 window: &mut Window,
15043 cx: &mut Context<Self>,
15044 ) {
15045 if matches!(self.mode, EditorMode::SingleLine) {
15046 cx.propagate();
15047 return;
15048 }
15049
15050 self.change_selections(Default::default(), window, cx, |s| {
15051 s.move_with(&mut |map, selection| {
15052 selection.collapse_to(
15053 movement::start_of_excerpt(
15054 map,
15055 selection.head(),
15056 workspace::searchable::Direction::Next,
15057 ),
15058 SelectionGoal::None,
15059 )
15060 });
15061 })
15062 }
15063
15064 pub fn move_to_end_of_excerpt(
15065 &mut self,
15066 _: &MoveToEndOfExcerpt,
15067 window: &mut Window,
15068 cx: &mut Context<Self>,
15069 ) {
15070 if matches!(self.mode, EditorMode::SingleLine) {
15071 cx.propagate();
15072 return;
15073 }
15074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15075 self.change_selections(Default::default(), window, cx, |s| {
15076 s.move_with(&mut |map, selection| {
15077 selection.collapse_to(
15078 movement::end_of_excerpt(
15079 map,
15080 selection.head(),
15081 workspace::searchable::Direction::Next,
15082 ),
15083 SelectionGoal::None,
15084 )
15085 });
15086 })
15087 }
15088
15089 pub fn move_to_end_of_previous_excerpt(
15090 &mut self,
15091 _: &MoveToEndOfPreviousExcerpt,
15092 window: &mut Window,
15093 cx: &mut Context<Self>,
15094 ) {
15095 if matches!(self.mode, EditorMode::SingleLine) {
15096 cx.propagate();
15097 return;
15098 }
15099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15100 self.change_selections(Default::default(), window, cx, |s| {
15101 s.move_with(&mut |map, selection| {
15102 selection.collapse_to(
15103 movement::end_of_excerpt(
15104 map,
15105 selection.head(),
15106 workspace::searchable::Direction::Prev,
15107 ),
15108 SelectionGoal::None,
15109 )
15110 });
15111 })
15112 }
15113
15114 pub fn select_to_start_of_excerpt(
15115 &mut self,
15116 _: &SelectToStartOfExcerpt,
15117 window: &mut Window,
15118 cx: &mut Context<Self>,
15119 ) {
15120 if matches!(self.mode, EditorMode::SingleLine) {
15121 cx.propagate();
15122 return;
15123 }
15124 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15125 self.change_selections(Default::default(), window, cx, |s| {
15126 s.move_heads_with(&mut |map, head, _| {
15127 (
15128 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15129 SelectionGoal::None,
15130 )
15131 });
15132 })
15133 }
15134
15135 pub fn select_to_start_of_next_excerpt(
15136 &mut self,
15137 _: &SelectToStartOfNextExcerpt,
15138 window: &mut Window,
15139 cx: &mut Context<Self>,
15140 ) {
15141 if matches!(self.mode, EditorMode::SingleLine) {
15142 cx.propagate();
15143 return;
15144 }
15145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15146 self.change_selections(Default::default(), window, cx, |s| {
15147 s.move_heads_with(&mut |map, head, _| {
15148 (
15149 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15150 SelectionGoal::None,
15151 )
15152 });
15153 })
15154 }
15155
15156 pub fn select_to_end_of_excerpt(
15157 &mut self,
15158 _: &SelectToEndOfExcerpt,
15159 window: &mut Window,
15160 cx: &mut Context<Self>,
15161 ) {
15162 if matches!(self.mode, EditorMode::SingleLine) {
15163 cx.propagate();
15164 return;
15165 }
15166 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15167 self.change_selections(Default::default(), window, cx, |s| {
15168 s.move_heads_with(&mut |map, head, _| {
15169 (
15170 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15171 SelectionGoal::None,
15172 )
15173 });
15174 })
15175 }
15176
15177 pub fn select_to_end_of_previous_excerpt(
15178 &mut self,
15179 _: &SelectToEndOfPreviousExcerpt,
15180 window: &mut Window,
15181 cx: &mut Context<Self>,
15182 ) {
15183 if matches!(self.mode, EditorMode::SingleLine) {
15184 cx.propagate();
15185 return;
15186 }
15187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15188 self.change_selections(Default::default(), window, cx, |s| {
15189 s.move_heads_with(&mut |map, head, _| {
15190 (
15191 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15192 SelectionGoal::None,
15193 )
15194 });
15195 })
15196 }
15197
15198 pub fn move_to_beginning(
15199 &mut self,
15200 _: &MoveToBeginning,
15201 window: &mut Window,
15202 cx: &mut Context<Self>,
15203 ) {
15204 if matches!(self.mode, EditorMode::SingleLine) {
15205 cx.propagate();
15206 return;
15207 }
15208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15209 self.change_selections(Default::default(), window, cx, |s| {
15210 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15211 });
15212 }
15213
15214 pub fn select_to_beginning(
15215 &mut self,
15216 _: &SelectToBeginning,
15217 window: &mut Window,
15218 cx: &mut Context<Self>,
15219 ) {
15220 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15221 selection.set_head(Point::zero(), SelectionGoal::None);
15222 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15223 self.change_selections(Default::default(), window, cx, |s| {
15224 s.select(vec![selection]);
15225 });
15226 }
15227
15228 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15229 if matches!(self.mode, EditorMode::SingleLine) {
15230 cx.propagate();
15231 return;
15232 }
15233 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15234 let cursor = self.buffer.read(cx).read(cx).len();
15235 self.change_selections(Default::default(), window, cx, |s| {
15236 s.select_ranges(vec![cursor..cursor])
15237 });
15238 }
15239
15240 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15241 self.nav_history = nav_history;
15242 }
15243
15244 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15245 self.nav_history.as_ref()
15246 }
15247
15248 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15249 self.push_to_nav_history(
15250 self.selections.newest_anchor().head(),
15251 None,
15252 false,
15253 true,
15254 cx,
15255 );
15256 }
15257
15258 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15259 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15260 let buffer = self.buffer.read(cx).read(cx);
15261 let cursor_position = cursor_anchor.to_point(&buffer);
15262 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15263 let scroll_top_row = scroll_anchor.top_row(&buffer);
15264 drop(buffer);
15265
15266 NavigationData {
15267 cursor_anchor,
15268 cursor_position,
15269 scroll_anchor,
15270 scroll_top_row,
15271 }
15272 }
15273
15274 fn navigation_entry(
15275 &self,
15276 cursor_anchor: Anchor,
15277 cx: &mut Context<Self>,
15278 ) -> Option<NavigationEntry> {
15279 let Some(history) = self.nav_history.clone() else {
15280 return None;
15281 };
15282 let data = self.navigation_data(cursor_anchor, cx);
15283 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15284 }
15285
15286 fn push_to_nav_history(
15287 &mut self,
15288 cursor_anchor: Anchor,
15289 new_position: Option<Point>,
15290 is_deactivate: bool,
15291 always: bool,
15292 cx: &mut Context<Self>,
15293 ) {
15294 let data = self.navigation_data(cursor_anchor, cx);
15295 if let Some(nav_history) = self.nav_history.as_mut() {
15296 if let Some(new_position) = new_position {
15297 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15298 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15299 return;
15300 }
15301 }
15302
15303 nav_history.push(Some(data), cx);
15304 cx.emit(EditorEvent::PushedToNavHistory {
15305 anchor: cursor_anchor,
15306 is_deactivate,
15307 })
15308 }
15309 }
15310
15311 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15313 let buffer = self.buffer.read(cx).snapshot(cx);
15314 let mut selection = self
15315 .selections
15316 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15317 selection.set_head(buffer.len(), SelectionGoal::None);
15318 self.change_selections(Default::default(), window, cx, |s| {
15319 s.select(vec![selection]);
15320 });
15321 }
15322
15323 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15325 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15326 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
15327 });
15328 }
15329
15330 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15333 let mut selections = self.selections.all::<Point>(&display_map);
15334 let max_point = display_map.buffer_snapshot().max_point();
15335 for selection in &mut selections {
15336 let rows = selection.spanned_rows(true, &display_map);
15337 selection.start = Point::new(rows.start.0, 0);
15338 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15339 selection.reversed = false;
15340 }
15341 self.change_selections(Default::default(), window, cx, |s| {
15342 s.select(selections);
15343 });
15344 }
15345
15346 pub fn split_selection_into_lines(
15347 &mut self,
15348 action: &SplitSelectionIntoLines,
15349 window: &mut Window,
15350 cx: &mut Context<Self>,
15351 ) {
15352 let selections = self
15353 .selections
15354 .all::<Point>(&self.display_snapshot(cx))
15355 .into_iter()
15356 .map(|selection| selection.start..selection.end)
15357 .collect::<Vec<_>>();
15358 self.unfold_ranges(&selections, true, true, cx);
15359
15360 let mut new_selection_ranges = Vec::new();
15361 {
15362 let buffer = self.buffer.read(cx).read(cx);
15363 for selection in selections {
15364 for row in selection.start.row..selection.end.row {
15365 let line_start = Point::new(row, 0);
15366 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15367
15368 if action.keep_selections {
15369 // Keep the selection range for each line
15370 let selection_start = if row == selection.start.row {
15371 selection.start
15372 } else {
15373 line_start
15374 };
15375 new_selection_ranges.push(selection_start..line_end);
15376 } else {
15377 // Collapse to cursor at end of line
15378 new_selection_ranges.push(line_end..line_end);
15379 }
15380 }
15381
15382 let is_multiline_selection = selection.start.row != selection.end.row;
15383 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15384 // so this action feels more ergonomic when paired with other selection operations
15385 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15386 if !should_skip_last {
15387 if action.keep_selections {
15388 if is_multiline_selection {
15389 let line_start = Point::new(selection.end.row, 0);
15390 new_selection_ranges.push(line_start..selection.end);
15391 } else {
15392 new_selection_ranges.push(selection.start..selection.end);
15393 }
15394 } else {
15395 new_selection_ranges.push(selection.end..selection.end);
15396 }
15397 }
15398 }
15399 }
15400 self.change_selections(Default::default(), window, cx, |s| {
15401 s.select_ranges(new_selection_ranges);
15402 });
15403 }
15404
15405 pub fn add_selection_above(
15406 &mut self,
15407 action: &AddSelectionAbove,
15408 window: &mut Window,
15409 cx: &mut Context<Self>,
15410 ) {
15411 self.add_selection(true, action.skip_soft_wrap, window, cx);
15412 }
15413
15414 pub fn add_selection_below(
15415 &mut self,
15416 action: &AddSelectionBelow,
15417 window: &mut Window,
15418 cx: &mut Context<Self>,
15419 ) {
15420 self.add_selection(false, action.skip_soft_wrap, window, cx);
15421 }
15422
15423 fn add_selection(
15424 &mut self,
15425 above: bool,
15426 skip_soft_wrap: bool,
15427 window: &mut Window,
15428 cx: &mut Context<Self>,
15429 ) {
15430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15431
15432 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15433 let all_selections = self.selections.all::<Point>(&display_map);
15434 let text_layout_details = self.text_layout_details(window, cx);
15435
15436 let (mut columnar_selections, new_selections_to_columnarize) = {
15437 if let Some(state) = self.add_selections_state.as_ref() {
15438 let columnar_selection_ids: HashSet<_> = state
15439 .groups
15440 .iter()
15441 .flat_map(|group| group.stack.iter())
15442 .copied()
15443 .collect();
15444
15445 all_selections
15446 .into_iter()
15447 .partition(|s| columnar_selection_ids.contains(&s.id))
15448 } else {
15449 (Vec::new(), all_selections)
15450 }
15451 };
15452
15453 let mut state = self
15454 .add_selections_state
15455 .take()
15456 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15457
15458 for selection in new_selections_to_columnarize {
15459 let range = selection.display_range(&display_map).sorted();
15460 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15461 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15462 let positions = start_x.min(end_x)..start_x.max(end_x);
15463 let mut stack = Vec::new();
15464 for row in range.start.row().0..=range.end.row().0 {
15465 if let Some(selection) = self.selections.build_columnar_selection(
15466 &display_map,
15467 DisplayRow(row),
15468 &positions,
15469 selection.reversed,
15470 &text_layout_details,
15471 ) {
15472 stack.push(selection.id);
15473 columnar_selections.push(selection);
15474 }
15475 }
15476 if !stack.is_empty() {
15477 if above {
15478 stack.reverse();
15479 }
15480 state.groups.push(AddSelectionsGroup { above, stack });
15481 }
15482 }
15483
15484 let mut final_selections = Vec::new();
15485 let end_row = if above {
15486 DisplayRow(0)
15487 } else {
15488 display_map.max_point().row()
15489 };
15490
15491 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15492 // positions to place new selections, so we need to keep track of the
15493 // column range of the oldest selection in each group, because
15494 // intermediate selections may have been clamped to shorter lines.
15495 // selections may have been clamped to shorter lines.
15496 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15497 let mut map = HashMap::default();
15498 for group in state.groups.iter() {
15499 if let Some(oldest_id) = group.stack.first() {
15500 if let Some(oldest_selection) =
15501 columnar_selections.iter().find(|s| s.id == *oldest_id)
15502 {
15503 let start_col = oldest_selection.start.column;
15504 let end_col = oldest_selection.end.column;
15505 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15506 for id in &group.stack {
15507 map.insert(*id, goal_columns.clone());
15508 }
15509 }
15510 }
15511 }
15512 map
15513 } else {
15514 HashMap::default()
15515 };
15516
15517 let mut last_added_item_per_group = HashMap::default();
15518 for group in state.groups.iter_mut() {
15519 if let Some(last_id) = group.stack.last() {
15520 last_added_item_per_group.insert(*last_id, group);
15521 }
15522 }
15523
15524 for selection in columnar_selections {
15525 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15526 if above == group.above {
15527 let range = selection.display_range(&display_map).sorted();
15528 debug_assert_eq!(range.start.row(), range.end.row());
15529 let row = range.start.row();
15530 let positions =
15531 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15532 Pixels::from(start)..Pixels::from(end)
15533 } else {
15534 let start_x =
15535 display_map.x_for_display_point(range.start, &text_layout_details);
15536 let end_x =
15537 display_map.x_for_display_point(range.end, &text_layout_details);
15538 start_x.min(end_x)..start_x.max(end_x)
15539 };
15540
15541 let maybe_new_selection = if skip_soft_wrap {
15542 let goal_columns = goal_columns_by_selection_id
15543 .remove(&selection.id)
15544 .unwrap_or_else(|| {
15545 let start_col = selection.start.column;
15546 let end_col = selection.end.column;
15547 start_col.min(end_col)..start_col.max(end_col)
15548 });
15549 self.selections.find_next_columnar_selection_by_buffer_row(
15550 &display_map,
15551 row,
15552 end_row,
15553 above,
15554 &goal_columns,
15555 selection.reversed,
15556 &text_layout_details,
15557 )
15558 } else {
15559 self.selections.find_next_columnar_selection_by_display_row(
15560 &display_map,
15561 row,
15562 end_row,
15563 above,
15564 &positions,
15565 selection.reversed,
15566 &text_layout_details,
15567 )
15568 };
15569
15570 if let Some(new_selection) = maybe_new_selection {
15571 group.stack.push(new_selection.id);
15572 if above {
15573 final_selections.push(new_selection);
15574 final_selections.push(selection);
15575 } else {
15576 final_selections.push(selection);
15577 final_selections.push(new_selection);
15578 }
15579 } else {
15580 final_selections.push(selection);
15581 }
15582 } else {
15583 group.stack.pop();
15584 }
15585 } else {
15586 final_selections.push(selection);
15587 }
15588 }
15589
15590 self.change_selections(Default::default(), window, cx, |s| {
15591 s.select(final_selections);
15592 });
15593
15594 let final_selection_ids: HashSet<_> = self
15595 .selections
15596 .all::<Point>(&display_map)
15597 .iter()
15598 .map(|s| s.id)
15599 .collect();
15600 state.groups.retain_mut(|group| {
15601 // selections might get merged above so we remove invalid items from stacks
15602 group.stack.retain(|id| final_selection_ids.contains(id));
15603
15604 // single selection in stack can be treated as initial state
15605 group.stack.len() > 1
15606 });
15607
15608 if !state.groups.is_empty() {
15609 self.add_selections_state = Some(state);
15610 }
15611 }
15612
15613 pub fn insert_snippet_at_selections(
15614 &mut self,
15615 action: &InsertSnippet,
15616 window: &mut Window,
15617 cx: &mut Context<Self>,
15618 ) {
15619 self.try_insert_snippet_at_selections(action, window, cx)
15620 .log_err();
15621 }
15622
15623 fn try_insert_snippet_at_selections(
15624 &mut self,
15625 action: &InsertSnippet,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) -> Result<()> {
15629 let insertion_ranges = self
15630 .selections
15631 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15632 .into_iter()
15633 .map(|selection| selection.range())
15634 .collect_vec();
15635
15636 let snippet = if let Some(snippet_body) = &action.snippet {
15637 if action.language.is_none() && action.name.is_none() {
15638 Snippet::parse(snippet_body)?
15639 } else {
15640 bail!("`snippet` is mutually exclusive with `language` and `name`")
15641 }
15642 } else if let Some(name) = &action.name {
15643 let project = self.project().context("no project")?;
15644 let snippet_store = project.read(cx).snippets().read(cx);
15645 let snippet = snippet_store
15646 .snippets_for(action.language.clone(), cx)
15647 .into_iter()
15648 .find(|snippet| snippet.name == *name)
15649 .context("snippet not found")?;
15650 Snippet::parse(&snippet.body)?
15651 } else {
15652 // todo(andrew): open modal to select snippet
15653 bail!("`name` or `snippet` is required")
15654 };
15655
15656 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15657 }
15658
15659 fn select_match_ranges(
15660 &mut self,
15661 range: Range<MultiBufferOffset>,
15662 reversed: bool,
15663 replace_newest: bool,
15664 auto_scroll: Option<Autoscroll>,
15665 window: &mut Window,
15666 cx: &mut Context<Editor>,
15667 ) {
15668 self.unfold_ranges(
15669 std::slice::from_ref(&range),
15670 false,
15671 auto_scroll.is_some(),
15672 cx,
15673 );
15674 let effects = if let Some(scroll) = auto_scroll {
15675 SelectionEffects::scroll(scroll)
15676 } else {
15677 SelectionEffects::no_scroll()
15678 };
15679 self.change_selections(effects, window, cx, |s| {
15680 if replace_newest {
15681 s.delete(s.newest_anchor().id);
15682 }
15683 if reversed {
15684 s.insert_range(range.end..range.start);
15685 } else {
15686 s.insert_range(range);
15687 }
15688 });
15689 }
15690
15691 pub fn select_next_match_internal(
15692 &mut self,
15693 display_map: &DisplaySnapshot,
15694 replace_newest: bool,
15695 autoscroll: Option<Autoscroll>,
15696 window: &mut Window,
15697 cx: &mut Context<Self>,
15698 ) -> Result<()> {
15699 let buffer = display_map.buffer_snapshot();
15700 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15701 if let Some(mut select_next_state) = self.select_next_state.take() {
15702 let query = &select_next_state.query;
15703 if !select_next_state.done {
15704 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15705 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15706 let mut next_selected_range = None;
15707
15708 let bytes_after_last_selection =
15709 buffer.bytes_in_range(last_selection.end..buffer.len());
15710 let bytes_before_first_selection =
15711 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15712 let query_matches = query
15713 .stream_find_iter(bytes_after_last_selection)
15714 .map(|result| (last_selection.end, result))
15715 .chain(
15716 query
15717 .stream_find_iter(bytes_before_first_selection)
15718 .map(|result| (MultiBufferOffset(0), result)),
15719 );
15720
15721 for (start_offset, query_match) in query_matches {
15722 let query_match = query_match.unwrap(); // can only fail due to I/O
15723 let offset_range =
15724 start_offset + query_match.start()..start_offset + query_match.end();
15725
15726 if !select_next_state.wordwise
15727 || (!buffer.is_inside_word(offset_range.start, None)
15728 && !buffer.is_inside_word(offset_range.end, None))
15729 {
15730 let idx = selections
15731 .partition_point(|selection| selection.end <= offset_range.start);
15732 let overlaps = selections
15733 .get(idx)
15734 .map_or(false, |selection| selection.start < offset_range.end);
15735
15736 if !overlaps {
15737 next_selected_range = Some(offset_range);
15738 break;
15739 }
15740 }
15741 }
15742
15743 if let Some(next_selected_range) = next_selected_range {
15744 self.select_match_ranges(
15745 next_selected_range,
15746 last_selection.reversed,
15747 replace_newest,
15748 autoscroll,
15749 window,
15750 cx,
15751 );
15752 } else {
15753 select_next_state.done = true;
15754 }
15755 }
15756
15757 self.select_next_state = Some(select_next_state);
15758 } else {
15759 let mut only_carets = true;
15760 let mut same_text_selected = true;
15761 let mut selected_text = None;
15762
15763 let mut selections_iter = selections.iter().peekable();
15764 while let Some(selection) = selections_iter.next() {
15765 if selection.start != selection.end {
15766 only_carets = false;
15767 }
15768
15769 if same_text_selected {
15770 if selected_text.is_none() {
15771 selected_text =
15772 Some(buffer.text_for_range(selection.range()).collect::<String>());
15773 }
15774
15775 if let Some(next_selection) = selections_iter.peek() {
15776 if next_selection.len() == selection.len() {
15777 let next_selected_text = buffer
15778 .text_for_range(next_selection.range())
15779 .collect::<String>();
15780 if Some(next_selected_text) != selected_text {
15781 same_text_selected = false;
15782 selected_text = None;
15783 }
15784 } else {
15785 same_text_selected = false;
15786 selected_text = None;
15787 }
15788 }
15789 }
15790 }
15791
15792 if only_carets {
15793 for selection in &mut selections {
15794 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15795 selection.start = word_range.start;
15796 selection.end = word_range.end;
15797 selection.goal = SelectionGoal::None;
15798 selection.reversed = false;
15799 self.select_match_ranges(
15800 selection.start..selection.end,
15801 selection.reversed,
15802 replace_newest,
15803 autoscroll,
15804 window,
15805 cx,
15806 );
15807 }
15808
15809 if selections.len() == 1 {
15810 let selection = selections
15811 .last()
15812 .expect("ensured that there's only one selection");
15813 let query = buffer
15814 .text_for_range(selection.start..selection.end)
15815 .collect::<String>();
15816 let is_empty = query.is_empty();
15817 let select_state = SelectNextState {
15818 query: self.build_query(&[query], cx)?,
15819 wordwise: true,
15820 done: is_empty,
15821 };
15822 self.select_next_state = Some(select_state);
15823 } else {
15824 self.select_next_state = None;
15825 }
15826 } else if let Some(selected_text) = selected_text {
15827 self.select_next_state = Some(SelectNextState {
15828 query: self.build_query(&[selected_text], cx)?,
15829 wordwise: false,
15830 done: false,
15831 });
15832 self.select_next_match_internal(
15833 display_map,
15834 replace_newest,
15835 autoscroll,
15836 window,
15837 cx,
15838 )?;
15839 }
15840 }
15841 Ok(())
15842 }
15843
15844 pub fn select_all_matches(
15845 &mut self,
15846 _action: &SelectAllMatches,
15847 window: &mut Window,
15848 cx: &mut Context<Self>,
15849 ) -> Result<()> {
15850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15851
15852 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15853
15854 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15855 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15856 else {
15857 return Ok(());
15858 };
15859
15860 let mut new_selections = Vec::new();
15861
15862 let reversed = self
15863 .selections
15864 .oldest::<MultiBufferOffset>(&display_map)
15865 .reversed;
15866 let buffer = display_map.buffer_snapshot();
15867 let query_matches = select_next_state
15868 .query
15869 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15870
15871 for query_match in query_matches.into_iter() {
15872 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15873 let offset_range = if reversed {
15874 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15875 } else {
15876 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15877 };
15878
15879 if !select_next_state.wordwise
15880 || (!buffer.is_inside_word(offset_range.start, None)
15881 && !buffer.is_inside_word(offset_range.end, None))
15882 {
15883 new_selections.push(offset_range.start..offset_range.end);
15884 }
15885 }
15886
15887 select_next_state.done = true;
15888
15889 if new_selections.is_empty() {
15890 log::error!("bug: new_selections is empty in select_all_matches");
15891 return Ok(());
15892 }
15893
15894 self.unfold_ranges(&new_selections, false, false, cx);
15895 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15896 selections.select_ranges(new_selections)
15897 });
15898
15899 Ok(())
15900 }
15901
15902 pub fn select_next(
15903 &mut self,
15904 action: &SelectNext,
15905 window: &mut Window,
15906 cx: &mut Context<Self>,
15907 ) -> Result<()> {
15908 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15909 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15910 self.select_next_match_internal(
15911 &display_map,
15912 action.replace_newest,
15913 Some(Autoscroll::newest()),
15914 window,
15915 cx,
15916 )
15917 }
15918
15919 pub fn select_previous(
15920 &mut self,
15921 action: &SelectPrevious,
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 let buffer = display_map.buffer_snapshot();
15928 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15929 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15930 let query = &select_prev_state.query;
15931 if !select_prev_state.done {
15932 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15933 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15934 let mut next_selected_range = None;
15935 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15936 let bytes_before_last_selection =
15937 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15938 let bytes_after_first_selection =
15939 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15940 let query_matches = query
15941 .stream_find_iter(bytes_before_last_selection)
15942 .map(|result| (last_selection.start, result))
15943 .chain(
15944 query
15945 .stream_find_iter(bytes_after_first_selection)
15946 .map(|result| (buffer.len(), result)),
15947 );
15948 for (end_offset, query_match) in query_matches {
15949 let query_match = query_match.unwrap(); // can only fail due to I/O
15950 let offset_range =
15951 end_offset - query_match.end()..end_offset - query_match.start();
15952
15953 if !select_prev_state.wordwise
15954 || (!buffer.is_inside_word(offset_range.start, None)
15955 && !buffer.is_inside_word(offset_range.end, None))
15956 {
15957 next_selected_range = Some(offset_range);
15958 break;
15959 }
15960 }
15961
15962 if let Some(next_selected_range) = next_selected_range {
15963 self.select_match_ranges(
15964 next_selected_range,
15965 last_selection.reversed,
15966 action.replace_newest,
15967 Some(Autoscroll::newest()),
15968 window,
15969 cx,
15970 );
15971 } else {
15972 select_prev_state.done = true;
15973 }
15974 }
15975
15976 self.select_prev_state = Some(select_prev_state);
15977 } else {
15978 let mut only_carets = true;
15979 let mut same_text_selected = true;
15980 let mut selected_text = None;
15981
15982 let mut selections_iter = selections.iter().peekable();
15983 while let Some(selection) = selections_iter.next() {
15984 if selection.start != selection.end {
15985 only_carets = false;
15986 }
15987
15988 if same_text_selected {
15989 if selected_text.is_none() {
15990 selected_text =
15991 Some(buffer.text_for_range(selection.range()).collect::<String>());
15992 }
15993
15994 if let Some(next_selection) = selections_iter.peek() {
15995 if next_selection.len() == selection.len() {
15996 let next_selected_text = buffer
15997 .text_for_range(next_selection.range())
15998 .collect::<String>();
15999 if Some(next_selected_text) != selected_text {
16000 same_text_selected = false;
16001 selected_text = None;
16002 }
16003 } else {
16004 same_text_selected = false;
16005 selected_text = None;
16006 }
16007 }
16008 }
16009 }
16010
16011 if only_carets {
16012 for selection in &mut selections {
16013 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16014 selection.start = word_range.start;
16015 selection.end = word_range.end;
16016 selection.goal = SelectionGoal::None;
16017 selection.reversed = false;
16018 self.select_match_ranges(
16019 selection.start..selection.end,
16020 selection.reversed,
16021 action.replace_newest,
16022 Some(Autoscroll::newest()),
16023 window,
16024 cx,
16025 );
16026 }
16027 if selections.len() == 1 {
16028 let selection = selections
16029 .last()
16030 .expect("ensured that there's only one selection");
16031 let query = buffer
16032 .text_for_range(selection.start..selection.end)
16033 .collect::<String>();
16034 let is_empty = query.is_empty();
16035 let select_state = SelectNextState {
16036 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16037 wordwise: true,
16038 done: is_empty,
16039 };
16040 self.select_prev_state = Some(select_state);
16041 } else {
16042 self.select_prev_state = None;
16043 }
16044 } else if let Some(selected_text) = selected_text {
16045 self.select_prev_state = Some(SelectNextState {
16046 query: self
16047 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16048 wordwise: false,
16049 done: false,
16050 });
16051 self.select_previous(action, window, cx)?;
16052 }
16053 }
16054 Ok(())
16055 }
16056
16057 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16058 /// setting the case sensitivity based on the global
16059 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16060 /// editor's settings.
16061 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16062 where
16063 I: IntoIterator<Item = P>,
16064 P: AsRef<[u8]>,
16065 {
16066 let case_sensitive = self
16067 .select_next_is_case_sensitive
16068 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16069
16070 let mut builder = AhoCorasickBuilder::new();
16071 builder.ascii_case_insensitive(!case_sensitive);
16072 builder.build(patterns)
16073 }
16074
16075 pub fn find_next_match(
16076 &mut self,
16077 _: &FindNextMatch,
16078 window: &mut Window,
16079 cx: &mut Context<Self>,
16080 ) -> Result<()> {
16081 let selections = self.selections.disjoint_anchors_arc();
16082 match selections.first() {
16083 Some(first) if selections.len() >= 2 => {
16084 self.change_selections(Default::default(), window, cx, |s| {
16085 s.select_ranges([first.range()]);
16086 });
16087 }
16088 _ => self.select_next(
16089 &SelectNext {
16090 replace_newest: true,
16091 },
16092 window,
16093 cx,
16094 )?,
16095 }
16096 Ok(())
16097 }
16098
16099 pub fn find_previous_match(
16100 &mut self,
16101 _: &FindPreviousMatch,
16102 window: &mut Window,
16103 cx: &mut Context<Self>,
16104 ) -> Result<()> {
16105 let selections = self.selections.disjoint_anchors_arc();
16106 match selections.last() {
16107 Some(last) if selections.len() >= 2 => {
16108 self.change_selections(Default::default(), window, cx, |s| {
16109 s.select_ranges([last.range()]);
16110 });
16111 }
16112 _ => self.select_previous(
16113 &SelectPrevious {
16114 replace_newest: true,
16115 },
16116 window,
16117 cx,
16118 )?,
16119 }
16120 Ok(())
16121 }
16122
16123 pub fn toggle_comments(
16124 &mut self,
16125 action: &ToggleComments,
16126 window: &mut Window,
16127 cx: &mut Context<Self>,
16128 ) {
16129 if self.read_only(cx) {
16130 return;
16131 }
16132 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16133 let text_layout_details = &self.text_layout_details(window, cx);
16134 self.transact(window, cx, |this, window, cx| {
16135 let mut selections = this
16136 .selections
16137 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16138 let mut edits = Vec::new();
16139 let mut selection_edit_ranges = Vec::new();
16140 let mut last_toggled_row = None;
16141 let snapshot = this.buffer.read(cx).read(cx);
16142 let empty_str: Arc<str> = Arc::default();
16143 let mut suffixes_inserted = Vec::new();
16144 let ignore_indent = action.ignore_indent;
16145
16146 fn comment_prefix_range(
16147 snapshot: &MultiBufferSnapshot,
16148 row: MultiBufferRow,
16149 comment_prefix: &str,
16150 comment_prefix_whitespace: &str,
16151 ignore_indent: bool,
16152 ) -> Range<Point> {
16153 let indent_size = if ignore_indent {
16154 0
16155 } else {
16156 snapshot.indent_size_for_line(row).len
16157 };
16158
16159 let start = Point::new(row.0, indent_size);
16160
16161 let mut line_bytes = snapshot
16162 .bytes_in_range(start..snapshot.max_point())
16163 .flatten()
16164 .copied();
16165
16166 // If this line currently begins with the line comment prefix, then record
16167 // the range containing the prefix.
16168 if line_bytes
16169 .by_ref()
16170 .take(comment_prefix.len())
16171 .eq(comment_prefix.bytes())
16172 {
16173 // Include any whitespace that matches the comment prefix.
16174 let matching_whitespace_len = line_bytes
16175 .zip(comment_prefix_whitespace.bytes())
16176 .take_while(|(a, b)| a == b)
16177 .count() as u32;
16178 let end = Point::new(
16179 start.row,
16180 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16181 );
16182 start..end
16183 } else {
16184 start..start
16185 }
16186 }
16187
16188 fn comment_suffix_range(
16189 snapshot: &MultiBufferSnapshot,
16190 row: MultiBufferRow,
16191 comment_suffix: &str,
16192 comment_suffix_has_leading_space: bool,
16193 ) -> Range<Point> {
16194 let end = Point::new(row.0, snapshot.line_len(row));
16195 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16196
16197 let mut line_end_bytes = snapshot
16198 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16199 .flatten()
16200 .copied();
16201
16202 let leading_space_len = if suffix_start_column > 0
16203 && line_end_bytes.next() == Some(b' ')
16204 && comment_suffix_has_leading_space
16205 {
16206 1
16207 } else {
16208 0
16209 };
16210
16211 // If this line currently begins with the line comment prefix, then record
16212 // the range containing the prefix.
16213 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16214 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16215 start..end
16216 } else {
16217 end..end
16218 }
16219 }
16220
16221 // TODO: Handle selections that cross excerpts
16222 for selection in &mut selections {
16223 let start_column = snapshot
16224 .indent_size_for_line(MultiBufferRow(selection.start.row))
16225 .len;
16226 let language = if let Some(language) =
16227 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16228 {
16229 language
16230 } else {
16231 continue;
16232 };
16233
16234 selection_edit_ranges.clear();
16235
16236 // If multiple selections contain a given row, avoid processing that
16237 // row more than once.
16238 let mut start_row = MultiBufferRow(selection.start.row);
16239 if last_toggled_row == Some(start_row) {
16240 start_row = start_row.next_row();
16241 }
16242 let end_row =
16243 if selection.end.row > selection.start.row && selection.end.column == 0 {
16244 MultiBufferRow(selection.end.row - 1)
16245 } else {
16246 MultiBufferRow(selection.end.row)
16247 };
16248 last_toggled_row = Some(end_row);
16249
16250 if start_row > end_row {
16251 continue;
16252 }
16253
16254 // If the language has line comments, toggle those.
16255 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16256
16257 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16258 if ignore_indent {
16259 full_comment_prefixes = full_comment_prefixes
16260 .into_iter()
16261 .map(|s| Arc::from(s.trim_end()))
16262 .collect();
16263 }
16264
16265 if !full_comment_prefixes.is_empty() {
16266 let first_prefix = full_comment_prefixes
16267 .first()
16268 .expect("prefixes is non-empty");
16269 let prefix_trimmed_lengths = full_comment_prefixes
16270 .iter()
16271 .map(|p| p.trim_end_matches(' ').len())
16272 .collect::<SmallVec<[usize; 4]>>();
16273
16274 let mut all_selection_lines_are_comments = true;
16275
16276 for row in start_row.0..=end_row.0 {
16277 let row = MultiBufferRow(row);
16278 if start_row < end_row && snapshot.is_line_blank(row) {
16279 continue;
16280 }
16281
16282 let prefix_range = full_comment_prefixes
16283 .iter()
16284 .zip(prefix_trimmed_lengths.iter().copied())
16285 .map(|(prefix, trimmed_prefix_len)| {
16286 comment_prefix_range(
16287 snapshot.deref(),
16288 row,
16289 &prefix[..trimmed_prefix_len],
16290 &prefix[trimmed_prefix_len..],
16291 ignore_indent,
16292 )
16293 })
16294 .max_by_key(|range| range.end.column - range.start.column)
16295 .expect("prefixes is non-empty");
16296
16297 if prefix_range.is_empty() {
16298 all_selection_lines_are_comments = false;
16299 }
16300
16301 selection_edit_ranges.push(prefix_range);
16302 }
16303
16304 if all_selection_lines_are_comments {
16305 edits.extend(
16306 selection_edit_ranges
16307 .iter()
16308 .cloned()
16309 .map(|range| (range, empty_str.clone())),
16310 );
16311 } else {
16312 let min_column = selection_edit_ranges
16313 .iter()
16314 .map(|range| range.start.column)
16315 .min()
16316 .unwrap_or(0);
16317 edits.extend(selection_edit_ranges.iter().map(|range| {
16318 let position = Point::new(range.start.row, min_column);
16319 (position..position, first_prefix.clone())
16320 }));
16321 }
16322 } else if let Some(BlockCommentConfig {
16323 start: full_comment_prefix,
16324 end: comment_suffix,
16325 ..
16326 }) = language.block_comment()
16327 {
16328 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16329 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16330 let prefix_range = comment_prefix_range(
16331 snapshot.deref(),
16332 start_row,
16333 comment_prefix,
16334 comment_prefix_whitespace,
16335 ignore_indent,
16336 );
16337 let suffix_range = comment_suffix_range(
16338 snapshot.deref(),
16339 end_row,
16340 comment_suffix.trim_start_matches(' '),
16341 comment_suffix.starts_with(' '),
16342 );
16343
16344 if prefix_range.is_empty() || suffix_range.is_empty() {
16345 edits.push((
16346 prefix_range.start..prefix_range.start,
16347 full_comment_prefix.clone(),
16348 ));
16349 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16350 suffixes_inserted.push((end_row, comment_suffix.len()));
16351 } else {
16352 edits.push((prefix_range, empty_str.clone()));
16353 edits.push((suffix_range, empty_str.clone()));
16354 }
16355 } else {
16356 continue;
16357 }
16358 }
16359
16360 drop(snapshot);
16361 this.buffer.update(cx, |buffer, cx| {
16362 buffer.edit(edits, None, cx);
16363 });
16364
16365 // Adjust selections so that they end before any comment suffixes that
16366 // were inserted.
16367 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16368 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16369 let snapshot = this.buffer.read(cx).read(cx);
16370 for selection in &mut selections {
16371 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16372 match row.cmp(&MultiBufferRow(selection.end.row)) {
16373 Ordering::Less => {
16374 suffixes_inserted.next();
16375 continue;
16376 }
16377 Ordering::Greater => break,
16378 Ordering::Equal => {
16379 if selection.end.column == snapshot.line_len(row) {
16380 if selection.is_empty() {
16381 selection.start.column -= suffix_len as u32;
16382 }
16383 selection.end.column -= suffix_len as u32;
16384 }
16385 break;
16386 }
16387 }
16388 }
16389 }
16390
16391 drop(snapshot);
16392 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16393
16394 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16395 let selections_on_single_row = selections.windows(2).all(|selections| {
16396 selections[0].start.row == selections[1].start.row
16397 && selections[0].end.row == selections[1].end.row
16398 && selections[0].start.row == selections[0].end.row
16399 });
16400 let selections_selecting = selections
16401 .iter()
16402 .any(|selection| selection.start != selection.end);
16403 let advance_downwards = action.advance_downwards
16404 && selections_on_single_row
16405 && !selections_selecting
16406 && !matches!(this.mode, EditorMode::SingleLine);
16407
16408 if advance_downwards {
16409 let snapshot = this.buffer.read(cx).snapshot(cx);
16410
16411 this.change_selections(Default::default(), window, cx, |s| {
16412 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16413 let mut point = display_point.to_point(display_snapshot);
16414 point.row += 1;
16415 point = snapshot.clip_point(point, Bias::Left);
16416 let display_point = point.to_display_point(display_snapshot);
16417 let goal = SelectionGoal::HorizontalPosition(
16418 display_snapshot
16419 .x_for_display_point(display_point, text_layout_details)
16420 .into(),
16421 );
16422 (display_point, goal)
16423 })
16424 });
16425 }
16426 });
16427 }
16428
16429 pub fn select_enclosing_symbol(
16430 &mut self,
16431 _: &SelectEnclosingSymbol,
16432 window: &mut Window,
16433 cx: &mut Context<Self>,
16434 ) {
16435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16436
16437 let buffer = self.buffer.read(cx).snapshot(cx);
16438 let old_selections = self
16439 .selections
16440 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16441 .into_boxed_slice();
16442
16443 fn update_selection(
16444 selection: &Selection<MultiBufferOffset>,
16445 buffer_snap: &MultiBufferSnapshot,
16446 ) -> Option<Selection<MultiBufferOffset>> {
16447 let cursor = selection.head();
16448 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16449 for symbol in symbols.iter().rev() {
16450 let start = symbol.range.start.to_offset(buffer_snap);
16451 let end = symbol.range.end.to_offset(buffer_snap);
16452 let new_range = start..end;
16453 if start < selection.start || end > selection.end {
16454 return Some(Selection {
16455 id: selection.id,
16456 start: new_range.start,
16457 end: new_range.end,
16458 goal: SelectionGoal::None,
16459 reversed: selection.reversed,
16460 });
16461 }
16462 }
16463 None
16464 }
16465
16466 let mut selected_larger_symbol = false;
16467 let new_selections = old_selections
16468 .iter()
16469 .map(|selection| match update_selection(selection, &buffer) {
16470 Some(new_selection) => {
16471 if new_selection.range() != selection.range() {
16472 selected_larger_symbol = true;
16473 }
16474 new_selection
16475 }
16476 None => selection.clone(),
16477 })
16478 .collect::<Vec<_>>();
16479
16480 if selected_larger_symbol {
16481 self.change_selections(Default::default(), window, cx, |s| {
16482 s.select(new_selections);
16483 });
16484 }
16485 }
16486
16487 pub fn select_larger_syntax_node(
16488 &mut self,
16489 _: &SelectLargerSyntaxNode,
16490 window: &mut Window,
16491 cx: &mut Context<Self>,
16492 ) {
16493 let Some(visible_row_count) = self.visible_row_count() else {
16494 return;
16495 };
16496 let old_selections: Box<[_]> = self
16497 .selections
16498 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16499 .into();
16500 if old_selections.is_empty() {
16501 return;
16502 }
16503
16504 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16505
16506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16507 let buffer = self.buffer.read(cx).snapshot(cx);
16508
16509 let mut selected_larger_node = false;
16510 let mut new_selections = old_selections
16511 .iter()
16512 .map(|selection| {
16513 let old_range = selection.start..selection.end;
16514
16515 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16516 // manually select word at selection
16517 if ["string_content", "inline"].contains(&node.kind()) {
16518 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16519 // ignore if word is already selected
16520 if !word_range.is_empty() && old_range != word_range {
16521 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16522 // only select word if start and end point belongs to same word
16523 if word_range == last_word_range {
16524 selected_larger_node = true;
16525 return Selection {
16526 id: selection.id,
16527 start: word_range.start,
16528 end: word_range.end,
16529 goal: SelectionGoal::None,
16530 reversed: selection.reversed,
16531 };
16532 }
16533 }
16534 }
16535 }
16536
16537 let mut new_range = old_range.clone();
16538 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16539 new_range = range;
16540 if !node.is_named() {
16541 continue;
16542 }
16543 if !display_map.intersects_fold(new_range.start)
16544 && !display_map.intersects_fold(new_range.end)
16545 {
16546 break;
16547 }
16548 }
16549
16550 selected_larger_node |= new_range != old_range;
16551 Selection {
16552 id: selection.id,
16553 start: new_range.start,
16554 end: new_range.end,
16555 goal: SelectionGoal::None,
16556 reversed: selection.reversed,
16557 }
16558 })
16559 .collect::<Vec<_>>();
16560
16561 if !selected_larger_node {
16562 return; // don't put this call in the history
16563 }
16564
16565 // scroll based on transformation done to the last selection created by the user
16566 let (last_old, last_new) = old_selections
16567 .last()
16568 .zip(new_selections.last().cloned())
16569 .expect("old_selections isn't empty");
16570
16571 // revert selection
16572 let is_selection_reversed = {
16573 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16574 new_selections.last_mut().expect("checked above").reversed =
16575 should_newest_selection_be_reversed;
16576 should_newest_selection_be_reversed
16577 };
16578
16579 if selected_larger_node {
16580 self.select_syntax_node_history.disable_clearing = true;
16581 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16582 s.select(new_selections.clone());
16583 });
16584 self.select_syntax_node_history.disable_clearing = false;
16585 }
16586
16587 let start_row = last_new.start.to_display_point(&display_map).row().0;
16588 let end_row = last_new.end.to_display_point(&display_map).row().0;
16589 let selection_height = end_row - start_row + 1;
16590 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16591
16592 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16593 let scroll_behavior = if fits_on_the_screen {
16594 self.request_autoscroll(Autoscroll::fit(), cx);
16595 SelectSyntaxNodeScrollBehavior::FitSelection
16596 } else if is_selection_reversed {
16597 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16598 SelectSyntaxNodeScrollBehavior::CursorTop
16599 } else {
16600 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16601 SelectSyntaxNodeScrollBehavior::CursorBottom
16602 };
16603
16604 self.select_syntax_node_history.push((
16605 old_selections,
16606 scroll_behavior,
16607 is_selection_reversed,
16608 ));
16609 }
16610
16611 pub fn select_smaller_syntax_node(
16612 &mut self,
16613 _: &SelectSmallerSyntaxNode,
16614 window: &mut Window,
16615 cx: &mut Context<Self>,
16616 ) {
16617 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16618
16619 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16620 self.select_syntax_node_history.pop()
16621 {
16622 if let Some(selection) = selections.last_mut() {
16623 selection.reversed = is_selection_reversed;
16624 }
16625
16626 self.select_syntax_node_history.disable_clearing = true;
16627 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16628 s.select(selections.to_vec());
16629 });
16630 self.select_syntax_node_history.disable_clearing = false;
16631
16632 match scroll_behavior {
16633 SelectSyntaxNodeScrollBehavior::CursorTop => {
16634 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16635 }
16636 SelectSyntaxNodeScrollBehavior::FitSelection => {
16637 self.request_autoscroll(Autoscroll::fit(), cx);
16638 }
16639 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16640 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16641 }
16642 }
16643 }
16644 }
16645
16646 pub fn unwrap_syntax_node(
16647 &mut self,
16648 _: &UnwrapSyntaxNode,
16649 window: &mut Window,
16650 cx: &mut Context<Self>,
16651 ) {
16652 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16653
16654 let buffer = self.buffer.read(cx).snapshot(cx);
16655 let selections = self
16656 .selections
16657 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16658 .into_iter()
16659 // subtracting the offset requires sorting
16660 .sorted_by_key(|i| i.start);
16661
16662 let full_edits = selections
16663 .into_iter()
16664 .filter_map(|selection| {
16665 let child = if selection.is_empty()
16666 && let Some((_, ancestor_range)) =
16667 buffer.syntax_ancestor(selection.start..selection.end)
16668 {
16669 ancestor_range
16670 } else {
16671 selection.range()
16672 };
16673
16674 let mut parent = child.clone();
16675 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16676 parent = ancestor_range;
16677 if parent.start < child.start || parent.end > child.end {
16678 break;
16679 }
16680 }
16681
16682 if parent == child {
16683 return None;
16684 }
16685 let text = buffer.text_for_range(child).collect::<String>();
16686 Some((selection.id, parent, text))
16687 })
16688 .collect::<Vec<_>>();
16689 if full_edits.is_empty() {
16690 return;
16691 }
16692
16693 self.transact(window, cx, |this, window, cx| {
16694 this.buffer.update(cx, |buffer, cx| {
16695 buffer.edit(
16696 full_edits
16697 .iter()
16698 .map(|(_, p, t)| (p.clone(), t.clone()))
16699 .collect::<Vec<_>>(),
16700 None,
16701 cx,
16702 );
16703 });
16704 this.change_selections(Default::default(), window, cx, |s| {
16705 let mut offset = 0;
16706 let mut selections = vec![];
16707 for (id, parent, text) in full_edits {
16708 let start = parent.start - offset;
16709 offset += (parent.end - parent.start) - text.len();
16710 selections.push(Selection {
16711 id,
16712 start,
16713 end: start + text.len(),
16714 reversed: false,
16715 goal: Default::default(),
16716 });
16717 }
16718 s.select(selections);
16719 });
16720 });
16721 }
16722
16723 pub fn select_next_syntax_node(
16724 &mut self,
16725 _: &SelectNextSyntaxNode,
16726 window: &mut Window,
16727 cx: &mut Context<Self>,
16728 ) {
16729 let old_selections: Box<[_]> = self
16730 .selections
16731 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16732 .into();
16733 if old_selections.is_empty() {
16734 return;
16735 }
16736
16737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16738
16739 let buffer = self.buffer.read(cx).snapshot(cx);
16740 let mut selected_sibling = false;
16741
16742 let new_selections = old_selections
16743 .iter()
16744 .map(|selection| {
16745 let old_range = selection.start..selection.end;
16746
16747 let old_range =
16748 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16749 let excerpt = buffer.excerpt_containing(old_range.clone());
16750
16751 if let Some(mut excerpt) = excerpt
16752 && let Some(node) = excerpt
16753 .buffer()
16754 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16755 {
16756 let new_range = excerpt.map_range_from_buffer(
16757 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16758 );
16759 selected_sibling = true;
16760 Selection {
16761 id: selection.id,
16762 start: new_range.start,
16763 end: new_range.end,
16764 goal: SelectionGoal::None,
16765 reversed: selection.reversed,
16766 }
16767 } else {
16768 selection.clone()
16769 }
16770 })
16771 .collect::<Vec<_>>();
16772
16773 if selected_sibling {
16774 self.change_selections(
16775 SelectionEffects::scroll(Autoscroll::fit()),
16776 window,
16777 cx,
16778 |s| {
16779 s.select(new_selections);
16780 },
16781 );
16782 }
16783 }
16784
16785 pub fn select_prev_syntax_node(
16786 &mut self,
16787 _: &SelectPreviousSyntaxNode,
16788 window: &mut Window,
16789 cx: &mut Context<Self>,
16790 ) {
16791 let old_selections: Box<[_]> = self
16792 .selections
16793 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16794 .into();
16795 if old_selections.is_empty() {
16796 return;
16797 }
16798
16799 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16800
16801 let buffer = self.buffer.read(cx).snapshot(cx);
16802 let mut selected_sibling = false;
16803
16804 let new_selections = old_selections
16805 .iter()
16806 .map(|selection| {
16807 let old_range = selection.start..selection.end;
16808 let old_range =
16809 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16810 let excerpt = buffer.excerpt_containing(old_range.clone());
16811
16812 if let Some(mut excerpt) = excerpt
16813 && let Some(node) = excerpt
16814 .buffer()
16815 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16816 {
16817 let new_range = excerpt.map_range_from_buffer(
16818 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16819 );
16820 selected_sibling = true;
16821 Selection {
16822 id: selection.id,
16823 start: new_range.start,
16824 end: new_range.end,
16825 goal: SelectionGoal::None,
16826 reversed: selection.reversed,
16827 }
16828 } else {
16829 selection.clone()
16830 }
16831 })
16832 .collect::<Vec<_>>();
16833
16834 if selected_sibling {
16835 self.change_selections(
16836 SelectionEffects::scroll(Autoscroll::fit()),
16837 window,
16838 cx,
16839 |s| {
16840 s.select(new_selections);
16841 },
16842 );
16843 }
16844 }
16845
16846 pub fn move_to_start_of_larger_syntax_node(
16847 &mut self,
16848 _: &MoveToStartOfLargerSyntaxNode,
16849 window: &mut Window,
16850 cx: &mut Context<Self>,
16851 ) {
16852 self.move_cursors_to_syntax_nodes(window, cx, false);
16853 }
16854
16855 pub fn move_to_end_of_larger_syntax_node(
16856 &mut self,
16857 _: &MoveToEndOfLargerSyntaxNode,
16858 window: &mut Window,
16859 cx: &mut Context<Self>,
16860 ) {
16861 self.move_cursors_to_syntax_nodes(window, cx, true);
16862 }
16863
16864 fn find_syntax_node_boundary(
16865 &self,
16866 selection_pos: MultiBufferOffset,
16867 move_to_end: bool,
16868 display_map: &DisplaySnapshot,
16869 buffer: &MultiBufferSnapshot,
16870 ) -> MultiBufferOffset {
16871 let old_range = selection_pos..selection_pos;
16872 let mut new_pos = selection_pos;
16873 let mut search_range = old_range;
16874 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16875 search_range = range.clone();
16876 if !node.is_named()
16877 || display_map.intersects_fold(range.start)
16878 || display_map.intersects_fold(range.end)
16879 // If cursor is already at the end of the syntax node, continue searching
16880 || (move_to_end && range.end == selection_pos)
16881 // If cursor is already at the start of the syntax node, continue searching
16882 || (!move_to_end && range.start == selection_pos)
16883 {
16884 continue;
16885 }
16886
16887 // If we found a string_content node, find the largest parent that is still string_content
16888 // Enables us to skip to the end of strings without taking multiple steps inside the string
16889 let (_, final_range) = if node.kind() == "string_content" {
16890 let mut current_node = node;
16891 let mut current_range = range;
16892 while let Some((parent, parent_range)) =
16893 buffer.syntax_ancestor(current_range.clone())
16894 {
16895 if parent.kind() == "string_content" {
16896 current_node = parent;
16897 current_range = parent_range;
16898 } else {
16899 break;
16900 }
16901 }
16902
16903 (current_node, current_range)
16904 } else {
16905 (node, range)
16906 };
16907
16908 new_pos = if move_to_end {
16909 final_range.end
16910 } else {
16911 final_range.start
16912 };
16913
16914 break;
16915 }
16916
16917 new_pos
16918 }
16919
16920 fn move_cursors_to_syntax_nodes(
16921 &mut self,
16922 window: &mut Window,
16923 cx: &mut Context<Self>,
16924 move_to_end: bool,
16925 ) -> bool {
16926 let old_selections: Box<[_]> = self
16927 .selections
16928 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16929 .into();
16930 if old_selections.is_empty() {
16931 return false;
16932 }
16933
16934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16935
16936 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16937 let buffer = self.buffer.read(cx).snapshot(cx);
16938
16939 let mut any_cursor_moved = false;
16940 let new_selections = old_selections
16941 .iter()
16942 .map(|selection| {
16943 if !selection.is_empty() {
16944 return selection.clone();
16945 }
16946
16947 let selection_pos = selection.head();
16948 let new_pos = self.find_syntax_node_boundary(
16949 selection_pos,
16950 move_to_end,
16951 &display_map,
16952 &buffer,
16953 );
16954
16955 any_cursor_moved |= new_pos != selection_pos;
16956
16957 Selection {
16958 id: selection.id,
16959 start: new_pos,
16960 end: new_pos,
16961 goal: SelectionGoal::None,
16962 reversed: false,
16963 }
16964 })
16965 .collect::<Vec<_>>();
16966
16967 self.change_selections(Default::default(), window, cx, |s| {
16968 s.select(new_selections);
16969 });
16970 self.request_autoscroll(Autoscroll::newest(), cx);
16971
16972 any_cursor_moved
16973 }
16974
16975 pub fn select_to_start_of_larger_syntax_node(
16976 &mut self,
16977 _: &SelectToStartOfLargerSyntaxNode,
16978 window: &mut Window,
16979 cx: &mut Context<Self>,
16980 ) {
16981 self.select_to_syntax_nodes(window, cx, false);
16982 }
16983
16984 pub fn select_to_end_of_larger_syntax_node(
16985 &mut self,
16986 _: &SelectToEndOfLargerSyntaxNode,
16987 window: &mut Window,
16988 cx: &mut Context<Self>,
16989 ) {
16990 self.select_to_syntax_nodes(window, cx, true);
16991 }
16992
16993 fn select_to_syntax_nodes(
16994 &mut self,
16995 window: &mut Window,
16996 cx: &mut Context<Self>,
16997 move_to_end: bool,
16998 ) {
16999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17000
17001 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17002 let buffer = self.buffer.read(cx).snapshot(cx);
17003 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17004
17005 let new_selections = old_selections
17006 .iter()
17007 .map(|selection| {
17008 let new_pos = self.find_syntax_node_boundary(
17009 selection.head(),
17010 move_to_end,
17011 &display_map,
17012 &buffer,
17013 );
17014
17015 let mut new_selection = selection.clone();
17016 new_selection.set_head(new_pos, SelectionGoal::None);
17017 new_selection
17018 })
17019 .collect::<Vec<_>>();
17020
17021 self.change_selections(Default::default(), window, cx, |s| {
17022 s.select(new_selections);
17023 });
17024 }
17025
17026 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
17027 if !EditorSettings::get_global(cx).gutter.runnables || !self.enable_runnables {
17028 self.clear_tasks();
17029 return Task::ready(());
17030 }
17031 let project = self.project().map(Entity::downgrade);
17032 let task_sources = self.lsp_task_sources(cx);
17033 let multi_buffer = self.buffer.downgrade();
17034 cx.spawn_in(window, async move |editor, cx| {
17035 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
17036 let Some(project) = project.and_then(|p| p.upgrade()) else {
17037 return;
17038 };
17039 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
17040 this.display_map.update(cx, |map, cx| map.snapshot(cx))
17041 }) else {
17042 return;
17043 };
17044
17045 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
17046 if hide_runnables {
17047 return;
17048 }
17049 let new_rows =
17050 cx.background_spawn({
17051 let snapshot = display_snapshot.clone();
17052 async move {
17053 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
17054 }
17055 })
17056 .await;
17057 let Ok(lsp_tasks) =
17058 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
17059 else {
17060 return;
17061 };
17062 let lsp_tasks = lsp_tasks.await;
17063
17064 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
17065 lsp_tasks
17066 .into_iter()
17067 .flat_map(|(kind, tasks)| {
17068 tasks.into_iter().filter_map(move |(location, task)| {
17069 Some((kind.clone(), location?, task))
17070 })
17071 })
17072 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
17073 let buffer = location.target.buffer;
17074 let buffer_snapshot = buffer.read(cx).snapshot();
17075 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
17076 |(excerpt_id, snapshot, _)| {
17077 if snapshot.remote_id() == buffer_snapshot.remote_id() {
17078 display_snapshot
17079 .buffer_snapshot()
17080 .anchor_in_excerpt(excerpt_id, location.target.range.start)
17081 } else {
17082 None
17083 }
17084 },
17085 );
17086 if let Some(offset) = offset {
17087 let task_buffer_range =
17088 location.target.range.to_point(&buffer_snapshot);
17089 let context_buffer_range =
17090 task_buffer_range.to_offset(&buffer_snapshot);
17091 let context_range = BufferOffset(context_buffer_range.start)
17092 ..BufferOffset(context_buffer_range.end);
17093
17094 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
17095 .or_insert_with(|| RunnableTasks {
17096 templates: Vec::new(),
17097 offset,
17098 column: task_buffer_range.start.column,
17099 extra_variables: HashMap::default(),
17100 context_range,
17101 })
17102 .templates
17103 .push((kind, task.original_task().clone()));
17104 }
17105
17106 acc
17107 })
17108 }) else {
17109 return;
17110 };
17111
17112 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
17113 buffer.language_settings(cx).tasks.prefer_lsp
17114 }) else {
17115 return;
17116 };
17117
17118 let rows = Self::runnable_rows(
17119 project,
17120 display_snapshot,
17121 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
17122 new_rows,
17123 cx.clone(),
17124 )
17125 .await;
17126 editor
17127 .update(cx, |editor, _| {
17128 editor.clear_tasks();
17129 for (key, mut value) in rows {
17130 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
17131 value.templates.extend(lsp_tasks.templates);
17132 }
17133
17134 editor.insert_tasks(key, value);
17135 }
17136 for (key, value) in lsp_tasks_by_rows {
17137 editor.insert_tasks(key, value);
17138 }
17139 })
17140 .ok();
17141 })
17142 }
17143 fn fetch_runnable_ranges(
17144 snapshot: &DisplaySnapshot,
17145 range: Range<Anchor>,
17146 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
17147 snapshot.buffer_snapshot().runnable_ranges(range).collect()
17148 }
17149
17150 fn runnable_rows(
17151 project: Entity<Project>,
17152 snapshot: DisplaySnapshot,
17153 prefer_lsp: bool,
17154 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
17155 cx: AsyncWindowContext,
17156 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
17157 cx.spawn(async move |cx| {
17158 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
17159 for (run_range, mut runnable) in runnable_ranges {
17160 let Some(tasks) = cx
17161 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
17162 .ok()
17163 else {
17164 continue;
17165 };
17166 let mut tasks = tasks.await;
17167
17168 if prefer_lsp {
17169 tasks.retain(|(task_kind, _)| {
17170 !matches!(task_kind, TaskSourceKind::Language { .. })
17171 });
17172 }
17173 if tasks.is_empty() {
17174 continue;
17175 }
17176
17177 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
17178 let Some(row) = snapshot
17179 .buffer_snapshot()
17180 .buffer_line_for_row(MultiBufferRow(point.row))
17181 .map(|(_, range)| range.start.row)
17182 else {
17183 continue;
17184 };
17185
17186 let context_range =
17187 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
17188 runnable_rows.push((
17189 (runnable.buffer_id, row),
17190 RunnableTasks {
17191 templates: tasks,
17192 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
17193 context_range,
17194 column: point.column,
17195 extra_variables: runnable.extra_captures,
17196 },
17197 ));
17198 }
17199 runnable_rows
17200 })
17201 }
17202
17203 fn templates_with_tags(
17204 project: &Entity<Project>,
17205 runnable: &mut Runnable,
17206 cx: &mut App,
17207 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
17208 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
17209 let (worktree_id, file) = project
17210 .buffer_for_id(runnable.buffer, cx)
17211 .and_then(|buffer| buffer.read(cx).file())
17212 .map(|file| (file.worktree_id(cx), file.clone()))
17213 .unzip();
17214
17215 (
17216 project.task_store().read(cx).task_inventory().cloned(),
17217 worktree_id,
17218 file,
17219 )
17220 });
17221
17222 let tags = mem::take(&mut runnable.tags);
17223 let language = runnable.language.clone();
17224 cx.spawn(async move |cx| {
17225 let mut templates_with_tags = Vec::new();
17226 if let Some(inventory) = inventory {
17227 for RunnableTag(tag) in tags {
17228 let new_tasks = inventory.update(cx, |inventory, cx| {
17229 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
17230 });
17231 templates_with_tags.extend(new_tasks.await.into_iter().filter(
17232 move |(_, template)| {
17233 template.tags.iter().any(|source_tag| source_tag == &tag)
17234 },
17235 ));
17236 }
17237 }
17238 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
17239
17240 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
17241 // Strongest source wins; if we have worktree tag binding, prefer that to
17242 // global and language bindings;
17243 // if we have a global binding, prefer that to language binding.
17244 let first_mismatch = templates_with_tags
17245 .iter()
17246 .position(|(tag_source, _)| tag_source != leading_tag_source);
17247 if let Some(index) = first_mismatch {
17248 templates_with_tags.truncate(index);
17249 }
17250 }
17251
17252 templates_with_tags
17253 })
17254 }
17255
17256 pub fn move_to_enclosing_bracket(
17257 &mut self,
17258 _: &MoveToEnclosingBracket,
17259 window: &mut Window,
17260 cx: &mut Context<Self>,
17261 ) {
17262 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17263 self.change_selections(Default::default(), window, cx, |s| {
17264 s.move_offsets_with(&mut |snapshot, selection| {
17265 let Some(enclosing_bracket_ranges) =
17266 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17267 else {
17268 return;
17269 };
17270
17271 let mut best_length = usize::MAX;
17272 let mut best_inside = false;
17273 let mut best_in_bracket_range = false;
17274 let mut best_destination = None;
17275 for (open, close) in enclosing_bracket_ranges {
17276 let close = close.to_inclusive();
17277 let length = *close.end() - open.start;
17278 let inside = selection.start >= open.end && selection.end <= *close.start();
17279 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17280 || close.contains(&selection.head());
17281
17282 // If best is next to a bracket and current isn't, skip
17283 if !in_bracket_range && best_in_bracket_range {
17284 continue;
17285 }
17286
17287 // Prefer smaller lengths unless best is inside and current isn't
17288 if length > best_length && (best_inside || !inside) {
17289 continue;
17290 }
17291
17292 best_length = length;
17293 best_inside = inside;
17294 best_in_bracket_range = in_bracket_range;
17295 best_destination = Some(
17296 if close.contains(&selection.start) && close.contains(&selection.end) {
17297 if inside { open.end } else { open.start }
17298 } else if inside {
17299 *close.start()
17300 } else {
17301 *close.end()
17302 },
17303 );
17304 }
17305
17306 if let Some(destination) = best_destination {
17307 selection.collapse_to(destination, SelectionGoal::None);
17308 }
17309 })
17310 });
17311 }
17312
17313 pub fn undo_selection(
17314 &mut self,
17315 _: &UndoSelection,
17316 window: &mut Window,
17317 cx: &mut Context<Self>,
17318 ) {
17319 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17320 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17321 self.selection_history.mode = SelectionHistoryMode::Undoing;
17322 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17323 this.end_selection(window, cx);
17324 this.change_selections(
17325 SelectionEffects::scroll(Autoscroll::newest()),
17326 window,
17327 cx,
17328 |s| s.select_anchors(entry.selections.to_vec()),
17329 );
17330 });
17331 self.selection_history.mode = SelectionHistoryMode::Normal;
17332
17333 self.select_next_state = entry.select_next_state;
17334 self.select_prev_state = entry.select_prev_state;
17335 self.add_selections_state = entry.add_selections_state;
17336 }
17337 }
17338
17339 pub fn redo_selection(
17340 &mut self,
17341 _: &RedoSelection,
17342 window: &mut Window,
17343 cx: &mut Context<Self>,
17344 ) {
17345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17346 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17347 self.selection_history.mode = SelectionHistoryMode::Redoing;
17348 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17349 this.end_selection(window, cx);
17350 this.change_selections(
17351 SelectionEffects::scroll(Autoscroll::newest()),
17352 window,
17353 cx,
17354 |s| s.select_anchors(entry.selections.to_vec()),
17355 );
17356 });
17357 self.selection_history.mode = SelectionHistoryMode::Normal;
17358
17359 self.select_next_state = entry.select_next_state;
17360 self.select_prev_state = entry.select_prev_state;
17361 self.add_selections_state = entry.add_selections_state;
17362 }
17363 }
17364
17365 pub fn expand_excerpts(
17366 &mut self,
17367 action: &ExpandExcerpts,
17368 _: &mut Window,
17369 cx: &mut Context<Self>,
17370 ) {
17371 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17372 }
17373
17374 pub fn expand_excerpts_down(
17375 &mut self,
17376 action: &ExpandExcerptsDown,
17377 _: &mut Window,
17378 cx: &mut Context<Self>,
17379 ) {
17380 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17381 }
17382
17383 pub fn expand_excerpts_up(
17384 &mut self,
17385 action: &ExpandExcerptsUp,
17386 _: &mut Window,
17387 cx: &mut Context<Self>,
17388 ) {
17389 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17390 }
17391
17392 pub fn expand_excerpts_for_direction(
17393 &mut self,
17394 lines: u32,
17395 direction: ExpandExcerptDirection,
17396 cx: &mut Context<Self>,
17397 ) {
17398 let selections = self.selections.disjoint_anchors_arc();
17399
17400 let lines = if lines == 0 {
17401 EditorSettings::get_global(cx).expand_excerpt_lines
17402 } else {
17403 lines
17404 };
17405
17406 let snapshot = self.buffer.read(cx).snapshot(cx);
17407 let excerpt_ids = selections
17408 .iter()
17409 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17410 .unique()
17411 .sorted()
17412 .collect::<Vec<_>>();
17413
17414 if self.delegate_expand_excerpts {
17415 cx.emit(EditorEvent::ExpandExcerptsRequested {
17416 excerpt_ids,
17417 lines,
17418 direction,
17419 });
17420 return;
17421 }
17422
17423 self.buffer.update(cx, |buffer, cx| {
17424 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17425 })
17426 }
17427
17428 pub fn expand_excerpt(
17429 &mut self,
17430 excerpt: ExcerptId,
17431 direction: ExpandExcerptDirection,
17432 window: &mut Window,
17433 cx: &mut Context<Self>,
17434 ) {
17435 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17436
17437 if self.delegate_expand_excerpts {
17438 cx.emit(EditorEvent::ExpandExcerptsRequested {
17439 excerpt_ids: vec![excerpt],
17440 lines: lines_to_expand,
17441 direction,
17442 });
17443 return;
17444 }
17445
17446 let current_scroll_position = self.scroll_position(cx);
17447 let mut scroll = None;
17448
17449 if direction == ExpandExcerptDirection::Down {
17450 let multi_buffer = self.buffer.read(cx);
17451 let snapshot = multi_buffer.snapshot(cx);
17452 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17453 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17454 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17455 {
17456 let buffer_snapshot = buffer.read(cx).snapshot();
17457 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17458 let last_row = buffer_snapshot.max_point().row;
17459 let lines_below = last_row.saturating_sub(excerpt_end_row);
17460 if lines_below >= lines_to_expand {
17461 scroll = Some(
17462 current_scroll_position
17463 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17464 );
17465 }
17466 }
17467 }
17468 if direction == ExpandExcerptDirection::Up
17469 && self
17470 .buffer
17471 .read(cx)
17472 .snapshot(cx)
17473 .excerpt_before(excerpt)
17474 .is_none()
17475 {
17476 scroll = Some(current_scroll_position);
17477 }
17478
17479 self.buffer.update(cx, |buffer, cx| {
17480 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17481 });
17482
17483 if let Some(new_scroll_position) = scroll {
17484 self.set_scroll_position(new_scroll_position, window, cx);
17485 }
17486 }
17487
17488 pub fn go_to_singleton_buffer_point(
17489 &mut self,
17490 point: Point,
17491 window: &mut Window,
17492 cx: &mut Context<Self>,
17493 ) {
17494 self.go_to_singleton_buffer_range(point..point, window, cx);
17495 }
17496
17497 pub fn go_to_singleton_buffer_range(
17498 &mut self,
17499 range: Range<Point>,
17500 window: &mut Window,
17501 cx: &mut Context<Self>,
17502 ) {
17503 let multibuffer = self.buffer().read(cx);
17504 let Some(buffer) = multibuffer.as_singleton() else {
17505 return;
17506 };
17507 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17508 return;
17509 };
17510 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17511 return;
17512 };
17513 self.change_selections(
17514 SelectionEffects::default().nav_history(true),
17515 window,
17516 cx,
17517 |s| s.select_anchor_ranges([start..end]),
17518 );
17519 }
17520
17521 pub fn go_to_diagnostic(
17522 &mut self,
17523 action: &GoToDiagnostic,
17524 window: &mut Window,
17525 cx: &mut Context<Self>,
17526 ) {
17527 if !self.diagnostics_enabled() {
17528 return;
17529 }
17530 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17531 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17532 }
17533
17534 pub fn go_to_prev_diagnostic(
17535 &mut self,
17536 action: &GoToPreviousDiagnostic,
17537 window: &mut Window,
17538 cx: &mut Context<Self>,
17539 ) {
17540 if !self.diagnostics_enabled() {
17541 return;
17542 }
17543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17544 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17545 }
17546
17547 pub fn go_to_diagnostic_impl(
17548 &mut self,
17549 direction: Direction,
17550 severity: GoToDiagnosticSeverityFilter,
17551 window: &mut Window,
17552 cx: &mut Context<Self>,
17553 ) {
17554 let buffer = self.buffer.read(cx).snapshot(cx);
17555 let selection = self
17556 .selections
17557 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17558
17559 let mut active_group_id = None;
17560 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17561 && active_group.active_range.start.to_offset(&buffer) == selection.start
17562 {
17563 active_group_id = Some(active_group.group_id);
17564 }
17565
17566 fn filtered<'a>(
17567 severity: GoToDiagnosticSeverityFilter,
17568 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17569 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17570 diagnostics
17571 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17572 .filter(|entry| entry.range.start != entry.range.end)
17573 .filter(|entry| !entry.diagnostic.is_unnecessary)
17574 }
17575
17576 let before = filtered(
17577 severity,
17578 buffer
17579 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17580 .filter(|entry| entry.range.start <= selection.start),
17581 );
17582 let after = filtered(
17583 severity,
17584 buffer
17585 .diagnostics_in_range(selection.start..buffer.len())
17586 .filter(|entry| entry.range.start >= selection.start),
17587 );
17588
17589 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17590 if direction == Direction::Prev {
17591 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17592 {
17593 for diagnostic in prev_diagnostics.into_iter().rev() {
17594 if diagnostic.range.start != selection.start
17595 || active_group_id
17596 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17597 {
17598 found = Some(diagnostic);
17599 break 'outer;
17600 }
17601 }
17602 }
17603 } else {
17604 for diagnostic in after.chain(before) {
17605 if diagnostic.range.start != selection.start
17606 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17607 {
17608 found = Some(diagnostic);
17609 break;
17610 }
17611 }
17612 }
17613 let Some(next_diagnostic) = found else {
17614 return;
17615 };
17616
17617 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17618 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17619 return;
17620 };
17621 let snapshot = self.snapshot(window, cx);
17622 if snapshot.intersects_fold(next_diagnostic.range.start) {
17623 self.unfold_ranges(
17624 std::slice::from_ref(&next_diagnostic.range),
17625 true,
17626 false,
17627 cx,
17628 );
17629 }
17630 self.change_selections(Default::default(), window, cx, |s| {
17631 s.select_ranges(vec![
17632 next_diagnostic.range.start..next_diagnostic.range.start,
17633 ])
17634 });
17635 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17636 self.refresh_edit_prediction(false, true, window, cx);
17637 }
17638
17639 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17640 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17641 let snapshot = self.snapshot(window, cx);
17642 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17643 self.go_to_hunk_before_or_after_position(
17644 &snapshot,
17645 selection.head(),
17646 Direction::Next,
17647 window,
17648 cx,
17649 );
17650 }
17651
17652 pub fn go_to_hunk_before_or_after_position(
17653 &mut self,
17654 snapshot: &EditorSnapshot,
17655 position: Point,
17656 direction: Direction,
17657 window: &mut Window,
17658 cx: &mut Context<Editor>,
17659 ) {
17660 let row = if direction == Direction::Next {
17661 self.hunk_after_position(snapshot, position)
17662 .map(|hunk| hunk.row_range.start)
17663 } else {
17664 self.hunk_before_position(snapshot, position)
17665 };
17666
17667 if let Some(row) = row {
17668 let destination = Point::new(row.0, 0);
17669 let autoscroll = Autoscroll::center();
17670
17671 self.unfold_ranges(&[destination..destination], false, false, cx);
17672 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17673 s.select_ranges([destination..destination]);
17674 });
17675 }
17676 }
17677
17678 fn hunk_after_position(
17679 &mut self,
17680 snapshot: &EditorSnapshot,
17681 position: Point,
17682 ) -> Option<MultiBufferDiffHunk> {
17683 snapshot
17684 .buffer_snapshot()
17685 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17686 .find(|hunk| hunk.row_range.start.0 > position.row)
17687 .or_else(|| {
17688 snapshot
17689 .buffer_snapshot()
17690 .diff_hunks_in_range(Point::zero()..position)
17691 .find(|hunk| hunk.row_range.end.0 < position.row)
17692 })
17693 }
17694
17695 fn go_to_prev_hunk(
17696 &mut self,
17697 _: &GoToPreviousHunk,
17698 window: &mut Window,
17699 cx: &mut Context<Self>,
17700 ) {
17701 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17702 let snapshot = self.snapshot(window, cx);
17703 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17704 self.go_to_hunk_before_or_after_position(
17705 &snapshot,
17706 selection.head(),
17707 Direction::Prev,
17708 window,
17709 cx,
17710 );
17711 }
17712
17713 fn hunk_before_position(
17714 &mut self,
17715 snapshot: &EditorSnapshot,
17716 position: Point,
17717 ) -> Option<MultiBufferRow> {
17718 snapshot
17719 .buffer_snapshot()
17720 .diff_hunk_before(position)
17721 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17722 }
17723
17724 fn go_to_next_change(
17725 &mut self,
17726 _: &GoToNextChange,
17727 window: &mut Window,
17728 cx: &mut Context<Self>,
17729 ) {
17730 if let Some(selections) = self
17731 .change_list
17732 .next_change(1, Direction::Next)
17733 .map(|s| s.to_vec())
17734 {
17735 self.change_selections(Default::default(), window, cx, |s| {
17736 let map = s.display_snapshot();
17737 s.select_display_ranges(selections.iter().map(|a| {
17738 let point = a.to_display_point(&map);
17739 point..point
17740 }))
17741 })
17742 }
17743 }
17744
17745 fn go_to_previous_change(
17746 &mut self,
17747 _: &GoToPreviousChange,
17748 window: &mut Window,
17749 cx: &mut Context<Self>,
17750 ) {
17751 if let Some(selections) = self
17752 .change_list
17753 .next_change(1, Direction::Prev)
17754 .map(|s| s.to_vec())
17755 {
17756 self.change_selections(Default::default(), window, cx, |s| {
17757 let map = s.display_snapshot();
17758 s.select_display_ranges(selections.iter().map(|a| {
17759 let point = a.to_display_point(&map);
17760 point..point
17761 }))
17762 })
17763 }
17764 }
17765
17766 pub fn go_to_next_document_highlight(
17767 &mut self,
17768 _: &GoToNextDocumentHighlight,
17769 window: &mut Window,
17770 cx: &mut Context<Self>,
17771 ) {
17772 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17773 }
17774
17775 pub fn go_to_prev_document_highlight(
17776 &mut self,
17777 _: &GoToPreviousDocumentHighlight,
17778 window: &mut Window,
17779 cx: &mut Context<Self>,
17780 ) {
17781 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17782 }
17783
17784 pub fn go_to_document_highlight_before_or_after_position(
17785 &mut self,
17786 direction: Direction,
17787 window: &mut Window,
17788 cx: &mut Context<Editor>,
17789 ) {
17790 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17791 let snapshot = self.snapshot(window, cx);
17792 let buffer = &snapshot.buffer_snapshot();
17793 let position = self
17794 .selections
17795 .newest::<Point>(&snapshot.display_snapshot)
17796 .head();
17797 let anchor_position = buffer.anchor_after(position);
17798
17799 // Get all document highlights (both read and write)
17800 let mut all_highlights = Vec::new();
17801
17802 if let Some((_, read_highlights)) = self
17803 .background_highlights
17804 .get(&HighlightKey::DocumentHighlightRead)
17805 {
17806 all_highlights.extend(read_highlights.iter());
17807 }
17808
17809 if let Some((_, write_highlights)) = self
17810 .background_highlights
17811 .get(&HighlightKey::DocumentHighlightWrite)
17812 {
17813 all_highlights.extend(write_highlights.iter());
17814 }
17815
17816 if all_highlights.is_empty() {
17817 return;
17818 }
17819
17820 // Sort highlights by position
17821 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17822
17823 let target_highlight = match direction {
17824 Direction::Next => {
17825 // Find the first highlight after the current position
17826 all_highlights
17827 .iter()
17828 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17829 }
17830 Direction::Prev => {
17831 // Find the last highlight before the current position
17832 all_highlights
17833 .iter()
17834 .rev()
17835 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17836 }
17837 };
17838
17839 if let Some(highlight) = target_highlight {
17840 let destination = highlight.start.to_point(buffer);
17841 let autoscroll = Autoscroll::center();
17842
17843 self.unfold_ranges(&[destination..destination], false, false, cx);
17844 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17845 s.select_ranges([destination..destination]);
17846 });
17847 }
17848 }
17849
17850 fn go_to_line<T: 'static>(
17851 &mut self,
17852 position: Anchor,
17853 highlight_color: Option<Hsla>,
17854 window: &mut Window,
17855 cx: &mut Context<Self>,
17856 ) {
17857 let snapshot = self.snapshot(window, cx).display_snapshot;
17858 let position = position.to_point(&snapshot.buffer_snapshot());
17859 let start = snapshot
17860 .buffer_snapshot()
17861 .clip_point(Point::new(position.row, 0), Bias::Left);
17862 let end = start + Point::new(1, 0);
17863 let start = snapshot.buffer_snapshot().anchor_before(start);
17864 let end = snapshot.buffer_snapshot().anchor_before(end);
17865
17866 self.highlight_rows::<T>(
17867 start..end,
17868 highlight_color
17869 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17870 Default::default(),
17871 cx,
17872 );
17873
17874 if self.buffer.read(cx).is_singleton() {
17875 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17876 }
17877 }
17878
17879 pub fn go_to_definition(
17880 &mut self,
17881 _: &GoToDefinition,
17882 window: &mut Window,
17883 cx: &mut Context<Self>,
17884 ) -> Task<Result<Navigated>> {
17885 let definition =
17886 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17887 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17888 cx.spawn_in(window, async move |editor, cx| {
17889 if definition.await? == Navigated::Yes {
17890 return Ok(Navigated::Yes);
17891 }
17892 match fallback_strategy {
17893 GoToDefinitionFallback::None => Ok(Navigated::No),
17894 GoToDefinitionFallback::FindAllReferences => {
17895 match editor.update_in(cx, |editor, window, cx| {
17896 editor.find_all_references(&FindAllReferences::default(), window, cx)
17897 })? {
17898 Some(references) => references.await,
17899 None => Ok(Navigated::No),
17900 }
17901 }
17902 }
17903 })
17904 }
17905
17906 pub fn go_to_declaration(
17907 &mut self,
17908 _: &GoToDeclaration,
17909 window: &mut Window,
17910 cx: &mut Context<Self>,
17911 ) -> Task<Result<Navigated>> {
17912 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17913 }
17914
17915 pub fn go_to_declaration_split(
17916 &mut self,
17917 _: &GoToDeclaration,
17918 window: &mut Window,
17919 cx: &mut Context<Self>,
17920 ) -> Task<Result<Navigated>> {
17921 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17922 }
17923
17924 pub fn go_to_implementation(
17925 &mut self,
17926 _: &GoToImplementation,
17927 window: &mut Window,
17928 cx: &mut Context<Self>,
17929 ) -> Task<Result<Navigated>> {
17930 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17931 }
17932
17933 pub fn go_to_implementation_split(
17934 &mut self,
17935 _: &GoToImplementationSplit,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) -> Task<Result<Navigated>> {
17939 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17940 }
17941
17942 pub fn go_to_type_definition(
17943 &mut self,
17944 _: &GoToTypeDefinition,
17945 window: &mut Window,
17946 cx: &mut Context<Self>,
17947 ) -> Task<Result<Navigated>> {
17948 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17949 }
17950
17951 pub fn go_to_definition_split(
17952 &mut self,
17953 _: &GoToDefinitionSplit,
17954 window: &mut Window,
17955 cx: &mut Context<Self>,
17956 ) -> Task<Result<Navigated>> {
17957 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17958 }
17959
17960 pub fn go_to_type_definition_split(
17961 &mut self,
17962 _: &GoToTypeDefinitionSplit,
17963 window: &mut Window,
17964 cx: &mut Context<Self>,
17965 ) -> Task<Result<Navigated>> {
17966 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17967 }
17968
17969 fn go_to_definition_of_kind(
17970 &mut self,
17971 kind: GotoDefinitionKind,
17972 split: bool,
17973 window: &mut Window,
17974 cx: &mut Context<Self>,
17975 ) -> Task<Result<Navigated>> {
17976 let Some(provider) = self.semantics_provider.clone() else {
17977 return Task::ready(Ok(Navigated::No));
17978 };
17979 let head = self
17980 .selections
17981 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17982 .head();
17983 let buffer = self.buffer.read(cx);
17984 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17985 return Task::ready(Ok(Navigated::No));
17986 };
17987 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17988 return Task::ready(Ok(Navigated::No));
17989 };
17990
17991 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17992
17993 cx.spawn_in(window, async move |editor, cx| {
17994 let Some(definitions) = definitions.await? else {
17995 return Ok(Navigated::No);
17996 };
17997 let navigated = editor
17998 .update_in(cx, |editor, window, cx| {
17999 editor.navigate_to_hover_links(
18000 Some(kind),
18001 definitions
18002 .into_iter()
18003 .filter(|location| {
18004 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18005 })
18006 .map(HoverLink::Text)
18007 .collect::<Vec<_>>(),
18008 nav_entry,
18009 split,
18010 window,
18011 cx,
18012 )
18013 })?
18014 .await?;
18015 anyhow::Ok(navigated)
18016 })
18017 }
18018
18019 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18020 let selection = self.selections.newest_anchor();
18021 let head = selection.head();
18022 let tail = selection.tail();
18023
18024 let Some((buffer, start_position)) =
18025 self.buffer.read(cx).text_anchor_for_position(head, cx)
18026 else {
18027 return;
18028 };
18029
18030 let end_position = if head != tail {
18031 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18032 return;
18033 };
18034 Some(pos)
18035 } else {
18036 None
18037 };
18038
18039 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18040 let url = if let Some(end_pos) = end_position {
18041 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18042 } else {
18043 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18044 };
18045
18046 if let Some(url) = url {
18047 cx.update(|window, cx| {
18048 if parse_zed_link(&url, cx).is_some() {
18049 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18050 } else {
18051 cx.open_url(&url);
18052 }
18053 })?;
18054 }
18055
18056 anyhow::Ok(())
18057 });
18058
18059 url_finder.detach();
18060 }
18061
18062 pub fn open_selected_filename(
18063 &mut self,
18064 _: &OpenSelectedFilename,
18065 window: &mut Window,
18066 cx: &mut Context<Self>,
18067 ) {
18068 let Some(workspace) = self.workspace() else {
18069 return;
18070 };
18071
18072 let position = self.selections.newest_anchor().head();
18073
18074 let Some((buffer, buffer_position)) =
18075 self.buffer.read(cx).text_anchor_for_position(position, cx)
18076 else {
18077 return;
18078 };
18079
18080 let project = self.project.clone();
18081
18082 cx.spawn_in(window, async move |_, cx| {
18083 let result = find_file(&buffer, project, buffer_position, cx).await;
18084
18085 if let Some((_, path)) = result {
18086 workspace
18087 .update_in(cx, |workspace, window, cx| {
18088 workspace.open_resolved_path(path, window, cx)
18089 })?
18090 .await?;
18091 }
18092 anyhow::Ok(())
18093 })
18094 .detach();
18095 }
18096
18097 pub(crate) fn navigate_to_hover_links(
18098 &mut self,
18099 kind: Option<GotoDefinitionKind>,
18100 definitions: Vec<HoverLink>,
18101 origin: Option<NavigationEntry>,
18102 split: bool,
18103 window: &mut Window,
18104 cx: &mut Context<Editor>,
18105 ) -> Task<Result<Navigated>> {
18106 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18107 let mut first_url_or_file = None;
18108 let definitions: Vec<_> = definitions
18109 .into_iter()
18110 .filter_map(|def| match def {
18111 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18112 HoverLink::InlayHint(lsp_location, server_id) => {
18113 let computation =
18114 self.compute_target_location(lsp_location, server_id, window, cx);
18115 Some(cx.background_spawn(computation))
18116 }
18117 HoverLink::Url(url) => {
18118 first_url_or_file = Some(Either::Left(url));
18119 None
18120 }
18121 HoverLink::File(path) => {
18122 first_url_or_file = Some(Either::Right(path));
18123 None
18124 }
18125 })
18126 .collect();
18127
18128 let workspace = self.workspace();
18129
18130 cx.spawn_in(window, async move |editor, cx| {
18131 let locations: Vec<Location> = future::join_all(definitions)
18132 .await
18133 .into_iter()
18134 .filter_map(|location| location.transpose())
18135 .collect::<Result<_>>()
18136 .context("location tasks")?;
18137 let mut locations = cx.update(|_, cx| {
18138 locations
18139 .into_iter()
18140 .map(|location| {
18141 let buffer = location.buffer.read(cx);
18142 (location.buffer, location.range.to_point(buffer))
18143 })
18144 .into_group_map()
18145 })?;
18146 let mut num_locations = 0;
18147 for ranges in locations.values_mut() {
18148 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18149 ranges.dedup();
18150 num_locations += ranges.len();
18151 }
18152
18153 if num_locations > 1 {
18154 let tab_kind = match kind {
18155 Some(GotoDefinitionKind::Implementation) => "Implementations",
18156 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18157 Some(GotoDefinitionKind::Declaration) => "Declarations",
18158 Some(GotoDefinitionKind::Type) => "Types",
18159 };
18160 let title = editor
18161 .update_in(cx, |_, _, cx| {
18162 let target = locations
18163 .iter()
18164 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18165 .map(|(buffer, location)| {
18166 buffer
18167 .read(cx)
18168 .text_for_range(location.clone())
18169 .collect::<String>()
18170 })
18171 .filter(|text| !text.contains('\n'))
18172 .unique()
18173 .take(3)
18174 .join(", ");
18175 if target.is_empty() {
18176 tab_kind.to_owned()
18177 } else {
18178 format!("{tab_kind} for {target}")
18179 }
18180 })
18181 .context("buffer title")?;
18182
18183 let Some(workspace) = workspace else {
18184 return Ok(Navigated::No);
18185 };
18186
18187 let opened = workspace
18188 .update_in(cx, |workspace, window, cx| {
18189 let allow_preview = PreviewTabsSettings::get_global(cx)
18190 .enable_preview_multibuffer_from_code_navigation;
18191 if let Some((target_editor, target_pane)) =
18192 Self::open_locations_in_multibuffer(
18193 workspace,
18194 locations,
18195 title,
18196 split,
18197 allow_preview,
18198 MultibufferSelectionMode::First,
18199 window,
18200 cx,
18201 )
18202 {
18203 // We create our own nav history instead of using
18204 // `target_editor.nav_history` because `nav_history`
18205 // seems to be populated asynchronously when an item
18206 // is added to a pane
18207 let mut nav_history = target_pane
18208 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18209 target_editor.update(cx, |editor, cx| {
18210 let nav_data = editor
18211 .navigation_data(editor.selections.newest_anchor().head(), cx);
18212 let target =
18213 Some(nav_history.navigation_entry(Some(
18214 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18215 )));
18216 nav_history.push_tag(origin, target);
18217 })
18218 }
18219 })
18220 .is_ok();
18221
18222 anyhow::Ok(Navigated::from_bool(opened))
18223 } else if num_locations == 0 {
18224 // If there is one url or file, open it directly
18225 match first_url_or_file {
18226 Some(Either::Left(url)) => {
18227 cx.update(|window, cx| {
18228 if parse_zed_link(&url, cx).is_some() {
18229 window
18230 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18231 } else {
18232 cx.open_url(&url);
18233 }
18234 })?;
18235 Ok(Navigated::Yes)
18236 }
18237 Some(Either::Right(path)) => {
18238 // TODO(andrew): respect preview tab settings
18239 // `enable_keep_preview_on_code_navigation` and
18240 // `enable_preview_file_from_code_navigation`
18241 let Some(workspace) = workspace else {
18242 return Ok(Navigated::No);
18243 };
18244 workspace
18245 .update_in(cx, |workspace, window, cx| {
18246 workspace.open_resolved_path(path, window, cx)
18247 })?
18248 .await?;
18249 Ok(Navigated::Yes)
18250 }
18251 None => Ok(Navigated::No),
18252 }
18253 } else {
18254 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18255 let target_range = target_ranges.first().unwrap().clone();
18256
18257 editor.update_in(cx, |editor, window, cx| {
18258 let range = editor.range_for_match(&target_range);
18259 let range = collapse_multiline_range(range);
18260
18261 if !split
18262 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18263 {
18264 editor.go_to_singleton_buffer_range(range, window, cx);
18265
18266 let target =
18267 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18268 if let Some(mut nav_history) = editor.nav_history.clone() {
18269 nav_history.push_tag(origin, target);
18270 }
18271 } else {
18272 let Some(workspace) = workspace else {
18273 return Navigated::No;
18274 };
18275 let pane = workspace.read(cx).active_pane().clone();
18276 window.defer(cx, move |window, cx| {
18277 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18278 workspace.update(cx, |workspace, cx| {
18279 let pane = if split {
18280 workspace.adjacent_pane(window, cx)
18281 } else {
18282 workspace.active_pane().clone()
18283 };
18284
18285 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18286 let keep_old_preview = preview_tabs_settings
18287 .enable_keep_preview_on_code_navigation;
18288 let allow_new_preview = preview_tabs_settings
18289 .enable_preview_file_from_code_navigation;
18290
18291 let editor = workspace.open_project_item(
18292 pane.clone(),
18293 target_buffer.clone(),
18294 true,
18295 true,
18296 keep_old_preview,
18297 allow_new_preview,
18298 window,
18299 cx,
18300 );
18301 (editor, pane)
18302 });
18303 // We create our own nav history instead of using
18304 // `target_editor.nav_history` because `nav_history`
18305 // seems to be populated asynchronously when an item
18306 // is added to a pane
18307 let mut nav_history = target_pane
18308 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18309 target_editor.update(cx, |target_editor, cx| {
18310 // When selecting a definition in a different buffer, disable the nav history
18311 // to avoid creating a history entry at the previous cursor location.
18312 pane.update(cx, |pane, _| pane.disable_history());
18313 target_editor.go_to_singleton_buffer_range(range, window, cx);
18314
18315 let nav_data = target_editor.navigation_data(
18316 target_editor.selections.newest_anchor().head(),
18317 cx,
18318 );
18319 let target =
18320 Some(nav_history.navigation_entry(Some(
18321 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18322 )));
18323 nav_history.push_tag(origin, target);
18324 pane.update(cx, |pane, _| pane.enable_history());
18325 });
18326 });
18327 }
18328 Navigated::Yes
18329 })
18330 }
18331 })
18332 }
18333
18334 fn compute_target_location(
18335 &self,
18336 lsp_location: lsp::Location,
18337 server_id: LanguageServerId,
18338 window: &mut Window,
18339 cx: &mut Context<Self>,
18340 ) -> Task<anyhow::Result<Option<Location>>> {
18341 let Some(project) = self.project.clone() else {
18342 return Task::ready(Ok(None));
18343 };
18344
18345 cx.spawn_in(window, async move |editor, cx| {
18346 let location_task = editor.update(cx, |_, cx| {
18347 project.update(cx, |project, cx| {
18348 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18349 })
18350 })?;
18351 let location = Some({
18352 let target_buffer_handle = location_task.await.context("open local buffer")?;
18353 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18354 let target_start = target_buffer
18355 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18356 let target_end = target_buffer
18357 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18358 target_buffer.anchor_after(target_start)
18359 ..target_buffer.anchor_before(target_end)
18360 });
18361 Location {
18362 buffer: target_buffer_handle,
18363 range,
18364 }
18365 });
18366 Ok(location)
18367 })
18368 }
18369
18370 fn go_to_next_reference(
18371 &mut self,
18372 _: &GoToNextReference,
18373 window: &mut Window,
18374 cx: &mut Context<Self>,
18375 ) {
18376 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18377 if let Some(task) = task {
18378 task.detach();
18379 };
18380 }
18381
18382 fn go_to_prev_reference(
18383 &mut self,
18384 _: &GoToPreviousReference,
18385 window: &mut Window,
18386 cx: &mut Context<Self>,
18387 ) {
18388 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18389 if let Some(task) = task {
18390 task.detach();
18391 };
18392 }
18393
18394 pub fn go_to_reference_before_or_after_position(
18395 &mut self,
18396 direction: Direction,
18397 count: usize,
18398 window: &mut Window,
18399 cx: &mut Context<Self>,
18400 ) -> Option<Task<Result<()>>> {
18401 let selection = self.selections.newest_anchor();
18402 let head = selection.head();
18403
18404 let multi_buffer = self.buffer.read(cx);
18405
18406 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18407 let workspace = self.workspace()?;
18408 let project = workspace.read(cx).project().clone();
18409 let references =
18410 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18411 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18412 let Some(locations) = references.await? else {
18413 return Ok(());
18414 };
18415
18416 if locations.is_empty() {
18417 // totally normal - the cursor may be on something which is not
18418 // a symbol (e.g. a keyword)
18419 log::info!("no references found under cursor");
18420 return Ok(());
18421 }
18422
18423 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18424
18425 let (locations, current_location_index) =
18426 multi_buffer.update(cx, |multi_buffer, cx| {
18427 let mut locations = locations
18428 .into_iter()
18429 .filter_map(|loc| {
18430 let start = multi_buffer.buffer_anchor_to_anchor(
18431 &loc.buffer,
18432 loc.range.start,
18433 cx,
18434 )?;
18435 let end = multi_buffer.buffer_anchor_to_anchor(
18436 &loc.buffer,
18437 loc.range.end,
18438 cx,
18439 )?;
18440 Some(start..end)
18441 })
18442 .collect::<Vec<_>>();
18443
18444 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18445 // There is an O(n) implementation, but given this list will be
18446 // small (usually <100 items), the extra O(log(n)) factor isn't
18447 // worth the (surprisingly large amount of) extra complexity.
18448 locations
18449 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18450
18451 let head_offset = head.to_offset(&multi_buffer_snapshot);
18452
18453 let current_location_index = locations.iter().position(|loc| {
18454 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18455 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18456 });
18457
18458 (locations, current_location_index)
18459 });
18460
18461 let Some(current_location_index) = current_location_index else {
18462 // This indicates something has gone wrong, because we already
18463 // handle the "no references" case above
18464 log::error!(
18465 "failed to find current reference under cursor. Total references: {}",
18466 locations.len()
18467 );
18468 return Ok(());
18469 };
18470
18471 let destination_location_index = match direction {
18472 Direction::Next => (current_location_index + count) % locations.len(),
18473 Direction::Prev => {
18474 (current_location_index + locations.len() - count % locations.len())
18475 % locations.len()
18476 }
18477 };
18478
18479 // TODO(cameron): is this needed?
18480 // the thinking is to avoid "jumping to the current location" (avoid
18481 // polluting "jumplist" in vim terms)
18482 if current_location_index == destination_location_index {
18483 return Ok(());
18484 }
18485
18486 let Range { start, end } = locations[destination_location_index];
18487
18488 editor.update_in(cx, |editor, window, cx| {
18489 let effects = SelectionEffects::default();
18490
18491 editor.unfold_ranges(&[start..end], false, false, cx);
18492 editor.change_selections(effects, window, cx, |s| {
18493 s.select_ranges([start..start]);
18494 });
18495 })?;
18496
18497 Ok(())
18498 }))
18499 }
18500
18501 pub fn find_all_references(
18502 &mut self,
18503 action: &FindAllReferences,
18504 window: &mut Window,
18505 cx: &mut Context<Self>,
18506 ) -> Option<Task<Result<Navigated>>> {
18507 let always_open_multibuffer = action.always_open_multibuffer;
18508 let selection = self.selections.newest_anchor();
18509 let multi_buffer = self.buffer.read(cx);
18510 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18511 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18512 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18513 let head = selection_offset.head();
18514
18515 let head_anchor = multi_buffer_snapshot.anchor_at(
18516 head,
18517 if head < selection_offset.tail() {
18518 Bias::Right
18519 } else {
18520 Bias::Left
18521 },
18522 );
18523
18524 match self
18525 .find_all_references_task_sources
18526 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18527 {
18528 Ok(_) => {
18529 log::info!(
18530 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18531 );
18532 return None;
18533 }
18534 Err(i) => {
18535 self.find_all_references_task_sources.insert(i, head_anchor);
18536 }
18537 }
18538
18539 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18540 let workspace = self.workspace()?;
18541 let project = workspace.read(cx).project().clone();
18542 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18543 Some(cx.spawn_in(window, async move |editor, cx| {
18544 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18545 if let Ok(i) = editor
18546 .find_all_references_task_sources
18547 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18548 {
18549 editor.find_all_references_task_sources.remove(i);
18550 }
18551 });
18552
18553 let Some(locations) = references.await? else {
18554 return anyhow::Ok(Navigated::No);
18555 };
18556 let mut locations = cx.update(|_, cx| {
18557 locations
18558 .into_iter()
18559 .map(|location| {
18560 let buffer = location.buffer.read(cx);
18561 (location.buffer, location.range.to_point(buffer))
18562 })
18563 // if special-casing the single-match case, remove ranges
18564 // that intersect current selection
18565 .filter(|(location_buffer, location)| {
18566 if always_open_multibuffer || &buffer != location_buffer {
18567 return true;
18568 }
18569
18570 !location.contains_inclusive(&selection_point.range())
18571 })
18572 .into_group_map()
18573 })?;
18574 if locations.is_empty() {
18575 return anyhow::Ok(Navigated::No);
18576 }
18577 for ranges in locations.values_mut() {
18578 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18579 ranges.dedup();
18580 }
18581 let mut num_locations = 0;
18582 for ranges in locations.values_mut() {
18583 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18584 ranges.dedup();
18585 num_locations += ranges.len();
18586 }
18587
18588 if num_locations == 1 && !always_open_multibuffer {
18589 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18590 let target_range = target_ranges.first().unwrap().clone();
18591
18592 return editor.update_in(cx, |editor, window, cx| {
18593 let range = target_range.to_point(target_buffer.read(cx));
18594 let range = editor.range_for_match(&range);
18595 let range = range.start..range.start;
18596
18597 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18598 editor.go_to_singleton_buffer_range(range, window, cx);
18599 } else {
18600 let pane = workspace.read(cx).active_pane().clone();
18601 window.defer(cx, move |window, cx| {
18602 let target_editor: Entity<Self> =
18603 workspace.update(cx, |workspace, cx| {
18604 let pane = workspace.active_pane().clone();
18605
18606 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18607 let keep_old_preview = preview_tabs_settings
18608 .enable_keep_preview_on_code_navigation;
18609 let allow_new_preview = preview_tabs_settings
18610 .enable_preview_file_from_code_navigation;
18611
18612 workspace.open_project_item(
18613 pane,
18614 target_buffer.clone(),
18615 true,
18616 true,
18617 keep_old_preview,
18618 allow_new_preview,
18619 window,
18620 cx,
18621 )
18622 });
18623 target_editor.update(cx, |target_editor, cx| {
18624 // When selecting a definition in a different buffer, disable the nav history
18625 // to avoid creating a history entry at the previous cursor location.
18626 pane.update(cx, |pane, _| pane.disable_history());
18627 target_editor.go_to_singleton_buffer_range(range, window, cx);
18628 pane.update(cx, |pane, _| pane.enable_history());
18629 });
18630 });
18631 }
18632 Navigated::No
18633 });
18634 }
18635
18636 workspace.update_in(cx, |workspace, window, cx| {
18637 let target = locations
18638 .iter()
18639 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18640 .map(|(buffer, location)| {
18641 buffer
18642 .read(cx)
18643 .text_for_range(location.clone())
18644 .collect::<String>()
18645 })
18646 .filter(|text| !text.contains('\n'))
18647 .unique()
18648 .take(3)
18649 .join(", ");
18650 let title = if target.is_empty() {
18651 "References".to_owned()
18652 } else {
18653 format!("References to {target}")
18654 };
18655 let allow_preview = PreviewTabsSettings::get_global(cx)
18656 .enable_preview_multibuffer_from_code_navigation;
18657 Self::open_locations_in_multibuffer(
18658 workspace,
18659 locations,
18660 title,
18661 false,
18662 allow_preview,
18663 MultibufferSelectionMode::First,
18664 window,
18665 cx,
18666 );
18667 Navigated::Yes
18668 })
18669 }))
18670 }
18671
18672 /// Opens a multibuffer with the given project locations in it.
18673 pub fn open_locations_in_multibuffer(
18674 workspace: &mut Workspace,
18675 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18676 title: String,
18677 split: bool,
18678 allow_preview: bool,
18679 multibuffer_selection_mode: MultibufferSelectionMode,
18680 window: &mut Window,
18681 cx: &mut Context<Workspace>,
18682 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18683 if locations.is_empty() {
18684 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18685 return None;
18686 }
18687
18688 let capability = workspace.project().read(cx).capability();
18689 let mut ranges = <Vec<Range<Anchor>>>::new();
18690
18691 // a key to find existing multibuffer editors with the same set of locations
18692 // to prevent us from opening more and more multibuffer tabs for searches and the like
18693 let mut key = (title.clone(), vec![]);
18694 let excerpt_buffer = cx.new(|cx| {
18695 let key = &mut key.1;
18696 let mut multibuffer = MultiBuffer::new(capability);
18697 for (buffer, mut ranges_for_buffer) in locations {
18698 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18699 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18700 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18701 PathKey::for_buffer(&buffer, cx),
18702 buffer.clone(),
18703 ranges_for_buffer,
18704 multibuffer_context_lines(cx),
18705 cx,
18706 );
18707 ranges.extend(new_ranges)
18708 }
18709
18710 multibuffer.with_title(title)
18711 });
18712 let existing = workspace.active_pane().update(cx, |pane, cx| {
18713 pane.items()
18714 .filter_map(|item| item.downcast::<Editor>())
18715 .find(|editor| {
18716 editor
18717 .read(cx)
18718 .lookup_key
18719 .as_ref()
18720 .and_then(|it| {
18721 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18722 })
18723 .is_some_and(|it| *it == key)
18724 })
18725 });
18726 let was_existing = existing.is_some();
18727 let editor = existing.unwrap_or_else(|| {
18728 cx.new(|cx| {
18729 let mut editor = Editor::for_multibuffer(
18730 excerpt_buffer,
18731 Some(workspace.project().clone()),
18732 window,
18733 cx,
18734 );
18735 editor.lookup_key = Some(Box::new(key));
18736 editor
18737 })
18738 });
18739 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18740 MultibufferSelectionMode::First => {
18741 if let Some(first_range) = ranges.first() {
18742 editor.change_selections(
18743 SelectionEffects::no_scroll(),
18744 window,
18745 cx,
18746 |selections| {
18747 selections.clear_disjoint();
18748 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18749 },
18750 );
18751 }
18752 editor.highlight_background(
18753 HighlightKey::Editor,
18754 &ranges,
18755 |_, theme| theme.colors().editor_highlighted_line_background,
18756 cx,
18757 );
18758 }
18759 MultibufferSelectionMode::All => {
18760 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18761 selections.clear_disjoint();
18762 selections.select_anchor_ranges(ranges);
18763 });
18764 }
18765 });
18766
18767 let item = Box::new(editor.clone());
18768
18769 let pane = if split {
18770 workspace.adjacent_pane(window, cx)
18771 } else {
18772 workspace.active_pane().clone()
18773 };
18774 let activate_pane = split;
18775
18776 let mut destination_index = None;
18777 pane.update(cx, |pane, cx| {
18778 if allow_preview && !was_existing {
18779 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18780 }
18781 if was_existing && !allow_preview {
18782 pane.unpreview_item_if_preview(item.item_id());
18783 }
18784 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18785 });
18786
18787 Some((editor, pane))
18788 }
18789
18790 pub fn rename(
18791 &mut self,
18792 _: &Rename,
18793 window: &mut Window,
18794 cx: &mut Context<Self>,
18795 ) -> Option<Task<Result<()>>> {
18796 use language::ToOffset as _;
18797
18798 let provider = self.semantics_provider.clone()?;
18799 let selection = self.selections.newest_anchor().clone();
18800 let (cursor_buffer, cursor_buffer_position) = self
18801 .buffer
18802 .read(cx)
18803 .text_anchor_for_position(selection.head(), cx)?;
18804 let (tail_buffer, cursor_buffer_position_end) = self
18805 .buffer
18806 .read(cx)
18807 .text_anchor_for_position(selection.tail(), cx)?;
18808 if tail_buffer != cursor_buffer {
18809 return None;
18810 }
18811
18812 let snapshot = cursor_buffer.read(cx).snapshot();
18813 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18814 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18815 let prepare_rename = provider
18816 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18817 .unwrap_or_else(|| Task::ready(Ok(None)));
18818 drop(snapshot);
18819
18820 Some(cx.spawn_in(window, async move |this, cx| {
18821 let rename_range = if let Some(range) = prepare_rename.await? {
18822 Some(range)
18823 } else {
18824 this.update(cx, |this, cx| {
18825 let buffer = this.buffer.read(cx).snapshot(cx);
18826 let mut buffer_highlights = this
18827 .document_highlights_for_position(selection.head(), &buffer)
18828 .filter(|highlight| {
18829 highlight.start.excerpt_id == selection.head().excerpt_id
18830 && highlight.end.excerpt_id == selection.head().excerpt_id
18831 });
18832 buffer_highlights
18833 .next()
18834 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18835 })?
18836 };
18837 if let Some(rename_range) = rename_range {
18838 this.update_in(cx, |this, window, cx| {
18839 let snapshot = cursor_buffer.read(cx).snapshot();
18840 let rename_buffer_range = rename_range.to_offset(&snapshot);
18841 let cursor_offset_in_rename_range =
18842 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18843 let cursor_offset_in_rename_range_end =
18844 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18845
18846 this.take_rename(false, window, cx);
18847 let buffer = this.buffer.read(cx).read(cx);
18848 let cursor_offset = selection.head().to_offset(&buffer);
18849 let rename_start =
18850 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18851 let rename_end = rename_start + rename_buffer_range.len();
18852 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18853 let mut old_highlight_id = None;
18854 let old_name: Arc<str> = buffer
18855 .chunks(rename_start..rename_end, true)
18856 .map(|chunk| {
18857 if old_highlight_id.is_none() {
18858 old_highlight_id = chunk.syntax_highlight_id;
18859 }
18860 chunk.text
18861 })
18862 .collect::<String>()
18863 .into();
18864
18865 drop(buffer);
18866
18867 // Position the selection in the rename editor so that it matches the current selection.
18868 this.show_local_selections = false;
18869 let rename_editor = cx.new(|cx| {
18870 let mut editor = Editor::single_line(window, cx);
18871 editor.buffer.update(cx, |buffer, cx| {
18872 buffer.edit(
18873 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18874 None,
18875 cx,
18876 )
18877 });
18878 let cursor_offset_in_rename_range =
18879 MultiBufferOffset(cursor_offset_in_rename_range);
18880 let cursor_offset_in_rename_range_end =
18881 MultiBufferOffset(cursor_offset_in_rename_range_end);
18882 let rename_selection_range = match cursor_offset_in_rename_range
18883 .cmp(&cursor_offset_in_rename_range_end)
18884 {
18885 Ordering::Equal => {
18886 editor.select_all(&SelectAll, window, cx);
18887 return editor;
18888 }
18889 Ordering::Less => {
18890 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18891 }
18892 Ordering::Greater => {
18893 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18894 }
18895 };
18896 if rename_selection_range.end.0 > old_name.len() {
18897 editor.select_all(&SelectAll, window, cx);
18898 } else {
18899 editor.change_selections(Default::default(), window, cx, |s| {
18900 s.select_ranges([rename_selection_range]);
18901 });
18902 }
18903 editor
18904 });
18905 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18906 if e == &EditorEvent::Focused {
18907 cx.emit(EditorEvent::FocusedIn)
18908 }
18909 })
18910 .detach();
18911
18912 let write_highlights =
18913 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
18914 let read_highlights =
18915 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
18916 let ranges = write_highlights
18917 .iter()
18918 .flat_map(|(_, ranges)| ranges.iter())
18919 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18920 .cloned()
18921 .collect();
18922
18923 this.highlight_text(
18924 HighlightKey::Rename,
18925 ranges,
18926 HighlightStyle {
18927 fade_out: Some(0.6),
18928 ..Default::default()
18929 },
18930 cx,
18931 );
18932 let rename_focus_handle = rename_editor.focus_handle(cx);
18933 window.focus(&rename_focus_handle, cx);
18934 let block_id = this.insert_blocks(
18935 [BlockProperties {
18936 style: BlockStyle::Flex,
18937 placement: BlockPlacement::Below(range.start),
18938 height: Some(1),
18939 render: Arc::new({
18940 let rename_editor = rename_editor.clone();
18941 move |cx: &mut BlockContext| {
18942 let mut text_style = cx.editor_style.text.clone();
18943 if let Some(highlight_style) = old_highlight_id
18944 .and_then(|h| h.style(&cx.editor_style.syntax))
18945 {
18946 text_style = text_style.highlight(highlight_style);
18947 }
18948 div()
18949 .block_mouse_except_scroll()
18950 .pl(cx.anchor_x)
18951 .child(EditorElement::new(
18952 &rename_editor,
18953 EditorStyle {
18954 background: cx.theme().system().transparent,
18955 local_player: cx.editor_style.local_player,
18956 text: text_style,
18957 scrollbar_width: cx.editor_style.scrollbar_width,
18958 syntax: cx.editor_style.syntax.clone(),
18959 status: cx.editor_style.status.clone(),
18960 inlay_hints_style: HighlightStyle {
18961 font_weight: Some(FontWeight::BOLD),
18962 ..make_inlay_hints_style(cx.app)
18963 },
18964 edit_prediction_styles: make_suggestion_styles(
18965 cx.app,
18966 ),
18967 ..EditorStyle::default()
18968 },
18969 ))
18970 .into_any_element()
18971 }
18972 }),
18973 priority: 0,
18974 }],
18975 Some(Autoscroll::fit()),
18976 cx,
18977 )[0];
18978 this.pending_rename = Some(RenameState {
18979 range,
18980 old_name,
18981 editor: rename_editor,
18982 block_id,
18983 });
18984 })?;
18985 }
18986
18987 Ok(())
18988 }))
18989 }
18990
18991 pub fn confirm_rename(
18992 &mut self,
18993 _: &ConfirmRename,
18994 window: &mut Window,
18995 cx: &mut Context<Self>,
18996 ) -> Option<Task<Result<()>>> {
18997 let rename = self.take_rename(false, window, cx)?;
18998 let workspace = self.workspace()?.downgrade();
18999 let (buffer, start) = self
19000 .buffer
19001 .read(cx)
19002 .text_anchor_for_position(rename.range.start, cx)?;
19003 let (end_buffer, _) = self
19004 .buffer
19005 .read(cx)
19006 .text_anchor_for_position(rename.range.end, cx)?;
19007 if buffer != end_buffer {
19008 return None;
19009 }
19010
19011 let old_name = rename.old_name;
19012 let new_name = rename.editor.read(cx).text(cx);
19013
19014 let rename = self.semantics_provider.as_ref()?.perform_rename(
19015 &buffer,
19016 start,
19017 new_name.clone(),
19018 cx,
19019 )?;
19020
19021 Some(cx.spawn_in(window, async move |editor, cx| {
19022 let project_transaction = rename.await?;
19023 Self::open_project_transaction(
19024 &editor,
19025 workspace,
19026 project_transaction,
19027 format!("Rename: {} → {}", old_name, new_name),
19028 cx,
19029 )
19030 .await?;
19031
19032 editor.update(cx, |editor, cx| {
19033 editor.refresh_document_highlights(cx);
19034 })?;
19035 Ok(())
19036 }))
19037 }
19038
19039 fn take_rename(
19040 &mut self,
19041 moving_cursor: bool,
19042 window: &mut Window,
19043 cx: &mut Context<Self>,
19044 ) -> Option<RenameState> {
19045 let rename = self.pending_rename.take()?;
19046 if rename.editor.focus_handle(cx).is_focused(window) {
19047 window.focus(&self.focus_handle, cx);
19048 }
19049
19050 self.remove_blocks(
19051 [rename.block_id].into_iter().collect(),
19052 Some(Autoscroll::fit()),
19053 cx,
19054 );
19055 self.clear_highlights(HighlightKey::Rename, cx);
19056 self.show_local_selections = true;
19057
19058 if moving_cursor {
19059 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19060 editor
19061 .selections
19062 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19063 .head()
19064 });
19065
19066 // Update the selection to match the position of the selection inside
19067 // the rename editor.
19068 let snapshot = self.buffer.read(cx).read(cx);
19069 let rename_range = rename.range.to_offset(&snapshot);
19070 let cursor_in_editor = snapshot
19071 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19072 .min(rename_range.end);
19073 drop(snapshot);
19074
19075 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19076 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19077 });
19078 } else {
19079 self.refresh_document_highlights(cx);
19080 }
19081
19082 Some(rename)
19083 }
19084
19085 pub fn pending_rename(&self) -> Option<&RenameState> {
19086 self.pending_rename.as_ref()
19087 }
19088
19089 fn format(
19090 &mut self,
19091 _: &Format,
19092 window: &mut Window,
19093 cx: &mut Context<Self>,
19094 ) -> Option<Task<Result<()>>> {
19095 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19096
19097 let project = match &self.project {
19098 Some(project) => project.clone(),
19099 None => return None,
19100 };
19101
19102 Some(self.perform_format(
19103 project,
19104 FormatTrigger::Manual,
19105 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19106 window,
19107 cx,
19108 ))
19109 }
19110
19111 fn format_selections(
19112 &mut self,
19113 _: &FormatSelections,
19114 window: &mut Window,
19115 cx: &mut Context<Self>,
19116 ) -> Option<Task<Result<()>>> {
19117 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19118
19119 let project = match &self.project {
19120 Some(project) => project.clone(),
19121 None => return None,
19122 };
19123
19124 let ranges = self
19125 .selections
19126 .all_adjusted(&self.display_snapshot(cx))
19127 .into_iter()
19128 .map(|selection| selection.range())
19129 .collect_vec();
19130
19131 Some(self.perform_format(
19132 project,
19133 FormatTrigger::Manual,
19134 FormatTarget::Ranges(ranges),
19135 window,
19136 cx,
19137 ))
19138 }
19139
19140 fn perform_format(
19141 &mut self,
19142 project: Entity<Project>,
19143 trigger: FormatTrigger,
19144 target: FormatTarget,
19145 window: &mut Window,
19146 cx: &mut Context<Self>,
19147 ) -> Task<Result<()>> {
19148 let buffer = self.buffer.clone();
19149 let (buffers, target) = match target {
19150 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19151 FormatTarget::Ranges(selection_ranges) => {
19152 let multi_buffer = buffer.read(cx);
19153 let snapshot = multi_buffer.read(cx);
19154 let mut buffers = HashSet::default();
19155 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19156 BTreeMap::new();
19157 for selection_range in selection_ranges {
19158 for (buffer, buffer_range, _) in
19159 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19160 {
19161 let buffer_id = buffer.remote_id();
19162 let start = buffer.anchor_before(buffer_range.start);
19163 let end = buffer.anchor_after(buffer_range.end);
19164 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19165 buffer_id_to_ranges
19166 .entry(buffer_id)
19167 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19168 .or_insert_with(|| vec![start..end]);
19169 }
19170 }
19171 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19172 }
19173 };
19174
19175 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19176 let selections_prev = transaction_id_prev
19177 .and_then(|transaction_id_prev| {
19178 // default to selections as they were after the last edit, if we have them,
19179 // instead of how they are now.
19180 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19181 // will take you back to where you made the last edit, instead of staying where you scrolled
19182 self.selection_history
19183 .transaction(transaction_id_prev)
19184 .map(|t| t.0.clone())
19185 })
19186 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19187
19188 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19189 let format = project.update(cx, |project, cx| {
19190 project.format(buffers, target, true, trigger, cx)
19191 });
19192
19193 cx.spawn_in(window, async move |editor, cx| {
19194 let transaction = futures::select_biased! {
19195 transaction = format.log_err().fuse() => transaction,
19196 () = timeout => {
19197 log::warn!("timed out waiting for formatting");
19198 None
19199 }
19200 };
19201
19202 buffer.update(cx, |buffer, cx| {
19203 if let Some(transaction) = transaction
19204 && !buffer.is_singleton()
19205 {
19206 buffer.push_transaction(&transaction.0, cx);
19207 }
19208 cx.notify();
19209 });
19210
19211 if let Some(transaction_id_now) =
19212 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19213 {
19214 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19215 if has_new_transaction {
19216 editor
19217 .update(cx, |editor, _| {
19218 editor
19219 .selection_history
19220 .insert_transaction(transaction_id_now, selections_prev);
19221 })
19222 .ok();
19223 }
19224 }
19225
19226 Ok(())
19227 })
19228 }
19229
19230 fn organize_imports(
19231 &mut self,
19232 _: &OrganizeImports,
19233 window: &mut Window,
19234 cx: &mut Context<Self>,
19235 ) -> Option<Task<Result<()>>> {
19236 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19237 let project = match &self.project {
19238 Some(project) => project.clone(),
19239 None => return None,
19240 };
19241 Some(self.perform_code_action_kind(
19242 project,
19243 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19244 window,
19245 cx,
19246 ))
19247 }
19248
19249 fn perform_code_action_kind(
19250 &mut self,
19251 project: Entity<Project>,
19252 kind: CodeActionKind,
19253 window: &mut Window,
19254 cx: &mut Context<Self>,
19255 ) -> Task<Result<()>> {
19256 let buffer = self.buffer.clone();
19257 let buffers = buffer.read(cx).all_buffers();
19258 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19259 let apply_action = project.update(cx, |project, cx| {
19260 project.apply_code_action_kind(buffers, kind, true, cx)
19261 });
19262 cx.spawn_in(window, async move |_, cx| {
19263 let transaction = futures::select_biased! {
19264 () = timeout => {
19265 log::warn!("timed out waiting for executing code action");
19266 None
19267 }
19268 transaction = apply_action.log_err().fuse() => transaction,
19269 };
19270 buffer.update(cx, |buffer, cx| {
19271 // check if we need this
19272 if let Some(transaction) = transaction
19273 && !buffer.is_singleton()
19274 {
19275 buffer.push_transaction(&transaction.0, cx);
19276 }
19277 cx.notify();
19278 });
19279 Ok(())
19280 })
19281 }
19282
19283 pub fn restart_language_server(
19284 &mut self,
19285 _: &RestartLanguageServer,
19286 _: &mut Window,
19287 cx: &mut Context<Self>,
19288 ) {
19289 if let Some(project) = self.project.clone() {
19290 self.buffer.update(cx, |multi_buffer, cx| {
19291 project.update(cx, |project, cx| {
19292 project.restart_language_servers_for_buffers(
19293 multi_buffer.all_buffers().into_iter().collect(),
19294 HashSet::default(),
19295 cx,
19296 );
19297 });
19298 })
19299 }
19300 }
19301
19302 pub fn stop_language_server(
19303 &mut self,
19304 _: &StopLanguageServer,
19305 _: &mut Window,
19306 cx: &mut Context<Self>,
19307 ) {
19308 if let Some(project) = self.project.clone() {
19309 self.buffer.update(cx, |multi_buffer, cx| {
19310 project.update(cx, |project, cx| {
19311 project.stop_language_servers_for_buffers(
19312 multi_buffer.all_buffers().into_iter().collect(),
19313 HashSet::default(),
19314 cx,
19315 );
19316 });
19317 });
19318 }
19319 }
19320
19321 fn cancel_language_server_work(
19322 workspace: &mut Workspace,
19323 _: &actions::CancelLanguageServerWork,
19324 _: &mut Window,
19325 cx: &mut Context<Workspace>,
19326 ) {
19327 let project = workspace.project();
19328 let buffers = workspace
19329 .active_item(cx)
19330 .and_then(|item| item.act_as::<Editor>(cx))
19331 .map_or(HashSet::default(), |editor| {
19332 editor.read(cx).buffer.read(cx).all_buffers()
19333 });
19334 project.update(cx, |project, cx| {
19335 project.cancel_language_server_work_for_buffers(buffers, cx);
19336 });
19337 }
19338
19339 fn show_character_palette(
19340 &mut self,
19341 _: &ShowCharacterPalette,
19342 window: &mut Window,
19343 _: &mut Context<Self>,
19344 ) {
19345 window.show_character_palette();
19346 }
19347
19348 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19349 if !self.diagnostics_enabled() {
19350 return;
19351 }
19352
19353 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19354 let buffer = self.buffer.read(cx).snapshot(cx);
19355 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19356 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19357 let is_valid = buffer
19358 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19359 .any(|entry| {
19360 entry.diagnostic.is_primary
19361 && !entry.range.is_empty()
19362 && entry.range.start == primary_range_start
19363 && entry.diagnostic.message == active_diagnostics.active_message
19364 });
19365
19366 if !is_valid {
19367 self.dismiss_diagnostics(cx);
19368 }
19369 }
19370 }
19371
19372 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19373 match &self.active_diagnostics {
19374 ActiveDiagnostic::Group(group) => Some(group),
19375 _ => None,
19376 }
19377 }
19378
19379 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19380 if !self.diagnostics_enabled() {
19381 return;
19382 }
19383 self.dismiss_diagnostics(cx);
19384 self.active_diagnostics = ActiveDiagnostic::All;
19385 }
19386
19387 fn activate_diagnostics(
19388 &mut self,
19389 buffer_id: BufferId,
19390 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19391 window: &mut Window,
19392 cx: &mut Context<Self>,
19393 ) {
19394 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19395 return;
19396 }
19397 self.dismiss_diagnostics(cx);
19398 let snapshot = self.snapshot(window, cx);
19399 let buffer = self.buffer.read(cx).snapshot(cx);
19400 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19401 return;
19402 };
19403
19404 let diagnostic_group = buffer
19405 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19406 .collect::<Vec<_>>();
19407
19408 let language_registry = self
19409 .project()
19410 .map(|project| project.read(cx).languages().clone());
19411
19412 let blocks = renderer.render_group(
19413 diagnostic_group,
19414 buffer_id,
19415 snapshot,
19416 cx.weak_entity(),
19417 language_registry,
19418 cx,
19419 );
19420
19421 let blocks = self.display_map.update(cx, |display_map, cx| {
19422 display_map.insert_blocks(blocks, cx).into_iter().collect()
19423 });
19424 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19425 active_range: buffer.anchor_before(diagnostic.range.start)
19426 ..buffer.anchor_after(diagnostic.range.end),
19427 active_message: diagnostic.diagnostic.message.clone(),
19428 group_id: diagnostic.diagnostic.group_id,
19429 blocks,
19430 });
19431 cx.notify();
19432 }
19433
19434 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19435 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19436 return;
19437 };
19438
19439 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19440 if let ActiveDiagnostic::Group(group) = prev {
19441 self.display_map.update(cx, |display_map, cx| {
19442 display_map.remove_blocks(group.blocks, cx);
19443 });
19444 cx.notify();
19445 }
19446 }
19447
19448 /// Disable inline diagnostics rendering for this editor.
19449 pub fn disable_inline_diagnostics(&mut self) {
19450 self.inline_diagnostics_enabled = false;
19451 self.inline_diagnostics_update = Task::ready(());
19452 self.inline_diagnostics.clear();
19453 }
19454
19455 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19456 self.diagnostics_enabled = false;
19457 self.dismiss_diagnostics(cx);
19458 self.inline_diagnostics_update = Task::ready(());
19459 self.inline_diagnostics.clear();
19460 }
19461
19462 pub fn disable_word_completions(&mut self) {
19463 self.word_completions_enabled = false;
19464 }
19465
19466 pub fn diagnostics_enabled(&self) -> bool {
19467 self.diagnostics_enabled && self.mode.is_full()
19468 }
19469
19470 pub fn inline_diagnostics_enabled(&self) -> bool {
19471 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19472 }
19473
19474 pub fn show_inline_diagnostics(&self) -> bool {
19475 self.show_inline_diagnostics
19476 }
19477
19478 pub fn toggle_inline_diagnostics(
19479 &mut self,
19480 _: &ToggleInlineDiagnostics,
19481 window: &mut Window,
19482 cx: &mut Context<Editor>,
19483 ) {
19484 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19485 self.refresh_inline_diagnostics(false, window, cx);
19486 }
19487
19488 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19489 self.diagnostics_max_severity = severity;
19490 self.display_map.update(cx, |display_map, _| {
19491 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19492 });
19493 }
19494
19495 pub fn toggle_diagnostics(
19496 &mut self,
19497 _: &ToggleDiagnostics,
19498 window: &mut Window,
19499 cx: &mut Context<Editor>,
19500 ) {
19501 if !self.diagnostics_enabled() {
19502 return;
19503 }
19504
19505 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19506 EditorSettings::get_global(cx)
19507 .diagnostics_max_severity
19508 .filter(|severity| severity != &DiagnosticSeverity::Off)
19509 .unwrap_or(DiagnosticSeverity::Hint)
19510 } else {
19511 DiagnosticSeverity::Off
19512 };
19513 self.set_max_diagnostics_severity(new_severity, cx);
19514 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19515 self.active_diagnostics = ActiveDiagnostic::None;
19516 self.inline_diagnostics_update = Task::ready(());
19517 self.inline_diagnostics.clear();
19518 } else {
19519 self.refresh_inline_diagnostics(false, window, cx);
19520 }
19521
19522 cx.notify();
19523 }
19524
19525 pub fn toggle_minimap(
19526 &mut self,
19527 _: &ToggleMinimap,
19528 window: &mut Window,
19529 cx: &mut Context<Editor>,
19530 ) {
19531 if self.supports_minimap(cx) {
19532 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19533 }
19534 }
19535
19536 fn refresh_inline_diagnostics(
19537 &mut self,
19538 debounce: bool,
19539 window: &mut Window,
19540 cx: &mut Context<Self>,
19541 ) {
19542 let max_severity = ProjectSettings::get_global(cx)
19543 .diagnostics
19544 .inline
19545 .max_severity
19546 .unwrap_or(self.diagnostics_max_severity);
19547
19548 if !self.inline_diagnostics_enabled()
19549 || !self.diagnostics_enabled()
19550 || !self.show_inline_diagnostics
19551 || max_severity == DiagnosticSeverity::Off
19552 {
19553 self.inline_diagnostics_update = Task::ready(());
19554 self.inline_diagnostics.clear();
19555 return;
19556 }
19557
19558 let debounce_ms = ProjectSettings::get_global(cx)
19559 .diagnostics
19560 .inline
19561 .update_debounce_ms;
19562 let debounce = if debounce && debounce_ms > 0 {
19563 Some(Duration::from_millis(debounce_ms))
19564 } else {
19565 None
19566 };
19567 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19568 if let Some(debounce) = debounce {
19569 cx.background_executor().timer(debounce).await;
19570 }
19571 let Some(snapshot) = editor.upgrade().map(|editor| {
19572 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19573 }) else {
19574 return;
19575 };
19576
19577 let new_inline_diagnostics = cx
19578 .background_spawn(async move {
19579 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19580 for diagnostic_entry in
19581 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19582 {
19583 let message = diagnostic_entry
19584 .diagnostic
19585 .message
19586 .split_once('\n')
19587 .map(|(line, _)| line)
19588 .map(SharedString::new)
19589 .unwrap_or_else(|| {
19590 SharedString::new(&*diagnostic_entry.diagnostic.message)
19591 });
19592 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19593 let (Ok(i) | Err(i)) = inline_diagnostics
19594 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19595 inline_diagnostics.insert(
19596 i,
19597 (
19598 start_anchor,
19599 InlineDiagnostic {
19600 message,
19601 group_id: diagnostic_entry.diagnostic.group_id,
19602 start: diagnostic_entry.range.start.to_point(&snapshot),
19603 is_primary: diagnostic_entry.diagnostic.is_primary,
19604 severity: diagnostic_entry.diagnostic.severity,
19605 },
19606 ),
19607 );
19608 }
19609 inline_diagnostics
19610 })
19611 .await;
19612
19613 editor
19614 .update(cx, |editor, cx| {
19615 editor.inline_diagnostics = new_inline_diagnostics;
19616 cx.notify();
19617 })
19618 .ok();
19619 });
19620 }
19621
19622 fn pull_diagnostics(
19623 &mut self,
19624 buffer_id: BufferId,
19625 _window: &Window,
19626 cx: &mut Context<Self>,
19627 ) -> Option<()> {
19628 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19629 // skip any LSP updates for it.
19630
19631 if self.active_diagnostics == ActiveDiagnostic::All
19632 || !self.mode().is_full()
19633 || !self.diagnostics_enabled()
19634 {
19635 return None;
19636 }
19637 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19638 .diagnostics
19639 .lsp_pull_diagnostics;
19640 if !pull_diagnostics_settings.enabled {
19641 return None;
19642 }
19643 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19644 let project = self.project()?.downgrade();
19645 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19646
19647 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19648 cx.background_executor().timer(debounce).await;
19649 if let Ok(task) = project.update(cx, |project, cx| {
19650 project.lsp_store().update(cx, |lsp_store, cx| {
19651 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19652 })
19653 }) {
19654 task.await.log_err();
19655 }
19656 project
19657 .update(cx, |project, cx| {
19658 project.lsp_store().update(cx, |lsp_store, cx| {
19659 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19660 })
19661 })
19662 .log_err();
19663 });
19664
19665 Some(())
19666 }
19667
19668 pub fn set_selections_from_remote(
19669 &mut self,
19670 selections: Vec<Selection<Anchor>>,
19671 pending_selection: Option<Selection<Anchor>>,
19672 window: &mut Window,
19673 cx: &mut Context<Self>,
19674 ) {
19675 let old_cursor_position = self.selections.newest_anchor().head();
19676 self.selections
19677 .change_with(&self.display_snapshot(cx), |s| {
19678 s.select_anchors(selections);
19679 if let Some(pending_selection) = pending_selection {
19680 s.set_pending(pending_selection, SelectMode::Character);
19681 } else {
19682 s.clear_pending();
19683 }
19684 });
19685 self.selections_did_change(
19686 false,
19687 &old_cursor_position,
19688 SelectionEffects::default(),
19689 window,
19690 cx,
19691 );
19692 }
19693
19694 pub fn transact(
19695 &mut self,
19696 window: &mut Window,
19697 cx: &mut Context<Self>,
19698 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19699 ) -> Option<TransactionId> {
19700 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19701 this.start_transaction_at(Instant::now(), window, cx);
19702 update(this, window, cx);
19703 this.end_transaction_at(Instant::now(), cx)
19704 })
19705 }
19706
19707 pub fn start_transaction_at(
19708 &mut self,
19709 now: Instant,
19710 window: &mut Window,
19711 cx: &mut Context<Self>,
19712 ) -> Option<TransactionId> {
19713 self.end_selection(window, cx);
19714 if let Some(tx_id) = self
19715 .buffer
19716 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19717 {
19718 self.selection_history
19719 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19720 cx.emit(EditorEvent::TransactionBegun {
19721 transaction_id: tx_id,
19722 });
19723 Some(tx_id)
19724 } else {
19725 None
19726 }
19727 }
19728
19729 pub fn end_transaction_at(
19730 &mut self,
19731 now: Instant,
19732 cx: &mut Context<Self>,
19733 ) -> Option<TransactionId> {
19734 if let Some(transaction_id) = self
19735 .buffer
19736 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19737 {
19738 if let Some((_, end_selections)) =
19739 self.selection_history.transaction_mut(transaction_id)
19740 {
19741 *end_selections = Some(self.selections.disjoint_anchors_arc());
19742 } else {
19743 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19744 }
19745
19746 cx.emit(EditorEvent::Edited { transaction_id });
19747 Some(transaction_id)
19748 } else {
19749 None
19750 }
19751 }
19752
19753 pub fn modify_transaction_selection_history(
19754 &mut self,
19755 transaction_id: TransactionId,
19756 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19757 ) -> bool {
19758 self.selection_history
19759 .transaction_mut(transaction_id)
19760 .map(modify)
19761 .is_some()
19762 }
19763
19764 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19765 if self.selection_mark_mode {
19766 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19767 s.move_with(&mut |_, sel| {
19768 sel.collapse_to(sel.head(), SelectionGoal::None);
19769 });
19770 })
19771 }
19772 self.selection_mark_mode = true;
19773 cx.notify();
19774 }
19775
19776 pub fn swap_selection_ends(
19777 &mut self,
19778 _: &actions::SwapSelectionEnds,
19779 window: &mut Window,
19780 cx: &mut Context<Self>,
19781 ) {
19782 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19783 s.move_with(&mut |_, sel| {
19784 if sel.start != sel.end {
19785 sel.reversed = !sel.reversed
19786 }
19787 });
19788 });
19789 self.request_autoscroll(Autoscroll::newest(), cx);
19790 cx.notify();
19791 }
19792
19793 pub fn toggle_focus(
19794 workspace: &mut Workspace,
19795 _: &actions::ToggleFocus,
19796 window: &mut Window,
19797 cx: &mut Context<Workspace>,
19798 ) {
19799 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19800 return;
19801 };
19802 workspace.activate_item(&item, true, true, window, cx);
19803 }
19804
19805 pub fn toggle_fold(
19806 &mut self,
19807 _: &actions::ToggleFold,
19808 window: &mut Window,
19809 cx: &mut Context<Self>,
19810 ) {
19811 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19812 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19813 let selection = self.selections.newest::<Point>(&display_map);
19814
19815 let range = if selection.is_empty() {
19816 let point = selection.head().to_display_point(&display_map);
19817 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19818 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19819 .to_point(&display_map);
19820 start..end
19821 } else {
19822 selection.range()
19823 };
19824 if display_map.folds_in_range(range).next().is_some() {
19825 self.unfold_lines(&Default::default(), window, cx)
19826 } else {
19827 self.fold(&Default::default(), window, cx)
19828 }
19829 } else {
19830 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19831 let buffer_ids: HashSet<_> = self
19832 .selections
19833 .disjoint_anchor_ranges()
19834 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19835 .collect();
19836
19837 let should_unfold = buffer_ids
19838 .iter()
19839 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19840
19841 for buffer_id in buffer_ids {
19842 if should_unfold {
19843 self.unfold_buffer(buffer_id, cx);
19844 } else {
19845 self.fold_buffer(buffer_id, cx);
19846 }
19847 }
19848 }
19849 }
19850
19851 pub fn toggle_fold_recursive(
19852 &mut self,
19853 _: &actions::ToggleFoldRecursive,
19854 window: &mut Window,
19855 cx: &mut Context<Self>,
19856 ) {
19857 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19858
19859 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19860 let range = if selection.is_empty() {
19861 let point = selection.head().to_display_point(&display_map);
19862 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19863 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19864 .to_point(&display_map);
19865 start..end
19866 } else {
19867 selection.range()
19868 };
19869 if display_map.folds_in_range(range).next().is_some() {
19870 self.unfold_recursive(&Default::default(), window, cx)
19871 } else {
19872 self.fold_recursive(&Default::default(), window, cx)
19873 }
19874 }
19875
19876 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19877 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19878 let mut to_fold = Vec::new();
19879 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19880 let selections = self.selections.all_adjusted(&display_map);
19881
19882 for selection in selections {
19883 let range = selection.range().sorted();
19884 let buffer_start_row = range.start.row;
19885
19886 if range.start.row != range.end.row {
19887 let mut found = false;
19888 let mut row = range.start.row;
19889 while row <= range.end.row {
19890 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19891 {
19892 found = true;
19893 row = crease.range().end.row + 1;
19894 to_fold.push(crease);
19895 } else {
19896 row += 1
19897 }
19898 }
19899 if found {
19900 continue;
19901 }
19902 }
19903
19904 for row in (0..=range.start.row).rev() {
19905 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19906 && crease.range().end.row >= buffer_start_row
19907 {
19908 to_fold.push(crease);
19909 if row <= range.start.row {
19910 break;
19911 }
19912 }
19913 }
19914 }
19915
19916 self.fold_creases(to_fold, true, window, cx);
19917 } else {
19918 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19919 let buffer_ids = self
19920 .selections
19921 .disjoint_anchor_ranges()
19922 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19923 .collect::<HashSet<_>>();
19924 for buffer_id in buffer_ids {
19925 self.fold_buffer(buffer_id, cx);
19926 }
19927 }
19928 }
19929
19930 pub fn toggle_fold_all(
19931 &mut self,
19932 _: &actions::ToggleFoldAll,
19933 window: &mut Window,
19934 cx: &mut Context<Self>,
19935 ) {
19936 let has_folds = if self.buffer.read(cx).is_singleton() {
19937 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19938 let has_folds = display_map
19939 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19940 .next()
19941 .is_some();
19942 has_folds
19943 } else {
19944 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19945 let has_folds = buffer_ids
19946 .iter()
19947 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19948 has_folds
19949 };
19950
19951 if has_folds {
19952 self.unfold_all(&actions::UnfoldAll, window, cx);
19953 } else {
19954 self.fold_all(&actions::FoldAll, window, cx);
19955 }
19956 }
19957
19958 fn fold_at_level(
19959 &mut self,
19960 fold_at: &FoldAtLevel,
19961 window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 if !self.buffer.read(cx).is_singleton() {
19965 return;
19966 }
19967
19968 let fold_at_level = fold_at.0;
19969 let snapshot = self.buffer.read(cx).snapshot(cx);
19970 let mut to_fold = Vec::new();
19971 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19972
19973 let row_ranges_to_keep: Vec<Range<u32>> = self
19974 .selections
19975 .all::<Point>(&self.display_snapshot(cx))
19976 .into_iter()
19977 .map(|sel| sel.start.row..sel.end.row)
19978 .collect();
19979
19980 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19981 while start_row < end_row {
19982 match self
19983 .snapshot(window, cx)
19984 .crease_for_buffer_row(MultiBufferRow(start_row))
19985 {
19986 Some(crease) => {
19987 let nested_start_row = crease.range().start.row + 1;
19988 let nested_end_row = crease.range().end.row;
19989
19990 if current_level < fold_at_level {
19991 stack.push((nested_start_row, nested_end_row, current_level + 1));
19992 } else if current_level == fold_at_level {
19993 // Fold iff there is no selection completely contained within the fold region
19994 if !row_ranges_to_keep.iter().any(|selection| {
19995 selection.end >= nested_start_row
19996 && selection.start <= nested_end_row
19997 }) {
19998 to_fold.push(crease);
19999 }
20000 }
20001
20002 start_row = nested_end_row + 1;
20003 }
20004 None => start_row += 1,
20005 }
20006 }
20007 }
20008
20009 self.fold_creases(to_fold, true, window, cx);
20010 }
20011
20012 pub fn fold_at_level_1(
20013 &mut self,
20014 _: &actions::FoldAtLevel1,
20015 window: &mut Window,
20016 cx: &mut Context<Self>,
20017 ) {
20018 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20019 }
20020
20021 pub fn fold_at_level_2(
20022 &mut self,
20023 _: &actions::FoldAtLevel2,
20024 window: &mut Window,
20025 cx: &mut Context<Self>,
20026 ) {
20027 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20028 }
20029
20030 pub fn fold_at_level_3(
20031 &mut self,
20032 _: &actions::FoldAtLevel3,
20033 window: &mut Window,
20034 cx: &mut Context<Self>,
20035 ) {
20036 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20037 }
20038
20039 pub fn fold_at_level_4(
20040 &mut self,
20041 _: &actions::FoldAtLevel4,
20042 window: &mut Window,
20043 cx: &mut Context<Self>,
20044 ) {
20045 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20046 }
20047
20048 pub fn fold_at_level_5(
20049 &mut self,
20050 _: &actions::FoldAtLevel5,
20051 window: &mut Window,
20052 cx: &mut Context<Self>,
20053 ) {
20054 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20055 }
20056
20057 pub fn fold_at_level_6(
20058 &mut self,
20059 _: &actions::FoldAtLevel6,
20060 window: &mut Window,
20061 cx: &mut Context<Self>,
20062 ) {
20063 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20064 }
20065
20066 pub fn fold_at_level_7(
20067 &mut self,
20068 _: &actions::FoldAtLevel7,
20069 window: &mut Window,
20070 cx: &mut Context<Self>,
20071 ) {
20072 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20073 }
20074
20075 pub fn fold_at_level_8(
20076 &mut self,
20077 _: &actions::FoldAtLevel8,
20078 window: &mut Window,
20079 cx: &mut Context<Self>,
20080 ) {
20081 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20082 }
20083
20084 pub fn fold_at_level_9(
20085 &mut self,
20086 _: &actions::FoldAtLevel9,
20087 window: &mut Window,
20088 cx: &mut Context<Self>,
20089 ) {
20090 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20091 }
20092
20093 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20094 if self.buffer.read(cx).is_singleton() {
20095 let mut fold_ranges = Vec::new();
20096 let snapshot = self.buffer.read(cx).snapshot(cx);
20097
20098 for row in 0..snapshot.max_row().0 {
20099 if let Some(foldable_range) = self
20100 .snapshot(window, cx)
20101 .crease_for_buffer_row(MultiBufferRow(row))
20102 {
20103 fold_ranges.push(foldable_range);
20104 }
20105 }
20106
20107 self.fold_creases(fold_ranges, true, window, cx);
20108 } else {
20109 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20110 editor
20111 .update_in(cx, |editor, _, cx| {
20112 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20113 editor.fold_buffer(buffer_id, cx);
20114 }
20115 })
20116 .ok();
20117 });
20118 }
20119 }
20120
20121 pub fn fold_function_bodies(
20122 &mut self,
20123 _: &actions::FoldFunctionBodies,
20124 window: &mut Window,
20125 cx: &mut Context<Self>,
20126 ) {
20127 let snapshot = self.buffer.read(cx).snapshot(cx);
20128
20129 let ranges = snapshot
20130 .text_object_ranges(
20131 MultiBufferOffset(0)..snapshot.len(),
20132 TreeSitterOptions::default(),
20133 )
20134 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20135 .collect::<Vec<_>>();
20136
20137 let creases = ranges
20138 .into_iter()
20139 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20140 .collect();
20141
20142 self.fold_creases(creases, true, window, cx);
20143 }
20144
20145 pub fn fold_recursive(
20146 &mut self,
20147 _: &actions::FoldRecursive,
20148 window: &mut Window,
20149 cx: &mut Context<Self>,
20150 ) {
20151 let mut to_fold = Vec::new();
20152 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20153 let selections = self.selections.all_adjusted(&display_map);
20154
20155 for selection in selections {
20156 let range = selection.range().sorted();
20157 let buffer_start_row = range.start.row;
20158
20159 if range.start.row != range.end.row {
20160 let mut found = false;
20161 for row in range.start.row..=range.end.row {
20162 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20163 found = true;
20164 to_fold.push(crease);
20165 }
20166 }
20167 if found {
20168 continue;
20169 }
20170 }
20171
20172 for row in (0..=range.start.row).rev() {
20173 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20174 if crease.range().end.row >= buffer_start_row {
20175 to_fold.push(crease);
20176 } else {
20177 break;
20178 }
20179 }
20180 }
20181 }
20182
20183 self.fold_creases(to_fold, true, window, cx);
20184 }
20185
20186 pub fn fold_at(
20187 &mut self,
20188 buffer_row: MultiBufferRow,
20189 window: &mut Window,
20190 cx: &mut Context<Self>,
20191 ) {
20192 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20193
20194 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20195 let autoscroll = self
20196 .selections
20197 .all::<Point>(&display_map)
20198 .iter()
20199 .any(|selection| crease.range().overlaps(&selection.range()));
20200
20201 self.fold_creases(vec![crease], autoscroll, window, cx);
20202 }
20203 }
20204
20205 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20206 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20207 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20208 let buffer = display_map.buffer_snapshot();
20209 let selections = self.selections.all::<Point>(&display_map);
20210 let ranges = selections
20211 .iter()
20212 .map(|s| {
20213 let range = s.display_range(&display_map).sorted();
20214 let mut start = range.start.to_point(&display_map);
20215 let mut end = range.end.to_point(&display_map);
20216 start.column = 0;
20217 end.column = buffer.line_len(MultiBufferRow(end.row));
20218 start..end
20219 })
20220 .collect::<Vec<_>>();
20221
20222 self.unfold_ranges(&ranges, true, true, cx);
20223 } else {
20224 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20225 let buffer_ids = self
20226 .selections
20227 .disjoint_anchor_ranges()
20228 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20229 .collect::<HashSet<_>>();
20230 for buffer_id in buffer_ids {
20231 self.unfold_buffer(buffer_id, cx);
20232 }
20233 }
20234 }
20235
20236 pub fn unfold_recursive(
20237 &mut self,
20238 _: &UnfoldRecursive,
20239 _window: &mut Window,
20240 cx: &mut Context<Self>,
20241 ) {
20242 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20243 let selections = self.selections.all::<Point>(&display_map);
20244 let ranges = selections
20245 .iter()
20246 .map(|s| {
20247 let mut range = s.display_range(&display_map).sorted();
20248 *range.start.column_mut() = 0;
20249 *range.end.column_mut() = display_map.line_len(range.end.row());
20250 let start = range.start.to_point(&display_map);
20251 let end = range.end.to_point(&display_map);
20252 start..end
20253 })
20254 .collect::<Vec<_>>();
20255
20256 self.unfold_ranges(&ranges, true, true, cx);
20257 }
20258
20259 pub fn unfold_at(
20260 &mut self,
20261 buffer_row: MultiBufferRow,
20262 _window: &mut Window,
20263 cx: &mut Context<Self>,
20264 ) {
20265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20266
20267 let intersection_range = Point::new(buffer_row.0, 0)
20268 ..Point::new(
20269 buffer_row.0,
20270 display_map.buffer_snapshot().line_len(buffer_row),
20271 );
20272
20273 let autoscroll = self
20274 .selections
20275 .all::<Point>(&display_map)
20276 .iter()
20277 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20278
20279 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20280 }
20281
20282 pub fn unfold_all(
20283 &mut self,
20284 _: &actions::UnfoldAll,
20285 _window: &mut Window,
20286 cx: &mut Context<Self>,
20287 ) {
20288 if self.buffer.read(cx).is_singleton() {
20289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20290 self.unfold_ranges(
20291 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20292 true,
20293 true,
20294 cx,
20295 );
20296 } else {
20297 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20298 editor
20299 .update(cx, |editor, cx| {
20300 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20301 editor.unfold_buffer(buffer_id, cx);
20302 }
20303 })
20304 .ok();
20305 });
20306 }
20307 }
20308
20309 pub fn fold_selected_ranges(
20310 &mut self,
20311 _: &FoldSelectedRanges,
20312 window: &mut Window,
20313 cx: &mut Context<Self>,
20314 ) {
20315 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20316 let selections = self.selections.all_adjusted(&display_map);
20317 let ranges = selections
20318 .into_iter()
20319 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20320 .collect::<Vec<_>>();
20321 self.fold_creases(ranges, true, window, cx);
20322 }
20323
20324 pub fn fold_ranges<T: ToOffset + Clone>(
20325 &mut self,
20326 ranges: Vec<Range<T>>,
20327 auto_scroll: bool,
20328 window: &mut Window,
20329 cx: &mut Context<Self>,
20330 ) {
20331 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20332 let ranges = ranges
20333 .into_iter()
20334 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20335 .collect::<Vec<_>>();
20336 self.fold_creases(ranges, auto_scroll, window, cx);
20337 }
20338
20339 pub fn fold_creases<T: ToOffset + Clone>(
20340 &mut self,
20341 creases: Vec<Crease<T>>,
20342 auto_scroll: bool,
20343 _window: &mut Window,
20344 cx: &mut Context<Self>,
20345 ) {
20346 if creases.is_empty() {
20347 return;
20348 }
20349
20350 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20351
20352 if auto_scroll {
20353 self.request_autoscroll(Autoscroll::fit(), cx);
20354 }
20355
20356 cx.notify();
20357
20358 self.scrollbar_marker_state.dirty = true;
20359 self.folds_did_change(cx);
20360 }
20361
20362 /// Removes any folds whose ranges intersect any of the given ranges.
20363 pub fn unfold_ranges<T: ToOffset + Clone>(
20364 &mut self,
20365 ranges: &[Range<T>],
20366 inclusive: bool,
20367 auto_scroll: bool,
20368 cx: &mut Context<Self>,
20369 ) {
20370 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20371 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20372 });
20373 self.folds_did_change(cx);
20374 }
20375
20376 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20377 self.fold_buffers([buffer_id], cx);
20378 }
20379
20380 pub fn fold_buffers(
20381 &mut self,
20382 buffer_ids: impl IntoIterator<Item = BufferId>,
20383 cx: &mut Context<Self>,
20384 ) {
20385 if self.buffer().read(cx).is_singleton() {
20386 return;
20387 }
20388
20389 let ids_to_fold: Vec<BufferId> = buffer_ids
20390 .into_iter()
20391 .filter(|id| !self.is_buffer_folded(*id, cx))
20392 .collect();
20393
20394 if ids_to_fold.is_empty() {
20395 return;
20396 }
20397
20398 let mut all_folded_excerpt_ids = Vec::new();
20399 for buffer_id in &ids_to_fold {
20400 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20401 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _)| id));
20402 }
20403
20404 self.display_map.update(cx, |display_map, cx| {
20405 display_map.fold_buffers(ids_to_fold.clone(), cx)
20406 });
20407
20408 let snapshot = self.display_snapshot(cx);
20409 self.selections.change_with(&snapshot, |selections| {
20410 for buffer_id in ids_to_fold {
20411 selections.remove_selections_from_buffer(buffer_id);
20412 }
20413 });
20414
20415 cx.emit(EditorEvent::BufferFoldToggled {
20416 ids: all_folded_excerpt_ids,
20417 folded: true,
20418 });
20419 cx.notify();
20420 }
20421
20422 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20423 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20424 return;
20425 }
20426 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20427 self.display_map.update(cx, |display_map, cx| {
20428 display_map.unfold_buffers([buffer_id], cx);
20429 });
20430 cx.emit(EditorEvent::BufferFoldToggled {
20431 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20432 folded: false,
20433 });
20434 cx.notify();
20435 }
20436
20437 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20438 self.display_map.read(cx).is_buffer_folded(buffer)
20439 }
20440
20441 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20442 if self.buffer().read(cx).is_singleton() {
20443 return false;
20444 }
20445 !self.folded_buffers(cx).is_empty()
20446 }
20447
20448 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20449 self.display_map.read(cx).folded_buffers()
20450 }
20451
20452 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20453 self.display_map.update(cx, |display_map, cx| {
20454 display_map.disable_header_for_buffer(buffer_id, cx);
20455 });
20456 cx.notify();
20457 }
20458
20459 /// Removes any folds with the given ranges.
20460 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20461 &mut self,
20462 ranges: &[Range<T>],
20463 type_id: TypeId,
20464 auto_scroll: bool,
20465 cx: &mut Context<Self>,
20466 ) {
20467 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20468 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20469 });
20470 self.folds_did_change(cx);
20471 }
20472
20473 fn remove_folds_with<T: ToOffset + Clone>(
20474 &mut self,
20475 ranges: &[Range<T>],
20476 auto_scroll: bool,
20477 cx: &mut Context<Self>,
20478 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20479 ) {
20480 if ranges.is_empty() {
20481 return;
20482 }
20483
20484 let mut buffers_affected = HashSet::default();
20485 let multi_buffer = self.buffer().read(cx);
20486 for range in ranges {
20487 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20488 buffers_affected.insert(buffer.read(cx).remote_id());
20489 };
20490 }
20491
20492 self.display_map.update(cx, update);
20493
20494 if auto_scroll {
20495 self.request_autoscroll(Autoscroll::fit(), cx);
20496 }
20497
20498 cx.notify();
20499 self.scrollbar_marker_state.dirty = true;
20500 self.active_indent_guides_state.dirty = true;
20501 }
20502
20503 pub fn update_renderer_widths(
20504 &mut self,
20505 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20506 cx: &mut Context<Self>,
20507 ) -> bool {
20508 self.display_map
20509 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20510 }
20511
20512 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20513 self.display_map.read(cx).fold_placeholder.clone()
20514 }
20515
20516 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20517 self.buffer.update(cx, |buffer, cx| {
20518 buffer.set_all_diff_hunks_expanded(cx);
20519 });
20520 }
20521
20522 pub fn expand_all_diff_hunks(
20523 &mut self,
20524 _: &ExpandAllDiffHunks,
20525 _window: &mut Window,
20526 cx: &mut Context<Self>,
20527 ) {
20528 self.buffer.update(cx, |buffer, cx| {
20529 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20530 });
20531 }
20532
20533 pub fn collapse_all_diff_hunks(
20534 &mut self,
20535 _: &CollapseAllDiffHunks,
20536 _window: &mut Window,
20537 cx: &mut Context<Self>,
20538 ) {
20539 self.buffer.update(cx, |buffer, cx| {
20540 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20541 });
20542 }
20543
20544 pub fn toggle_selected_diff_hunks(
20545 &mut self,
20546 _: &ToggleSelectedDiffHunks,
20547 _window: &mut Window,
20548 cx: &mut Context<Self>,
20549 ) {
20550 let ranges: Vec<_> = self
20551 .selections
20552 .disjoint_anchors()
20553 .iter()
20554 .map(|s| s.range())
20555 .collect();
20556 self.toggle_diff_hunks_in_ranges(ranges, cx);
20557 }
20558
20559 pub fn diff_hunks_in_ranges<'a>(
20560 &'a self,
20561 ranges: &'a [Range<Anchor>],
20562 buffer: &'a MultiBufferSnapshot,
20563 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20564 ranges.iter().flat_map(move |range| {
20565 let end_excerpt_id = range.end.excerpt_id;
20566 let range = range.to_point(buffer);
20567 let mut peek_end = range.end;
20568 if range.end.row < buffer.max_row().0 {
20569 peek_end = Point::new(range.end.row + 1, 0);
20570 }
20571 buffer
20572 .diff_hunks_in_range(range.start..peek_end)
20573 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20574 })
20575 }
20576
20577 pub fn has_stageable_diff_hunks_in_ranges(
20578 &self,
20579 ranges: &[Range<Anchor>],
20580 snapshot: &MultiBufferSnapshot,
20581 ) -> bool {
20582 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20583 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20584 }
20585
20586 pub fn toggle_staged_selected_diff_hunks(
20587 &mut self,
20588 _: &::git::ToggleStaged,
20589 _: &mut Window,
20590 cx: &mut Context<Self>,
20591 ) {
20592 let snapshot = self.buffer.read(cx).snapshot(cx);
20593 let ranges: Vec<_> = self
20594 .selections
20595 .disjoint_anchors()
20596 .iter()
20597 .map(|s| s.range())
20598 .collect();
20599 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20600 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20601 }
20602
20603 pub fn set_render_diff_hunk_controls(
20604 &mut self,
20605 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20606 cx: &mut Context<Self>,
20607 ) {
20608 self.render_diff_hunk_controls = render_diff_hunk_controls;
20609 cx.notify();
20610 }
20611
20612 pub fn stage_and_next(
20613 &mut self,
20614 _: &::git::StageAndNext,
20615 window: &mut Window,
20616 cx: &mut Context<Self>,
20617 ) {
20618 self.do_stage_or_unstage_and_next(true, window, cx);
20619 }
20620
20621 pub fn unstage_and_next(
20622 &mut self,
20623 _: &::git::UnstageAndNext,
20624 window: &mut Window,
20625 cx: &mut Context<Self>,
20626 ) {
20627 self.do_stage_or_unstage_and_next(false, window, cx);
20628 }
20629
20630 pub fn stage_or_unstage_diff_hunks(
20631 &mut self,
20632 stage: bool,
20633 ranges: Vec<Range<Anchor>>,
20634 cx: &mut Context<Self>,
20635 ) {
20636 if self.delegate_stage_and_restore {
20637 let snapshot = self.buffer.read(cx).snapshot(cx);
20638 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20639 if !hunks.is_empty() {
20640 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20641 }
20642 return;
20643 }
20644 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20645 cx.spawn(async move |this, cx| {
20646 task.await?;
20647 this.update(cx, |this, cx| {
20648 let snapshot = this.buffer.read(cx).snapshot(cx);
20649 let chunk_by = this
20650 .diff_hunks_in_ranges(&ranges, &snapshot)
20651 .chunk_by(|hunk| hunk.buffer_id);
20652 for (buffer_id, hunks) in &chunk_by {
20653 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20654 }
20655 })
20656 })
20657 .detach_and_log_err(cx);
20658 }
20659
20660 fn save_buffers_for_ranges_if_needed(
20661 &mut self,
20662 ranges: &[Range<Anchor>],
20663 cx: &mut Context<Editor>,
20664 ) -> Task<Result<()>> {
20665 let multibuffer = self.buffer.read(cx);
20666 let snapshot = multibuffer.read(cx);
20667 let buffer_ids: HashSet<_> = ranges
20668 .iter()
20669 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20670 .collect();
20671 drop(snapshot);
20672
20673 let mut buffers = HashSet::default();
20674 for buffer_id in buffer_ids {
20675 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20676 let buffer = buffer_entity.read(cx);
20677 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20678 {
20679 buffers.insert(buffer_entity);
20680 }
20681 }
20682 }
20683
20684 if let Some(project) = &self.project {
20685 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20686 } else {
20687 Task::ready(Ok(()))
20688 }
20689 }
20690
20691 fn do_stage_or_unstage_and_next(
20692 &mut self,
20693 stage: bool,
20694 window: &mut Window,
20695 cx: &mut Context<Self>,
20696 ) {
20697 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20698
20699 if ranges.iter().any(|range| range.start != range.end) {
20700 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20701 return;
20702 }
20703
20704 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20705 let snapshot = self.snapshot(window, cx);
20706 let position = self
20707 .selections
20708 .newest::<Point>(&snapshot.display_snapshot)
20709 .head();
20710 let mut row = snapshot
20711 .buffer_snapshot()
20712 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20713 .find(|hunk| hunk.row_range.start.0 > position.row)
20714 .map(|hunk| hunk.row_range.start);
20715
20716 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20717 // Outside of the project diff editor, wrap around to the beginning.
20718 if !all_diff_hunks_expanded {
20719 row = row.or_else(|| {
20720 snapshot
20721 .buffer_snapshot()
20722 .diff_hunks_in_range(Point::zero()..position)
20723 .find(|hunk| hunk.row_range.end.0 < position.row)
20724 .map(|hunk| hunk.row_range.start)
20725 });
20726 }
20727
20728 if let Some(row) = row {
20729 let destination = Point::new(row.0, 0);
20730 let autoscroll = Autoscroll::center();
20731
20732 self.unfold_ranges(&[destination..destination], false, false, cx);
20733 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20734 s.select_ranges([destination..destination]);
20735 });
20736 }
20737 }
20738
20739 pub(crate) fn do_stage_or_unstage(
20740 &self,
20741 stage: bool,
20742 buffer_id: BufferId,
20743 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20744 cx: &mut App,
20745 ) -> Option<()> {
20746 let project = self.project()?;
20747 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20748 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20749 let buffer_snapshot = buffer.read(cx).snapshot();
20750 let file_exists = buffer_snapshot
20751 .file()
20752 .is_some_and(|file| file.disk_state().exists());
20753 diff.update(cx, |diff, cx| {
20754 diff.stage_or_unstage_hunks(
20755 stage,
20756 &hunks
20757 .map(|hunk| buffer_diff::DiffHunk {
20758 buffer_range: hunk.buffer_range,
20759 // We don't need to pass in word diffs here because they're only used for rendering and
20760 // this function changes internal state
20761 base_word_diffs: Vec::default(),
20762 buffer_word_diffs: Vec::default(),
20763 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20764 ..hunk.diff_base_byte_range.end.0,
20765 secondary_status: hunk.status.secondary,
20766 range: Point::zero()..Point::zero(), // unused
20767 })
20768 .collect::<Vec<_>>(),
20769 &buffer_snapshot,
20770 file_exists,
20771 cx,
20772 )
20773 });
20774 None
20775 }
20776
20777 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20778 let ranges: Vec<_> = self
20779 .selections
20780 .disjoint_anchors()
20781 .iter()
20782 .map(|s| s.range())
20783 .collect();
20784 self.buffer
20785 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20786 }
20787
20788 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20789 self.buffer.update(cx, |buffer, cx| {
20790 let ranges = vec![Anchor::min()..Anchor::max()];
20791 if !buffer.all_diff_hunks_expanded()
20792 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20793 {
20794 buffer.collapse_diff_hunks(ranges, cx);
20795 true
20796 } else {
20797 false
20798 }
20799 })
20800 }
20801
20802 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20803 if self.buffer.read(cx).all_diff_hunks_expanded() {
20804 return true;
20805 }
20806 let ranges = vec![Anchor::min()..Anchor::max()];
20807 self.buffer
20808 .read(cx)
20809 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20810 }
20811
20812 fn toggle_diff_hunks_in_ranges(
20813 &mut self,
20814 ranges: Vec<Range<Anchor>>,
20815 cx: &mut Context<Editor>,
20816 ) {
20817 self.buffer.update(cx, |buffer, cx| {
20818 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20819 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20820 })
20821 }
20822
20823 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20824 self.buffer.update(cx, |buffer, cx| {
20825 buffer.toggle_single_diff_hunk(range, cx);
20826 })
20827 }
20828
20829 pub(crate) fn apply_all_diff_hunks(
20830 &mut self,
20831 _: &ApplyAllDiffHunks,
20832 window: &mut Window,
20833 cx: &mut Context<Self>,
20834 ) {
20835 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20836
20837 let buffers = self.buffer.read(cx).all_buffers();
20838 for branch_buffer in buffers {
20839 branch_buffer.update(cx, |branch_buffer, cx| {
20840 branch_buffer.merge_into_base(Vec::new(), cx);
20841 });
20842 }
20843
20844 if let Some(project) = self.project.clone() {
20845 self.save(
20846 SaveOptions {
20847 format: true,
20848 autosave: false,
20849 },
20850 project,
20851 window,
20852 cx,
20853 )
20854 .detach_and_log_err(cx);
20855 }
20856 }
20857
20858 pub(crate) fn apply_selected_diff_hunks(
20859 &mut self,
20860 _: &ApplyDiffHunk,
20861 window: &mut Window,
20862 cx: &mut Context<Self>,
20863 ) {
20864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20865 let snapshot = self.snapshot(window, cx);
20866 let hunks = snapshot.hunks_for_ranges(
20867 self.selections
20868 .all(&snapshot.display_snapshot)
20869 .into_iter()
20870 .map(|selection| selection.range()),
20871 );
20872 let mut ranges_by_buffer = HashMap::default();
20873 self.transact(window, cx, |editor, _window, cx| {
20874 for hunk in hunks {
20875 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20876 ranges_by_buffer
20877 .entry(buffer.clone())
20878 .or_insert_with(Vec::new)
20879 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20880 }
20881 }
20882
20883 for (buffer, ranges) in ranges_by_buffer {
20884 buffer.update(cx, |buffer, cx| {
20885 buffer.merge_into_base(ranges, cx);
20886 });
20887 }
20888 });
20889
20890 if let Some(project) = self.project.clone() {
20891 self.save(
20892 SaveOptions {
20893 format: true,
20894 autosave: false,
20895 },
20896 project,
20897 window,
20898 cx,
20899 )
20900 .detach_and_log_err(cx);
20901 }
20902 }
20903
20904 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20905 if hovered != self.gutter_hovered {
20906 self.gutter_hovered = hovered;
20907 cx.notify();
20908 }
20909 }
20910
20911 pub fn insert_blocks(
20912 &mut self,
20913 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20914 autoscroll: Option<Autoscroll>,
20915 cx: &mut Context<Self>,
20916 ) -> Vec<CustomBlockId> {
20917 let blocks = self
20918 .display_map
20919 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20920 if let Some(autoscroll) = autoscroll {
20921 self.request_autoscroll(autoscroll, cx);
20922 }
20923 cx.notify();
20924 blocks
20925 }
20926
20927 pub fn resize_blocks(
20928 &mut self,
20929 heights: HashMap<CustomBlockId, u32>,
20930 autoscroll: Option<Autoscroll>,
20931 cx: &mut Context<Self>,
20932 ) {
20933 self.display_map
20934 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20935 if let Some(autoscroll) = autoscroll {
20936 self.request_autoscroll(autoscroll, cx);
20937 }
20938 cx.notify();
20939 }
20940
20941 pub fn replace_blocks(
20942 &mut self,
20943 renderers: HashMap<CustomBlockId, RenderBlock>,
20944 autoscroll: Option<Autoscroll>,
20945 cx: &mut Context<Self>,
20946 ) {
20947 self.display_map
20948 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20949 if let Some(autoscroll) = autoscroll {
20950 self.request_autoscroll(autoscroll, cx);
20951 }
20952 cx.notify();
20953 }
20954
20955 pub fn remove_blocks(
20956 &mut self,
20957 block_ids: HashSet<CustomBlockId>,
20958 autoscroll: Option<Autoscroll>,
20959 cx: &mut Context<Self>,
20960 ) {
20961 self.display_map.update(cx, |display_map, cx| {
20962 display_map.remove_blocks(block_ids, cx)
20963 });
20964 if let Some(autoscroll) = autoscroll {
20965 self.request_autoscroll(autoscroll, cx);
20966 }
20967 cx.notify();
20968 }
20969
20970 pub fn row_for_block(
20971 &self,
20972 block_id: CustomBlockId,
20973 cx: &mut Context<Self>,
20974 ) -> Option<DisplayRow> {
20975 self.display_map
20976 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20977 }
20978
20979 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20980 self.focused_block = Some(focused_block);
20981 }
20982
20983 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20984 self.focused_block.take()
20985 }
20986
20987 pub fn insert_creases(
20988 &mut self,
20989 creases: impl IntoIterator<Item = Crease<Anchor>>,
20990 cx: &mut Context<Self>,
20991 ) -> Vec<CreaseId> {
20992 self.display_map
20993 .update(cx, |map, cx| map.insert_creases(creases, cx))
20994 }
20995
20996 pub fn remove_creases(
20997 &mut self,
20998 ids: impl IntoIterator<Item = CreaseId>,
20999 cx: &mut Context<Self>,
21000 ) -> Vec<(CreaseId, Range<Anchor>)> {
21001 self.display_map
21002 .update(cx, |map, cx| map.remove_creases(ids, cx))
21003 }
21004
21005 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21006 self.display_map
21007 .update(cx, |map, cx| map.snapshot(cx))
21008 .longest_row()
21009 }
21010
21011 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21012 self.display_map
21013 .update(cx, |map, cx| map.snapshot(cx))
21014 .max_point()
21015 }
21016
21017 pub fn text(&self, cx: &App) -> String {
21018 self.buffer.read(cx).read(cx).text()
21019 }
21020
21021 pub fn is_empty(&self, cx: &App) -> bool {
21022 self.buffer.read(cx).read(cx).is_empty()
21023 }
21024
21025 pub fn text_option(&self, cx: &App) -> Option<String> {
21026 let text = self.text(cx);
21027 let text = text.trim();
21028
21029 if text.is_empty() {
21030 return None;
21031 }
21032
21033 Some(text.to_string())
21034 }
21035
21036 pub fn set_text(
21037 &mut self,
21038 text: impl Into<Arc<str>>,
21039 window: &mut Window,
21040 cx: &mut Context<Self>,
21041 ) {
21042 self.transact(window, cx, |this, _, cx| {
21043 this.buffer
21044 .read(cx)
21045 .as_singleton()
21046 .expect("you can only call set_text on editors for singleton buffers")
21047 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21048 });
21049 }
21050
21051 pub fn display_text(&self, cx: &mut App) -> String {
21052 self.display_map
21053 .update(cx, |map, cx| map.snapshot(cx))
21054 .text()
21055 }
21056
21057 fn create_minimap(
21058 &self,
21059 minimap_settings: MinimapSettings,
21060 window: &mut Window,
21061 cx: &mut Context<Self>,
21062 ) -> Option<Entity<Self>> {
21063 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21064 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21065 }
21066
21067 fn initialize_new_minimap(
21068 &self,
21069 minimap_settings: MinimapSettings,
21070 window: &mut Window,
21071 cx: &mut Context<Self>,
21072 ) -> Entity<Self> {
21073 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21074 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21075
21076 let mut minimap = Editor::new_internal(
21077 EditorMode::Minimap {
21078 parent: cx.weak_entity(),
21079 },
21080 self.buffer.clone(),
21081 None,
21082 Some(self.display_map.clone()),
21083 window,
21084 cx,
21085 );
21086 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21087 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21088 minimap.scroll_manager.clone_state(
21089 &self.scroll_manager,
21090 &my_snapshot,
21091 &minimap_snapshot,
21092 cx,
21093 );
21094 minimap.set_text_style_refinement(TextStyleRefinement {
21095 font_size: Some(MINIMAP_FONT_SIZE),
21096 font_weight: Some(MINIMAP_FONT_WEIGHT),
21097 font_family: Some(MINIMAP_FONT_FAMILY),
21098 ..Default::default()
21099 });
21100 minimap.update_minimap_configuration(minimap_settings, cx);
21101 cx.new(|_| minimap)
21102 }
21103
21104 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21105 let current_line_highlight = minimap_settings
21106 .current_line_highlight
21107 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21108 self.set_current_line_highlight(Some(current_line_highlight));
21109 }
21110
21111 pub fn minimap(&self) -> Option<&Entity<Self>> {
21112 self.minimap
21113 .as_ref()
21114 .filter(|_| self.minimap_visibility.visible())
21115 }
21116
21117 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21118 let mut wrap_guides = smallvec![];
21119
21120 if self.show_wrap_guides == Some(false) {
21121 return wrap_guides;
21122 }
21123
21124 let settings = self.buffer.read(cx).language_settings(cx);
21125 if settings.show_wrap_guides {
21126 match self.soft_wrap_mode(cx) {
21127 SoftWrap::Column(soft_wrap) => {
21128 wrap_guides.push((soft_wrap as usize, true));
21129 }
21130 SoftWrap::Bounded(soft_wrap) => {
21131 wrap_guides.push((soft_wrap as usize, true));
21132 }
21133 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21134 }
21135 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21136 }
21137
21138 wrap_guides
21139 }
21140
21141 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21142 let settings = self.buffer.read(cx).language_settings(cx);
21143 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21144 match mode {
21145 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21146 SoftWrap::None
21147 }
21148 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21149 language_settings::SoftWrap::PreferredLineLength => {
21150 SoftWrap::Column(settings.preferred_line_length)
21151 }
21152 language_settings::SoftWrap::Bounded => {
21153 SoftWrap::Bounded(settings.preferred_line_length)
21154 }
21155 }
21156 }
21157
21158 pub fn set_soft_wrap_mode(
21159 &mut self,
21160 mode: language_settings::SoftWrap,
21161 cx: &mut Context<Self>,
21162 ) {
21163 self.soft_wrap_mode_override = Some(mode);
21164 cx.notify();
21165 }
21166
21167 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21168 self.hard_wrap = hard_wrap;
21169 cx.notify();
21170 }
21171
21172 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21173 self.text_style_refinement = Some(style);
21174 }
21175
21176 /// called by the Element so we know what style we were most recently rendered with.
21177 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21178 // We intentionally do not inform the display map about the minimap style
21179 // so that wrapping is not recalculated and stays consistent for the editor
21180 // and its linked minimap.
21181 if !self.mode.is_minimap() {
21182 let font = style.text.font();
21183 let font_size = style.text.font_size.to_pixels(window.rem_size());
21184 let display_map = self
21185 .placeholder_display_map
21186 .as_ref()
21187 .filter(|_| self.is_empty(cx))
21188 .unwrap_or(&self.display_map);
21189
21190 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21191 }
21192 self.style = Some(style);
21193 }
21194
21195 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21196 if self.style.is_none() {
21197 self.style = Some(self.create_style(cx));
21198 }
21199 self.style.as_ref().unwrap()
21200 }
21201
21202 // Called by the element. This method is not designed to be called outside of the editor
21203 // element's layout code because it does not notify when rewrapping is computed synchronously.
21204 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21205 if self.is_empty(cx) {
21206 self.placeholder_display_map
21207 .as_ref()
21208 .map_or(false, |display_map| {
21209 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21210 })
21211 } else {
21212 self.display_map
21213 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21214 }
21215 }
21216
21217 pub fn set_soft_wrap(&mut self) {
21218 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21219 }
21220
21221 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21222 if self.soft_wrap_mode_override.is_some() {
21223 self.soft_wrap_mode_override.take();
21224 } else {
21225 let soft_wrap = match self.soft_wrap_mode(cx) {
21226 SoftWrap::GitDiff => return,
21227 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21228 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21229 language_settings::SoftWrap::None
21230 }
21231 };
21232 self.soft_wrap_mode_override = Some(soft_wrap);
21233 }
21234 cx.notify();
21235 }
21236
21237 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21238 let Some(workspace) = self.workspace() else {
21239 return;
21240 };
21241 let fs = workspace.read(cx).app_state().fs.clone();
21242 let current_show = TabBarSettings::get_global(cx).show;
21243 update_settings_file(fs, cx, move |setting, _| {
21244 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21245 });
21246 }
21247
21248 pub fn toggle_indent_guides(
21249 &mut self,
21250 _: &ToggleIndentGuides,
21251 _: &mut Window,
21252 cx: &mut Context<Self>,
21253 ) {
21254 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21255 self.buffer
21256 .read(cx)
21257 .language_settings(cx)
21258 .indent_guides
21259 .enabled
21260 });
21261 self.show_indent_guides = Some(!currently_enabled);
21262 cx.notify();
21263 }
21264
21265 fn should_show_indent_guides(&self) -> Option<bool> {
21266 self.show_indent_guides
21267 }
21268
21269 pub fn disable_indent_guides_for_buffer(
21270 &mut self,
21271 buffer_id: BufferId,
21272 cx: &mut Context<Self>,
21273 ) {
21274 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21275 cx.notify();
21276 }
21277
21278 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21279 self.buffers_with_disabled_indent_guides
21280 .contains(&buffer_id)
21281 }
21282
21283 pub fn toggle_line_numbers(
21284 &mut self,
21285 _: &ToggleLineNumbers,
21286 _: &mut Window,
21287 cx: &mut Context<Self>,
21288 ) {
21289 let mut editor_settings = EditorSettings::get_global(cx).clone();
21290 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21291 EditorSettings::override_global(editor_settings, cx);
21292 }
21293
21294 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21295 if let Some(show_line_numbers) = self.show_line_numbers {
21296 return show_line_numbers;
21297 }
21298 EditorSettings::get_global(cx).gutter.line_numbers
21299 }
21300
21301 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21302 match (
21303 self.use_relative_line_numbers,
21304 EditorSettings::get_global(cx).relative_line_numbers,
21305 ) {
21306 (None, setting) => setting,
21307 (Some(false), _) => RelativeLineNumbers::Disabled,
21308 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21309 (Some(true), _) => RelativeLineNumbers::Enabled,
21310 }
21311 }
21312
21313 pub fn toggle_relative_line_numbers(
21314 &mut self,
21315 _: &ToggleRelativeLineNumbers,
21316 _: &mut Window,
21317 cx: &mut Context<Self>,
21318 ) {
21319 let is_relative = self.relative_line_numbers(cx);
21320 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21321 }
21322
21323 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21324 self.use_relative_line_numbers = is_relative;
21325 cx.notify();
21326 }
21327
21328 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21329 self.show_gutter = show_gutter;
21330 cx.notify();
21331 }
21332
21333 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21334 self.show_scrollbars = ScrollbarAxes {
21335 horizontal: show,
21336 vertical: show,
21337 };
21338 cx.notify();
21339 }
21340
21341 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21342 self.show_scrollbars.vertical = show;
21343 cx.notify();
21344 }
21345
21346 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21347 self.show_scrollbars.horizontal = show;
21348 cx.notify();
21349 }
21350
21351 pub fn set_minimap_visibility(
21352 &mut self,
21353 minimap_visibility: MinimapVisibility,
21354 window: &mut Window,
21355 cx: &mut Context<Self>,
21356 ) {
21357 if self.minimap_visibility != minimap_visibility {
21358 if minimap_visibility.visible() && self.minimap.is_none() {
21359 let minimap_settings = EditorSettings::get_global(cx).minimap;
21360 self.minimap =
21361 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21362 }
21363 self.minimap_visibility = minimap_visibility;
21364 cx.notify();
21365 }
21366 }
21367
21368 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21369 self.set_show_scrollbars(false, cx);
21370 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21371 }
21372
21373 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21374 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21375 }
21376
21377 /// Normally the text in full mode and auto height editors is padded on the
21378 /// left side by roughly half a character width for improved hit testing.
21379 ///
21380 /// Use this method to disable this for cases where this is not wanted (e.g.
21381 /// if you want to align the editor text with some other text above or below)
21382 /// or if you want to add this padding to single-line editors.
21383 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21384 self.offset_content = offset_content;
21385 cx.notify();
21386 }
21387
21388 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21389 self.show_line_numbers = Some(show_line_numbers);
21390 cx.notify();
21391 }
21392
21393 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21394 self.disable_expand_excerpt_buttons = true;
21395 cx.notify();
21396 }
21397
21398 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21399 self.number_deleted_lines = number;
21400 cx.notify();
21401 }
21402
21403 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21404 self.delegate_expand_excerpts = delegate;
21405 }
21406
21407 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21408 self.delegate_stage_and_restore = delegate;
21409 }
21410
21411 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21412 self.delegate_open_excerpts = delegate;
21413 }
21414
21415 pub fn set_on_local_selections_changed(
21416 &mut self,
21417 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21418 ) {
21419 self.on_local_selections_changed = callback;
21420 }
21421
21422 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21423 self.suppress_selection_callback = suppress;
21424 }
21425
21426 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21427 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21428 cx.notify();
21429 }
21430
21431 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21432 self.show_code_actions = Some(show_code_actions);
21433 cx.notify();
21434 }
21435
21436 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21437 self.show_runnables = Some(show_runnables);
21438 cx.notify();
21439 }
21440
21441 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21442 self.show_breakpoints = Some(show_breakpoints);
21443 cx.notify();
21444 }
21445
21446 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21447 self.show_diff_review_button = show;
21448 cx.notify();
21449 }
21450
21451 pub fn show_diff_review_button(&self) -> bool {
21452 self.show_diff_review_button
21453 }
21454
21455 pub fn render_diff_review_button(
21456 &self,
21457 display_row: DisplayRow,
21458 width: Pixels,
21459 cx: &mut Context<Self>,
21460 ) -> impl IntoElement {
21461 let text_color = cx.theme().colors().text;
21462 let icon_color = cx.theme().colors().icon_accent;
21463
21464 h_flex()
21465 .id("diff_review_button")
21466 .cursor_pointer()
21467 .w(width - px(1.))
21468 .h(relative(0.9))
21469 .justify_center()
21470 .rounded_sm()
21471 .border_1()
21472 .border_color(text_color.opacity(0.1))
21473 .bg(text_color.opacity(0.15))
21474 .hover(|s| {
21475 s.bg(icon_color.opacity(0.4))
21476 .border_color(icon_color.opacity(0.5))
21477 })
21478 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21479 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21480 .on_mouse_down(
21481 gpui::MouseButton::Left,
21482 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21483 editor.start_diff_review_drag(display_row, window, cx);
21484 }),
21485 )
21486 }
21487
21488 pub fn start_diff_review_drag(
21489 &mut self,
21490 display_row: DisplayRow,
21491 window: &mut Window,
21492 cx: &mut Context<Self>,
21493 ) {
21494 let snapshot = self.snapshot(window, cx);
21495 let point = snapshot
21496 .display_snapshot
21497 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21498 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21499 self.diff_review_drag_state = Some(DiffReviewDragState {
21500 start_anchor: anchor,
21501 current_anchor: anchor,
21502 });
21503 cx.notify();
21504 }
21505
21506 pub fn update_diff_review_drag(
21507 &mut self,
21508 display_row: DisplayRow,
21509 window: &mut Window,
21510 cx: &mut Context<Self>,
21511 ) {
21512 if self.diff_review_drag_state.is_none() {
21513 return;
21514 }
21515 let snapshot = self.snapshot(window, cx);
21516 let point = snapshot
21517 .display_snapshot
21518 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21519 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21520 if let Some(drag_state) = &mut self.diff_review_drag_state {
21521 drag_state.current_anchor = anchor;
21522 cx.notify();
21523 }
21524 }
21525
21526 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21527 if let Some(drag_state) = self.diff_review_drag_state.take() {
21528 let snapshot = self.snapshot(window, cx);
21529 let range = drag_state.row_range(&snapshot.display_snapshot);
21530 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21531 }
21532 cx.notify();
21533 }
21534
21535 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21536 self.diff_review_drag_state = None;
21537 cx.notify();
21538 }
21539
21540 /// Calculates the appropriate block height for the diff review overlay.
21541 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21542 /// and 2 lines per comment when expanded.
21543 fn calculate_overlay_height(
21544 &self,
21545 hunk_key: &DiffHunkKey,
21546 comments_expanded: bool,
21547 snapshot: &MultiBufferSnapshot,
21548 ) -> u32 {
21549 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21550 let base_height: u32 = 2; // Input row with avatar and buttons
21551
21552 if comment_count == 0 {
21553 base_height
21554 } else if comments_expanded {
21555 // Header (1 line) + 2 lines per comment
21556 base_height + 1 + (comment_count as u32 * 2)
21557 } else {
21558 // Just header when collapsed
21559 base_height + 1
21560 }
21561 }
21562
21563 pub fn show_diff_review_overlay(
21564 &mut self,
21565 display_range: Range<DisplayRow>,
21566 window: &mut Window,
21567 cx: &mut Context<Self>,
21568 ) {
21569 let Range { start, end } = display_range.sorted();
21570
21571 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21572 let editor_snapshot = self.snapshot(window, cx);
21573
21574 // Convert display rows to multibuffer points
21575 let start_point = editor_snapshot
21576 .display_snapshot
21577 .display_point_to_point(start.as_display_point(), Bias::Left);
21578 let end_point = editor_snapshot
21579 .display_snapshot
21580 .display_point_to_point(end.as_display_point(), Bias::Left);
21581 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21582
21583 // Create anchor range for the selected lines (start of first line to end of last line)
21584 let line_end = Point::new(
21585 end_point.row,
21586 buffer_snapshot.line_len(end_multi_buffer_row),
21587 );
21588 let anchor_range =
21589 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21590
21591 // Compute the hunk key for this display row
21592 let file_path = buffer_snapshot
21593 .file_at(start_point)
21594 .map(|file: &Arc<dyn language::File>| file.path().clone())
21595 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21596 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21597 let new_hunk_key = DiffHunkKey {
21598 file_path,
21599 hunk_start_anchor,
21600 };
21601
21602 // Check if we already have an overlay for this hunk
21603 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21604 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21605 }) {
21606 // Just focus the existing overlay's prompt editor
21607 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21608 window.focus(&focus_handle, cx);
21609 return;
21610 }
21611
21612 // Dismiss overlays that have no comments for their hunks
21613 self.dismiss_overlays_without_comments(cx);
21614
21615 // Get the current user's avatar URI from the project's user_store
21616 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21617 let user_store = project.read(cx).user_store();
21618 user_store
21619 .read(cx)
21620 .current_user()
21621 .map(|user| user.avatar_uri.clone())
21622 });
21623
21624 // Create anchor at the end of the last row so the block appears immediately below it
21625 // Use multibuffer coordinates for anchor creation
21626 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21627 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21628
21629 // Use the hunk key we already computed
21630 let hunk_key = new_hunk_key;
21631
21632 // Create the prompt editor for the review input
21633 let prompt_editor = cx.new(|cx| {
21634 let mut editor = Editor::single_line(window, cx);
21635 editor.set_placeholder_text("Add a review comment...", window, cx);
21636 editor
21637 });
21638
21639 // Register the Newline action on the prompt editor to submit the review
21640 let parent_editor = cx.entity().downgrade();
21641 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21642 prompt_editor.register_action({
21643 let parent_editor = parent_editor.clone();
21644 move |_: &crate::actions::Newline, window, cx| {
21645 if let Some(editor) = parent_editor.upgrade() {
21646 editor.update(cx, |editor, cx| {
21647 editor.submit_diff_review_comment(window, cx);
21648 });
21649 }
21650 }
21651 })
21652 });
21653
21654 // Calculate initial height based on existing comments for this hunk
21655 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21656
21657 // Create the overlay block
21658 let prompt_editor_for_render = prompt_editor.clone();
21659 let hunk_key_for_render = hunk_key.clone();
21660 let editor_handle = cx.entity().downgrade();
21661 let block = BlockProperties {
21662 style: BlockStyle::Sticky,
21663 placement: BlockPlacement::Below(anchor),
21664 height: Some(initial_height),
21665 render: Arc::new(move |cx| {
21666 Self::render_diff_review_overlay(
21667 &prompt_editor_for_render,
21668 &hunk_key_for_render,
21669 &editor_handle,
21670 cx,
21671 )
21672 }),
21673 priority: 0,
21674 };
21675
21676 let block_ids = self.insert_blocks([block], None, cx);
21677 let Some(block_id) = block_ids.into_iter().next() else {
21678 log::error!("Failed to insert diff review overlay block");
21679 return;
21680 };
21681
21682 self.diff_review_overlays.push(DiffReviewOverlay {
21683 anchor_range,
21684 block_id,
21685 prompt_editor: prompt_editor.clone(),
21686 hunk_key,
21687 comments_expanded: true,
21688 inline_edit_editors: HashMap::default(),
21689 inline_edit_subscriptions: HashMap::default(),
21690 user_avatar_uri,
21691 _subscription: subscription,
21692 });
21693
21694 // Focus the prompt editor
21695 let focus_handle = prompt_editor.focus_handle(cx);
21696 window.focus(&focus_handle, cx);
21697
21698 cx.notify();
21699 }
21700
21701 /// Dismisses all diff review overlays.
21702 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21703 if self.diff_review_overlays.is_empty() {
21704 return;
21705 }
21706 let block_ids: HashSet<_> = self
21707 .diff_review_overlays
21708 .drain(..)
21709 .map(|overlay| overlay.block_id)
21710 .collect();
21711 self.remove_blocks(block_ids, None, cx);
21712 cx.notify();
21713 }
21714
21715 /// Dismisses overlays that have no comments stored for their hunks.
21716 /// Keeps overlays that have at least one comment.
21717 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21718 let snapshot = self.buffer.read(cx).snapshot(cx);
21719
21720 // First, compute which overlays have comments (to avoid borrow issues with retain)
21721 let overlays_with_comments: Vec<bool> = self
21722 .diff_review_overlays
21723 .iter()
21724 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21725 .collect();
21726
21727 // Now collect block IDs to remove and retain overlays
21728 let mut block_ids_to_remove = HashSet::default();
21729 let mut index = 0;
21730 self.diff_review_overlays.retain(|overlay| {
21731 let has_comments = overlays_with_comments[index];
21732 index += 1;
21733 if !has_comments {
21734 block_ids_to_remove.insert(overlay.block_id);
21735 }
21736 has_comments
21737 });
21738
21739 if !block_ids_to_remove.is_empty() {
21740 self.remove_blocks(block_ids_to_remove, None, cx);
21741 cx.notify();
21742 }
21743 }
21744
21745 /// Refreshes the diff review overlay block to update its height and render function.
21746 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21747 fn refresh_diff_review_overlay_height(
21748 &mut self,
21749 hunk_key: &DiffHunkKey,
21750 _window: &mut Window,
21751 cx: &mut Context<Self>,
21752 ) {
21753 // Extract all needed data from overlay first to avoid borrow conflicts
21754 let snapshot = self.buffer.read(cx).snapshot(cx);
21755 let (comments_expanded, block_id, prompt_editor) = {
21756 let Some(overlay) = self
21757 .diff_review_overlays
21758 .iter()
21759 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21760 else {
21761 return;
21762 };
21763
21764 (
21765 overlay.comments_expanded,
21766 overlay.block_id,
21767 overlay.prompt_editor.clone(),
21768 )
21769 };
21770
21771 // Calculate new height
21772 let snapshot = self.buffer.read(cx).snapshot(cx);
21773 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21774
21775 // Update the block height using resize_blocks (avoids flicker)
21776 let mut heights = HashMap::default();
21777 heights.insert(block_id, new_height);
21778 self.resize_blocks(heights, None, cx);
21779
21780 // Update the render function using replace_blocks (avoids flicker)
21781 let hunk_key_for_render = hunk_key.clone();
21782 let editor_handle = cx.entity().downgrade();
21783 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21784 Arc::new(move |cx| {
21785 Self::render_diff_review_overlay(
21786 &prompt_editor,
21787 &hunk_key_for_render,
21788 &editor_handle,
21789 cx,
21790 )
21791 });
21792
21793 let mut renderers = HashMap::default();
21794 renderers.insert(block_id, render);
21795 self.replace_blocks(renderers, None, cx);
21796 }
21797
21798 /// Action handler for SubmitDiffReviewComment.
21799 pub fn submit_diff_review_comment_action(
21800 &mut self,
21801 _: &SubmitDiffReviewComment,
21802 window: &mut Window,
21803 cx: &mut Context<Self>,
21804 ) {
21805 self.submit_diff_review_comment(window, cx);
21806 }
21807
21808 /// Stores the diff review comment locally.
21809 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21810 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21811 // Find the overlay that currently has focus
21812 let overlay_index = self
21813 .diff_review_overlays
21814 .iter()
21815 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21816 let Some(overlay_index) = overlay_index else {
21817 return;
21818 };
21819 let overlay = &self.diff_review_overlays[overlay_index];
21820
21821 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21822 if comment_text.is_empty() {
21823 return;
21824 }
21825
21826 let anchor_range = overlay.anchor_range.clone();
21827 let hunk_key = overlay.hunk_key.clone();
21828
21829 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21830
21831 // Clear the prompt editor but keep the overlay open
21832 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21833 overlay.prompt_editor.update(cx, |editor, cx| {
21834 editor.clear(window, cx);
21835 });
21836 }
21837
21838 // Refresh the overlay to update the block height for the new comment
21839 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21840
21841 cx.notify();
21842 }
21843
21844 /// Returns the prompt editor for the diff review overlay, if one is active.
21845 /// This is primarily used for testing.
21846 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21847 self.diff_review_overlays
21848 .first()
21849 .map(|overlay| &overlay.prompt_editor)
21850 }
21851
21852 /// Returns the line range for the first diff review overlay, if one is active.
21853 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21854 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21855 let overlay = self.diff_review_overlays.first()?;
21856 let snapshot = self.buffer.read(cx).snapshot(cx);
21857 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21858 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21859 let start_row = snapshot
21860 .point_to_buffer_point(start_point)
21861 .map(|(_, p, _)| p.row)
21862 .unwrap_or(start_point.row);
21863 let end_row = snapshot
21864 .point_to_buffer_point(end_point)
21865 .map(|(_, p, _)| p.row)
21866 .unwrap_or(end_point.row);
21867 Some((start_row, end_row))
21868 }
21869
21870 /// Sets whether the comments section is expanded in the diff review overlay.
21871 /// This is primarily used for testing.
21872 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21873 for overlay in &mut self.diff_review_overlays {
21874 overlay.comments_expanded = expanded;
21875 }
21876 cx.notify();
21877 }
21878
21879 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21880 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21881 a.file_path == b.file_path
21882 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21883 }
21884
21885 /// Returns comments for a specific hunk, ordered by creation time.
21886 pub fn comments_for_hunk<'a>(
21887 &'a self,
21888 key: &DiffHunkKey,
21889 snapshot: &MultiBufferSnapshot,
21890 ) -> &'a [StoredReviewComment] {
21891 let key_point = key.hunk_start_anchor.to_point(snapshot);
21892 self.stored_review_comments
21893 .iter()
21894 .find(|(k, _)| {
21895 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21896 })
21897 .map(|(_, comments)| comments.as_slice())
21898 .unwrap_or(&[])
21899 }
21900
21901 /// Returns the total count of stored review comments across all hunks.
21902 pub fn total_review_comment_count(&self) -> usize {
21903 self.stored_review_comments
21904 .iter()
21905 .map(|(_, v)| v.len())
21906 .sum()
21907 }
21908
21909 /// Returns the count of comments for a specific hunk.
21910 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21911 let key_point = key.hunk_start_anchor.to_point(snapshot);
21912 self.stored_review_comments
21913 .iter()
21914 .find(|(k, _)| {
21915 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21916 })
21917 .map(|(_, v)| v.len())
21918 .unwrap_or(0)
21919 }
21920
21921 /// Adds a new review comment to a specific hunk.
21922 pub fn add_review_comment(
21923 &mut self,
21924 hunk_key: DiffHunkKey,
21925 comment: String,
21926 anchor_range: Range<Anchor>,
21927 cx: &mut Context<Self>,
21928 ) -> usize {
21929 let id = self.next_review_comment_id;
21930 self.next_review_comment_id += 1;
21931
21932 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21933
21934 let snapshot = self.buffer.read(cx).snapshot(cx);
21935 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21936
21937 // Find existing entry for this hunk or add a new one
21938 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21939 k.file_path == hunk_key.file_path
21940 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21941 }) {
21942 comments.push(stored_comment);
21943 } else {
21944 self.stored_review_comments
21945 .push((hunk_key, vec![stored_comment]));
21946 }
21947
21948 cx.emit(EditorEvent::ReviewCommentsChanged {
21949 total_count: self.total_review_comment_count(),
21950 });
21951 cx.notify();
21952 id
21953 }
21954
21955 /// Removes a review comment by ID from any hunk.
21956 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21957 for (_, comments) in self.stored_review_comments.iter_mut() {
21958 if let Some(index) = comments.iter().position(|c| c.id == id) {
21959 comments.remove(index);
21960 cx.emit(EditorEvent::ReviewCommentsChanged {
21961 total_count: self.total_review_comment_count(),
21962 });
21963 cx.notify();
21964 return true;
21965 }
21966 }
21967 false
21968 }
21969
21970 /// Updates a review comment's text by ID.
21971 pub fn update_review_comment(
21972 &mut self,
21973 id: usize,
21974 new_comment: String,
21975 cx: &mut Context<Self>,
21976 ) -> bool {
21977 for (_, comments) in self.stored_review_comments.iter_mut() {
21978 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21979 comment.comment = new_comment;
21980 comment.is_editing = false;
21981 cx.emit(EditorEvent::ReviewCommentsChanged {
21982 total_count: self.total_review_comment_count(),
21983 });
21984 cx.notify();
21985 return true;
21986 }
21987 }
21988 false
21989 }
21990
21991 /// Sets a comment's editing state.
21992 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21993 for (_, comments) in self.stored_review_comments.iter_mut() {
21994 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21995 comment.is_editing = is_editing;
21996 cx.notify();
21997 return;
21998 }
21999 }
22000 }
22001
22002 /// Takes all stored comments from all hunks, clearing the storage.
22003 /// Returns a Vec of (hunk_key, comments) pairs.
22004 pub fn take_all_review_comments(
22005 &mut self,
22006 cx: &mut Context<Self>,
22007 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22008 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22009 self.dismiss_all_diff_review_overlays(cx);
22010 let comments = std::mem::take(&mut self.stored_review_comments);
22011 // Reset the ID counter since all comments have been taken
22012 self.next_review_comment_id = 0;
22013 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22014 cx.notify();
22015 comments
22016 }
22017
22018 /// Removes review comments whose anchors are no longer valid or whose
22019 /// associated diff hunks no longer exist.
22020 ///
22021 /// This should be called when the buffer changes to prevent orphaned comments
22022 /// from accumulating.
22023 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22024 let snapshot = self.buffer.read(cx).snapshot(cx);
22025 let original_count = self.total_review_comment_count();
22026
22027 // Remove comments with invalid hunk anchors
22028 self.stored_review_comments
22029 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22030
22031 // Also clean up individual comments with invalid anchor ranges
22032 for (_, comments) in &mut self.stored_review_comments {
22033 comments.retain(|comment| {
22034 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22035 });
22036 }
22037
22038 // Remove empty hunk entries
22039 self.stored_review_comments
22040 .retain(|(_, comments)| !comments.is_empty());
22041
22042 let new_count = self.total_review_comment_count();
22043 if new_count != original_count {
22044 cx.emit(EditorEvent::ReviewCommentsChanged {
22045 total_count: new_count,
22046 });
22047 cx.notify();
22048 }
22049 }
22050
22051 /// Toggles the expanded state of the comments section in the overlay.
22052 pub fn toggle_review_comments_expanded(
22053 &mut self,
22054 _: &ToggleReviewCommentsExpanded,
22055 window: &mut Window,
22056 cx: &mut Context<Self>,
22057 ) {
22058 // Find the overlay that currently has focus, or use the first one
22059 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22060 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22061 overlay.comments_expanded = !overlay.comments_expanded;
22062 Some(overlay.hunk_key.clone())
22063 } else {
22064 None
22065 }
22066 });
22067
22068 // If no focused overlay found, toggle the first one
22069 let hunk_key = overlay_info.or_else(|| {
22070 self.diff_review_overlays.first_mut().map(|overlay| {
22071 overlay.comments_expanded = !overlay.comments_expanded;
22072 overlay.hunk_key.clone()
22073 })
22074 });
22075
22076 if let Some(hunk_key) = hunk_key {
22077 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22078 cx.notify();
22079 }
22080 }
22081
22082 /// Handles the EditReviewComment action - sets a comment into editing mode.
22083 pub fn edit_review_comment(
22084 &mut self,
22085 action: &EditReviewComment,
22086 window: &mut Window,
22087 cx: &mut Context<Self>,
22088 ) {
22089 let comment_id = action.id;
22090
22091 // Set the comment to editing mode
22092 self.set_comment_editing(comment_id, true, cx);
22093
22094 // Find the overlay that contains this comment and create an inline editor if needed
22095 // First, find which hunk this comment belongs to
22096 let hunk_key = self
22097 .stored_review_comments
22098 .iter()
22099 .find_map(|(key, comments)| {
22100 if comments.iter().any(|c| c.id == comment_id) {
22101 Some(key.clone())
22102 } else {
22103 None
22104 }
22105 });
22106
22107 let snapshot = self.buffer.read(cx).snapshot(cx);
22108 if let Some(hunk_key) = hunk_key {
22109 if let Some(overlay) = self
22110 .diff_review_overlays
22111 .iter_mut()
22112 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22113 {
22114 if let std::collections::hash_map::Entry::Vacant(entry) =
22115 overlay.inline_edit_editors.entry(comment_id)
22116 {
22117 // Find the comment text
22118 let comment_text = self
22119 .stored_review_comments
22120 .iter()
22121 .flat_map(|(_, comments)| comments)
22122 .find(|c| c.id == comment_id)
22123 .map(|c| c.comment.clone())
22124 .unwrap_or_default();
22125
22126 // Create inline editor
22127 let parent_editor = cx.entity().downgrade();
22128 let inline_editor = cx.new(|cx| {
22129 let mut editor = Editor::single_line(window, cx);
22130 editor.set_text(&*comment_text, window, cx);
22131 // Select all text for easy replacement
22132 editor.select_all(&crate::actions::SelectAll, window, cx);
22133 editor
22134 });
22135
22136 // Register the Newline action to confirm the edit
22137 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22138 inline_editor.register_action({
22139 let parent_editor = parent_editor.clone();
22140 move |_: &crate::actions::Newline, window, cx| {
22141 if let Some(editor) = parent_editor.upgrade() {
22142 editor.update(cx, |editor, cx| {
22143 editor.confirm_edit_review_comment(comment_id, window, cx);
22144 });
22145 }
22146 }
22147 })
22148 });
22149
22150 // Store the subscription to keep the action handler alive
22151 overlay
22152 .inline_edit_subscriptions
22153 .insert(comment_id, subscription);
22154
22155 // Focus the inline editor
22156 let focus_handle = inline_editor.focus_handle(cx);
22157 window.focus(&focus_handle, cx);
22158
22159 entry.insert(inline_editor);
22160 }
22161 }
22162 }
22163
22164 cx.notify();
22165 }
22166
22167 /// Confirms an inline edit of a review comment.
22168 pub fn confirm_edit_review_comment(
22169 &mut self,
22170 comment_id: usize,
22171 _window: &mut Window,
22172 cx: &mut Context<Self>,
22173 ) {
22174 // Get the new text from the inline editor
22175 // Find the overlay containing this comment's inline editor
22176 let snapshot = self.buffer.read(cx).snapshot(cx);
22177 let hunk_key = self
22178 .stored_review_comments
22179 .iter()
22180 .find_map(|(key, comments)| {
22181 if comments.iter().any(|c| c.id == comment_id) {
22182 Some(key.clone())
22183 } else {
22184 None
22185 }
22186 });
22187
22188 let new_text = hunk_key
22189 .as_ref()
22190 .and_then(|hunk_key| {
22191 self.diff_review_overlays
22192 .iter()
22193 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22194 })
22195 .as_ref()
22196 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22197 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22198
22199 if let Some(new_text) = new_text {
22200 if !new_text.is_empty() {
22201 self.update_review_comment(comment_id, new_text, cx);
22202 }
22203 }
22204
22205 // Remove the inline editor and its subscription
22206 if let Some(hunk_key) = hunk_key {
22207 if let Some(overlay) = self
22208 .diff_review_overlays
22209 .iter_mut()
22210 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22211 {
22212 overlay.inline_edit_editors.remove(&comment_id);
22213 overlay.inline_edit_subscriptions.remove(&comment_id);
22214 }
22215 }
22216
22217 // Clear editing state
22218 self.set_comment_editing(comment_id, false, cx);
22219 }
22220
22221 /// Cancels an inline edit of a review comment.
22222 pub fn cancel_edit_review_comment(
22223 &mut self,
22224 comment_id: usize,
22225 _window: &mut Window,
22226 cx: &mut Context<Self>,
22227 ) {
22228 // Find which hunk this comment belongs to
22229 let hunk_key = self
22230 .stored_review_comments
22231 .iter()
22232 .find_map(|(key, comments)| {
22233 if comments.iter().any(|c| c.id == comment_id) {
22234 Some(key.clone())
22235 } else {
22236 None
22237 }
22238 });
22239
22240 // Remove the inline editor and its subscription
22241 if let Some(hunk_key) = hunk_key {
22242 let snapshot = self.buffer.read(cx).snapshot(cx);
22243 if let Some(overlay) = self
22244 .diff_review_overlays
22245 .iter_mut()
22246 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22247 {
22248 overlay.inline_edit_editors.remove(&comment_id);
22249 overlay.inline_edit_subscriptions.remove(&comment_id);
22250 }
22251 }
22252
22253 // Clear editing state
22254 self.set_comment_editing(comment_id, false, cx);
22255 }
22256
22257 /// Action handler for ConfirmEditReviewComment.
22258 pub fn confirm_edit_review_comment_action(
22259 &mut self,
22260 action: &ConfirmEditReviewComment,
22261 window: &mut Window,
22262 cx: &mut Context<Self>,
22263 ) {
22264 self.confirm_edit_review_comment(action.id, window, cx);
22265 }
22266
22267 /// Action handler for CancelEditReviewComment.
22268 pub fn cancel_edit_review_comment_action(
22269 &mut self,
22270 action: &CancelEditReviewComment,
22271 window: &mut Window,
22272 cx: &mut Context<Self>,
22273 ) {
22274 self.cancel_edit_review_comment(action.id, window, cx);
22275 }
22276
22277 /// Handles the DeleteReviewComment action - removes a comment.
22278 pub fn delete_review_comment(
22279 &mut self,
22280 action: &DeleteReviewComment,
22281 window: &mut Window,
22282 cx: &mut Context<Self>,
22283 ) {
22284 // Get the hunk key before removing the comment
22285 // Find the hunk key from the comment itself
22286 let comment_id = action.id;
22287 let hunk_key = self
22288 .stored_review_comments
22289 .iter()
22290 .find_map(|(key, comments)| {
22291 if comments.iter().any(|c| c.id == comment_id) {
22292 Some(key.clone())
22293 } else {
22294 None
22295 }
22296 });
22297
22298 // Also get it from the overlay for refresh purposes
22299 let overlay_hunk_key = self
22300 .diff_review_overlays
22301 .first()
22302 .map(|o| o.hunk_key.clone());
22303
22304 self.remove_review_comment(action.id, cx);
22305
22306 // Refresh the overlay height after removing a comment
22307 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22308 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22309 }
22310 }
22311
22312 fn render_diff_review_overlay(
22313 prompt_editor: &Entity<Editor>,
22314 hunk_key: &DiffHunkKey,
22315 editor_handle: &WeakEntity<Editor>,
22316 cx: &mut BlockContext,
22317 ) -> AnyElement {
22318 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22319 if ranges.is_empty() {
22320 return None;
22321 }
22322 let formatted: Vec<String> = ranges
22323 .iter()
22324 .map(|(start, end)| {
22325 let start_line = start + 1;
22326 let end_line = end + 1;
22327 if start_line == end_line {
22328 format!("Line {start_line}")
22329 } else {
22330 format!("Lines {start_line}-{end_line}")
22331 }
22332 })
22333 .collect();
22334 // Don't show label for single line in single excerpt
22335 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22336 return None;
22337 }
22338 Some(formatted.join(" ⋯ "))
22339 }
22340
22341 let theme = cx.theme();
22342 let colors = theme.colors();
22343
22344 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22345 editor_handle
22346 .upgrade()
22347 .map(|editor| {
22348 let editor = editor.read(cx);
22349 let snapshot = editor.buffer().read(cx).snapshot(cx);
22350 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22351 let (expanded, editors, avatar_uri, line_ranges) = editor
22352 .diff_review_overlays
22353 .iter()
22354 .find(|overlay| {
22355 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22356 })
22357 .map(|o| {
22358 let start_point = o.anchor_range.start.to_point(&snapshot);
22359 let end_point = o.anchor_range.end.to_point(&snapshot);
22360 // Get line ranges per excerpt to detect discontinuities
22361 let buffer_ranges =
22362 snapshot.range_to_buffer_ranges(start_point..end_point);
22363 let ranges: Vec<(u32, u32)> = buffer_ranges
22364 .iter()
22365 .map(|(buffer, range, _)| {
22366 let start = buffer.offset_to_point(range.start.0).row;
22367 let end = buffer.offset_to_point(range.end.0).row;
22368 (start, end)
22369 })
22370 .collect();
22371 (
22372 o.comments_expanded,
22373 o.inline_edit_editors.clone(),
22374 o.user_avatar_uri.clone(),
22375 if ranges.is_empty() {
22376 None
22377 } else {
22378 Some(ranges)
22379 },
22380 )
22381 })
22382 .unwrap_or((true, HashMap::default(), None, None));
22383 (comments, expanded, editors, avatar_uri, line_ranges)
22384 })
22385 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22386
22387 let comment_count = comments.len();
22388 let avatar_size = px(20.);
22389 let action_icon_size = IconSize::XSmall;
22390
22391 v_flex()
22392 .w_full()
22393 .bg(colors.editor_background)
22394 .border_b_1()
22395 .border_color(colors.border)
22396 .px_2()
22397 .pb_2()
22398 .gap_2()
22399 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22400 .when_some(line_ranges, |el, ranges| {
22401 let label = format_line_ranges(&ranges);
22402 if let Some(label) = label {
22403 el.child(
22404 h_flex()
22405 .w_full()
22406 .px_2()
22407 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22408 )
22409 } else {
22410 el
22411 }
22412 })
22413 // Top row: editable input with user's avatar
22414 .child(
22415 h_flex()
22416 .w_full()
22417 .items_center()
22418 .gap_2()
22419 .px_2()
22420 .py_1p5()
22421 .rounded_md()
22422 .bg(colors.surface_background)
22423 .child(
22424 div()
22425 .size(avatar_size)
22426 .flex_shrink_0()
22427 .rounded_full()
22428 .overflow_hidden()
22429 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22430 Avatar::new(avatar_uri.clone())
22431 .size(avatar_size)
22432 .into_any_element()
22433 } else {
22434 Icon::new(IconName::Person)
22435 .size(IconSize::Small)
22436 .color(ui::Color::Muted)
22437 .into_any_element()
22438 }),
22439 )
22440 .child(
22441 div()
22442 .flex_1()
22443 .border_1()
22444 .border_color(colors.border)
22445 .rounded_md()
22446 .bg(colors.editor_background)
22447 .px_2()
22448 .py_1()
22449 .child(prompt_editor.clone()),
22450 )
22451 .child(
22452 h_flex()
22453 .flex_shrink_0()
22454 .gap_1()
22455 .child(
22456 IconButton::new("diff-review-close", IconName::Close)
22457 .icon_color(ui::Color::Muted)
22458 .icon_size(action_icon_size)
22459 .tooltip(Tooltip::text("Close"))
22460 .on_click(|_, window, cx| {
22461 window
22462 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22463 }),
22464 )
22465 .child(
22466 IconButton::new("diff-review-add", IconName::Return)
22467 .icon_color(ui::Color::Muted)
22468 .icon_size(action_icon_size)
22469 .tooltip(Tooltip::text("Add comment"))
22470 .on_click(|_, window, cx| {
22471 window.dispatch_action(
22472 Box::new(crate::actions::SubmitDiffReviewComment),
22473 cx,
22474 );
22475 }),
22476 ),
22477 ),
22478 )
22479 // Expandable comments section (only shown when there are comments)
22480 .when(comment_count > 0, |el| {
22481 el.child(Self::render_comments_section(
22482 comments,
22483 comments_expanded,
22484 inline_editors,
22485 user_avatar_uri,
22486 avatar_size,
22487 action_icon_size,
22488 colors,
22489 ))
22490 })
22491 .into_any_element()
22492 }
22493
22494 fn render_comments_section(
22495 comments: Vec<StoredReviewComment>,
22496 expanded: bool,
22497 inline_editors: HashMap<usize, Entity<Editor>>,
22498 user_avatar_uri: Option<SharedUri>,
22499 avatar_size: Pixels,
22500 action_icon_size: IconSize,
22501 colors: &theme::ThemeColors,
22502 ) -> impl IntoElement {
22503 let comment_count = comments.len();
22504
22505 v_flex()
22506 .w_full()
22507 .gap_1()
22508 // Header with expand/collapse toggle
22509 .child(
22510 h_flex()
22511 .id("review-comments-header")
22512 .w_full()
22513 .items_center()
22514 .gap_1()
22515 .px_2()
22516 .py_1()
22517 .cursor_pointer()
22518 .rounded_md()
22519 .hover(|style| style.bg(colors.ghost_element_hover))
22520 .on_click(|_, window: &mut Window, cx| {
22521 window.dispatch_action(
22522 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22523 cx,
22524 );
22525 })
22526 .child(
22527 Icon::new(if expanded {
22528 IconName::ChevronDown
22529 } else {
22530 IconName::ChevronRight
22531 })
22532 .size(IconSize::Small)
22533 .color(ui::Color::Muted),
22534 )
22535 .child(
22536 Label::new(format!(
22537 "{} Comment{}",
22538 comment_count,
22539 if comment_count == 1 { "" } else { "s" }
22540 ))
22541 .size(LabelSize::Small)
22542 .color(Color::Muted),
22543 ),
22544 )
22545 // Comments list (when expanded)
22546 .when(expanded, |el| {
22547 el.children(comments.into_iter().map(|comment| {
22548 let inline_editor = inline_editors.get(&comment.id).cloned();
22549 Self::render_comment_row(
22550 comment,
22551 inline_editor,
22552 user_avatar_uri.clone(),
22553 avatar_size,
22554 action_icon_size,
22555 colors,
22556 )
22557 }))
22558 })
22559 }
22560
22561 fn render_comment_row(
22562 comment: StoredReviewComment,
22563 inline_editor: Option<Entity<Editor>>,
22564 user_avatar_uri: Option<SharedUri>,
22565 avatar_size: Pixels,
22566 action_icon_size: IconSize,
22567 colors: &theme::ThemeColors,
22568 ) -> impl IntoElement {
22569 let comment_id = comment.id;
22570 let is_editing = inline_editor.is_some();
22571
22572 h_flex()
22573 .w_full()
22574 .items_center()
22575 .gap_2()
22576 .px_2()
22577 .py_1p5()
22578 .rounded_md()
22579 .bg(colors.surface_background)
22580 .child(
22581 div()
22582 .size(avatar_size)
22583 .flex_shrink_0()
22584 .rounded_full()
22585 .overflow_hidden()
22586 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22587 Avatar::new(avatar_uri.clone())
22588 .size(avatar_size)
22589 .into_any_element()
22590 } else {
22591 Icon::new(IconName::Person)
22592 .size(IconSize::Small)
22593 .color(ui::Color::Muted)
22594 .into_any_element()
22595 }),
22596 )
22597 .child(if let Some(editor) = inline_editor {
22598 // Inline edit mode: show an editable text field
22599 div()
22600 .flex_1()
22601 .border_1()
22602 .border_color(colors.border)
22603 .rounded_md()
22604 .bg(colors.editor_background)
22605 .px_2()
22606 .py_1()
22607 .child(editor)
22608 .into_any_element()
22609 } else {
22610 // Display mode: show the comment text
22611 div()
22612 .flex_1()
22613 .text_sm()
22614 .text_color(colors.text)
22615 .child(comment.comment)
22616 .into_any_element()
22617 })
22618 .child(if is_editing {
22619 // Editing mode: show close and confirm buttons
22620 h_flex()
22621 .gap_1()
22622 .child(
22623 IconButton::new(
22624 format!("diff-review-cancel-edit-{comment_id}"),
22625 IconName::Close,
22626 )
22627 .icon_color(ui::Color::Muted)
22628 .icon_size(action_icon_size)
22629 .tooltip(Tooltip::text("Cancel"))
22630 .on_click(move |_, window, cx| {
22631 window.dispatch_action(
22632 Box::new(crate::actions::CancelEditReviewComment {
22633 id: comment_id,
22634 }),
22635 cx,
22636 );
22637 }),
22638 )
22639 .child(
22640 IconButton::new(
22641 format!("diff-review-confirm-edit-{comment_id}"),
22642 IconName::Return,
22643 )
22644 .icon_color(ui::Color::Muted)
22645 .icon_size(action_icon_size)
22646 .tooltip(Tooltip::text("Confirm"))
22647 .on_click(move |_, window, cx| {
22648 window.dispatch_action(
22649 Box::new(crate::actions::ConfirmEditReviewComment {
22650 id: comment_id,
22651 }),
22652 cx,
22653 );
22654 }),
22655 )
22656 .into_any_element()
22657 } else {
22658 // Display mode: no action buttons for now (edit/delete not yet implemented)
22659 gpui::Empty.into_any_element()
22660 })
22661 }
22662
22663 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22664 if self.display_map.read(cx).masked != masked {
22665 self.display_map.update(cx, |map, _| map.masked = masked);
22666 }
22667 cx.notify()
22668 }
22669
22670 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22671 self.show_wrap_guides = Some(show_wrap_guides);
22672 cx.notify();
22673 }
22674
22675 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22676 self.show_indent_guides = Some(show_indent_guides);
22677 cx.notify();
22678 }
22679
22680 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22681 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22682 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22683 && let Some(dir) = file.abs_path(cx).parent()
22684 {
22685 return Some(dir.to_owned());
22686 }
22687 }
22688
22689 None
22690 }
22691
22692 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22693 self.active_excerpt(cx)?
22694 .1
22695 .read(cx)
22696 .file()
22697 .and_then(|f| f.as_local())
22698 }
22699
22700 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22701 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22702 let buffer = buffer.read(cx);
22703 if let Some(project_path) = buffer.project_path(cx) {
22704 let project = self.project()?.read(cx);
22705 project.absolute_path(&project_path, cx)
22706 } else {
22707 buffer
22708 .file()
22709 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22710 }
22711 })
22712 }
22713
22714 pub fn reveal_in_finder(
22715 &mut self,
22716 _: &RevealInFileManager,
22717 _window: &mut Window,
22718 cx: &mut Context<Self>,
22719 ) {
22720 if let Some(path) = self.target_file_abs_path(cx) {
22721 if let Some(project) = self.project() {
22722 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22723 } else {
22724 cx.reveal_path(&path);
22725 }
22726 }
22727 }
22728
22729 pub fn copy_path(
22730 &mut self,
22731 _: &zed_actions::workspace::CopyPath,
22732 _window: &mut Window,
22733 cx: &mut Context<Self>,
22734 ) {
22735 if let Some(path) = self.target_file_abs_path(cx)
22736 && let Some(path) = path.to_str()
22737 {
22738 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22739 } else {
22740 cx.propagate();
22741 }
22742 }
22743
22744 pub fn copy_relative_path(
22745 &mut self,
22746 _: &zed_actions::workspace::CopyRelativePath,
22747 _window: &mut Window,
22748 cx: &mut Context<Self>,
22749 ) {
22750 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22751 let project = self.project()?.read(cx);
22752 let path = buffer.read(cx).file()?.path();
22753 let path = path.display(project.path_style(cx));
22754 Some(path)
22755 }) {
22756 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22757 } else {
22758 cx.propagate();
22759 }
22760 }
22761
22762 /// Returns the project path for the editor's buffer, if any buffer is
22763 /// opened in the editor.
22764 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22765 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22766 buffer.read(cx).project_path(cx)
22767 } else {
22768 None
22769 }
22770 }
22771
22772 // Returns true if the editor handled a go-to-line request
22773 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22774 maybe!({
22775 let breakpoint_store = self.breakpoint_store.as_ref()?;
22776
22777 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22778 else {
22779 self.clear_row_highlights::<ActiveDebugLine>();
22780 return None;
22781 };
22782
22783 let position = active_stack_frame.position;
22784 let buffer_id = position.buffer_id?;
22785 let snapshot = self
22786 .project
22787 .as_ref()?
22788 .read(cx)
22789 .buffer_for_id(buffer_id, cx)?
22790 .read(cx)
22791 .snapshot();
22792
22793 let mut handled = false;
22794 for (id, ExcerptRange { context, .. }) in
22795 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22796 {
22797 if context.start.cmp(&position, &snapshot).is_ge()
22798 || context.end.cmp(&position, &snapshot).is_lt()
22799 {
22800 continue;
22801 }
22802 let snapshot = self.buffer.read(cx).snapshot(cx);
22803 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22804
22805 handled = true;
22806 self.clear_row_highlights::<ActiveDebugLine>();
22807
22808 self.go_to_line::<ActiveDebugLine>(
22809 multibuffer_anchor,
22810 Some(cx.theme().colors().editor_debugger_active_line_background),
22811 window,
22812 cx,
22813 );
22814
22815 cx.notify();
22816 }
22817
22818 handled.then_some(())
22819 })
22820 .is_some()
22821 }
22822
22823 pub fn copy_file_name_without_extension(
22824 &mut self,
22825 _: &CopyFileNameWithoutExtension,
22826 _: &mut Window,
22827 cx: &mut Context<Self>,
22828 ) {
22829 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22830 let file = buffer.read(cx).file()?;
22831 file.path().file_stem()
22832 }) {
22833 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22834 }
22835 }
22836
22837 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22838 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22839 let file = buffer.read(cx).file()?;
22840 Some(file.file_name(cx))
22841 }) {
22842 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22843 }
22844 }
22845
22846 pub fn toggle_git_blame(
22847 &mut self,
22848 _: &::git::Blame,
22849 window: &mut Window,
22850 cx: &mut Context<Self>,
22851 ) {
22852 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22853
22854 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22855 self.start_git_blame(true, window, cx);
22856 }
22857
22858 cx.notify();
22859 }
22860
22861 pub fn toggle_git_blame_inline(
22862 &mut self,
22863 _: &ToggleGitBlameInline,
22864 window: &mut Window,
22865 cx: &mut Context<Self>,
22866 ) {
22867 self.toggle_git_blame_inline_internal(true, window, cx);
22868 cx.notify();
22869 }
22870
22871 pub fn open_git_blame_commit(
22872 &mut self,
22873 _: &OpenGitBlameCommit,
22874 window: &mut Window,
22875 cx: &mut Context<Self>,
22876 ) {
22877 self.open_git_blame_commit_internal(window, cx);
22878 }
22879
22880 fn open_git_blame_commit_internal(
22881 &mut self,
22882 window: &mut Window,
22883 cx: &mut Context<Self>,
22884 ) -> Option<()> {
22885 let blame = self.blame.as_ref()?;
22886 let snapshot = self.snapshot(window, cx);
22887 let cursor = self
22888 .selections
22889 .newest::<Point>(&snapshot.display_snapshot)
22890 .head();
22891 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22892 let (_, blame_entry) = blame
22893 .update(cx, |blame, cx| {
22894 blame
22895 .blame_for_rows(
22896 &[RowInfo {
22897 buffer_id: Some(buffer.remote_id()),
22898 buffer_row: Some(point.row),
22899 ..Default::default()
22900 }],
22901 cx,
22902 )
22903 .next()
22904 })
22905 .flatten()?;
22906 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22907 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22908 let workspace = self.workspace()?.downgrade();
22909 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22910 None
22911 }
22912
22913 pub fn git_blame_inline_enabled(&self) -> bool {
22914 self.git_blame_inline_enabled
22915 }
22916
22917 pub fn toggle_selection_menu(
22918 &mut self,
22919 _: &ToggleSelectionMenu,
22920 _: &mut Window,
22921 cx: &mut Context<Self>,
22922 ) {
22923 self.show_selection_menu = self
22924 .show_selection_menu
22925 .map(|show_selections_menu| !show_selections_menu)
22926 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22927
22928 cx.notify();
22929 }
22930
22931 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22932 self.show_selection_menu
22933 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22934 }
22935
22936 fn start_git_blame(
22937 &mut self,
22938 user_triggered: bool,
22939 window: &mut Window,
22940 cx: &mut Context<Self>,
22941 ) {
22942 if let Some(project) = self.project() {
22943 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22944 && buffer.read(cx).file().is_none()
22945 {
22946 return;
22947 }
22948
22949 let focused = self.focus_handle(cx).contains_focused(window, cx);
22950
22951 let project = project.clone();
22952 let blame = cx
22953 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22954 self.blame_subscription =
22955 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22956 self.blame = Some(blame);
22957 }
22958 }
22959
22960 fn toggle_git_blame_inline_internal(
22961 &mut self,
22962 user_triggered: bool,
22963 window: &mut Window,
22964 cx: &mut Context<Self>,
22965 ) {
22966 if self.git_blame_inline_enabled {
22967 self.git_blame_inline_enabled = false;
22968 self.show_git_blame_inline = false;
22969 self.show_git_blame_inline_delay_task.take();
22970 } else {
22971 self.git_blame_inline_enabled = true;
22972 self.start_git_blame_inline(user_triggered, window, cx);
22973 }
22974
22975 cx.notify();
22976 }
22977
22978 fn start_git_blame_inline(
22979 &mut self,
22980 user_triggered: bool,
22981 window: &mut Window,
22982 cx: &mut Context<Self>,
22983 ) {
22984 self.start_git_blame(user_triggered, window, cx);
22985
22986 if ProjectSettings::get_global(cx)
22987 .git
22988 .inline_blame_delay()
22989 .is_some()
22990 {
22991 self.start_inline_blame_timer(window, cx);
22992 } else {
22993 self.show_git_blame_inline = true
22994 }
22995 }
22996
22997 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22998 self.blame.as_ref()
22999 }
23000
23001 pub fn show_git_blame_gutter(&self) -> bool {
23002 self.show_git_blame_gutter
23003 }
23004
23005 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23006 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23007 }
23008
23009 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23010 self.show_git_blame_inline
23011 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23012 && !self.newest_selection_head_on_empty_line(cx)
23013 && self.has_blame_entries(cx)
23014 }
23015
23016 fn has_blame_entries(&self, cx: &App) -> bool {
23017 self.blame()
23018 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23019 }
23020
23021 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23022 let cursor_anchor = self.selections.newest_anchor().head();
23023
23024 let snapshot = self.buffer.read(cx).snapshot(cx);
23025 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23026
23027 snapshot.line_len(buffer_row) == 0
23028 }
23029
23030 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23031 let buffer_and_selection = maybe!({
23032 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23033 let selection_range = selection.range();
23034
23035 let multi_buffer = self.buffer().read(cx);
23036 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23037 let buffer_ranges = multi_buffer_snapshot
23038 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23039
23040 let (buffer, range, _) = if selection.reversed {
23041 buffer_ranges.first()
23042 } else {
23043 buffer_ranges.last()
23044 }?;
23045
23046 let buffer_range = range.to_point(buffer);
23047
23048 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23049 return Some((
23050 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23051 buffer_range.start.row..buffer_range.end.row,
23052 ));
23053 };
23054
23055 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23056 let start =
23057 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23058 let end =
23059 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23060
23061 Some((
23062 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23063 start.row..end.row,
23064 ))
23065 });
23066
23067 let Some((buffer, selection)) = buffer_and_selection else {
23068 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23069 };
23070
23071 let Some(project) = self.project() else {
23072 return Task::ready(Err(anyhow!("editor does not have project")));
23073 };
23074
23075 project.update(cx, |project, cx| {
23076 project.get_permalink_to_line(&buffer, selection, cx)
23077 })
23078 }
23079
23080 pub fn copy_permalink_to_line(
23081 &mut self,
23082 _: &CopyPermalinkToLine,
23083 window: &mut Window,
23084 cx: &mut Context<Self>,
23085 ) {
23086 let permalink_task = self.get_permalink_to_line(cx);
23087 let workspace = self.workspace();
23088
23089 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23090 Ok(permalink) => {
23091 cx.update(|_, cx| {
23092 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23093 })
23094 .ok();
23095 }
23096 Err(err) => {
23097 let message = format!("Failed to copy permalink: {err}");
23098
23099 anyhow::Result::<()>::Err(err).log_err();
23100
23101 if let Some(workspace) = workspace {
23102 workspace
23103 .update_in(cx, |workspace, _, cx| {
23104 struct CopyPermalinkToLine;
23105
23106 workspace.show_toast(
23107 Toast::new(
23108 NotificationId::unique::<CopyPermalinkToLine>(),
23109 message,
23110 ),
23111 cx,
23112 )
23113 })
23114 .ok();
23115 }
23116 }
23117 })
23118 .detach();
23119 }
23120
23121 pub fn copy_file_location(
23122 &mut self,
23123 _: &CopyFileLocation,
23124 _: &mut Window,
23125 cx: &mut Context<Self>,
23126 ) {
23127 let selection = self
23128 .selections
23129 .newest::<Point>(&self.display_snapshot(cx))
23130 .start
23131 .row
23132 + 1;
23133 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23134 let project = self.project()?.read(cx);
23135 let file = buffer.read(cx).file()?;
23136 let path = file.path().display(project.path_style(cx));
23137
23138 Some(format!("{path}:{selection}"))
23139 }) {
23140 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23141 }
23142 }
23143
23144 pub fn open_permalink_to_line(
23145 &mut self,
23146 _: &OpenPermalinkToLine,
23147 window: &mut Window,
23148 cx: &mut Context<Self>,
23149 ) {
23150 let permalink_task = self.get_permalink_to_line(cx);
23151 let workspace = self.workspace();
23152
23153 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23154 Ok(permalink) => {
23155 cx.update(|_, cx| {
23156 cx.open_url(permalink.as_ref());
23157 })
23158 .ok();
23159 }
23160 Err(err) => {
23161 let message = format!("Failed to open permalink: {err}");
23162
23163 anyhow::Result::<()>::Err(err).log_err();
23164
23165 if let Some(workspace) = workspace {
23166 workspace.update(cx, |workspace, cx| {
23167 struct OpenPermalinkToLine;
23168
23169 workspace.show_toast(
23170 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23171 cx,
23172 )
23173 });
23174 }
23175 }
23176 })
23177 .detach();
23178 }
23179
23180 pub fn insert_uuid_v4(
23181 &mut self,
23182 _: &InsertUuidV4,
23183 window: &mut Window,
23184 cx: &mut Context<Self>,
23185 ) {
23186 self.insert_uuid(UuidVersion::V4, window, cx);
23187 }
23188
23189 pub fn insert_uuid_v7(
23190 &mut self,
23191 _: &InsertUuidV7,
23192 window: &mut Window,
23193 cx: &mut Context<Self>,
23194 ) {
23195 self.insert_uuid(UuidVersion::V7, window, cx);
23196 }
23197
23198 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23199 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23200 self.transact(window, cx, |this, window, cx| {
23201 let edits = this
23202 .selections
23203 .all::<Point>(&this.display_snapshot(cx))
23204 .into_iter()
23205 .map(|selection| {
23206 let uuid = match version {
23207 UuidVersion::V4 => uuid::Uuid::new_v4(),
23208 UuidVersion::V7 => uuid::Uuid::now_v7(),
23209 };
23210
23211 (selection.range(), uuid.to_string())
23212 });
23213 this.edit(edits, cx);
23214 this.refresh_edit_prediction(true, false, window, cx);
23215 });
23216 }
23217
23218 pub fn open_selections_in_multibuffer(
23219 &mut self,
23220 _: &OpenSelectionsInMultibuffer,
23221 window: &mut Window,
23222 cx: &mut Context<Self>,
23223 ) {
23224 let multibuffer = self.buffer.read(cx);
23225
23226 let Some(buffer) = multibuffer.as_singleton() else {
23227 return;
23228 };
23229
23230 let Some(workspace) = self.workspace() else {
23231 return;
23232 };
23233
23234 let title = multibuffer.title(cx).to_string();
23235
23236 let locations = self
23237 .selections
23238 .all_anchors(&self.display_snapshot(cx))
23239 .iter()
23240 .map(|selection| {
23241 (
23242 buffer.clone(),
23243 (selection.start.text_anchor..selection.end.text_anchor)
23244 .to_point(buffer.read(cx)),
23245 )
23246 })
23247 .into_group_map();
23248
23249 cx.spawn_in(window, async move |_, cx| {
23250 workspace.update_in(cx, |workspace, window, cx| {
23251 Self::open_locations_in_multibuffer(
23252 workspace,
23253 locations,
23254 format!("Selections for '{title}'"),
23255 false,
23256 false,
23257 MultibufferSelectionMode::All,
23258 window,
23259 cx,
23260 );
23261 })
23262 })
23263 .detach();
23264 }
23265
23266 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23267 /// last highlight added will be used.
23268 ///
23269 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23270 pub fn highlight_rows<T: 'static>(
23271 &mut self,
23272 range: Range<Anchor>,
23273 color: Hsla,
23274 options: RowHighlightOptions,
23275 cx: &mut Context<Self>,
23276 ) {
23277 let snapshot = self.buffer().read(cx).snapshot(cx);
23278 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23279 let ix = row_highlights.binary_search_by(|highlight| {
23280 Ordering::Equal
23281 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23282 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23283 });
23284
23285 if let Err(mut ix) = ix {
23286 let index = post_inc(&mut self.highlight_order);
23287
23288 // If this range intersects with the preceding highlight, then merge it with
23289 // the preceding highlight. Otherwise insert a new highlight.
23290 let mut merged = false;
23291 if ix > 0 {
23292 let prev_highlight = &mut row_highlights[ix - 1];
23293 if prev_highlight
23294 .range
23295 .end
23296 .cmp(&range.start, &snapshot)
23297 .is_ge()
23298 {
23299 ix -= 1;
23300 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23301 prev_highlight.range.end = range.end;
23302 }
23303 merged = true;
23304 prev_highlight.index = index;
23305 prev_highlight.color = color;
23306 prev_highlight.options = options;
23307 }
23308 }
23309
23310 if !merged {
23311 row_highlights.insert(
23312 ix,
23313 RowHighlight {
23314 range,
23315 index,
23316 color,
23317 options,
23318 type_id: TypeId::of::<T>(),
23319 },
23320 );
23321 }
23322
23323 // If any of the following highlights intersect with this one, merge them.
23324 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23325 let highlight = &row_highlights[ix];
23326 if next_highlight
23327 .range
23328 .start
23329 .cmp(&highlight.range.end, &snapshot)
23330 .is_le()
23331 {
23332 if next_highlight
23333 .range
23334 .end
23335 .cmp(&highlight.range.end, &snapshot)
23336 .is_gt()
23337 {
23338 row_highlights[ix].range.end = next_highlight.range.end;
23339 }
23340 row_highlights.remove(ix + 1);
23341 } else {
23342 break;
23343 }
23344 }
23345 }
23346 }
23347
23348 /// Remove any highlighted row ranges of the given type that intersect the
23349 /// given ranges.
23350 pub fn remove_highlighted_rows<T: 'static>(
23351 &mut self,
23352 ranges_to_remove: Vec<Range<Anchor>>,
23353 cx: &mut Context<Self>,
23354 ) {
23355 let snapshot = self.buffer().read(cx).snapshot(cx);
23356 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23357 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23358 row_highlights.retain(|highlight| {
23359 while let Some(range_to_remove) = ranges_to_remove.peek() {
23360 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23361 Ordering::Less | Ordering::Equal => {
23362 ranges_to_remove.next();
23363 }
23364 Ordering::Greater => {
23365 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23366 Ordering::Less | Ordering::Equal => {
23367 return false;
23368 }
23369 Ordering::Greater => break,
23370 }
23371 }
23372 }
23373 }
23374
23375 true
23376 })
23377 }
23378
23379 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23380 pub fn clear_row_highlights<T: 'static>(&mut self) {
23381 self.highlighted_rows.remove(&TypeId::of::<T>());
23382 }
23383
23384 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23385 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23386 self.highlighted_rows
23387 .get(&TypeId::of::<T>())
23388 .map_or(&[] as &[_], |vec| vec.as_slice())
23389 .iter()
23390 .map(|highlight| (highlight.range.clone(), highlight.color))
23391 }
23392
23393 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23394 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23395 /// Allows to ignore certain kinds of highlights.
23396 pub fn highlighted_display_rows(
23397 &self,
23398 window: &mut Window,
23399 cx: &mut App,
23400 ) -> BTreeMap<DisplayRow, LineHighlight> {
23401 let snapshot = self.snapshot(window, cx);
23402 let mut used_highlight_orders = HashMap::default();
23403 self.highlighted_rows
23404 .iter()
23405 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23406 .fold(
23407 BTreeMap::<DisplayRow, LineHighlight>::new(),
23408 |mut unique_rows, highlight| {
23409 let start = highlight.range.start.to_display_point(&snapshot);
23410 let end = highlight.range.end.to_display_point(&snapshot);
23411 let start_row = start.row().0;
23412 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23413 {
23414 end.row().0.saturating_sub(1)
23415 } else {
23416 end.row().0
23417 };
23418 for row in start_row..=end_row {
23419 let used_index =
23420 used_highlight_orders.entry(row).or_insert(highlight.index);
23421 if highlight.index >= *used_index {
23422 *used_index = highlight.index;
23423 unique_rows.insert(
23424 DisplayRow(row),
23425 LineHighlight {
23426 include_gutter: highlight.options.include_gutter,
23427 border: None,
23428 background: highlight.color.into(),
23429 type_id: Some(highlight.type_id),
23430 },
23431 );
23432 }
23433 }
23434 unique_rows
23435 },
23436 )
23437 }
23438
23439 pub fn highlighted_display_row_for_autoscroll(
23440 &self,
23441 snapshot: &DisplaySnapshot,
23442 ) -> Option<DisplayRow> {
23443 self.highlighted_rows
23444 .values()
23445 .flat_map(|highlighted_rows| highlighted_rows.iter())
23446 .filter_map(|highlight| {
23447 if highlight.options.autoscroll {
23448 Some(highlight.range.start.to_display_point(snapshot).row())
23449 } else {
23450 None
23451 }
23452 })
23453 .min()
23454 }
23455
23456 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23457 self.highlight_background(
23458 HighlightKey::SearchWithinRange,
23459 ranges,
23460 |_, colors| colors.colors().editor_document_highlight_read_background,
23461 cx,
23462 )
23463 }
23464
23465 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23466 self.breadcrumb_header = Some(new_header);
23467 }
23468
23469 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23470 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23471 }
23472
23473 pub fn highlight_background(
23474 &mut self,
23475 key: HighlightKey,
23476 ranges: &[Range<Anchor>],
23477 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23478 cx: &mut Context<Self>,
23479 ) {
23480 self.background_highlights
23481 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23482 self.scrollbar_marker_state.dirty = true;
23483 cx.notify();
23484 }
23485
23486 pub fn highlight_background_key(
23487 &mut self,
23488 key: HighlightKey,
23489 ranges: &[Range<Anchor>],
23490 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23491 cx: &mut Context<Self>,
23492 ) {
23493 self.background_highlights
23494 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23495 self.scrollbar_marker_state.dirty = true;
23496 cx.notify();
23497 }
23498
23499 pub fn clear_background_highlights(
23500 &mut self,
23501 key: HighlightKey,
23502 cx: &mut Context<Self>,
23503 ) -> Option<BackgroundHighlight> {
23504 let text_highlights = self.background_highlights.remove(&key)?;
23505 if !text_highlights.1.is_empty() {
23506 self.scrollbar_marker_state.dirty = true;
23507 cx.notify();
23508 }
23509 Some(text_highlights)
23510 }
23511
23512 pub fn highlight_gutter<T: 'static>(
23513 &mut self,
23514 ranges: impl Into<Vec<Range<Anchor>>>,
23515 color_fetcher: fn(&App) -> Hsla,
23516 cx: &mut Context<Self>,
23517 ) {
23518 self.gutter_highlights
23519 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23520 cx.notify();
23521 }
23522
23523 pub fn clear_gutter_highlights<T: 'static>(
23524 &mut self,
23525 cx: &mut Context<Self>,
23526 ) -> Option<GutterHighlight> {
23527 cx.notify();
23528 self.gutter_highlights.remove(&TypeId::of::<T>())
23529 }
23530
23531 pub fn insert_gutter_highlight<T: 'static>(
23532 &mut self,
23533 range: Range<Anchor>,
23534 color_fetcher: fn(&App) -> Hsla,
23535 cx: &mut Context<Self>,
23536 ) {
23537 let snapshot = self.buffer().read(cx).snapshot(cx);
23538 let mut highlights = self
23539 .gutter_highlights
23540 .remove(&TypeId::of::<T>())
23541 .map(|(_, highlights)| highlights)
23542 .unwrap_or_default();
23543 let ix = highlights.binary_search_by(|highlight| {
23544 Ordering::Equal
23545 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23546 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23547 });
23548 if let Err(ix) = ix {
23549 highlights.insert(ix, range);
23550 }
23551 self.gutter_highlights
23552 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23553 }
23554
23555 pub fn remove_gutter_highlights<T: 'static>(
23556 &mut self,
23557 ranges_to_remove: Vec<Range<Anchor>>,
23558 cx: &mut Context<Self>,
23559 ) {
23560 let snapshot = self.buffer().read(cx).snapshot(cx);
23561 let Some((color_fetcher, mut gutter_highlights)) =
23562 self.gutter_highlights.remove(&TypeId::of::<T>())
23563 else {
23564 return;
23565 };
23566 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23567 gutter_highlights.retain(|highlight| {
23568 while let Some(range_to_remove) = ranges_to_remove.peek() {
23569 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23570 Ordering::Less | Ordering::Equal => {
23571 ranges_to_remove.next();
23572 }
23573 Ordering::Greater => {
23574 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23575 Ordering::Less | Ordering::Equal => {
23576 return false;
23577 }
23578 Ordering::Greater => break,
23579 }
23580 }
23581 }
23582 }
23583
23584 true
23585 });
23586 self.gutter_highlights
23587 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23588 }
23589
23590 #[cfg(feature = "test-support")]
23591 pub fn all_text_highlights(
23592 &self,
23593 window: &mut Window,
23594 cx: &mut Context<Self>,
23595 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23596 let snapshot = self.snapshot(window, cx);
23597 self.display_map.update(cx, |display_map, _| {
23598 display_map
23599 .all_text_highlights()
23600 .map(|(_, highlight)| {
23601 let (style, ranges) = highlight.as_ref();
23602 (
23603 *style,
23604 ranges
23605 .iter()
23606 .map(|range| range.clone().to_display_points(&snapshot))
23607 .collect(),
23608 )
23609 })
23610 .collect()
23611 })
23612 }
23613
23614 #[cfg(feature = "test-support")]
23615 pub fn all_text_background_highlights(
23616 &self,
23617 window: &mut Window,
23618 cx: &mut Context<Self>,
23619 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23620 let snapshot = self.snapshot(window, cx);
23621 let buffer = &snapshot.buffer_snapshot();
23622 let start = buffer.anchor_before(MultiBufferOffset(0));
23623 let end = buffer.anchor_after(buffer.len());
23624 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23625 }
23626
23627 #[cfg(any(test, feature = "test-support"))]
23628 pub fn sorted_background_highlights_in_range(
23629 &self,
23630 search_range: Range<Anchor>,
23631 display_snapshot: &DisplaySnapshot,
23632 theme: &Theme,
23633 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23634 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23635 res.sort_by(|a, b| {
23636 a.0.start
23637 .cmp(&b.0.start)
23638 .then_with(|| a.0.end.cmp(&b.0.end))
23639 .then_with(|| a.1.cmp(&b.1))
23640 });
23641 res
23642 }
23643
23644 #[cfg(feature = "test-support")]
23645 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23646 let snapshot = self.buffer().read(cx).snapshot(cx);
23647
23648 let highlights = self
23649 .background_highlights
23650 .get(&HighlightKey::BufferSearchHighlights);
23651
23652 if let Some((_color, ranges)) = highlights {
23653 ranges
23654 .iter()
23655 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23656 .collect_vec()
23657 } else {
23658 vec![]
23659 }
23660 }
23661
23662 fn document_highlights_for_position<'a>(
23663 &'a self,
23664 position: Anchor,
23665 buffer: &'a MultiBufferSnapshot,
23666 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23667 let read_highlights = self
23668 .background_highlights
23669 .get(&HighlightKey::DocumentHighlightRead)
23670 .map(|h| &h.1);
23671 let write_highlights = self
23672 .background_highlights
23673 .get(&HighlightKey::DocumentHighlightWrite)
23674 .map(|h| &h.1);
23675 let left_position = position.bias_left(buffer);
23676 let right_position = position.bias_right(buffer);
23677 read_highlights
23678 .into_iter()
23679 .chain(write_highlights)
23680 .flat_map(move |ranges| {
23681 let start_ix = match ranges.binary_search_by(|probe| {
23682 let cmp = probe.end.cmp(&left_position, buffer);
23683 if cmp.is_ge() {
23684 Ordering::Greater
23685 } else {
23686 Ordering::Less
23687 }
23688 }) {
23689 Ok(i) | Err(i) => i,
23690 };
23691
23692 ranges[start_ix..]
23693 .iter()
23694 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23695 })
23696 }
23697
23698 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23699 self.background_highlights
23700 .get(&key)
23701 .is_some_and(|(_, highlights)| !highlights.is_empty())
23702 }
23703
23704 /// Returns all background highlights for a given range.
23705 ///
23706 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23707 pub fn background_highlights_in_range(
23708 &self,
23709 search_range: Range<Anchor>,
23710 display_snapshot: &DisplaySnapshot,
23711 theme: &Theme,
23712 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23713 let mut results = Vec::new();
23714 for (color_fetcher, ranges) in self.background_highlights.values() {
23715 let start_ix = match ranges.binary_search_by(|probe| {
23716 let cmp = probe
23717 .end
23718 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23719 if cmp.is_gt() {
23720 Ordering::Greater
23721 } else {
23722 Ordering::Less
23723 }
23724 }) {
23725 Ok(i) | Err(i) => i,
23726 };
23727 for (index, range) in ranges[start_ix..].iter().enumerate() {
23728 if range
23729 .start
23730 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23731 .is_ge()
23732 {
23733 break;
23734 }
23735
23736 let color = color_fetcher(&(start_ix + index), theme);
23737 let start = range.start.to_display_point(display_snapshot);
23738 let end = range.end.to_display_point(display_snapshot);
23739 results.push((start..end, color))
23740 }
23741 }
23742 results
23743 }
23744
23745 pub fn gutter_highlights_in_range(
23746 &self,
23747 search_range: Range<Anchor>,
23748 display_snapshot: &DisplaySnapshot,
23749 cx: &App,
23750 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23751 let mut results = Vec::new();
23752 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23753 let color = color_fetcher(cx);
23754 let start_ix = match ranges.binary_search_by(|probe| {
23755 let cmp = probe
23756 .end
23757 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23758 if cmp.is_gt() {
23759 Ordering::Greater
23760 } else {
23761 Ordering::Less
23762 }
23763 }) {
23764 Ok(i) | Err(i) => i,
23765 };
23766 for range in &ranges[start_ix..] {
23767 if range
23768 .start
23769 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23770 .is_ge()
23771 {
23772 break;
23773 }
23774
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 /// Get the text ranges corresponding to the redaction query
23784 pub fn redacted_ranges(
23785 &self,
23786 search_range: Range<Anchor>,
23787 display_snapshot: &DisplaySnapshot,
23788 cx: &App,
23789 ) -> Vec<Range<DisplayPoint>> {
23790 display_snapshot
23791 .buffer_snapshot()
23792 .redacted_ranges(search_range, |file| {
23793 if let Some(file) = file {
23794 file.is_private()
23795 && EditorSettings::get(
23796 Some(SettingsLocation {
23797 worktree_id: file.worktree_id(cx),
23798 path: file.path().as_ref(),
23799 }),
23800 cx,
23801 )
23802 .redact_private_values
23803 } else {
23804 false
23805 }
23806 })
23807 .map(|range| {
23808 range.start.to_display_point(display_snapshot)
23809 ..range.end.to_display_point(display_snapshot)
23810 })
23811 .collect()
23812 }
23813
23814 pub fn highlight_text_key(
23815 &mut self,
23816 key: HighlightKey,
23817 ranges: Vec<Range<Anchor>>,
23818 style: HighlightStyle,
23819 merge: bool,
23820 cx: &mut Context<Self>,
23821 ) {
23822 self.display_map.update(cx, |map, cx| {
23823 map.highlight_text(key, ranges, style, merge, cx);
23824 });
23825 cx.notify();
23826 }
23827
23828 pub fn highlight_text(
23829 &mut self,
23830 key: HighlightKey,
23831 ranges: Vec<Range<Anchor>>,
23832 style: HighlightStyle,
23833 cx: &mut Context<Self>,
23834 ) {
23835 self.display_map.update(cx, |map, cx| {
23836 map.highlight_text(key, ranges, style, false, cx)
23837 });
23838 cx.notify();
23839 }
23840
23841 pub fn text_highlights<'a>(
23842 &'a self,
23843 key: HighlightKey,
23844 cx: &'a App,
23845 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23846 self.display_map.read(cx).text_highlights(key)
23847 }
23848
23849 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23850 let cleared = self
23851 .display_map
23852 .update(cx, |map, _| map.clear_highlights(key));
23853 if cleared {
23854 cx.notify();
23855 }
23856 }
23857
23858 pub fn clear_highlights_with(
23859 &mut self,
23860 f: &mut dyn FnMut(&HighlightKey) -> bool,
23861 cx: &mut Context<Self>,
23862 ) {
23863 let cleared = self
23864 .display_map
23865 .update(cx, |map, _| map.clear_highlights_with(f));
23866 if cleared {
23867 cx.notify();
23868 }
23869 }
23870
23871 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23872 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23873 && self.focus_handle.is_focused(window)
23874 }
23875
23876 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23877 self.show_cursor_when_unfocused = is_enabled;
23878 cx.notify();
23879 }
23880
23881 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23882 cx.notify();
23883 }
23884
23885 fn on_debug_session_event(
23886 &mut self,
23887 _session: Entity<Session>,
23888 event: &SessionEvent,
23889 cx: &mut Context<Self>,
23890 ) {
23891 if let SessionEvent::InvalidateInlineValue = event {
23892 self.refresh_inline_values(cx);
23893 }
23894 }
23895
23896 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23897 let Some(project) = self.project.clone() else {
23898 return;
23899 };
23900
23901 if !self.inline_value_cache.enabled {
23902 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23903 self.splice_inlays(&inlays, Vec::new(), cx);
23904 return;
23905 }
23906
23907 let current_execution_position = self
23908 .highlighted_rows
23909 .get(&TypeId::of::<ActiveDebugLine>())
23910 .and_then(|lines| lines.last().map(|line| line.range.end));
23911
23912 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23913 let inline_values = editor
23914 .update(cx, |editor, cx| {
23915 let Some(current_execution_position) = current_execution_position else {
23916 return Some(Task::ready(Ok(Vec::new())));
23917 };
23918
23919 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23920 let snapshot = buffer.snapshot(cx);
23921
23922 let excerpt = snapshot.excerpt_containing(
23923 current_execution_position..current_execution_position,
23924 )?;
23925
23926 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23927 })?;
23928
23929 let range =
23930 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23931
23932 project.inline_values(buffer, range, cx)
23933 })
23934 .ok()
23935 .flatten()?
23936 .await
23937 .context("refreshing debugger inlays")
23938 .log_err()?;
23939
23940 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23941
23942 for (buffer_id, inline_value) in inline_values
23943 .into_iter()
23944 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23945 {
23946 buffer_inline_values
23947 .entry(buffer_id)
23948 .or_default()
23949 .push(inline_value);
23950 }
23951
23952 editor
23953 .update(cx, |editor, cx| {
23954 let snapshot = editor.buffer.read(cx).snapshot(cx);
23955 let mut new_inlays = Vec::default();
23956
23957 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23958 let buffer_id = buffer_snapshot.remote_id();
23959 buffer_inline_values
23960 .get(&buffer_id)
23961 .into_iter()
23962 .flatten()
23963 .for_each(|hint| {
23964 let inlay = Inlay::debugger(
23965 post_inc(&mut editor.next_inlay_id),
23966 Anchor::in_buffer(excerpt_id, hint.position),
23967 hint.text(),
23968 );
23969 if !inlay.text().chars().contains(&'\n') {
23970 new_inlays.push(inlay);
23971 }
23972 });
23973 }
23974
23975 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23976 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23977
23978 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23979 })
23980 .ok()?;
23981 Some(())
23982 });
23983 }
23984
23985 fn on_buffer_event(
23986 &mut self,
23987 multibuffer: &Entity<MultiBuffer>,
23988 event: &multi_buffer::Event,
23989 window: &mut Window,
23990 cx: &mut Context<Self>,
23991 ) {
23992 match event {
23993 multi_buffer::Event::Edited { edited_buffer } => {
23994 self.scrollbar_marker_state.dirty = true;
23995 self.active_indent_guides_state.dirty = true;
23996 self.refresh_active_diagnostics(cx);
23997 self.refresh_code_actions(window, cx);
23998 self.refresh_single_line_folds(window, cx);
23999 let snapshot = self.snapshot(window, cx);
24000 self.refresh_matching_bracket_highlights(&snapshot, cx);
24001 self.refresh_outline_symbols_at_cursor(cx);
24002 self.refresh_sticky_headers(&snapshot, cx);
24003 if self.has_active_edit_prediction() {
24004 self.update_visible_edit_prediction(window, cx);
24005 }
24006
24007 // Clean up orphaned review comments after edits
24008 self.cleanup_orphaned_review_comments(cx);
24009
24010 if let Some(buffer) = edited_buffer {
24011 if buffer.read(cx).file().is_none() {
24012 cx.emit(EditorEvent::TitleChanged);
24013 }
24014
24015 if self.project.is_some() {
24016 let buffer_id = buffer.read(cx).remote_id();
24017 self.register_buffer(buffer_id, cx);
24018 self.update_lsp_data(Some(buffer_id), window, cx);
24019 self.refresh_inlay_hints(
24020 InlayHintRefreshReason::BufferEdited(buffer_id),
24021 cx,
24022 );
24023 }
24024 }
24025
24026 cx.emit(EditorEvent::BufferEdited);
24027 cx.emit(SearchEvent::MatchesInvalidated);
24028
24029 let Some(project) = &self.project else { return };
24030 let (telemetry, is_via_ssh) = {
24031 let project = project.read(cx);
24032 let telemetry = project.client().telemetry().clone();
24033 let is_via_ssh = project.is_via_remote_server();
24034 (telemetry, is_via_ssh)
24035 };
24036 telemetry.log_edit_event("editor", is_via_ssh);
24037 }
24038 multi_buffer::Event::ExcerptsAdded {
24039 buffer,
24040 predecessor,
24041 excerpts,
24042 } => {
24043 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24044 let buffer_id = buffer.read(cx).remote_id();
24045 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24046 && let Some(project) = &self.project
24047 {
24048 update_uncommitted_diff_for_buffer(
24049 cx.entity(),
24050 project,
24051 [buffer.clone()],
24052 self.buffer.clone(),
24053 cx,
24054 )
24055 .detach();
24056 }
24057 self.semantic_token_state
24058 .invalidate_buffer(&buffer.read(cx).remote_id());
24059 self.update_lsp_data(Some(buffer_id), window, cx);
24060 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24061 self.colorize_brackets(false, cx);
24062 self.refresh_selected_text_highlights(true, window, cx);
24063 cx.emit(EditorEvent::ExcerptsAdded {
24064 buffer: buffer.clone(),
24065 predecessor: *predecessor,
24066 excerpts: excerpts.clone(),
24067 });
24068 }
24069 multi_buffer::Event::ExcerptsRemoved {
24070 ids,
24071 removed_buffer_ids,
24072 } => {
24073 if let Some(inlay_hints) = &mut self.inlay_hints {
24074 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24075 }
24076 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24077 for buffer_id in removed_buffer_ids {
24078 self.registered_buffers.remove(buffer_id);
24079 self.tasks
24080 .retain(|(task_buffer_id, _), _| task_buffer_id != buffer_id);
24081 self.semantic_token_state.invalidate_buffer(buffer_id);
24082 self.display_map.update(cx, |display_map, cx| {
24083 display_map.invalidate_semantic_highlights(*buffer_id);
24084 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24085 });
24086 }
24087 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24088 cx.emit(EditorEvent::ExcerptsRemoved {
24089 ids: ids.clone(),
24090 removed_buffer_ids: removed_buffer_ids.clone(),
24091 });
24092 }
24093 multi_buffer::Event::ExcerptsEdited {
24094 excerpt_ids,
24095 buffer_ids,
24096 } => {
24097 self.display_map.update(cx, |map, cx| {
24098 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24099 });
24100 cx.emit(EditorEvent::ExcerptsEdited {
24101 ids: excerpt_ids.clone(),
24102 });
24103 }
24104 multi_buffer::Event::ExcerptsExpanded { ids } => {
24105 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24106 self.refresh_document_highlights(cx);
24107 let snapshot = multibuffer.read(cx).snapshot(cx);
24108 for id in ids {
24109 self.fetched_tree_sitter_chunks.remove(id);
24110 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24111 self.semantic_token_state
24112 .invalidate_buffer(&buffer.remote_id());
24113 }
24114 }
24115 self.colorize_brackets(false, cx);
24116 self.update_lsp_data(None, window, cx);
24117 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24118 }
24119 multi_buffer::Event::Reparsed(buffer_id) => {
24120 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24121 self.refresh_selected_text_highlights(true, window, cx);
24122 self.colorize_brackets(true, cx);
24123 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24124
24125 cx.emit(EditorEvent::Reparsed(*buffer_id));
24126 }
24127 multi_buffer::Event::DiffHunksToggled => {
24128 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24129 }
24130 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24131 if !is_fresh_language {
24132 self.registered_buffers.remove(&buffer_id);
24133 }
24134 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24135 cx.emit(EditorEvent::Reparsed(*buffer_id));
24136 cx.notify();
24137 }
24138 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24139 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24140 multi_buffer::Event::FileHandleChanged
24141 | multi_buffer::Event::Reloaded
24142 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24143 multi_buffer::Event::DiagnosticsUpdated => {
24144 self.update_diagnostics_state(window, cx);
24145 }
24146 _ => {}
24147 };
24148 }
24149
24150 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24151 if !self.diagnostics_enabled() {
24152 return;
24153 }
24154 self.refresh_active_diagnostics(cx);
24155 self.refresh_inline_diagnostics(true, window, cx);
24156 self.scrollbar_marker_state.dirty = true;
24157 cx.notify();
24158 }
24159
24160 pub fn start_temporary_diff_override(&mut self) {
24161 self.load_diff_task.take();
24162 self.temporary_diff_override = true;
24163 }
24164
24165 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24166 self.temporary_diff_override = false;
24167 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24168 self.buffer.update(cx, |buffer, cx| {
24169 buffer.set_all_diff_hunks_collapsed(cx);
24170 });
24171
24172 if let Some(project) = self.project.clone() {
24173 self.load_diff_task = Some(
24174 update_uncommitted_diff_for_buffer(
24175 cx.entity(),
24176 &project,
24177 self.buffer.read(cx).all_buffers(),
24178 self.buffer.clone(),
24179 cx,
24180 )
24181 .shared(),
24182 );
24183 }
24184 }
24185
24186 fn on_display_map_changed(
24187 &mut self,
24188 _: Entity<DisplayMap>,
24189 _: &mut Window,
24190 cx: &mut Context<Self>,
24191 ) {
24192 cx.notify();
24193 }
24194
24195 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24196 if !self.mode.is_full() {
24197 return None;
24198 }
24199
24200 let theme_settings = theme::ThemeSettings::get_global(cx);
24201 let theme = cx.theme();
24202 let accent_colors = theme.accents().clone();
24203
24204 let accent_overrides = theme_settings
24205 .theme_overrides
24206 .get(theme.name.as_ref())
24207 .map(|theme_style| &theme_style.accents)
24208 .into_iter()
24209 .flatten()
24210 .chain(
24211 theme_settings
24212 .experimental_theme_overrides
24213 .as_ref()
24214 .map(|overrides| &overrides.accents)
24215 .into_iter()
24216 .flatten(),
24217 )
24218 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24219 .collect();
24220
24221 Some(AccentData {
24222 colors: accent_colors,
24223 overrides: accent_overrides,
24224 })
24225 }
24226
24227 fn fetch_applicable_language_settings(
24228 &self,
24229 cx: &App,
24230 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24231 if !self.mode.is_full() {
24232 return HashMap::default();
24233 }
24234
24235 self.buffer().read(cx).all_buffers().into_iter().fold(
24236 HashMap::default(),
24237 |mut acc, buffer| {
24238 let buffer = buffer.read(cx);
24239 let language = buffer.language().map(|language| language.name());
24240 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24241 let file = buffer.file();
24242 v.insert(language_settings(language, file, cx).into_owned());
24243 }
24244 acc
24245 },
24246 )
24247 }
24248
24249 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24250 let new_language_settings = self.fetch_applicable_language_settings(cx);
24251 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24252 self.applicable_language_settings = new_language_settings;
24253
24254 let new_accents = self.fetch_accent_data(cx);
24255 let accents_changed = new_accents != self.accent_data;
24256 self.accent_data = new_accents;
24257
24258 if self.diagnostics_enabled() {
24259 let new_severity = EditorSettings::get_global(cx)
24260 .diagnostics_max_severity
24261 .unwrap_or(DiagnosticSeverity::Hint);
24262 self.set_max_diagnostics_severity(new_severity, cx);
24263 }
24264 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24265 self.update_edit_prediction_settings(cx);
24266 self.refresh_edit_prediction(true, false, window, cx);
24267 self.refresh_inline_values(cx);
24268
24269 let old_cursor_shape = self.cursor_shape;
24270 let old_show_breadcrumbs = self.show_breadcrumbs;
24271
24272 {
24273 let editor_settings = EditorSettings::get_global(cx);
24274 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24275 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24276 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24277 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24278 }
24279
24280 if old_cursor_shape != self.cursor_shape {
24281 cx.emit(EditorEvent::CursorShapeChanged);
24282 }
24283
24284 if old_show_breadcrumbs != self.show_breadcrumbs {
24285 cx.emit(EditorEvent::BreadcrumbsChanged);
24286 }
24287
24288 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24289 let project_settings = ProjectSettings::get_global(cx);
24290 (
24291 project_settings.session.restore_unsaved_buffers,
24292 project_settings.diagnostics.inline.enabled,
24293 project_settings.git.inline_blame.enabled,
24294 )
24295 };
24296 self.buffer_serialization = self
24297 .should_serialize_buffer()
24298 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24299
24300 if self.mode.is_full() {
24301 if self.show_inline_diagnostics != show_inline_diagnostics {
24302 self.show_inline_diagnostics = show_inline_diagnostics;
24303 self.refresh_inline_diagnostics(false, window, cx);
24304 }
24305
24306 if self.git_blame_inline_enabled != inline_blame_enabled {
24307 self.toggle_git_blame_inline_internal(false, window, cx);
24308 }
24309
24310 let minimap_settings = EditorSettings::get_global(cx).minimap;
24311 if self.minimap_visibility != MinimapVisibility::Disabled {
24312 if self.minimap_visibility.settings_visibility()
24313 != minimap_settings.minimap_enabled()
24314 {
24315 self.set_minimap_visibility(
24316 MinimapVisibility::for_mode(self.mode(), cx),
24317 window,
24318 cx,
24319 );
24320 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24321 minimap_entity.update(cx, |minimap_editor, cx| {
24322 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24323 })
24324 }
24325 }
24326
24327 if language_settings_changed || accents_changed {
24328 self.colorize_brackets(true, cx);
24329 }
24330
24331 if language_settings_changed {
24332 self.clear_disabled_lsp_folding_ranges(window, cx);
24333 self.refresh_document_symbols(None, cx);
24334 }
24335
24336 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24337 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24338 }) {
24339 if !inlay_splice.is_empty() {
24340 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24341 }
24342 self.refresh_document_colors(None, window, cx);
24343 }
24344
24345 self.refresh_inlay_hints(
24346 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24347 self.selections.newest_anchor().head(),
24348 &self.buffer.read(cx).snapshot(cx),
24349 cx,
24350 )),
24351 cx,
24352 );
24353
24354 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24355 .global_lsp_settings
24356 .semantic_token_rules
24357 .clone();
24358 let semantic_token_rules_changed = self
24359 .semantic_token_state
24360 .update_rules(new_semantic_token_rules);
24361 if language_settings_changed || semantic_token_rules_changed {
24362 self.invalidate_semantic_tokens(None);
24363 self.refresh_semantic_tokens(None, None, cx);
24364 }
24365 }
24366
24367 cx.notify();
24368 }
24369
24370 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24371 if !self.mode.is_full() {
24372 return;
24373 }
24374
24375 let new_accents = self.fetch_accent_data(cx);
24376 if new_accents != self.accent_data {
24377 self.accent_data = new_accents;
24378 self.colorize_brackets(true, cx);
24379 }
24380
24381 self.invalidate_semantic_tokens(None);
24382 self.refresh_semantic_tokens(None, None, cx);
24383 }
24384
24385 pub fn set_searchable(&mut self, searchable: bool) {
24386 self.searchable = searchable;
24387 }
24388
24389 pub fn searchable(&self) -> bool {
24390 self.searchable
24391 }
24392
24393 pub fn open_excerpts_in_split(
24394 &mut self,
24395 _: &OpenExcerptsSplit,
24396 window: &mut Window,
24397 cx: &mut Context<Self>,
24398 ) {
24399 self.open_excerpts_common(None, true, window, cx)
24400 }
24401
24402 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24403 self.open_excerpts_common(None, false, window, cx)
24404 }
24405
24406 pub(crate) fn open_excerpts_common(
24407 &mut self,
24408 jump_data: Option<JumpData>,
24409 split: bool,
24410 window: &mut Window,
24411 cx: &mut Context<Self>,
24412 ) {
24413 if self.buffer.read(cx).is_singleton() {
24414 cx.propagate();
24415 return;
24416 }
24417
24418 let mut new_selections_by_buffer = HashMap::default();
24419 match &jump_data {
24420 Some(JumpData::MultiBufferPoint {
24421 excerpt_id,
24422 position,
24423 anchor,
24424 line_offset_from_top,
24425 }) => {
24426 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24427 if let Some(buffer) = multi_buffer_snapshot
24428 .buffer_id_for_excerpt(*excerpt_id)
24429 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24430 {
24431 let buffer_snapshot = buffer.read(cx).snapshot();
24432 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24433 language::ToPoint::to_point(anchor, &buffer_snapshot)
24434 } else {
24435 buffer_snapshot.clip_point(*position, Bias::Left)
24436 };
24437 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24438 new_selections_by_buffer.insert(
24439 buffer,
24440 (
24441 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24442 Some(*line_offset_from_top),
24443 ),
24444 );
24445 }
24446 }
24447 Some(JumpData::MultiBufferRow {
24448 row,
24449 line_offset_from_top,
24450 }) => {
24451 let point = MultiBufferPoint::new(row.0, 0);
24452 if let Some((buffer, buffer_point, _)) =
24453 self.buffer.read(cx).point_to_buffer_point(point, cx)
24454 {
24455 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24456 new_selections_by_buffer
24457 .entry(buffer)
24458 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24459 .0
24460 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24461 }
24462 }
24463 None => {
24464 let selections = self
24465 .selections
24466 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24467 let multi_buffer = self.buffer.read(cx);
24468 for selection in selections {
24469 for (snapshot, range, _, anchor) in multi_buffer
24470 .snapshot(cx)
24471 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24472 {
24473 if let Some(anchor) = anchor {
24474 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24475 else {
24476 continue;
24477 };
24478 let offset = text::ToOffset::to_offset(
24479 &anchor.text_anchor,
24480 &buffer_handle.read(cx).snapshot(),
24481 );
24482 let range = BufferOffset(offset)..BufferOffset(offset);
24483 new_selections_by_buffer
24484 .entry(buffer_handle)
24485 .or_insert((Vec::new(), None))
24486 .0
24487 .push(range)
24488 } else {
24489 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24490 else {
24491 continue;
24492 };
24493 new_selections_by_buffer
24494 .entry(buffer_handle)
24495 .or_insert((Vec::new(), None))
24496 .0
24497 .push(range)
24498 }
24499 }
24500 }
24501 }
24502 }
24503
24504 if self.delegate_open_excerpts {
24505 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24506 .into_iter()
24507 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24508 .collect();
24509 if !selections_by_buffer.is_empty() {
24510 cx.emit(EditorEvent::OpenExcerptsRequested {
24511 selections_by_buffer,
24512 split,
24513 });
24514 }
24515 return;
24516 }
24517
24518 let Some(workspace) = self.workspace() else {
24519 cx.propagate();
24520 return;
24521 };
24522
24523 new_selections_by_buffer
24524 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24525
24526 if new_selections_by_buffer.is_empty() {
24527 return;
24528 }
24529
24530 Self::open_buffers_in_workspace(
24531 workspace.downgrade(),
24532 new_selections_by_buffer,
24533 split,
24534 window,
24535 cx,
24536 );
24537 }
24538
24539 pub(crate) fn open_buffers_in_workspace(
24540 workspace: WeakEntity<Workspace>,
24541 new_selections_by_buffer: HashMap<
24542 Entity<language::Buffer>,
24543 (Vec<Range<BufferOffset>>, Option<u32>),
24544 >,
24545 split: bool,
24546 window: &mut Window,
24547 cx: &mut App,
24548 ) {
24549 // We defer the pane interaction because we ourselves are a workspace item
24550 // and activating a new item causes the pane to call a method on us reentrantly,
24551 // which panics if we're on the stack.
24552 window.defer(cx, move |window, cx| {
24553 workspace
24554 .update(cx, |workspace, cx| {
24555 let pane = if split {
24556 workspace.adjacent_pane(window, cx)
24557 } else {
24558 workspace.active_pane().clone()
24559 };
24560
24561 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24562 let buffer_read = buffer.read(cx);
24563 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24564 (true, project::File::from_dyn(Some(file)).is_some())
24565 } else {
24566 (false, false)
24567 };
24568
24569 // If project file is none workspace.open_project_item will fail to open the excerpt
24570 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24571 // so we check if there's a tab match in that case first
24572 let editor = (!has_file || !is_project_file)
24573 .then(|| {
24574 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24575 // so `workspace.open_project_item` will never find them, always opening a new editor.
24576 // Instead, we try to activate the existing editor in the pane first.
24577 let (editor, pane_item_index, pane_item_id) =
24578 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24579 let editor = item.downcast::<Editor>()?;
24580 let singleton_buffer =
24581 editor.read(cx).buffer().read(cx).as_singleton()?;
24582 if singleton_buffer == buffer {
24583 Some((editor, i, item.item_id()))
24584 } else {
24585 None
24586 }
24587 })?;
24588 pane.update(cx, |pane, cx| {
24589 pane.activate_item(pane_item_index, true, true, window, cx);
24590 if !PreviewTabsSettings::get_global(cx)
24591 .enable_preview_from_multibuffer
24592 {
24593 pane.unpreview_item_if_preview(pane_item_id);
24594 }
24595 });
24596 Some(editor)
24597 })
24598 .flatten()
24599 .unwrap_or_else(|| {
24600 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24601 .enable_keep_preview_on_code_navigation;
24602 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24603 .enable_preview_from_multibuffer;
24604 workspace.open_project_item::<Self>(
24605 pane.clone(),
24606 buffer,
24607 true,
24608 true,
24609 keep_old_preview,
24610 allow_new_preview,
24611 window,
24612 cx,
24613 )
24614 });
24615
24616 editor.update(cx, |editor, cx| {
24617 if has_file && !is_project_file {
24618 editor.set_read_only(true);
24619 }
24620 let autoscroll = match scroll_offset {
24621 Some(scroll_offset) => {
24622 Autoscroll::top_relative(scroll_offset as usize)
24623 }
24624 None => Autoscroll::newest(),
24625 };
24626 let nav_history = editor.nav_history.take();
24627 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24628 let Some((excerpt_id, _, buffer_snapshot)) =
24629 multibuffer_snapshot.as_singleton()
24630 else {
24631 return;
24632 };
24633 editor.change_selections(
24634 SelectionEffects::scroll(autoscroll),
24635 window,
24636 cx,
24637 |s| {
24638 s.select_ranges(ranges.into_iter().map(|range| {
24639 let range = buffer_snapshot.anchor_before(range.start)
24640 ..buffer_snapshot.anchor_after(range.end);
24641 multibuffer_snapshot
24642 .anchor_range_in_excerpt(excerpt_id, range)
24643 .unwrap()
24644 }));
24645 },
24646 );
24647 editor.nav_history = nav_history;
24648 });
24649 }
24650 })
24651 .ok();
24652 });
24653 }
24654
24655 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24656 let snapshot = self.buffer.read(cx).read(cx);
24657 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24658 Some(
24659 ranges
24660 .iter()
24661 .map(move |range| {
24662 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24663 })
24664 .collect(),
24665 )
24666 }
24667
24668 fn selection_replacement_ranges(
24669 &self,
24670 range: Range<MultiBufferOffsetUtf16>,
24671 cx: &mut App,
24672 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24673 let selections = self
24674 .selections
24675 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24676 let newest_selection = selections
24677 .iter()
24678 .max_by_key(|selection| selection.id)
24679 .unwrap();
24680 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24681 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24682 let snapshot = self.buffer.read(cx).read(cx);
24683 selections
24684 .into_iter()
24685 .map(|mut selection| {
24686 selection.start.0.0 =
24687 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24688 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24689 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24690 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24691 })
24692 .collect()
24693 }
24694
24695 fn report_editor_event(
24696 &self,
24697 reported_event: ReportEditorEvent,
24698 file_extension: Option<String>,
24699 cx: &App,
24700 ) {
24701 if cfg!(any(test, feature = "test-support")) {
24702 return;
24703 }
24704
24705 let Some(project) = &self.project else { return };
24706
24707 // If None, we are in a file without an extension
24708 let file = self
24709 .buffer
24710 .read(cx)
24711 .as_singleton()
24712 .and_then(|b| b.read(cx).file());
24713 let file_extension = file_extension.or(file
24714 .as_ref()
24715 .and_then(|file| Path::new(file.file_name(cx)).extension())
24716 .and_then(|e| e.to_str())
24717 .map(|a| a.to_string()));
24718
24719 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24720 .map(|vim_mode| vim_mode.0)
24721 .unwrap_or(false);
24722
24723 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24724 let copilot_enabled = edit_predictions_provider
24725 == language::language_settings::EditPredictionProvider::Copilot;
24726 let copilot_enabled_for_language = self
24727 .buffer
24728 .read(cx)
24729 .language_settings(cx)
24730 .show_edit_predictions;
24731
24732 let project = project.read(cx);
24733 let event_type = reported_event.event_type();
24734
24735 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24736 telemetry::event!(
24737 event_type,
24738 type = if auto_saved {"autosave"} else {"manual"},
24739 file_extension,
24740 vim_mode,
24741 copilot_enabled,
24742 copilot_enabled_for_language,
24743 edit_predictions_provider,
24744 is_via_ssh = project.is_via_remote_server(),
24745 );
24746 } else {
24747 telemetry::event!(
24748 event_type,
24749 file_extension,
24750 vim_mode,
24751 copilot_enabled,
24752 copilot_enabled_for_language,
24753 edit_predictions_provider,
24754 is_via_ssh = project.is_via_remote_server(),
24755 );
24756 };
24757 }
24758
24759 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24760 /// with each line being an array of {text, highlight} objects.
24761 fn copy_highlight_json(
24762 &mut self,
24763 _: &CopyHighlightJson,
24764 window: &mut Window,
24765 cx: &mut Context<Self>,
24766 ) {
24767 #[derive(Serialize)]
24768 struct Chunk<'a> {
24769 text: String,
24770 highlight: Option<&'a str>,
24771 }
24772
24773 let snapshot = self.buffer.read(cx).snapshot(cx);
24774 let range = self
24775 .selected_text_range(false, window, cx)
24776 .and_then(|selection| {
24777 if selection.range.is_empty() {
24778 None
24779 } else {
24780 Some(
24781 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24782 selection.range.start,
24783 )))
24784 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24785 selection.range.end,
24786 ))),
24787 )
24788 }
24789 })
24790 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24791
24792 let chunks = snapshot.chunks(range, true);
24793 let mut lines = Vec::new();
24794 let mut line: VecDeque<Chunk> = VecDeque::new();
24795
24796 let Some(style) = self.style.as_ref() else {
24797 return;
24798 };
24799
24800 for chunk in chunks {
24801 let highlight = chunk
24802 .syntax_highlight_id
24803 .and_then(|id| id.name(&style.syntax));
24804 let mut chunk_lines = chunk.text.split('\n').peekable();
24805 while let Some(text) = chunk_lines.next() {
24806 let mut merged_with_last_token = false;
24807 if let Some(last_token) = line.back_mut()
24808 && last_token.highlight == highlight
24809 {
24810 last_token.text.push_str(text);
24811 merged_with_last_token = true;
24812 }
24813
24814 if !merged_with_last_token {
24815 line.push_back(Chunk {
24816 text: text.into(),
24817 highlight,
24818 });
24819 }
24820
24821 if chunk_lines.peek().is_some() {
24822 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24823 line.pop_front();
24824 }
24825 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24826 line.pop_back();
24827 }
24828
24829 lines.push(mem::take(&mut line));
24830 }
24831 }
24832 }
24833
24834 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24835 return;
24836 };
24837 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24838 }
24839
24840 pub fn open_context_menu(
24841 &mut self,
24842 _: &OpenContextMenu,
24843 window: &mut Window,
24844 cx: &mut Context<Self>,
24845 ) {
24846 self.request_autoscroll(Autoscroll::newest(), cx);
24847 let position = self
24848 .selections
24849 .newest_display(&self.display_snapshot(cx))
24850 .start;
24851 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24852 }
24853
24854 pub fn replay_insert_event(
24855 &mut self,
24856 text: &str,
24857 relative_utf16_range: Option<Range<isize>>,
24858 window: &mut Window,
24859 cx: &mut Context<Self>,
24860 ) {
24861 if !self.input_enabled {
24862 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24863 return;
24864 }
24865 if let Some(relative_utf16_range) = relative_utf16_range {
24866 let selections = self
24867 .selections
24868 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24869 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24870 let new_ranges = selections.into_iter().map(|range| {
24871 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24872 range
24873 .head()
24874 .0
24875 .0
24876 .saturating_add_signed(relative_utf16_range.start),
24877 ));
24878 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24879 range
24880 .head()
24881 .0
24882 .0
24883 .saturating_add_signed(relative_utf16_range.end),
24884 ));
24885 start..end
24886 });
24887 s.select_ranges(new_ranges);
24888 });
24889 }
24890
24891 self.handle_input(text, window, cx);
24892 }
24893
24894 pub fn is_focused(&self, window: &Window) -> bool {
24895 self.focus_handle.is_focused(window)
24896 }
24897
24898 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24899 cx.emit(EditorEvent::Focused);
24900
24901 if let Some(descendant) = self
24902 .last_focused_descendant
24903 .take()
24904 .and_then(|descendant| descendant.upgrade())
24905 {
24906 window.focus(&descendant, cx);
24907 } else {
24908 if let Some(blame) = self.blame.as_ref() {
24909 blame.update(cx, GitBlame::focus)
24910 }
24911
24912 self.blink_manager.update(cx, BlinkManager::enable);
24913 self.show_cursor_names(window, cx);
24914 self.buffer.update(cx, |buffer, cx| {
24915 buffer.finalize_last_transaction(cx);
24916 if self.leader_id.is_none() {
24917 buffer.set_active_selections(
24918 &self.selections.disjoint_anchors_arc(),
24919 self.selections.line_mode(),
24920 self.cursor_shape,
24921 cx,
24922 );
24923 }
24924 });
24925
24926 if let Some(position_map) = self.last_position_map.clone() {
24927 EditorElement::mouse_moved(
24928 self,
24929 &MouseMoveEvent {
24930 position: window.mouse_position(),
24931 pressed_button: None,
24932 modifiers: window.modifiers(),
24933 },
24934 &position_map,
24935 None,
24936 window,
24937 cx,
24938 );
24939 }
24940 }
24941 }
24942
24943 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24944 cx.emit(EditorEvent::FocusedIn)
24945 }
24946
24947 fn handle_focus_out(
24948 &mut self,
24949 event: FocusOutEvent,
24950 _window: &mut Window,
24951 cx: &mut Context<Self>,
24952 ) {
24953 if event.blurred != self.focus_handle {
24954 self.last_focused_descendant = Some(event.blurred);
24955 }
24956 self.selection_drag_state = SelectionDragState::None;
24957 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24958 }
24959
24960 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24961 self.blink_manager.update(cx, BlinkManager::disable);
24962 self.buffer
24963 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24964
24965 if let Some(blame) = self.blame.as_ref() {
24966 blame.update(cx, GitBlame::blur)
24967 }
24968 if !self.hover_state.focused(window, cx) {
24969 hide_hover(self, cx);
24970 }
24971 if !self
24972 .context_menu
24973 .borrow()
24974 .as_ref()
24975 .is_some_and(|context_menu| context_menu.focused(window, cx))
24976 {
24977 self.hide_context_menu(window, cx);
24978 }
24979 self.take_active_edit_prediction(cx);
24980 cx.emit(EditorEvent::Blurred);
24981 cx.notify();
24982 }
24983
24984 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24985 let mut pending: String = window
24986 .pending_input_keystrokes()
24987 .into_iter()
24988 .flatten()
24989 .filter_map(|keystroke| keystroke.key_char.clone())
24990 .collect();
24991
24992 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24993 pending = "".to_string();
24994 }
24995
24996 let existing_pending = self
24997 .text_highlights(HighlightKey::PendingInput, cx)
24998 .map(|(_, ranges)| ranges.to_vec());
24999 if existing_pending.is_none() && pending.is_empty() {
25000 return;
25001 }
25002 let transaction =
25003 self.transact(window, cx, |this, window, cx| {
25004 let selections = this
25005 .selections
25006 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25007 let edits = selections
25008 .iter()
25009 .map(|selection| (selection.end..selection.end, pending.clone()));
25010 this.edit(edits, cx);
25011 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25012 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25013 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25014 }));
25015 });
25016 if let Some(existing_ranges) = existing_pending {
25017 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25018 this.edit(edits, cx);
25019 }
25020 });
25021
25022 let snapshot = self.snapshot(window, cx);
25023 let ranges = self
25024 .selections
25025 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25026 .into_iter()
25027 .map(|selection| {
25028 snapshot.buffer_snapshot().anchor_after(selection.end)
25029 ..snapshot
25030 .buffer_snapshot()
25031 .anchor_before(selection.end + pending.len())
25032 })
25033 .collect();
25034
25035 if pending.is_empty() {
25036 self.clear_highlights(HighlightKey::PendingInput, cx);
25037 } else {
25038 self.highlight_text(
25039 HighlightKey::PendingInput,
25040 ranges,
25041 HighlightStyle {
25042 underline: Some(UnderlineStyle {
25043 thickness: px(1.),
25044 color: None,
25045 wavy: false,
25046 }),
25047 ..Default::default()
25048 },
25049 cx,
25050 );
25051 }
25052
25053 self.ime_transaction = self.ime_transaction.or(transaction);
25054 if let Some(transaction) = self.ime_transaction {
25055 self.buffer.update(cx, |buffer, cx| {
25056 buffer.group_until_transaction(transaction, cx);
25057 });
25058 }
25059
25060 if self
25061 .text_highlights(HighlightKey::PendingInput, cx)
25062 .is_none()
25063 {
25064 self.ime_transaction.take();
25065 }
25066 }
25067
25068 pub fn register_action_renderer(
25069 &mut self,
25070 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25071 ) -> Subscription {
25072 let id = self.next_editor_action_id.post_inc();
25073 self.editor_actions
25074 .borrow_mut()
25075 .insert(id, Box::new(listener));
25076
25077 let editor_actions = self.editor_actions.clone();
25078 Subscription::new(move || {
25079 editor_actions.borrow_mut().remove(&id);
25080 })
25081 }
25082
25083 pub fn register_action<A: Action>(
25084 &mut self,
25085 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25086 ) -> Subscription {
25087 let id = self.next_editor_action_id.post_inc();
25088 let listener = Arc::new(listener);
25089 self.editor_actions.borrow_mut().insert(
25090 id,
25091 Box::new(move |_, window, _| {
25092 let listener = listener.clone();
25093 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25094 let action = action.downcast_ref().unwrap();
25095 if phase == DispatchPhase::Bubble {
25096 listener(action, window, cx)
25097 }
25098 })
25099 }),
25100 );
25101
25102 let editor_actions = self.editor_actions.clone();
25103 Subscription::new(move || {
25104 editor_actions.borrow_mut().remove(&id);
25105 })
25106 }
25107
25108 pub fn file_header_size(&self) -> u32 {
25109 FILE_HEADER_HEIGHT
25110 }
25111
25112 pub fn restore(
25113 &mut self,
25114 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25115 window: &mut Window,
25116 cx: &mut Context<Self>,
25117 ) {
25118 self.buffer().update(cx, |multi_buffer, cx| {
25119 for (buffer_id, changes) in revert_changes {
25120 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25121 buffer.update(cx, |buffer, cx| {
25122 buffer.edit(
25123 changes
25124 .into_iter()
25125 .map(|(range, text)| (range, text.to_string())),
25126 None,
25127 cx,
25128 );
25129 });
25130 }
25131 }
25132 });
25133 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25134 selections.refresh()
25135 });
25136 }
25137
25138 pub fn to_pixel_point(
25139 &mut self,
25140 source: Anchor,
25141 editor_snapshot: &EditorSnapshot,
25142 window: &mut Window,
25143 cx: &mut App,
25144 ) -> Option<gpui::Point<Pixels>> {
25145 let source_point = source.to_display_point(editor_snapshot);
25146 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25147 }
25148
25149 pub fn display_to_pixel_point(
25150 &mut self,
25151 source: DisplayPoint,
25152 editor_snapshot: &EditorSnapshot,
25153 window: &mut Window,
25154 cx: &mut App,
25155 ) -> Option<gpui::Point<Pixels>> {
25156 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25157 let text_layout_details = self.text_layout_details(window, cx);
25158 let scroll_top = text_layout_details
25159 .scroll_anchor
25160 .scroll_position(editor_snapshot)
25161 .y;
25162
25163 if source.row().as_f64() < scroll_top.floor() {
25164 return None;
25165 }
25166 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25167 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25168 Some(gpui::Point::new(source_x, source_y))
25169 }
25170
25171 pub fn has_visible_completions_menu(&self) -> bool {
25172 !self.edit_prediction_preview_is_active()
25173 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25174 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25175 })
25176 }
25177
25178 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25179 if self.mode.is_minimap() {
25180 return;
25181 }
25182 self.addons
25183 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25184 }
25185
25186 pub fn unregister_addon<T: Addon>(&mut self) {
25187 self.addons.remove(&std::any::TypeId::of::<T>());
25188 }
25189
25190 pub fn addon<T: Addon>(&self) -> Option<&T> {
25191 let type_id = std::any::TypeId::of::<T>();
25192 self.addons
25193 .get(&type_id)
25194 .and_then(|item| item.to_any().downcast_ref::<T>())
25195 }
25196
25197 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25198 let type_id = std::any::TypeId::of::<T>();
25199 self.addons
25200 .get_mut(&type_id)
25201 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25202 }
25203
25204 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25205 let text_layout_details = self.text_layout_details(window, cx);
25206 let style = &text_layout_details.editor_style;
25207 let font_id = window.text_system().resolve_font(&style.text.font());
25208 let font_size = style.text.font_size.to_pixels(window.rem_size());
25209 let line_height = style.text.line_height_in_pixels(window.rem_size());
25210 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25211 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25212
25213 CharacterDimensions {
25214 em_width,
25215 em_advance,
25216 line_height,
25217 }
25218 }
25219
25220 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25221 self.load_diff_task.clone()
25222 }
25223
25224 fn read_metadata_from_db(
25225 &mut self,
25226 item_id: u64,
25227 workspace_id: WorkspaceId,
25228 window: &mut Window,
25229 cx: &mut Context<Editor>,
25230 ) {
25231 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25232 && !self.mode.is_minimap()
25233 && WorkspaceSettings::get(None, cx).restore_on_startup
25234 != RestoreOnStartupBehavior::EmptyTab
25235 {
25236 let buffer_snapshot = OnceCell::new();
25237
25238 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
25239 && !folds.is_empty()
25240 {
25241 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25242 let snapshot_len = snapshot.len().0;
25243
25244 // Helper: search for fingerprint in buffer, return offset if found
25245 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25246 // Ensure we start at a character boundary (defensive)
25247 let search_start = snapshot
25248 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25249 .0;
25250 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25251
25252 let mut byte_offset = search_start;
25253 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25254 if byte_offset > search_end {
25255 break;
25256 }
25257 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25258 return Some(byte_offset);
25259 }
25260 byte_offset += ch.len_utf8();
25261 }
25262 None
25263 };
25264
25265 // Track search position to handle duplicate fingerprints correctly.
25266 // Folds are stored in document order, so we advance after each match.
25267 let mut search_start = 0usize;
25268
25269 let valid_folds: Vec<_> = folds
25270 .into_iter()
25271 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25272 // Skip folds without fingerprints (old data before migration)
25273 let sfp = start_fp?;
25274 let efp = end_fp?;
25275 let efp_len = efp.len();
25276
25277 // Fast path: check if fingerprints match at stored offsets
25278 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25279 let start_matches = stored_start < snapshot_len
25280 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25281 let efp_check_pos = stored_end.saturating_sub(efp_len);
25282 let end_matches = efp_check_pos >= stored_start
25283 && stored_end <= snapshot_len
25284 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25285
25286 let (new_start, new_end) = if start_matches && end_matches {
25287 // Offsets unchanged, use stored values
25288 (stored_start, stored_end)
25289 } else if sfp == efp {
25290 // Short fold: identical fingerprints can only match once per search
25291 // Use stored fold length to compute new_end
25292 let new_start = find_fingerprint(&sfp, search_start)?;
25293 let fold_len = stored_end - stored_start;
25294 let new_end = new_start + fold_len;
25295 (new_start, new_end)
25296 } else {
25297 // Slow path: search for fingerprints in buffer
25298 let new_start = find_fingerprint(&sfp, search_start)?;
25299 // Search for end_fp after start, then add efp_len to get actual fold end
25300 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25301 let new_end = efp_pos + efp_len;
25302 (new_start, new_end)
25303 };
25304
25305 // Advance search position for next fold
25306 search_start = new_end;
25307
25308 // Validate fold makes sense (end must be after start)
25309 if new_end <= new_start {
25310 return None;
25311 }
25312
25313 Some(
25314 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25315 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25316 )
25317 })
25318 .collect();
25319
25320 if !valid_folds.is_empty() {
25321 self.fold_ranges(valid_folds, false, window, cx);
25322
25323 // Migrate folds to current entity_id before workspace cleanup runs.
25324 // Entity IDs change between sessions, but workspace cleanup deletes
25325 // old editor rows (cascading to folds) based on current entity IDs.
25326 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
25327 if new_editor_id != item_id {
25328 cx.spawn(async move |_, _| {
25329 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
25330 .await
25331 .log_err();
25332 })
25333 .detach();
25334 }
25335 }
25336 }
25337
25338 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25339 && !selections.is_empty()
25340 {
25341 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25342 // skip adding the initial selection to selection history
25343 self.selection_history.mode = SelectionHistoryMode::Skipping;
25344 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25345 s.select_ranges(selections.into_iter().map(|(start, end)| {
25346 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25347 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25348 }));
25349 });
25350 self.selection_history.mode = SelectionHistoryMode::Normal;
25351 };
25352 }
25353
25354 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25355 }
25356
25357 fn update_lsp_data(
25358 &mut self,
25359 for_buffer: Option<BufferId>,
25360 window: &mut Window,
25361 cx: &mut Context<'_, Self>,
25362 ) {
25363 if !self.enable_lsp_data {
25364 return;
25365 }
25366
25367 if let Some(buffer_id) = for_buffer {
25368 self.pull_diagnostics(buffer_id, window, cx);
25369 }
25370 self.refresh_semantic_tokens(for_buffer, None, cx);
25371 self.refresh_document_colors(for_buffer, window, cx);
25372 self.refresh_folding_ranges(for_buffer, window, cx);
25373 self.refresh_document_symbols(for_buffer, cx);
25374 }
25375
25376 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25377 if !self.mode().is_full() {
25378 return;
25379 }
25380 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25381 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25382 }
25383 }
25384
25385 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25386 if !self.mode().is_full() {
25387 return;
25388 }
25389
25390 if !self.registered_buffers.contains_key(&buffer_id)
25391 && let Some(project) = self.project.as_ref()
25392 {
25393 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25394 project.update(cx, |project, cx| {
25395 self.registered_buffers.insert(
25396 buffer_id,
25397 project.register_buffer_with_language_servers(&buffer, cx),
25398 );
25399 });
25400 } else {
25401 self.registered_buffers.remove(&buffer_id);
25402 }
25403 }
25404 }
25405
25406 fn create_style(&self, cx: &App) -> EditorStyle {
25407 let settings = ThemeSettings::get_global(cx);
25408
25409 let mut text_style = match self.mode {
25410 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25411 color: cx.theme().colors().editor_foreground,
25412 font_family: settings.ui_font.family.clone(),
25413 font_features: settings.ui_font.features.clone(),
25414 font_fallbacks: settings.ui_font.fallbacks.clone(),
25415 font_size: rems(0.875).into(),
25416 font_weight: settings.ui_font.weight,
25417 line_height: relative(settings.buffer_line_height.value()),
25418 ..Default::default()
25419 },
25420 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25421 color: cx.theme().colors().editor_foreground,
25422 font_family: settings.buffer_font.family.clone(),
25423 font_features: settings.buffer_font.features.clone(),
25424 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25425 font_size: settings.buffer_font_size(cx).into(),
25426 font_weight: settings.buffer_font.weight,
25427 line_height: relative(settings.buffer_line_height.value()),
25428 ..Default::default()
25429 },
25430 };
25431 if let Some(text_style_refinement) = &self.text_style_refinement {
25432 text_style.refine(text_style_refinement)
25433 }
25434
25435 let background = match self.mode {
25436 EditorMode::SingleLine => cx.theme().system().transparent,
25437 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25438 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25439 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25440 };
25441
25442 EditorStyle {
25443 background,
25444 border: cx.theme().colors().border,
25445 local_player: cx.theme().players().local(),
25446 text: text_style,
25447 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25448 syntax: cx.theme().syntax().clone(),
25449 status: cx.theme().status().clone(),
25450 inlay_hints_style: make_inlay_hints_style(cx),
25451 edit_prediction_styles: make_suggestion_styles(cx),
25452 unnecessary_code_fade: settings.unnecessary_code_fade,
25453 show_underlines: self.diagnostics_enabled(),
25454 }
25455 }
25456
25457 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
25458 let multibuffer = self.buffer().read(cx);
25459 let is_singleton = multibuffer.is_singleton();
25460 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25461 let buffer = multibuffer.buffer(*buffer_id)?;
25462
25463 let buffer = buffer.read(cx);
25464 let settings = ThemeSettings::get_global(cx);
25465 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25466 let mut breadcrumbs = if is_singleton {
25467 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25468 buffer
25469 .snapshot()
25470 .resolve_file_path(
25471 self.project
25472 .as_ref()
25473 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25474 .unwrap_or_default(),
25475 cx,
25476 )
25477 .unwrap_or_else(|| {
25478 if multibuffer.is_singleton() {
25479 multibuffer.title(cx).to_string()
25480 } else {
25481 "untitled".to_string()
25482 }
25483 })
25484 });
25485 vec![BreadcrumbText {
25486 text,
25487 highlights: None,
25488 font: Some(settings.buffer_font.clone()),
25489 }]
25490 } else {
25491 vec![]
25492 };
25493
25494 breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
25495 text: symbol.text.clone(),
25496 highlights: Some(symbol.highlight_ranges.clone()),
25497 font: Some(settings.buffer_font.clone()),
25498 }));
25499 Some(breadcrumbs)
25500 }
25501
25502 fn disable_lsp_data(&mut self) {
25503 self.enable_lsp_data = false;
25504 }
25505
25506 fn disable_runnables(&mut self) {
25507 self.enable_runnables = false;
25508 }
25509}
25510
25511fn edit_for_markdown_paste<'a>(
25512 buffer: &MultiBufferSnapshot,
25513 range: Range<MultiBufferOffset>,
25514 to_insert: &'a str,
25515 url: Option<url::Url>,
25516) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25517 if url.is_none() {
25518 return (range, Cow::Borrowed(to_insert));
25519 };
25520
25521 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25522
25523 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25524 Cow::Borrowed(to_insert)
25525 } else {
25526 Cow::Owned(format!("[{old_text}]({to_insert})"))
25527 };
25528 (range, new_text)
25529}
25530
25531fn process_completion_for_edit(
25532 completion: &Completion,
25533 intent: CompletionIntent,
25534 buffer: &Entity<Buffer>,
25535 cursor_position: &text::Anchor,
25536 cx: &mut Context<Editor>,
25537) -> CompletionEdit {
25538 let buffer = buffer.read(cx);
25539 let buffer_snapshot = buffer.snapshot();
25540 let (snippet, new_text) = if completion.is_snippet() {
25541 let mut snippet_source = completion.new_text.clone();
25542 // Workaround for typescript language server issues so that methods don't expand within
25543 // strings and functions with type expressions. The previous point is used because the query
25544 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25545 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25546 let previous_point = if previous_point.column > 0 {
25547 cursor_position.to_previous_offset(&buffer_snapshot)
25548 } else {
25549 cursor_position.to_offset(&buffer_snapshot)
25550 };
25551 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25552 && scope.prefers_label_for_snippet_in_completion()
25553 && let Some(label) = completion.label()
25554 && matches!(
25555 completion.kind(),
25556 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25557 )
25558 {
25559 snippet_source = label;
25560 }
25561 match Snippet::parse(&snippet_source).log_err() {
25562 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25563 None => (None, completion.new_text.clone()),
25564 }
25565 } else {
25566 (None, completion.new_text.clone())
25567 };
25568
25569 let mut range_to_replace = {
25570 let replace_range = &completion.replace_range;
25571 if let CompletionSource::Lsp {
25572 insert_range: Some(insert_range),
25573 ..
25574 } = &completion.source
25575 {
25576 debug_assert_eq!(
25577 insert_range.start, replace_range.start,
25578 "insert_range and replace_range should start at the same position"
25579 );
25580 debug_assert!(
25581 insert_range
25582 .start
25583 .cmp(cursor_position, &buffer_snapshot)
25584 .is_le(),
25585 "insert_range should start before or at cursor position"
25586 );
25587 debug_assert!(
25588 replace_range
25589 .start
25590 .cmp(cursor_position, &buffer_snapshot)
25591 .is_le(),
25592 "replace_range should start before or at cursor position"
25593 );
25594
25595 let should_replace = match intent {
25596 CompletionIntent::CompleteWithInsert => false,
25597 CompletionIntent::CompleteWithReplace => true,
25598 CompletionIntent::Complete | CompletionIntent::Compose => {
25599 let insert_mode =
25600 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25601 .completions
25602 .lsp_insert_mode;
25603 match insert_mode {
25604 LspInsertMode::Insert => false,
25605 LspInsertMode::Replace => true,
25606 LspInsertMode::ReplaceSubsequence => {
25607 let mut text_to_replace = buffer.chars_for_range(
25608 buffer.anchor_before(replace_range.start)
25609 ..buffer.anchor_after(replace_range.end),
25610 );
25611 let mut current_needle = text_to_replace.next();
25612 for haystack_ch in completion.label.text.chars() {
25613 if let Some(needle_ch) = current_needle
25614 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25615 {
25616 current_needle = text_to_replace.next();
25617 }
25618 }
25619 current_needle.is_none()
25620 }
25621 LspInsertMode::ReplaceSuffix => {
25622 if replace_range
25623 .end
25624 .cmp(cursor_position, &buffer_snapshot)
25625 .is_gt()
25626 {
25627 let range_after_cursor = *cursor_position..replace_range.end;
25628 let text_after_cursor = buffer
25629 .text_for_range(
25630 buffer.anchor_before(range_after_cursor.start)
25631 ..buffer.anchor_after(range_after_cursor.end),
25632 )
25633 .collect::<String>()
25634 .to_ascii_lowercase();
25635 completion
25636 .label
25637 .text
25638 .to_ascii_lowercase()
25639 .ends_with(&text_after_cursor)
25640 } else {
25641 true
25642 }
25643 }
25644 }
25645 }
25646 };
25647
25648 if should_replace {
25649 replace_range.clone()
25650 } else {
25651 insert_range.clone()
25652 }
25653 } else {
25654 replace_range.clone()
25655 }
25656 };
25657
25658 if range_to_replace
25659 .end
25660 .cmp(cursor_position, &buffer_snapshot)
25661 .is_lt()
25662 {
25663 range_to_replace.end = *cursor_position;
25664 }
25665
25666 let replace_range = range_to_replace.to_offset(buffer);
25667 CompletionEdit {
25668 new_text,
25669 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25670 snippet,
25671 }
25672}
25673
25674struct CompletionEdit {
25675 new_text: String,
25676 replace_range: Range<BufferOffset>,
25677 snippet: Option<Snippet>,
25678}
25679
25680fn comment_delimiter_for_newline(
25681 start_point: &Point,
25682 buffer: &MultiBufferSnapshot,
25683 language: &LanguageScope,
25684) -> Option<Arc<str>> {
25685 let delimiters = language.line_comment_prefixes();
25686 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25687 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25688
25689 let num_of_whitespaces = snapshot
25690 .chars_for_range(range.clone())
25691 .take_while(|c| c.is_whitespace())
25692 .count();
25693 let comment_candidate = snapshot
25694 .chars_for_range(range.clone())
25695 .skip(num_of_whitespaces)
25696 .take(max_len_of_delimiter)
25697 .collect::<String>();
25698 let (delimiter, trimmed_len) = delimiters
25699 .iter()
25700 .filter_map(|delimiter| {
25701 let prefix = delimiter.trim_end();
25702 if comment_candidate.starts_with(prefix) {
25703 Some((delimiter, prefix.len()))
25704 } else {
25705 None
25706 }
25707 })
25708 .max_by_key(|(_, len)| *len)?;
25709
25710 if let Some(BlockCommentConfig {
25711 start: block_start, ..
25712 }) = language.block_comment()
25713 {
25714 let block_start_trimmed = block_start.trim_end();
25715 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25716 let line_content = snapshot
25717 .chars_for_range(range)
25718 .skip(num_of_whitespaces)
25719 .take(block_start_trimmed.len())
25720 .collect::<String>();
25721
25722 if line_content.starts_with(block_start_trimmed) {
25723 return None;
25724 }
25725 }
25726 }
25727
25728 let cursor_is_placed_after_comment_marker =
25729 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25730 if cursor_is_placed_after_comment_marker {
25731 Some(delimiter.clone())
25732 } else {
25733 None
25734 }
25735}
25736
25737fn documentation_delimiter_for_newline(
25738 start_point: &Point,
25739 buffer: &MultiBufferSnapshot,
25740 language: &LanguageScope,
25741 newline_config: &mut NewlineConfig,
25742) -> Option<Arc<str>> {
25743 let BlockCommentConfig {
25744 start: start_tag,
25745 end: end_tag,
25746 prefix: delimiter,
25747 tab_size: len,
25748 } = language.documentation_comment()?;
25749 let is_within_block_comment = buffer
25750 .language_scope_at(*start_point)
25751 .is_some_and(|scope| scope.override_name() == Some("comment"));
25752 if !is_within_block_comment {
25753 return None;
25754 }
25755
25756 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25757
25758 let num_of_whitespaces = snapshot
25759 .chars_for_range(range.clone())
25760 .take_while(|c| c.is_whitespace())
25761 .count();
25762
25763 // 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.
25764 let column = start_point.column;
25765 let cursor_is_after_start_tag = {
25766 let start_tag_len = start_tag.len();
25767 let start_tag_line = snapshot
25768 .chars_for_range(range.clone())
25769 .skip(num_of_whitespaces)
25770 .take(start_tag_len)
25771 .collect::<String>();
25772 if start_tag_line.starts_with(start_tag.as_ref()) {
25773 num_of_whitespaces + start_tag_len <= column as usize
25774 } else {
25775 false
25776 }
25777 };
25778
25779 let cursor_is_after_delimiter = {
25780 let delimiter_trim = delimiter.trim_end();
25781 let delimiter_line = snapshot
25782 .chars_for_range(range.clone())
25783 .skip(num_of_whitespaces)
25784 .take(delimiter_trim.len())
25785 .collect::<String>();
25786 if delimiter_line.starts_with(delimiter_trim) {
25787 num_of_whitespaces + delimiter_trim.len() <= column as usize
25788 } else {
25789 false
25790 }
25791 };
25792
25793 let mut needs_extra_line = false;
25794 let mut extra_line_additional_indent = IndentSize::spaces(0);
25795
25796 let cursor_is_before_end_tag_if_exists = {
25797 let mut char_position = 0u32;
25798 let mut end_tag_offset = None;
25799
25800 'outer: for chunk in snapshot.text_for_range(range) {
25801 if let Some(byte_pos) = chunk.find(&**end_tag) {
25802 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25803 end_tag_offset = Some(char_position + chars_before_match);
25804 break 'outer;
25805 }
25806 char_position += chunk.chars().count() as u32;
25807 }
25808
25809 if let Some(end_tag_offset) = end_tag_offset {
25810 let cursor_is_before_end_tag = column <= end_tag_offset;
25811 if cursor_is_after_start_tag {
25812 if cursor_is_before_end_tag {
25813 needs_extra_line = true;
25814 }
25815 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25816 if cursor_is_at_start_of_end_tag {
25817 extra_line_additional_indent.len = *len;
25818 }
25819 }
25820 cursor_is_before_end_tag
25821 } else {
25822 true
25823 }
25824 };
25825
25826 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25827 && cursor_is_before_end_tag_if_exists
25828 {
25829 let additional_indent = if cursor_is_after_start_tag {
25830 IndentSize::spaces(*len)
25831 } else {
25832 IndentSize::spaces(0)
25833 };
25834
25835 *newline_config = NewlineConfig::Newline {
25836 additional_indent,
25837 extra_line_additional_indent: if needs_extra_line {
25838 Some(extra_line_additional_indent)
25839 } else {
25840 None
25841 },
25842 prevent_auto_indent: true,
25843 };
25844 Some(delimiter.clone())
25845 } else {
25846 None
25847 }
25848}
25849
25850const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25851
25852fn list_delimiter_for_newline(
25853 start_point: &Point,
25854 buffer: &MultiBufferSnapshot,
25855 language: &LanguageScope,
25856 newline_config: &mut NewlineConfig,
25857) -> Option<Arc<str>> {
25858 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25859
25860 let num_of_whitespaces = snapshot
25861 .chars_for_range(range.clone())
25862 .take_while(|c| c.is_whitespace())
25863 .count();
25864
25865 let task_list_entries: Vec<_> = language
25866 .task_list()
25867 .into_iter()
25868 .flat_map(|config| {
25869 config
25870 .prefixes
25871 .iter()
25872 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25873 })
25874 .collect();
25875 let unordered_list_entries: Vec<_> = language
25876 .unordered_list()
25877 .iter()
25878 .map(|marker| (marker.as_ref(), marker.as_ref()))
25879 .collect();
25880
25881 let all_entries: Vec<_> = task_list_entries
25882 .into_iter()
25883 .chain(unordered_list_entries)
25884 .collect();
25885
25886 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25887 let candidate: String = snapshot
25888 .chars_for_range(range.clone())
25889 .skip(num_of_whitespaces)
25890 .take(max_prefix_len)
25891 .collect();
25892
25893 if let Some((prefix, continuation)) = all_entries
25894 .iter()
25895 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25896 .max_by_key(|(prefix, _)| prefix.len())
25897 {
25898 let end_of_prefix = num_of_whitespaces + prefix.len();
25899 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25900 let has_content_after_marker = snapshot
25901 .chars_for_range(range)
25902 .skip(end_of_prefix)
25903 .any(|c| !c.is_whitespace());
25904
25905 if has_content_after_marker && cursor_is_after_prefix {
25906 return Some((*continuation).into());
25907 }
25908
25909 if start_point.column as usize == end_of_prefix {
25910 if num_of_whitespaces == 0 {
25911 *newline_config = NewlineConfig::ClearCurrentLine;
25912 } else {
25913 *newline_config = NewlineConfig::UnindentCurrentLine {
25914 continuation: (*continuation).into(),
25915 };
25916 }
25917 }
25918
25919 return None;
25920 }
25921 }
25922
25923 let candidate: String = snapshot
25924 .chars_for_range(range.clone())
25925 .skip(num_of_whitespaces)
25926 .take(ORDERED_LIST_MAX_MARKER_LEN)
25927 .collect();
25928
25929 for ordered_config in language.ordered_list() {
25930 let regex = match Regex::new(&ordered_config.pattern) {
25931 Ok(r) => r,
25932 Err(_) => continue,
25933 };
25934
25935 if let Some(captures) = regex.captures(&candidate) {
25936 let full_match = captures.get(0)?;
25937 let marker_len = full_match.len();
25938 let end_of_prefix = num_of_whitespaces + marker_len;
25939 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25940
25941 let has_content_after_marker = snapshot
25942 .chars_for_range(range)
25943 .skip(end_of_prefix)
25944 .any(|c| !c.is_whitespace());
25945
25946 if has_content_after_marker && cursor_is_after_prefix {
25947 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25948 let continuation = ordered_config
25949 .format
25950 .replace("{1}", &(number + 1).to_string());
25951 return Some(continuation.into());
25952 }
25953
25954 if start_point.column as usize == end_of_prefix {
25955 let continuation = ordered_config.format.replace("{1}", "1");
25956 if num_of_whitespaces == 0 {
25957 *newline_config = NewlineConfig::ClearCurrentLine;
25958 } else {
25959 *newline_config = NewlineConfig::UnindentCurrentLine {
25960 continuation: continuation.into(),
25961 };
25962 }
25963 }
25964
25965 return None;
25966 }
25967 }
25968
25969 None
25970}
25971
25972fn is_list_prefix_row(
25973 row: MultiBufferRow,
25974 buffer: &MultiBufferSnapshot,
25975 language: &LanguageScope,
25976) -> bool {
25977 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25978 return false;
25979 };
25980
25981 let num_of_whitespaces = snapshot
25982 .chars_for_range(range.clone())
25983 .take_while(|c| c.is_whitespace())
25984 .count();
25985
25986 let task_list_prefixes: Vec<_> = language
25987 .task_list()
25988 .into_iter()
25989 .flat_map(|config| {
25990 config
25991 .prefixes
25992 .iter()
25993 .map(|p| p.as_ref())
25994 .collect::<Vec<_>>()
25995 })
25996 .collect();
25997 let unordered_list_markers: Vec<_> = language
25998 .unordered_list()
25999 .iter()
26000 .map(|marker| marker.as_ref())
26001 .collect();
26002 let all_prefixes: Vec<_> = task_list_prefixes
26003 .into_iter()
26004 .chain(unordered_list_markers)
26005 .collect();
26006 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26007 let candidate: String = snapshot
26008 .chars_for_range(range.clone())
26009 .skip(num_of_whitespaces)
26010 .take(max_prefix_len)
26011 .collect();
26012 if all_prefixes
26013 .iter()
26014 .any(|prefix| candidate.starts_with(*prefix))
26015 {
26016 return true;
26017 }
26018 }
26019
26020 let ordered_list_candidate: String = snapshot
26021 .chars_for_range(range)
26022 .skip(num_of_whitespaces)
26023 .take(ORDERED_LIST_MAX_MARKER_LEN)
26024 .collect();
26025 for ordered_config in language.ordered_list() {
26026 let regex = match Regex::new(&ordered_config.pattern) {
26027 Ok(r) => r,
26028 Err(_) => continue,
26029 };
26030 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26031 return captures.get(0).is_some();
26032 }
26033 }
26034
26035 false
26036}
26037
26038#[derive(Debug)]
26039enum NewlineConfig {
26040 /// Insert newline with optional additional indent and optional extra blank line
26041 Newline {
26042 additional_indent: IndentSize,
26043 extra_line_additional_indent: Option<IndentSize>,
26044 prevent_auto_indent: bool,
26045 },
26046 /// Clear the current line
26047 ClearCurrentLine,
26048 /// Unindent the current line and add continuation
26049 UnindentCurrentLine { continuation: Arc<str> },
26050}
26051
26052impl NewlineConfig {
26053 fn has_extra_line(&self) -> bool {
26054 matches!(
26055 self,
26056 Self::Newline {
26057 extra_line_additional_indent: Some(_),
26058 ..
26059 }
26060 )
26061 }
26062
26063 fn insert_extra_newline_brackets(
26064 buffer: &MultiBufferSnapshot,
26065 range: Range<MultiBufferOffset>,
26066 language: &language::LanguageScope,
26067 ) -> bool {
26068 let leading_whitespace_len = buffer
26069 .reversed_chars_at(range.start)
26070 .take_while(|c| c.is_whitespace() && *c != '\n')
26071 .map(|c| c.len_utf8())
26072 .sum::<usize>();
26073 let trailing_whitespace_len = buffer
26074 .chars_at(range.end)
26075 .take_while(|c| c.is_whitespace() && *c != '\n')
26076 .map(|c| c.len_utf8())
26077 .sum::<usize>();
26078 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26079
26080 language.brackets().any(|(pair, enabled)| {
26081 let pair_start = pair.start.trim_end();
26082 let pair_end = pair.end.trim_start();
26083
26084 enabled
26085 && pair.newline
26086 && buffer.contains_str_at(range.end, pair_end)
26087 && buffer.contains_str_at(
26088 range.start.saturating_sub_usize(pair_start.len()),
26089 pair_start,
26090 )
26091 })
26092 }
26093
26094 fn insert_extra_newline_tree_sitter(
26095 buffer: &MultiBufferSnapshot,
26096 range: Range<MultiBufferOffset>,
26097 ) -> bool {
26098 let (buffer, range) = match buffer
26099 .range_to_buffer_ranges(range.start..=range.end)
26100 .as_slice()
26101 {
26102 [(buffer, range, _)] => (*buffer, range.clone()),
26103 _ => return false,
26104 };
26105 let pair = {
26106 let mut result: Option<BracketMatch<usize>> = None;
26107
26108 for pair in buffer
26109 .all_bracket_ranges(range.start.0..range.end.0)
26110 .filter(move |pair| {
26111 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26112 })
26113 {
26114 let len = pair.close_range.end - pair.open_range.start;
26115
26116 if let Some(existing) = &result {
26117 let existing_len = existing.close_range.end - existing.open_range.start;
26118 if len > existing_len {
26119 continue;
26120 }
26121 }
26122
26123 result = Some(pair);
26124 }
26125
26126 result
26127 };
26128 let Some(pair) = pair else {
26129 return false;
26130 };
26131 pair.newline_only
26132 && buffer
26133 .chars_for_range(pair.open_range.end..range.start.0)
26134 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26135 .all(|c| c.is_whitespace() && c != '\n')
26136 }
26137}
26138
26139fn update_uncommitted_diff_for_buffer(
26140 editor: Entity<Editor>,
26141 project: &Entity<Project>,
26142 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26143 buffer: Entity<MultiBuffer>,
26144 cx: &mut App,
26145) -> Task<()> {
26146 let mut tasks = Vec::new();
26147 project.update(cx, |project, cx| {
26148 for buffer in buffers {
26149 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26150 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26151 }
26152 }
26153 });
26154 cx.spawn(async move |cx| {
26155 let diffs = future::join_all(tasks).await;
26156 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26157 return;
26158 }
26159
26160 buffer.update(cx, |buffer, cx| {
26161 for diff in diffs.into_iter().flatten() {
26162 buffer.add_diff(diff, cx);
26163 }
26164 });
26165 })
26166}
26167
26168fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26169 let tab_size = tab_size.get() as usize;
26170 let mut width = offset;
26171
26172 for ch in text.chars() {
26173 width += if ch == '\t' {
26174 tab_size - (width % tab_size)
26175 } else {
26176 1
26177 };
26178 }
26179
26180 width - offset
26181}
26182
26183#[cfg(test)]
26184mod tests {
26185 use super::*;
26186
26187 #[test]
26188 fn test_string_size_with_expanded_tabs() {
26189 let nz = |val| NonZeroU32::new(val).unwrap();
26190 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26191 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26192 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26193 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26194 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26195 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26196 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26197 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26198 }
26199}
26200
26201/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26202struct WordBreakingTokenizer<'a> {
26203 input: &'a str,
26204}
26205
26206impl<'a> WordBreakingTokenizer<'a> {
26207 fn new(input: &'a str) -> Self {
26208 Self { input }
26209 }
26210}
26211
26212fn is_char_ideographic(ch: char) -> bool {
26213 use unicode_script::Script::*;
26214 use unicode_script::UnicodeScript;
26215 matches!(ch.script(), Han | Tangut | Yi)
26216}
26217
26218fn is_grapheme_ideographic(text: &str) -> bool {
26219 text.chars().any(is_char_ideographic)
26220}
26221
26222fn is_grapheme_whitespace(text: &str) -> bool {
26223 text.chars().any(|x| x.is_whitespace())
26224}
26225
26226fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26227 text.chars()
26228 .next()
26229 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26230}
26231
26232#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26233enum WordBreakToken<'a> {
26234 Word { token: &'a str, grapheme_len: usize },
26235 InlineWhitespace { token: &'a str, grapheme_len: usize },
26236 Newline,
26237}
26238
26239impl<'a> Iterator for WordBreakingTokenizer<'a> {
26240 /// Yields a span, the count of graphemes in the token, and whether it was
26241 /// whitespace. Note that it also breaks at word boundaries.
26242 type Item = WordBreakToken<'a>;
26243
26244 fn next(&mut self) -> Option<Self::Item> {
26245 use unicode_segmentation::UnicodeSegmentation;
26246 if self.input.is_empty() {
26247 return None;
26248 }
26249
26250 let mut iter = self.input.graphemes(true).peekable();
26251 let mut offset = 0;
26252 let mut grapheme_len = 0;
26253 if let Some(first_grapheme) = iter.next() {
26254 let is_newline = first_grapheme == "\n";
26255 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26256 offset += first_grapheme.len();
26257 grapheme_len += 1;
26258 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26259 if let Some(grapheme) = iter.peek().copied()
26260 && should_stay_with_preceding_ideograph(grapheme)
26261 {
26262 offset += grapheme.len();
26263 grapheme_len += 1;
26264 }
26265 } else {
26266 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26267 let mut next_word_bound = words.peek().copied();
26268 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26269 next_word_bound = words.next();
26270 }
26271 while let Some(grapheme) = iter.peek().copied() {
26272 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26273 break;
26274 };
26275 if is_grapheme_whitespace(grapheme) != is_whitespace
26276 || (grapheme == "\n") != is_newline
26277 {
26278 break;
26279 };
26280 offset += grapheme.len();
26281 grapheme_len += 1;
26282 iter.next();
26283 }
26284 }
26285 let token = &self.input[..offset];
26286 self.input = &self.input[offset..];
26287 if token == "\n" {
26288 Some(WordBreakToken::Newline)
26289 } else if is_whitespace {
26290 Some(WordBreakToken::InlineWhitespace {
26291 token,
26292 grapheme_len,
26293 })
26294 } else {
26295 Some(WordBreakToken::Word {
26296 token,
26297 grapheme_len,
26298 })
26299 }
26300 } else {
26301 None
26302 }
26303 }
26304}
26305
26306#[test]
26307fn test_word_breaking_tokenizer() {
26308 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26309 ("", &[]),
26310 (" ", &[whitespace(" ", 2)]),
26311 ("Ʒ", &[word("Ʒ", 1)]),
26312 ("Ǽ", &[word("Ǽ", 1)]),
26313 ("⋑", &[word("⋑", 1)]),
26314 ("⋑⋑", &[word("⋑⋑", 2)]),
26315 (
26316 "原理,进而",
26317 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26318 ),
26319 (
26320 "hello world",
26321 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26322 ),
26323 (
26324 "hello, world",
26325 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26326 ),
26327 (
26328 " hello world",
26329 &[
26330 whitespace(" ", 2),
26331 word("hello", 5),
26332 whitespace(" ", 1),
26333 word("world", 5),
26334 ],
26335 ),
26336 (
26337 "这是什么 \n 钢笔",
26338 &[
26339 word("这", 1),
26340 word("是", 1),
26341 word("什", 1),
26342 word("么", 1),
26343 whitespace(" ", 1),
26344 newline(),
26345 whitespace(" ", 1),
26346 word("钢", 1),
26347 word("笔", 1),
26348 ],
26349 ),
26350 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26351 ];
26352
26353 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26354 WordBreakToken::Word {
26355 token,
26356 grapheme_len,
26357 }
26358 }
26359
26360 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26361 WordBreakToken::InlineWhitespace {
26362 token,
26363 grapheme_len,
26364 }
26365 }
26366
26367 fn newline() -> WordBreakToken<'static> {
26368 WordBreakToken::Newline
26369 }
26370
26371 for (input, result) in tests {
26372 assert_eq!(
26373 WordBreakingTokenizer::new(input)
26374 .collect::<Vec<_>>()
26375 .as_slice(),
26376 *result,
26377 );
26378 }
26379}
26380
26381fn wrap_with_prefix(
26382 first_line_prefix: String,
26383 subsequent_lines_prefix: String,
26384 unwrapped_text: String,
26385 wrap_column: usize,
26386 tab_size: NonZeroU32,
26387 preserve_existing_whitespace: bool,
26388) -> String {
26389 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26390 let subsequent_lines_prefix_len =
26391 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26392 let mut wrapped_text = String::new();
26393 let mut current_line = first_line_prefix;
26394 let mut is_first_line = true;
26395
26396 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26397 let mut current_line_len = first_line_prefix_len;
26398 let mut in_whitespace = false;
26399 for token in tokenizer {
26400 let have_preceding_whitespace = in_whitespace;
26401 match token {
26402 WordBreakToken::Word {
26403 token,
26404 grapheme_len,
26405 } => {
26406 in_whitespace = false;
26407 let current_prefix_len = if is_first_line {
26408 first_line_prefix_len
26409 } else {
26410 subsequent_lines_prefix_len
26411 };
26412 if current_line_len + grapheme_len > wrap_column
26413 && current_line_len != current_prefix_len
26414 {
26415 wrapped_text.push_str(current_line.trim_end());
26416 wrapped_text.push('\n');
26417 is_first_line = false;
26418 current_line = subsequent_lines_prefix.clone();
26419 current_line_len = subsequent_lines_prefix_len;
26420 }
26421 current_line.push_str(token);
26422 current_line_len += grapheme_len;
26423 }
26424 WordBreakToken::InlineWhitespace {
26425 mut token,
26426 mut grapheme_len,
26427 } => {
26428 in_whitespace = true;
26429 if have_preceding_whitespace && !preserve_existing_whitespace {
26430 continue;
26431 }
26432 if !preserve_existing_whitespace {
26433 // Keep a single whitespace grapheme as-is
26434 if let Some(first) =
26435 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26436 {
26437 token = first;
26438 } else {
26439 token = " ";
26440 }
26441 grapheme_len = 1;
26442 }
26443 let current_prefix_len = if is_first_line {
26444 first_line_prefix_len
26445 } else {
26446 subsequent_lines_prefix_len
26447 };
26448 if current_line_len + grapheme_len > wrap_column {
26449 wrapped_text.push_str(current_line.trim_end());
26450 wrapped_text.push('\n');
26451 is_first_line = false;
26452 current_line = subsequent_lines_prefix.clone();
26453 current_line_len = subsequent_lines_prefix_len;
26454 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26455 current_line.push_str(token);
26456 current_line_len += grapheme_len;
26457 }
26458 }
26459 WordBreakToken::Newline => {
26460 in_whitespace = true;
26461 let current_prefix_len = if is_first_line {
26462 first_line_prefix_len
26463 } else {
26464 subsequent_lines_prefix_len
26465 };
26466 if preserve_existing_whitespace {
26467 wrapped_text.push_str(current_line.trim_end());
26468 wrapped_text.push('\n');
26469 is_first_line = false;
26470 current_line = subsequent_lines_prefix.clone();
26471 current_line_len = subsequent_lines_prefix_len;
26472 } else if have_preceding_whitespace {
26473 continue;
26474 } else if current_line_len + 1 > wrap_column
26475 && current_line_len != current_prefix_len
26476 {
26477 wrapped_text.push_str(current_line.trim_end());
26478 wrapped_text.push('\n');
26479 is_first_line = false;
26480 current_line = subsequent_lines_prefix.clone();
26481 current_line_len = subsequent_lines_prefix_len;
26482 } else if current_line_len != current_prefix_len {
26483 current_line.push(' ');
26484 current_line_len += 1;
26485 }
26486 }
26487 }
26488 }
26489
26490 if !current_line.is_empty() {
26491 wrapped_text.push_str(¤t_line);
26492 }
26493 wrapped_text
26494}
26495
26496#[test]
26497fn test_wrap_with_prefix() {
26498 assert_eq!(
26499 wrap_with_prefix(
26500 "# ".to_string(),
26501 "# ".to_string(),
26502 "abcdefg".to_string(),
26503 4,
26504 NonZeroU32::new(4).unwrap(),
26505 false,
26506 ),
26507 "# abcdefg"
26508 );
26509 assert_eq!(
26510 wrap_with_prefix(
26511 "".to_string(),
26512 "".to_string(),
26513 "\thello world".to_string(),
26514 8,
26515 NonZeroU32::new(4).unwrap(),
26516 false,
26517 ),
26518 "hello\nworld"
26519 );
26520 assert_eq!(
26521 wrap_with_prefix(
26522 "// ".to_string(),
26523 "// ".to_string(),
26524 "xx \nyy zz aa bb cc".to_string(),
26525 12,
26526 NonZeroU32::new(4).unwrap(),
26527 false,
26528 ),
26529 "// xx yy zz\n// aa bb cc"
26530 );
26531 assert_eq!(
26532 wrap_with_prefix(
26533 String::new(),
26534 String::new(),
26535 "这是什么 \n 钢笔".to_string(),
26536 3,
26537 NonZeroU32::new(4).unwrap(),
26538 false,
26539 ),
26540 "这是什\n么 钢\n笔"
26541 );
26542 assert_eq!(
26543 wrap_with_prefix(
26544 String::new(),
26545 String::new(),
26546 format!("foo{}bar", '\u{2009}'), // thin space
26547 80,
26548 NonZeroU32::new(4).unwrap(),
26549 false,
26550 ),
26551 format!("foo{}bar", '\u{2009}')
26552 );
26553}
26554
26555pub trait CollaborationHub {
26556 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26557 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26558 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26559}
26560
26561impl CollaborationHub for Entity<Project> {
26562 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26563 self.read(cx).collaborators()
26564 }
26565
26566 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26567 self.read(cx).user_store().read(cx).participant_indices()
26568 }
26569
26570 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26571 let this = self.read(cx);
26572 let user_ids = this.collaborators().values().map(|c| c.user_id);
26573 this.user_store().read(cx).participant_names(user_ids, cx)
26574 }
26575}
26576
26577pub trait SemanticsProvider {
26578 fn hover(
26579 &self,
26580 buffer: &Entity<Buffer>,
26581 position: text::Anchor,
26582 cx: &mut App,
26583 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26584
26585 fn inline_values(
26586 &self,
26587 buffer_handle: Entity<Buffer>,
26588 range: Range<text::Anchor>,
26589 cx: &mut App,
26590 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26591
26592 fn applicable_inlay_chunks(
26593 &self,
26594 buffer: &Entity<Buffer>,
26595 ranges: &[Range<text::Anchor>],
26596 cx: &mut App,
26597 ) -> Vec<Range<BufferRow>>;
26598
26599 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26600
26601 fn inlay_hints(
26602 &self,
26603 invalidate: InvalidationStrategy,
26604 buffer: Entity<Buffer>,
26605 ranges: Vec<Range<text::Anchor>>,
26606 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26607 cx: &mut App,
26608 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26609
26610 fn semantic_tokens(
26611 &self,
26612 buffer: Entity<Buffer>,
26613 refresh: Option<RefreshForServer>,
26614 cx: &mut App,
26615 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>;
26616
26617 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26618
26619 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26620
26621 fn document_highlights(
26622 &self,
26623 buffer: &Entity<Buffer>,
26624 position: text::Anchor,
26625 cx: &mut App,
26626 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26627
26628 fn definitions(
26629 &self,
26630 buffer: &Entity<Buffer>,
26631 position: text::Anchor,
26632 kind: GotoDefinitionKind,
26633 cx: &mut App,
26634 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26635
26636 fn range_for_rename(
26637 &self,
26638 buffer: &Entity<Buffer>,
26639 position: text::Anchor,
26640 cx: &mut App,
26641 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26642
26643 fn perform_rename(
26644 &self,
26645 buffer: &Entity<Buffer>,
26646 position: text::Anchor,
26647 new_name: String,
26648 cx: &mut App,
26649 ) -> Option<Task<Result<ProjectTransaction>>>;
26650}
26651
26652pub trait CompletionProvider {
26653 fn completions(
26654 &self,
26655 excerpt_id: ExcerptId,
26656 buffer: &Entity<Buffer>,
26657 buffer_position: text::Anchor,
26658 trigger: CompletionContext,
26659 window: &mut Window,
26660 cx: &mut Context<Editor>,
26661 ) -> Task<Result<Vec<CompletionResponse>>>;
26662
26663 fn resolve_completions(
26664 &self,
26665 _buffer: Entity<Buffer>,
26666 _completion_indices: Vec<usize>,
26667 _completions: Rc<RefCell<Box<[Completion]>>>,
26668 _cx: &mut Context<Editor>,
26669 ) -> Task<Result<bool>> {
26670 Task::ready(Ok(false))
26671 }
26672
26673 fn apply_additional_edits_for_completion(
26674 &self,
26675 _buffer: Entity<Buffer>,
26676 _completions: Rc<RefCell<Box<[Completion]>>>,
26677 _completion_index: usize,
26678 _push_to_history: bool,
26679 _cx: &mut Context<Editor>,
26680 ) -> Task<Result<Option<language::Transaction>>> {
26681 Task::ready(Ok(None))
26682 }
26683
26684 fn is_completion_trigger(
26685 &self,
26686 buffer: &Entity<Buffer>,
26687 position: language::Anchor,
26688 text: &str,
26689 trigger_in_words: bool,
26690 cx: &mut Context<Editor>,
26691 ) -> bool;
26692
26693 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26694
26695 fn sort_completions(&self) -> bool {
26696 true
26697 }
26698
26699 fn filter_completions(&self) -> bool {
26700 true
26701 }
26702
26703 fn show_snippets(&self) -> bool {
26704 false
26705 }
26706}
26707
26708pub trait CodeActionProvider {
26709 fn id(&self) -> Arc<str>;
26710
26711 fn code_actions(
26712 &self,
26713 buffer: &Entity<Buffer>,
26714 range: Range<text::Anchor>,
26715 window: &mut Window,
26716 cx: &mut App,
26717 ) -> Task<Result<Vec<CodeAction>>>;
26718
26719 fn apply_code_action(
26720 &self,
26721 buffer_handle: Entity<Buffer>,
26722 action: CodeAction,
26723 excerpt_id: ExcerptId,
26724 push_to_history: bool,
26725 window: &mut Window,
26726 cx: &mut App,
26727 ) -> Task<Result<ProjectTransaction>>;
26728}
26729
26730impl CodeActionProvider for Entity<Project> {
26731 fn id(&self) -> Arc<str> {
26732 "project".into()
26733 }
26734
26735 fn code_actions(
26736 &self,
26737 buffer: &Entity<Buffer>,
26738 range: Range<text::Anchor>,
26739 _window: &mut Window,
26740 cx: &mut App,
26741 ) -> Task<Result<Vec<CodeAction>>> {
26742 self.update(cx, |project, cx| {
26743 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26744 let code_actions = project.code_actions(buffer, range, None, cx);
26745 cx.background_spawn(async move {
26746 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26747 Ok(code_lens_actions
26748 .context("code lens fetch")?
26749 .into_iter()
26750 .flatten()
26751 .chain(
26752 code_actions
26753 .context("code action fetch")?
26754 .into_iter()
26755 .flatten(),
26756 )
26757 .collect())
26758 })
26759 })
26760 }
26761
26762 fn apply_code_action(
26763 &self,
26764 buffer_handle: Entity<Buffer>,
26765 action: CodeAction,
26766 _excerpt_id: ExcerptId,
26767 push_to_history: bool,
26768 _window: &mut Window,
26769 cx: &mut App,
26770 ) -> Task<Result<ProjectTransaction>> {
26771 self.update(cx, |project, cx| {
26772 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26773 })
26774 }
26775}
26776
26777fn snippet_completions(
26778 project: &Project,
26779 buffer: &Entity<Buffer>,
26780 buffer_anchor: text::Anchor,
26781 classifier: CharClassifier,
26782 cx: &mut App,
26783) -> Task<Result<CompletionResponse>> {
26784 let languages = buffer.read(cx).languages_at(buffer_anchor);
26785 let snippet_store = project.snippets().read(cx);
26786
26787 let scopes: Vec<_> = languages
26788 .iter()
26789 .filter_map(|language| {
26790 let language_name = language.lsp_id();
26791 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26792
26793 if snippets.is_empty() {
26794 None
26795 } else {
26796 Some((language.default_scope(), snippets))
26797 }
26798 })
26799 .collect();
26800
26801 if scopes.is_empty() {
26802 return Task::ready(Ok(CompletionResponse {
26803 completions: vec![],
26804 display_options: CompletionDisplayOptions::default(),
26805 is_incomplete: false,
26806 }));
26807 }
26808
26809 let snapshot = buffer.read(cx).text_snapshot();
26810 let executor = cx.background_executor().clone();
26811
26812 cx.background_spawn(async move {
26813 let is_word_char = |c| classifier.is_word(c);
26814
26815 let mut is_incomplete = false;
26816 let mut completions: Vec<Completion> = Vec::new();
26817
26818 const MAX_PREFIX_LEN: usize = 128;
26819 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26820 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26821 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26822
26823 let max_buffer_window: String = snapshot
26824 .text_for_range(window_start..buffer_offset)
26825 .collect();
26826
26827 if max_buffer_window.is_empty() {
26828 return Ok(CompletionResponse {
26829 completions: vec![],
26830 display_options: CompletionDisplayOptions::default(),
26831 is_incomplete: true,
26832 });
26833 }
26834
26835 for (_scope, snippets) in scopes.into_iter() {
26836 // Sort snippets by word count to match longer snippet prefixes first.
26837 let mut sorted_snippet_candidates = snippets
26838 .iter()
26839 .enumerate()
26840 .flat_map(|(snippet_ix, snippet)| {
26841 snippet
26842 .prefix
26843 .iter()
26844 .enumerate()
26845 .map(move |(prefix_ix, prefix)| {
26846 let word_count =
26847 snippet_candidate_suffixes(prefix, is_word_char).count();
26848 ((snippet_ix, prefix_ix), prefix, word_count)
26849 })
26850 })
26851 .collect_vec();
26852 sorted_snippet_candidates
26853 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26854
26855 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26856
26857 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26858 .take(
26859 sorted_snippet_candidates
26860 .first()
26861 .map(|(_, _, word_count)| *word_count)
26862 .unwrap_or_default(),
26863 )
26864 .collect_vec();
26865
26866 const MAX_RESULTS: usize = 100;
26867 // Each match also remembers how many characters from the buffer it consumed
26868 let mut matches: Vec<(StringMatch, usize)> = vec![];
26869
26870 let mut snippet_list_cutoff_index = 0;
26871 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26872 let word_count = buffer_index + 1;
26873 // Increase `snippet_list_cutoff_index` until we have all of the
26874 // snippets with sufficiently many words.
26875 while sorted_snippet_candidates
26876 .get(snippet_list_cutoff_index)
26877 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26878 *snippet_word_count >= word_count
26879 })
26880 {
26881 snippet_list_cutoff_index += 1;
26882 }
26883
26884 // Take only the candidates with at least `word_count` many words
26885 let snippet_candidates_at_word_len =
26886 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26887
26888 let candidates = snippet_candidates_at_word_len
26889 .iter()
26890 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26891 .enumerate() // index in `sorted_snippet_candidates`
26892 // First char must match
26893 .filter(|(_ix, prefix)| {
26894 itertools::equal(
26895 prefix
26896 .chars()
26897 .next()
26898 .into_iter()
26899 .flat_map(|c| c.to_lowercase()),
26900 buffer_window
26901 .chars()
26902 .next()
26903 .into_iter()
26904 .flat_map(|c| c.to_lowercase()),
26905 )
26906 })
26907 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26908 .collect::<Vec<StringMatchCandidate>>();
26909
26910 matches.extend(
26911 fuzzy::match_strings(
26912 &candidates,
26913 &buffer_window,
26914 buffer_window.chars().any(|c| c.is_uppercase()),
26915 true,
26916 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26917 &Default::default(),
26918 executor.clone(),
26919 )
26920 .await
26921 .into_iter()
26922 .map(|string_match| (string_match, buffer_window.len())),
26923 );
26924
26925 if matches.len() >= MAX_RESULTS {
26926 break;
26927 }
26928 }
26929
26930 let to_lsp = |point: &text::Anchor| {
26931 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26932 point_to_lsp(end)
26933 };
26934 let lsp_end = to_lsp(&buffer_anchor);
26935
26936 if matches.len() >= MAX_RESULTS {
26937 is_incomplete = true;
26938 }
26939
26940 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26941 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26942 sorted_snippet_candidates[string_match.candidate_id];
26943 let snippet = &snippets[snippet_index];
26944 let start = buffer_offset - buffer_window_len;
26945 let start = snapshot.anchor_before(start);
26946 let range = start..buffer_anchor;
26947 let lsp_start = to_lsp(&start);
26948 let lsp_range = lsp::Range {
26949 start: lsp_start,
26950 end: lsp_end,
26951 };
26952 Completion {
26953 replace_range: range,
26954 new_text: snippet.body.clone(),
26955 source: CompletionSource::Lsp {
26956 insert_range: None,
26957 server_id: LanguageServerId(usize::MAX),
26958 resolved: true,
26959 lsp_completion: Box::new(lsp::CompletionItem {
26960 label: snippet.prefix.first().unwrap().clone(),
26961 kind: Some(CompletionItemKind::SNIPPET),
26962 label_details: snippet.description.as_ref().map(|description| {
26963 lsp::CompletionItemLabelDetails {
26964 detail: Some(description.clone()),
26965 description: None,
26966 }
26967 }),
26968 insert_text_format: Some(InsertTextFormat::SNIPPET),
26969 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26970 lsp::InsertReplaceEdit {
26971 new_text: snippet.body.clone(),
26972 insert: lsp_range,
26973 replace: lsp_range,
26974 },
26975 )),
26976 filter_text: Some(snippet.body.clone()),
26977 sort_text: Some(char::MAX.to_string()),
26978 ..lsp::CompletionItem::default()
26979 }),
26980 lsp_defaults: None,
26981 },
26982 label: CodeLabel {
26983 text: matching_prefix.clone(),
26984 runs: Vec::new(),
26985 filter_range: 0..matching_prefix.len(),
26986 },
26987 icon_path: None,
26988 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26989 single_line: snippet.name.clone().into(),
26990 plain_text: snippet
26991 .description
26992 .clone()
26993 .map(|description| description.into()),
26994 }),
26995 insert_text_mode: None,
26996 confirm: None,
26997 match_start: Some(start),
26998 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26999 }
27000 }));
27001 }
27002
27003 Ok(CompletionResponse {
27004 completions,
27005 display_options: CompletionDisplayOptions::default(),
27006 is_incomplete,
27007 })
27008 })
27009}
27010
27011impl CompletionProvider for Entity<Project> {
27012 fn completions(
27013 &self,
27014 _excerpt_id: ExcerptId,
27015 buffer: &Entity<Buffer>,
27016 buffer_position: text::Anchor,
27017 options: CompletionContext,
27018 _window: &mut Window,
27019 cx: &mut Context<Editor>,
27020 ) -> Task<Result<Vec<CompletionResponse>>> {
27021 self.update(cx, |project, cx| {
27022 let task = project.completions(buffer, buffer_position, options, cx);
27023 cx.background_spawn(task)
27024 })
27025 }
27026
27027 fn resolve_completions(
27028 &self,
27029 buffer: Entity<Buffer>,
27030 completion_indices: Vec<usize>,
27031 completions: Rc<RefCell<Box<[Completion]>>>,
27032 cx: &mut Context<Editor>,
27033 ) -> Task<Result<bool>> {
27034 self.update(cx, |project, cx| {
27035 project.lsp_store().update(cx, |lsp_store, cx| {
27036 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27037 })
27038 })
27039 }
27040
27041 fn apply_additional_edits_for_completion(
27042 &self,
27043 buffer: Entity<Buffer>,
27044 completions: Rc<RefCell<Box<[Completion]>>>,
27045 completion_index: usize,
27046 push_to_history: bool,
27047 cx: &mut Context<Editor>,
27048 ) -> Task<Result<Option<language::Transaction>>> {
27049 self.update(cx, |project, cx| {
27050 project.lsp_store().update(cx, |lsp_store, cx| {
27051 lsp_store.apply_additional_edits_for_completion(
27052 buffer,
27053 completions,
27054 completion_index,
27055 push_to_history,
27056 cx,
27057 )
27058 })
27059 })
27060 }
27061
27062 fn is_completion_trigger(
27063 &self,
27064 buffer: &Entity<Buffer>,
27065 position: language::Anchor,
27066 text: &str,
27067 trigger_in_words: bool,
27068 cx: &mut Context<Editor>,
27069 ) -> bool {
27070 let mut chars = text.chars();
27071 let char = if let Some(char) = chars.next() {
27072 char
27073 } else {
27074 return false;
27075 };
27076 if chars.next().is_some() {
27077 return false;
27078 }
27079
27080 let buffer = buffer.read(cx);
27081 let snapshot = buffer.snapshot();
27082 let classifier = snapshot
27083 .char_classifier_at(position)
27084 .scope_context(Some(CharScopeContext::Completion));
27085 if trigger_in_words && classifier.is_word(char) {
27086 return true;
27087 }
27088
27089 buffer.completion_triggers().contains(text)
27090 }
27091
27092 fn show_snippets(&self) -> bool {
27093 true
27094 }
27095}
27096
27097impl SemanticsProvider for Entity<Project> {
27098 fn hover(
27099 &self,
27100 buffer: &Entity<Buffer>,
27101 position: text::Anchor,
27102 cx: &mut App,
27103 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27104 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
27105 }
27106
27107 fn document_highlights(
27108 &self,
27109 buffer: &Entity<Buffer>,
27110 position: text::Anchor,
27111 cx: &mut App,
27112 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27113 Some(self.update(cx, |project, cx| {
27114 project.document_highlights(buffer, position, cx)
27115 }))
27116 }
27117
27118 fn definitions(
27119 &self,
27120 buffer: &Entity<Buffer>,
27121 position: text::Anchor,
27122 kind: GotoDefinitionKind,
27123 cx: &mut App,
27124 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27125 Some(self.update(cx, |project, cx| match kind {
27126 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27127 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27128 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27129 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27130 }))
27131 }
27132
27133 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27134 self.update(cx, |project, cx| {
27135 if project
27136 .active_debug_session(cx)
27137 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27138 {
27139 return true;
27140 }
27141
27142 buffer.update(cx, |buffer, cx| {
27143 project.any_language_server_supports_inlay_hints(buffer, cx)
27144 })
27145 })
27146 }
27147
27148 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27149 self.update(cx, |project, cx| {
27150 buffer.update(cx, |buffer, cx| {
27151 project.any_language_server_supports_semantic_tokens(buffer, cx)
27152 })
27153 })
27154 }
27155
27156 fn inline_values(
27157 &self,
27158 buffer_handle: Entity<Buffer>,
27159 range: Range<text::Anchor>,
27160 cx: &mut App,
27161 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27162 self.update(cx, |project, cx| {
27163 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27164
27165 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27166 })
27167 }
27168
27169 fn applicable_inlay_chunks(
27170 &self,
27171 buffer: &Entity<Buffer>,
27172 ranges: &[Range<text::Anchor>],
27173 cx: &mut App,
27174 ) -> Vec<Range<BufferRow>> {
27175 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27176 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27177 })
27178 }
27179
27180 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27181 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
27182 lsp_store.invalidate_inlay_hints(for_buffers)
27183 });
27184 }
27185
27186 fn inlay_hints(
27187 &self,
27188 invalidate: InvalidationStrategy,
27189 buffer: Entity<Buffer>,
27190 ranges: Vec<Range<text::Anchor>>,
27191 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27192 cx: &mut App,
27193 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27194 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27195 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27196 }))
27197 }
27198
27199 fn semantic_tokens(
27200 &self,
27201 buffer: Entity<Buffer>,
27202 refresh: Option<RefreshForServer>,
27203 cx: &mut App,
27204 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>> {
27205 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27206 lsp_store.semantic_tokens(buffer, refresh, cx)
27207 })
27208 }
27209
27210 fn range_for_rename(
27211 &self,
27212 buffer: &Entity<Buffer>,
27213 position: text::Anchor,
27214 cx: &mut App,
27215 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27216 Some(self.update(cx, |project, cx| {
27217 let buffer = buffer.clone();
27218 let task = project.prepare_rename(buffer.clone(), position, cx);
27219 cx.spawn(async move |_, cx| {
27220 Ok(match task.await? {
27221 PrepareRenameResponse::Success(range) => Some(range),
27222 PrepareRenameResponse::InvalidPosition => None,
27223 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27224 // Fallback on using TreeSitter info to determine identifier range
27225 buffer.read_with(cx, |buffer, _| {
27226 let snapshot = buffer.snapshot();
27227 let (range, kind) = snapshot.surrounding_word(position, None);
27228 if kind != Some(CharKind::Word) {
27229 return None;
27230 }
27231 Some(
27232 snapshot.anchor_before(range.start)
27233 ..snapshot.anchor_after(range.end),
27234 )
27235 })
27236 }
27237 })
27238 })
27239 }))
27240 }
27241
27242 fn perform_rename(
27243 &self,
27244 buffer: &Entity<Buffer>,
27245 position: text::Anchor,
27246 new_name: String,
27247 cx: &mut App,
27248 ) -> Option<Task<Result<ProjectTransaction>>> {
27249 Some(self.update(cx, |project, cx| {
27250 project.perform_rename(buffer.clone(), position, new_name, cx)
27251 }))
27252 }
27253}
27254
27255fn consume_contiguous_rows(
27256 contiguous_row_selections: &mut Vec<Selection<Point>>,
27257 selection: &Selection<Point>,
27258 display_map: &DisplaySnapshot,
27259 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27260) -> (MultiBufferRow, MultiBufferRow) {
27261 contiguous_row_selections.push(selection.clone());
27262 let start_row = starting_row(selection, display_map);
27263 let mut end_row = ending_row(selection, display_map);
27264
27265 while let Some(next_selection) = selections.peek() {
27266 if next_selection.start.row <= end_row.0 {
27267 end_row = ending_row(next_selection, display_map);
27268 contiguous_row_selections.push(selections.next().unwrap().clone());
27269 } else {
27270 break;
27271 }
27272 }
27273 (start_row, end_row)
27274}
27275
27276fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27277 if selection.start.column > 0 {
27278 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27279 } else {
27280 MultiBufferRow(selection.start.row)
27281 }
27282}
27283
27284fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27285 if next_selection.end.column > 0 || next_selection.is_empty() {
27286 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27287 } else {
27288 MultiBufferRow(next_selection.end.row)
27289 }
27290}
27291
27292impl EditorSnapshot {
27293 pub fn remote_selections_in_range<'a>(
27294 &'a self,
27295 range: &'a Range<Anchor>,
27296 collaboration_hub: &dyn CollaborationHub,
27297 cx: &'a App,
27298 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27299 let participant_names = collaboration_hub.user_names(cx);
27300 let participant_indices = collaboration_hub.user_participant_indices(cx);
27301 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27302 let collaborators_by_replica_id = collaborators_by_peer_id
27303 .values()
27304 .map(|collaborator| (collaborator.replica_id, collaborator))
27305 .collect::<HashMap<_, _>>();
27306 self.buffer_snapshot()
27307 .selections_in_range(range, false)
27308 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27309 if replica_id == ReplicaId::AGENT {
27310 Some(RemoteSelection {
27311 replica_id,
27312 selection,
27313 cursor_shape,
27314 line_mode,
27315 collaborator_id: CollaboratorId::Agent,
27316 user_name: Some("Agent".into()),
27317 color: cx.theme().players().agent(),
27318 })
27319 } else {
27320 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27321 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27322 let user_name = participant_names.get(&collaborator.user_id).cloned();
27323 Some(RemoteSelection {
27324 replica_id,
27325 selection,
27326 cursor_shape,
27327 line_mode,
27328 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27329 user_name,
27330 color: if let Some(index) = participant_index {
27331 cx.theme().players().color_for_participant(index.0)
27332 } else {
27333 cx.theme().players().absent()
27334 },
27335 })
27336 }
27337 })
27338 }
27339
27340 pub fn hunks_for_ranges(
27341 &self,
27342 ranges: impl IntoIterator<Item = Range<Point>>,
27343 ) -> Vec<MultiBufferDiffHunk> {
27344 let mut hunks = Vec::new();
27345 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27346 HashMap::default();
27347 for query_range in ranges {
27348 let query_rows =
27349 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27350 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27351 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27352 ) {
27353 // Include deleted hunks that are adjacent to the query range, because
27354 // otherwise they would be missed.
27355 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27356 if hunk.status().is_deleted() {
27357 intersects_range |= hunk.row_range.start == query_rows.end;
27358 intersects_range |= hunk.row_range.end == query_rows.start;
27359 }
27360 if intersects_range {
27361 if !processed_buffer_rows
27362 .entry(hunk.buffer_id)
27363 .or_default()
27364 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27365 {
27366 continue;
27367 }
27368 hunks.push(hunk);
27369 }
27370 }
27371 }
27372
27373 hunks
27374 }
27375
27376 fn display_diff_hunks_for_rows<'a>(
27377 &'a self,
27378 display_rows: Range<DisplayRow>,
27379 folded_buffers: &'a HashSet<BufferId>,
27380 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27381 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27382 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27383
27384 self.buffer_snapshot()
27385 .diff_hunks_in_range(buffer_start..buffer_end)
27386 .filter_map(|hunk| {
27387 if folded_buffers.contains(&hunk.buffer_id)
27388 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27389 {
27390 return None;
27391 }
27392
27393 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27394 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27395 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27396 let line_len = self.buffer_snapshot().line_len(last_row);
27397 Point::new(last_row.0, line_len)
27398 } else {
27399 Point::new(hunk.row_range.end.0, 0)
27400 };
27401
27402 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27403 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27404
27405 let display_hunk = if hunk_display_start.column() != 0 {
27406 DisplayDiffHunk::Folded {
27407 display_row: hunk_display_start.row(),
27408 }
27409 } else {
27410 let mut end_row = hunk_display_end.row();
27411 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27412 end_row.0 += 1;
27413 }
27414 let is_created_file = hunk.is_created_file();
27415
27416 DisplayDiffHunk::Unfolded {
27417 status: hunk.status(),
27418 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27419 ..hunk.diff_base_byte_range.end.0,
27420 word_diffs: hunk.word_diffs,
27421 display_row_range: hunk_display_start.row()..end_row,
27422 multi_buffer_range: Anchor::range_in_buffer(
27423 hunk.excerpt_id,
27424 hunk.buffer_range,
27425 ),
27426 is_created_file,
27427 }
27428 };
27429
27430 Some(display_hunk)
27431 })
27432 }
27433
27434 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27435 self.display_snapshot
27436 .buffer_snapshot()
27437 .language_at(position)
27438 }
27439
27440 pub fn is_focused(&self) -> bool {
27441 self.is_focused
27442 }
27443
27444 pub fn placeholder_text(&self) -> Option<String> {
27445 self.placeholder_display_snapshot
27446 .as_ref()
27447 .map(|display_map| display_map.text())
27448 }
27449
27450 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27451 self.scroll_anchor.scroll_position(&self.display_snapshot)
27452 }
27453
27454 pub fn gutter_dimensions(
27455 &self,
27456 font_id: FontId,
27457 font_size: Pixels,
27458 style: &EditorStyle,
27459 window: &mut Window,
27460 cx: &App,
27461 ) -> GutterDimensions {
27462 if self.show_gutter
27463 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27464 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27465 {
27466 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27467 matches!(
27468 ProjectSettings::get_global(cx).git.git_gutter,
27469 GitGutterSetting::TrackedFiles
27470 )
27471 });
27472 let gutter_settings = EditorSettings::get_global(cx).gutter;
27473 let show_line_numbers = self
27474 .show_line_numbers
27475 .unwrap_or(gutter_settings.line_numbers);
27476 let line_gutter_width = if show_line_numbers {
27477 // Avoid flicker-like gutter resizes when the line number gains another digit by
27478 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27479 let min_width_for_number_on_gutter =
27480 ch_advance * gutter_settings.min_line_number_digits as f32;
27481 self.max_line_number_width(style, window)
27482 .max(min_width_for_number_on_gutter)
27483 } else {
27484 0.0.into()
27485 };
27486
27487 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27488 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27489
27490 let git_blame_entries_width =
27491 self.git_blame_gutter_max_author_length
27492 .map(|max_author_length| {
27493 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27494 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27495
27496 /// The number of characters to dedicate to gaps and margins.
27497 const SPACING_WIDTH: usize = 4;
27498
27499 let max_char_count = max_author_length.min(renderer.max_author_length())
27500 + ::git::SHORT_SHA_LENGTH
27501 + MAX_RELATIVE_TIMESTAMP.len()
27502 + SPACING_WIDTH;
27503
27504 ch_advance * max_char_count
27505 });
27506
27507 let is_singleton = self.buffer_snapshot().is_singleton();
27508
27509 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27510 left_padding += if !is_singleton {
27511 ch_width * 4.0
27512 } else if show_runnables || show_breakpoints {
27513 ch_width * 3.0
27514 } else if show_git_gutter && show_line_numbers {
27515 ch_width * 2.0
27516 } else if show_git_gutter || show_line_numbers {
27517 ch_width
27518 } else {
27519 px(0.)
27520 };
27521
27522 let shows_folds = is_singleton && gutter_settings.folds;
27523
27524 let right_padding = if shows_folds && show_line_numbers {
27525 ch_width * 4.0
27526 } else if shows_folds || (!is_singleton && show_line_numbers) {
27527 ch_width * 3.0
27528 } else if show_line_numbers {
27529 ch_width
27530 } else {
27531 px(0.)
27532 };
27533
27534 GutterDimensions {
27535 left_padding,
27536 right_padding,
27537 width: line_gutter_width + left_padding + right_padding,
27538 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27539 git_blame_entries_width,
27540 }
27541 } else if self.offset_content {
27542 GutterDimensions::default_with_margin(font_id, font_size, cx)
27543 } else {
27544 GutterDimensions::default()
27545 }
27546 }
27547
27548 pub fn render_crease_toggle(
27549 &self,
27550 buffer_row: MultiBufferRow,
27551 row_contains_cursor: bool,
27552 editor: Entity<Editor>,
27553 window: &mut Window,
27554 cx: &mut App,
27555 ) -> Option<AnyElement> {
27556 let folded = self.is_line_folded(buffer_row);
27557 let mut is_foldable = false;
27558
27559 if let Some(crease) = self
27560 .crease_snapshot
27561 .query_row(buffer_row, self.buffer_snapshot())
27562 {
27563 is_foldable = true;
27564 match crease {
27565 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27566 if let Some(render_toggle) = render_toggle {
27567 let toggle_callback =
27568 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27569 if folded {
27570 editor.update(cx, |editor, cx| {
27571 editor.fold_at(buffer_row, window, cx)
27572 });
27573 } else {
27574 editor.update(cx, |editor, cx| {
27575 editor.unfold_at(buffer_row, window, cx)
27576 });
27577 }
27578 });
27579 return Some((render_toggle)(
27580 buffer_row,
27581 folded,
27582 toggle_callback,
27583 window,
27584 cx,
27585 ));
27586 }
27587 }
27588 }
27589 }
27590
27591 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27592
27593 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27594 Some(
27595 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27596 .toggle_state(folded)
27597 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27598 if folded {
27599 this.unfold_at(buffer_row, window, cx);
27600 } else {
27601 this.fold_at(buffer_row, window, cx);
27602 }
27603 }))
27604 .into_any_element(),
27605 )
27606 } else {
27607 None
27608 }
27609 }
27610
27611 pub fn render_crease_trailer(
27612 &self,
27613 buffer_row: MultiBufferRow,
27614 window: &mut Window,
27615 cx: &mut App,
27616 ) -> Option<AnyElement> {
27617 let folded = self.is_line_folded(buffer_row);
27618 if let Crease::Inline { render_trailer, .. } = self
27619 .crease_snapshot
27620 .query_row(buffer_row, self.buffer_snapshot())?
27621 {
27622 let render_trailer = render_trailer.as_ref()?;
27623 Some(render_trailer(buffer_row, folded, window, cx))
27624 } else {
27625 None
27626 }
27627 }
27628
27629 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27630 let digit_count = self.widest_line_number().ilog10() + 1;
27631 column_pixels(style, digit_count as usize, window)
27632 }
27633
27634 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27635 ///
27636 /// This is positive if `base` is before `line`.
27637 fn relative_line_delta(
27638 &self,
27639 current_selection_head: DisplayRow,
27640 first_visible_row: DisplayRow,
27641 consider_wrapped_lines: bool,
27642 ) -> i64 {
27643 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27644 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27645
27646 if consider_wrapped_lines {
27647 let wrap_snapshot = self.wrap_snapshot();
27648 let base_wrap_row = wrap_snapshot
27649 .make_wrap_point(current_selection_head, Bias::Left)
27650 .row();
27651 let wrap_row = wrap_snapshot
27652 .make_wrap_point(first_visible_row, Bias::Left)
27653 .row();
27654
27655 wrap_row.0 as i64 - base_wrap_row.0 as i64
27656 } else {
27657 let fold_snapshot = self.fold_snapshot();
27658 let base_fold_row = fold_snapshot
27659 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27660 .row();
27661 let fold_row = fold_snapshot
27662 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27663 .row();
27664
27665 fold_row as i64 - base_fold_row as i64
27666 }
27667 }
27668
27669 /// Returns the unsigned relative line number to display for each row in `rows`.
27670 ///
27671 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27672 pub fn calculate_relative_line_numbers(
27673 &self,
27674 rows: &Range<DisplayRow>,
27675 current_selection_head: DisplayRow,
27676 count_wrapped_lines: bool,
27677 ) -> HashMap<DisplayRow, u32> {
27678 let initial_offset =
27679 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27680
27681 self.row_infos(rows.start)
27682 .take(rows.len())
27683 .enumerate()
27684 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27685 .filter(|(_row, row_info)| {
27686 row_info.buffer_row.is_some()
27687 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27688 })
27689 .enumerate()
27690 .filter_map(|(i, (row, row_info))| {
27691 // We want to ensure here that the current line has absolute
27692 // numbering, even if we are in a soft-wrapped line. With the
27693 // exception that if we are in a deleted line, we should number this
27694 // relative with 0, as otherwise it would have no line number at all
27695 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27696
27697 (relative_line_number != 0
27698 || row_info
27699 .diff_status
27700 .is_some_and(|status| status.is_deleted()))
27701 .then_some((row, relative_line_number))
27702 })
27703 .collect()
27704 }
27705}
27706
27707pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27708 let font_size = style.text.font_size.to_pixels(window.rem_size());
27709 let layout = window.text_system().shape_line(
27710 SharedString::from(" ".repeat(column)),
27711 font_size,
27712 &[TextRun {
27713 len: column,
27714 font: style.text.font(),
27715 color: Hsla::default(),
27716 ..Default::default()
27717 }],
27718 None,
27719 );
27720
27721 layout.width
27722}
27723
27724impl Deref for EditorSnapshot {
27725 type Target = DisplaySnapshot;
27726
27727 fn deref(&self) -> &Self::Target {
27728 &self.display_snapshot
27729 }
27730}
27731
27732#[derive(Clone, Debug, PartialEq, Eq)]
27733pub enum EditorEvent {
27734 /// Emitted when the stored review comments change (added, removed, or updated).
27735 ReviewCommentsChanged {
27736 /// The new total count of review comments.
27737 total_count: usize,
27738 },
27739 InputIgnored {
27740 text: Arc<str>,
27741 },
27742 InputHandled {
27743 utf16_range_to_replace: Option<Range<isize>>,
27744 text: Arc<str>,
27745 },
27746 ExcerptsAdded {
27747 buffer: Entity<Buffer>,
27748 predecessor: ExcerptId,
27749 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27750 },
27751 ExcerptsRemoved {
27752 ids: Vec<ExcerptId>,
27753 removed_buffer_ids: Vec<BufferId>,
27754 },
27755 BufferFoldToggled {
27756 ids: Vec<ExcerptId>,
27757 folded: bool,
27758 },
27759 ExcerptsEdited {
27760 ids: Vec<ExcerptId>,
27761 },
27762 ExcerptsExpanded {
27763 ids: Vec<ExcerptId>,
27764 },
27765 ExpandExcerptsRequested {
27766 excerpt_ids: Vec<ExcerptId>,
27767 lines: u32,
27768 direction: ExpandExcerptDirection,
27769 },
27770 StageOrUnstageRequested {
27771 stage: bool,
27772 hunks: Vec<MultiBufferDiffHunk>,
27773 },
27774 OpenExcerptsRequested {
27775 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
27776 split: bool,
27777 },
27778 RestoreRequested {
27779 hunks: Vec<MultiBufferDiffHunk>,
27780 },
27781 BufferEdited,
27782 Edited {
27783 transaction_id: clock::Lamport,
27784 },
27785 Reparsed(BufferId),
27786 Focused,
27787 FocusedIn,
27788 Blurred,
27789 DirtyChanged,
27790 Saved,
27791 TitleChanged,
27792 SelectionsChanged {
27793 local: bool,
27794 },
27795 ScrollPositionChanged {
27796 local: bool,
27797 autoscroll: bool,
27798 },
27799 TransactionUndone {
27800 transaction_id: clock::Lamport,
27801 },
27802 TransactionBegun {
27803 transaction_id: clock::Lamport,
27804 },
27805 CursorShapeChanged,
27806 BreadcrumbsChanged,
27807 OutlineSymbolsChanged,
27808 PushedToNavHistory {
27809 anchor: Anchor,
27810 is_deactivate: bool,
27811 },
27812}
27813
27814impl EventEmitter<EditorEvent> for Editor {}
27815
27816impl Focusable for Editor {
27817 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27818 self.focus_handle.clone()
27819 }
27820}
27821
27822impl Render for Editor {
27823 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27824 EditorElement::new(&cx.entity(), self.create_style(cx))
27825 }
27826}
27827
27828impl EntityInputHandler for Editor {
27829 fn text_for_range(
27830 &mut self,
27831 range_utf16: Range<usize>,
27832 adjusted_range: &mut Option<Range<usize>>,
27833 _: &mut Window,
27834 cx: &mut Context<Self>,
27835 ) -> Option<String> {
27836 let snapshot = self.buffer.read(cx).read(cx);
27837 let start = snapshot.clip_offset_utf16(
27838 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27839 Bias::Left,
27840 );
27841 let end = snapshot.clip_offset_utf16(
27842 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27843 Bias::Right,
27844 );
27845 if (start.0.0..end.0.0) != range_utf16 {
27846 adjusted_range.replace(start.0.0..end.0.0);
27847 }
27848 Some(snapshot.text_for_range(start..end).collect())
27849 }
27850
27851 fn selected_text_range(
27852 &mut self,
27853 ignore_disabled_input: bool,
27854 _: &mut Window,
27855 cx: &mut Context<Self>,
27856 ) -> Option<UTF16Selection> {
27857 // Prevent the IME menu from appearing when holding down an alphabetic key
27858 // while input is disabled.
27859 if !ignore_disabled_input && !self.input_enabled {
27860 return None;
27861 }
27862
27863 let selection = self
27864 .selections
27865 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27866 let range = selection.range();
27867
27868 Some(UTF16Selection {
27869 range: range.start.0.0..range.end.0.0,
27870 reversed: selection.reversed,
27871 })
27872 }
27873
27874 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27875 let snapshot = self.buffer.read(cx).read(cx);
27876 let range = self
27877 .text_highlights(HighlightKey::InputComposition, cx)?
27878 .1
27879 .first()?;
27880 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27881 }
27882
27883 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27884 self.clear_highlights(HighlightKey::InputComposition, cx);
27885 self.ime_transaction.take();
27886 }
27887
27888 fn replace_text_in_range(
27889 &mut self,
27890 range_utf16: Option<Range<usize>>,
27891 text: &str,
27892 window: &mut Window,
27893 cx: &mut Context<Self>,
27894 ) {
27895 if !self.input_enabled {
27896 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27897 return;
27898 }
27899
27900 self.transact(window, cx, |this, window, cx| {
27901 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27902 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27903 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27904 Some(this.selection_replacement_ranges(range_utf16, cx))
27905 } else {
27906 this.marked_text_ranges(cx)
27907 };
27908
27909 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27910 let newest_selection_id = this.selections.newest_anchor().id;
27911 this.selections
27912 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27913 .iter()
27914 .zip(ranges_to_replace.iter())
27915 .find_map(|(selection, range)| {
27916 if selection.id == newest_selection_id {
27917 Some(
27918 (range.start.0.0 as isize - selection.head().0.0 as isize)
27919 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27920 )
27921 } else {
27922 None
27923 }
27924 })
27925 });
27926
27927 cx.emit(EditorEvent::InputHandled {
27928 utf16_range_to_replace: range_to_replace,
27929 text: text.into(),
27930 });
27931
27932 if let Some(new_selected_ranges) = new_selected_ranges {
27933 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27934 selections.select_ranges(new_selected_ranges)
27935 });
27936 this.backspace(&Default::default(), window, cx);
27937 }
27938
27939 this.handle_input(text, window, cx);
27940 });
27941
27942 if let Some(transaction) = self.ime_transaction {
27943 self.buffer.update(cx, |buffer, cx| {
27944 buffer.group_until_transaction(transaction, cx);
27945 });
27946 }
27947
27948 self.unmark_text(window, cx);
27949 }
27950
27951 fn replace_and_mark_text_in_range(
27952 &mut self,
27953 range_utf16: Option<Range<usize>>,
27954 text: &str,
27955 new_selected_range_utf16: Option<Range<usize>>,
27956 window: &mut Window,
27957 cx: &mut Context<Self>,
27958 ) {
27959 if !self.input_enabled {
27960 return;
27961 }
27962
27963 let transaction = self.transact(window, cx, |this, window, cx| {
27964 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27965 let snapshot = this.buffer.read(cx).read(cx);
27966 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27967 for marked_range in &mut marked_ranges {
27968 marked_range.end = marked_range.start + relative_range_utf16.end;
27969 marked_range.start += relative_range_utf16.start;
27970 marked_range.start =
27971 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27972 marked_range.end =
27973 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27974 }
27975 }
27976 Some(marked_ranges)
27977 } else if let Some(range_utf16) = range_utf16 {
27978 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27979 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27980 Some(this.selection_replacement_ranges(range_utf16, cx))
27981 } else {
27982 None
27983 };
27984
27985 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27986 let newest_selection_id = this.selections.newest_anchor().id;
27987 this.selections
27988 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27989 .iter()
27990 .zip(ranges_to_replace.iter())
27991 .find_map(|(selection, range)| {
27992 if selection.id == newest_selection_id {
27993 Some(
27994 (range.start.0.0 as isize - selection.head().0.0 as isize)
27995 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27996 )
27997 } else {
27998 None
27999 }
28000 })
28001 });
28002
28003 cx.emit(EditorEvent::InputHandled {
28004 utf16_range_to_replace: range_to_replace,
28005 text: text.into(),
28006 });
28007
28008 if let Some(ranges) = ranges_to_replace {
28009 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28010 s.select_ranges(ranges)
28011 });
28012 }
28013
28014 let marked_ranges = {
28015 let snapshot = this.buffer.read(cx).read(cx);
28016 this.selections
28017 .disjoint_anchors_arc()
28018 .iter()
28019 .map(|selection| {
28020 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28021 })
28022 .collect::<Vec<_>>()
28023 };
28024
28025 if text.is_empty() {
28026 this.unmark_text(window, cx);
28027 } else {
28028 this.highlight_text(
28029 HighlightKey::InputComposition,
28030 marked_ranges.clone(),
28031 HighlightStyle {
28032 underline: Some(UnderlineStyle {
28033 thickness: px(1.),
28034 color: None,
28035 wavy: false,
28036 }),
28037 ..Default::default()
28038 },
28039 cx,
28040 );
28041 }
28042
28043 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28044 let use_autoclose = this.use_autoclose;
28045 let use_auto_surround = this.use_auto_surround;
28046 this.set_use_autoclose(false);
28047 this.set_use_auto_surround(false);
28048 this.handle_input(text, window, cx);
28049 this.set_use_autoclose(use_autoclose);
28050 this.set_use_auto_surround(use_auto_surround);
28051
28052 if let Some(new_selected_range) = new_selected_range_utf16 {
28053 let snapshot = this.buffer.read(cx).read(cx);
28054 let new_selected_ranges = marked_ranges
28055 .into_iter()
28056 .map(|marked_range| {
28057 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28058 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28059 insertion_start.0 + new_selected_range.start,
28060 ));
28061 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28062 insertion_start.0 + new_selected_range.end,
28063 ));
28064 snapshot.clip_offset_utf16(new_start, Bias::Left)
28065 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28066 })
28067 .collect::<Vec<_>>();
28068
28069 drop(snapshot);
28070 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28071 selections.select_ranges(new_selected_ranges)
28072 });
28073 }
28074 });
28075
28076 self.ime_transaction = self.ime_transaction.or(transaction);
28077 if let Some(transaction) = self.ime_transaction {
28078 self.buffer.update(cx, |buffer, cx| {
28079 buffer.group_until_transaction(transaction, cx);
28080 });
28081 }
28082
28083 if self
28084 .text_highlights(HighlightKey::InputComposition, cx)
28085 .is_none()
28086 {
28087 self.ime_transaction.take();
28088 }
28089 }
28090
28091 fn bounds_for_range(
28092 &mut self,
28093 range_utf16: Range<usize>,
28094 element_bounds: gpui::Bounds<Pixels>,
28095 window: &mut Window,
28096 cx: &mut Context<Self>,
28097 ) -> Option<gpui::Bounds<Pixels>> {
28098 let text_layout_details = self.text_layout_details(window, cx);
28099 let CharacterDimensions {
28100 em_width,
28101 em_advance,
28102 line_height,
28103 } = self.character_dimensions(window, cx);
28104
28105 let snapshot = self.snapshot(window, cx);
28106 let scroll_position = snapshot.scroll_position();
28107 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28108
28109 let start =
28110 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28111 let x = Pixels::from(
28112 ScrollOffset::from(
28113 snapshot.x_for_display_point(start, &text_layout_details)
28114 + self.gutter_dimensions.full_width(),
28115 ) - scroll_left,
28116 );
28117 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28118
28119 Some(Bounds {
28120 origin: element_bounds.origin + point(x, y),
28121 size: size(em_width, line_height),
28122 })
28123 }
28124
28125 fn character_index_for_point(
28126 &mut self,
28127 point: gpui::Point<Pixels>,
28128 _window: &mut Window,
28129 _cx: &mut Context<Self>,
28130 ) -> Option<usize> {
28131 let position_map = self.last_position_map.as_ref()?;
28132 if !position_map.text_hitbox.contains(&point) {
28133 return None;
28134 }
28135 let display_point = position_map.point_for_position(point).previous_valid;
28136 let anchor = position_map
28137 .snapshot
28138 .display_point_to_anchor(display_point, Bias::Left);
28139 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28140 Some(utf16_offset.0.0)
28141 }
28142
28143 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28144 self.input_enabled
28145 }
28146}
28147
28148trait SelectionExt {
28149 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28150 fn spanned_rows(
28151 &self,
28152 include_end_if_at_line_start: bool,
28153 map: &DisplaySnapshot,
28154 ) -> Range<MultiBufferRow>;
28155}
28156
28157impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28158 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28159 let start = self
28160 .start
28161 .to_point(map.buffer_snapshot())
28162 .to_display_point(map);
28163 let end = self
28164 .end
28165 .to_point(map.buffer_snapshot())
28166 .to_display_point(map);
28167 if self.reversed {
28168 end..start
28169 } else {
28170 start..end
28171 }
28172 }
28173
28174 fn spanned_rows(
28175 &self,
28176 include_end_if_at_line_start: bool,
28177 map: &DisplaySnapshot,
28178 ) -> Range<MultiBufferRow> {
28179 let start = self.start.to_point(map.buffer_snapshot());
28180 let mut end = self.end.to_point(map.buffer_snapshot());
28181 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28182 end.row -= 1;
28183 }
28184
28185 let buffer_start = map.prev_line_boundary(start).0;
28186 let buffer_end = map.next_line_boundary(end).0;
28187 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28188 }
28189}
28190
28191impl<T: InvalidationRegion> InvalidationStack<T> {
28192 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28193 where
28194 S: Clone + ToOffset,
28195 {
28196 while let Some(region) = self.last() {
28197 let all_selections_inside_invalidation_ranges =
28198 if selections.len() == region.ranges().len() {
28199 selections
28200 .iter()
28201 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28202 .all(|(selection, invalidation_range)| {
28203 let head = selection.head().to_offset(buffer);
28204 invalidation_range.start <= head && invalidation_range.end >= head
28205 })
28206 } else {
28207 false
28208 };
28209
28210 if all_selections_inside_invalidation_ranges {
28211 break;
28212 } else {
28213 self.pop();
28214 }
28215 }
28216 }
28217}
28218
28219#[derive(Clone)]
28220struct ErasedEditorImpl(Entity<Editor>);
28221
28222impl ui_input::ErasedEditor for ErasedEditorImpl {
28223 fn text(&self, cx: &App) -> String {
28224 self.0.read(cx).text(cx)
28225 }
28226
28227 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28228 self.0.update(cx, |this, cx| {
28229 this.set_text(text, window, cx);
28230 })
28231 }
28232
28233 fn clear(&self, window: &mut Window, cx: &mut App) {
28234 self.0.update(cx, |this, cx| this.clear(window, cx));
28235 }
28236
28237 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28238 self.0.update(cx, |this, cx| {
28239 this.set_placeholder_text(text, window, cx);
28240 });
28241 }
28242
28243 fn focus_handle(&self, cx: &App) -> FocusHandle {
28244 self.0.read(cx).focus_handle(cx)
28245 }
28246
28247 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28248 let settings = ThemeSettings::get_global(cx);
28249 let theme_color = cx.theme().colors();
28250
28251 let text_style = TextStyle {
28252 font_family: settings.ui_font.family.clone(),
28253 font_features: settings.ui_font.features.clone(),
28254 font_size: rems(0.875).into(),
28255 font_weight: settings.buffer_font.weight,
28256 font_style: FontStyle::Normal,
28257 line_height: relative(1.2),
28258 color: theme_color.text,
28259 ..Default::default()
28260 };
28261 let editor_style = EditorStyle {
28262 background: theme_color.ghost_element_background,
28263 local_player: cx.theme().players().local(),
28264 syntax: cx.theme().syntax().clone(),
28265 text: text_style,
28266 ..Default::default()
28267 };
28268 EditorElement::new(&self.0, editor_style).into_any()
28269 }
28270
28271 fn as_any(&self) -> &dyn Any {
28272 &self.0
28273 }
28274
28275 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28276 self.0.update(cx, |editor, cx| {
28277 let editor_offset = editor.buffer().read(cx).len(cx);
28278 editor.change_selections(
28279 SelectionEffects::scroll(Autoscroll::Next),
28280 window,
28281 cx,
28282 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28283 );
28284 });
28285 }
28286
28287 fn subscribe(
28288 &self,
28289 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28290 window: &mut Window,
28291 cx: &mut App,
28292 ) -> Subscription {
28293 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28294 let event = match event {
28295 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28296 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28297 _ => return,
28298 };
28299 (callback)(event, window, cx);
28300 })
28301 }
28302
28303 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28304 self.0.update(cx, |editor, cx| {
28305 editor.set_masked(masked, cx);
28306 });
28307 }
28308}
28309impl<T> Default for InvalidationStack<T> {
28310 fn default() -> Self {
28311 Self(Default::default())
28312 }
28313}
28314
28315impl<T> Deref for InvalidationStack<T> {
28316 type Target = Vec<T>;
28317
28318 fn deref(&self) -> &Self::Target {
28319 &self.0
28320 }
28321}
28322
28323impl<T> DerefMut for InvalidationStack<T> {
28324 fn deref_mut(&mut self) -> &mut Self::Target {
28325 &mut self.0
28326 }
28327}
28328
28329impl InvalidationRegion for SnippetState {
28330 fn ranges(&self) -> &[Range<Anchor>] {
28331 &self.ranges[self.active_index]
28332 }
28333}
28334
28335fn edit_prediction_edit_text(
28336 current_snapshot: &BufferSnapshot,
28337 edits: &[(Range<Anchor>, impl AsRef<str>)],
28338 edit_preview: &EditPreview,
28339 include_deletions: bool,
28340 cx: &App,
28341) -> HighlightedText {
28342 let edits = edits
28343 .iter()
28344 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28345 .collect::<Vec<_>>();
28346
28347 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28348}
28349
28350fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28351 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
28352 // Just show the raw edit text with basic styling
28353 let mut text = String::new();
28354 let mut highlights = Vec::new();
28355
28356 let insertion_highlight_style = HighlightStyle {
28357 color: Some(cx.theme().colors().text),
28358 ..Default::default()
28359 };
28360
28361 for (_, edit_text) in edits {
28362 let start_offset = text.len();
28363 text.push_str(edit_text);
28364 let end_offset = text.len();
28365
28366 if start_offset < end_offset {
28367 highlights.push((start_offset..end_offset, insertion_highlight_style));
28368 }
28369 }
28370
28371 HighlightedText {
28372 text: text.into(),
28373 highlights,
28374 }
28375}
28376
28377pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28378 match severity {
28379 lsp::DiagnosticSeverity::ERROR => colors.error,
28380 lsp::DiagnosticSeverity::WARNING => colors.warning,
28381 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28382 lsp::DiagnosticSeverity::HINT => colors.info,
28383 _ => colors.ignored,
28384 }
28385}
28386
28387pub fn styled_runs_for_code_label<'a>(
28388 label: &'a CodeLabel,
28389 syntax_theme: &'a theme::SyntaxTheme,
28390 local_player: &'a theme::PlayerColor,
28391) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28392 let fade_out = HighlightStyle {
28393 fade_out: Some(0.35),
28394 ..Default::default()
28395 };
28396
28397 let mut prev_end = label.filter_range.end;
28398 label
28399 .runs
28400 .iter()
28401 .enumerate()
28402 .flat_map(move |(ix, (range, highlight_id))| {
28403 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28404 HighlightStyle {
28405 color: Some(local_player.cursor),
28406 ..Default::default()
28407 }
28408 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28409 HighlightStyle {
28410 background_color: Some(local_player.selection),
28411 ..Default::default()
28412 }
28413 } else if let Some(style) = highlight_id.style(syntax_theme) {
28414 style
28415 } else {
28416 return Default::default();
28417 };
28418 let muted_style = style.highlight(fade_out);
28419
28420 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28421 if range.start >= label.filter_range.end {
28422 if range.start > prev_end {
28423 runs.push((prev_end..range.start, fade_out));
28424 }
28425 runs.push((range.clone(), muted_style));
28426 } else if range.end <= label.filter_range.end {
28427 runs.push((range.clone(), style));
28428 } else {
28429 runs.push((range.start..label.filter_range.end, style));
28430 runs.push((label.filter_range.end..range.end, muted_style));
28431 }
28432 prev_end = cmp::max(prev_end, range.end);
28433
28434 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28435 runs.push((prev_end..label.text.len(), fade_out));
28436 }
28437
28438 runs
28439 })
28440}
28441
28442pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28443 let mut prev_index = 0;
28444 let mut prev_codepoint: Option<char> = None;
28445 text.char_indices()
28446 .chain([(text.len(), '\0')])
28447 .filter_map(move |(index, codepoint)| {
28448 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28449 let is_boundary = index == text.len()
28450 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28451 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28452 if is_boundary {
28453 let chunk = &text[prev_index..index];
28454 prev_index = index;
28455 Some(chunk)
28456 } else {
28457 None
28458 }
28459 })
28460}
28461
28462/// Given a string of text immediately before the cursor, iterates over possible
28463/// strings a snippet could match to. More precisely: returns an iterator over
28464/// suffixes of `text` created by splitting at word boundaries (before & after
28465/// every non-word character).
28466///
28467/// Shorter suffixes are returned first.
28468pub(crate) fn snippet_candidate_suffixes(
28469 text: &str,
28470 is_word_char: impl Fn(char) -> bool,
28471) -> impl std::iter::Iterator<Item = &str> {
28472 let mut prev_index = text.len();
28473 let mut prev_codepoint = None;
28474 text.char_indices()
28475 .rev()
28476 .chain([(0, '\0')])
28477 .filter_map(move |(index, codepoint)| {
28478 let prev_index = std::mem::replace(&mut prev_index, index);
28479 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28480 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28481 None
28482 } else {
28483 let chunk = &text[prev_index..]; // go to end of string
28484 Some(chunk)
28485 }
28486 })
28487}
28488
28489pub trait RangeToAnchorExt: Sized {
28490 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28491
28492 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28493 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28494 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28495 }
28496}
28497
28498impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28499 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28500 let start_offset = self.start.to_offset(snapshot);
28501 let end_offset = self.end.to_offset(snapshot);
28502 if start_offset == end_offset {
28503 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28504 } else {
28505 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28506 }
28507 }
28508}
28509
28510pub trait RowExt {
28511 fn as_f64(&self) -> f64;
28512
28513 fn next_row(&self) -> Self;
28514
28515 fn previous_row(&self) -> Self;
28516
28517 fn minus(&self, other: Self) -> u32;
28518}
28519
28520impl RowExt for DisplayRow {
28521 fn as_f64(&self) -> f64 {
28522 self.0 as _
28523 }
28524
28525 fn next_row(&self) -> Self {
28526 Self(self.0 + 1)
28527 }
28528
28529 fn previous_row(&self) -> Self {
28530 Self(self.0.saturating_sub(1))
28531 }
28532
28533 fn minus(&self, other: Self) -> u32 {
28534 self.0 - other.0
28535 }
28536}
28537
28538impl RowExt for MultiBufferRow {
28539 fn as_f64(&self) -> f64 {
28540 self.0 as _
28541 }
28542
28543 fn next_row(&self) -> Self {
28544 Self(self.0 + 1)
28545 }
28546
28547 fn previous_row(&self) -> Self {
28548 Self(self.0.saturating_sub(1))
28549 }
28550
28551 fn minus(&self, other: Self) -> u32 {
28552 self.0 - other.0
28553 }
28554}
28555
28556trait RowRangeExt {
28557 type Row;
28558
28559 fn len(&self) -> usize;
28560
28561 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28562}
28563
28564impl RowRangeExt for Range<MultiBufferRow> {
28565 type Row = MultiBufferRow;
28566
28567 fn len(&self) -> usize {
28568 (self.end.0 - self.start.0) as usize
28569 }
28570
28571 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28572 (self.start.0..self.end.0).map(MultiBufferRow)
28573 }
28574}
28575
28576impl RowRangeExt for Range<DisplayRow> {
28577 type Row = DisplayRow;
28578
28579 fn len(&self) -> usize {
28580 (self.end.0 - self.start.0) as usize
28581 }
28582
28583 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28584 (self.start.0..self.end.0).map(DisplayRow)
28585 }
28586}
28587
28588/// If select range has more than one line, we
28589/// just point the cursor to range.start.
28590fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28591 if range.start.row == range.end.row {
28592 range
28593 } else {
28594 range.start..range.start
28595 }
28596}
28597pub struct KillRing(ClipboardItem);
28598impl Global for KillRing {}
28599
28600const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28601
28602enum BreakpointPromptEditAction {
28603 Log,
28604 Condition,
28605 HitCondition,
28606}
28607
28608struct BreakpointPromptEditor {
28609 pub(crate) prompt: Entity<Editor>,
28610 editor: WeakEntity<Editor>,
28611 breakpoint_anchor: Anchor,
28612 breakpoint: Breakpoint,
28613 edit_action: BreakpointPromptEditAction,
28614 block_ids: HashSet<CustomBlockId>,
28615 editor_margins: Arc<Mutex<EditorMargins>>,
28616 _subscriptions: Vec<Subscription>,
28617}
28618
28619impl BreakpointPromptEditor {
28620 const MAX_LINES: u8 = 4;
28621
28622 fn new(
28623 editor: WeakEntity<Editor>,
28624 breakpoint_anchor: Anchor,
28625 breakpoint: Breakpoint,
28626 edit_action: BreakpointPromptEditAction,
28627 window: &mut Window,
28628 cx: &mut Context<Self>,
28629 ) -> Self {
28630 let base_text = match edit_action {
28631 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28632 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28633 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28634 }
28635 .map(|msg| msg.to_string())
28636 .unwrap_or_default();
28637
28638 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28639 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28640
28641 let prompt = cx.new(|cx| {
28642 let mut prompt = Editor::new(
28643 EditorMode::AutoHeight {
28644 min_lines: 1,
28645 max_lines: Some(Self::MAX_LINES as usize),
28646 },
28647 buffer,
28648 None,
28649 window,
28650 cx,
28651 );
28652 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28653 prompt.set_show_cursor_when_unfocused(false, cx);
28654 prompt.set_placeholder_text(
28655 match edit_action {
28656 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28657 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28658 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28659 },
28660 window,
28661 cx,
28662 );
28663
28664 prompt
28665 });
28666
28667 Self {
28668 prompt,
28669 editor,
28670 breakpoint_anchor,
28671 breakpoint,
28672 edit_action,
28673 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28674 block_ids: Default::default(),
28675 _subscriptions: vec![],
28676 }
28677 }
28678
28679 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28680 self.block_ids.extend(block_ids)
28681 }
28682
28683 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28684 if let Some(editor) = self.editor.upgrade() {
28685 let message = self
28686 .prompt
28687 .read(cx)
28688 .buffer
28689 .read(cx)
28690 .as_singleton()
28691 .expect("A multi buffer in breakpoint prompt isn't possible")
28692 .read(cx)
28693 .as_rope()
28694 .to_string();
28695
28696 editor.update(cx, |editor, cx| {
28697 editor.edit_breakpoint_at_anchor(
28698 self.breakpoint_anchor,
28699 self.breakpoint.clone(),
28700 match self.edit_action {
28701 BreakpointPromptEditAction::Log => {
28702 BreakpointEditAction::EditLogMessage(message.into())
28703 }
28704 BreakpointPromptEditAction::Condition => {
28705 BreakpointEditAction::EditCondition(message.into())
28706 }
28707 BreakpointPromptEditAction::HitCondition => {
28708 BreakpointEditAction::EditHitCondition(message.into())
28709 }
28710 },
28711 cx,
28712 );
28713
28714 editor.remove_blocks(self.block_ids.clone(), None, cx);
28715 cx.focus_self(window);
28716 });
28717 }
28718 }
28719
28720 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28721 self.editor
28722 .update(cx, |editor, cx| {
28723 editor.remove_blocks(self.block_ids.clone(), None, cx);
28724 window.focus(&editor.focus_handle, cx);
28725 })
28726 .log_err();
28727 }
28728
28729 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28730 let settings = ThemeSettings::get_global(cx);
28731 let text_style = TextStyle {
28732 color: if self.prompt.read(cx).read_only(cx) {
28733 cx.theme().colors().text_disabled
28734 } else {
28735 cx.theme().colors().text
28736 },
28737 font_family: settings.buffer_font.family.clone(),
28738 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28739 font_size: settings.buffer_font_size(cx).into(),
28740 font_weight: settings.buffer_font.weight,
28741 line_height: relative(settings.buffer_line_height.value()),
28742 ..Default::default()
28743 };
28744 EditorElement::new(
28745 &self.prompt,
28746 EditorStyle {
28747 background: cx.theme().colors().editor_background,
28748 local_player: cx.theme().players().local(),
28749 text: text_style,
28750 ..Default::default()
28751 },
28752 )
28753 }
28754}
28755
28756impl Render for BreakpointPromptEditor {
28757 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28758 let editor_margins = *self.editor_margins.lock();
28759 let gutter_dimensions = editor_margins.gutter;
28760 h_flex()
28761 .key_context("Editor")
28762 .bg(cx.theme().colors().editor_background)
28763 .border_y_1()
28764 .border_color(cx.theme().status().info_border)
28765 .size_full()
28766 .py(window.line_height() / 2.5)
28767 .on_action(cx.listener(Self::confirm))
28768 .on_action(cx.listener(Self::cancel))
28769 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28770 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28771 }
28772}
28773
28774impl Focusable for BreakpointPromptEditor {
28775 fn focus_handle(&self, cx: &App) -> FocusHandle {
28776 self.prompt.focus_handle(cx)
28777 }
28778}
28779
28780fn all_edits_insertions_or_deletions(
28781 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28782 snapshot: &MultiBufferSnapshot,
28783) -> bool {
28784 let mut all_insertions = true;
28785 let mut all_deletions = true;
28786
28787 for (range, new_text) in edits.iter() {
28788 let range_is_empty = range.to_offset(snapshot).is_empty();
28789 let text_is_empty = new_text.is_empty();
28790
28791 if range_is_empty != text_is_empty {
28792 if range_is_empty {
28793 all_deletions = false;
28794 } else {
28795 all_insertions = false;
28796 }
28797 } else {
28798 return false;
28799 }
28800
28801 if !all_insertions && !all_deletions {
28802 return false;
28803 }
28804 }
28805 all_insertions || all_deletions
28806}
28807
28808struct MissingEditPredictionKeybindingTooltip;
28809
28810impl Render for MissingEditPredictionKeybindingTooltip {
28811 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28812 ui::tooltip_container(cx, |container, cx| {
28813 container
28814 .flex_shrink_0()
28815 .max_w_80()
28816 .min_h(rems_from_px(124.))
28817 .justify_between()
28818 .child(
28819 v_flex()
28820 .flex_1()
28821 .text_ui_sm(cx)
28822 .child(Label::new("Conflict with Accept Keybinding"))
28823 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28824 )
28825 .child(
28826 h_flex()
28827 .pb_1()
28828 .gap_1()
28829 .items_end()
28830 .w_full()
28831 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28832 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28833 }))
28834 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28835 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28836 })),
28837 )
28838 })
28839 }
28840}
28841
28842#[derive(Debug, Clone, Copy, PartialEq)]
28843pub struct LineHighlight {
28844 pub background: Background,
28845 pub border: Option<gpui::Hsla>,
28846 pub include_gutter: bool,
28847 pub type_id: Option<TypeId>,
28848}
28849
28850struct LineManipulationResult {
28851 pub new_text: String,
28852 pub line_count_before: usize,
28853 pub line_count_after: usize,
28854}
28855
28856fn render_diff_hunk_controls(
28857 row: u32,
28858 status: &DiffHunkStatus,
28859 hunk_range: Range<Anchor>,
28860 is_created_file: bool,
28861 line_height: Pixels,
28862 editor: &Entity<Editor>,
28863 _window: &mut Window,
28864 cx: &mut App,
28865) -> AnyElement {
28866 h_flex()
28867 .h(line_height)
28868 .mr_1()
28869 .gap_1()
28870 .px_0p5()
28871 .pb_1()
28872 .border_x_1()
28873 .border_b_1()
28874 .border_color(cx.theme().colors().border_variant)
28875 .rounded_b_lg()
28876 .bg(cx.theme().colors().editor_background)
28877 .gap_1()
28878 .block_mouse_except_scroll()
28879 .shadow_md()
28880 .child(if status.has_secondary_hunk() {
28881 Button::new(("stage", row as u64), "Stage")
28882 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28883 .tooltip({
28884 let focus_handle = editor.focus_handle(cx);
28885 move |_window, cx| {
28886 Tooltip::for_action_in(
28887 "Stage Hunk",
28888 &::git::ToggleStaged,
28889 &focus_handle,
28890 cx,
28891 )
28892 }
28893 })
28894 .on_click({
28895 let editor = editor.clone();
28896 move |_event, _window, cx| {
28897 editor.update(cx, |editor, cx| {
28898 editor.stage_or_unstage_diff_hunks(
28899 true,
28900 vec![hunk_range.start..hunk_range.start],
28901 cx,
28902 );
28903 });
28904 }
28905 })
28906 } else {
28907 Button::new(("unstage", row as u64), "Unstage")
28908 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28909 .tooltip({
28910 let focus_handle = editor.focus_handle(cx);
28911 move |_window, cx| {
28912 Tooltip::for_action_in(
28913 "Unstage Hunk",
28914 &::git::ToggleStaged,
28915 &focus_handle,
28916 cx,
28917 )
28918 }
28919 })
28920 .on_click({
28921 let editor = editor.clone();
28922 move |_event, _window, cx| {
28923 editor.update(cx, |editor, cx| {
28924 editor.stage_or_unstage_diff_hunks(
28925 false,
28926 vec![hunk_range.start..hunk_range.start],
28927 cx,
28928 );
28929 });
28930 }
28931 })
28932 })
28933 .child(
28934 Button::new(("restore", row as u64), "Restore")
28935 .tooltip({
28936 let focus_handle = editor.focus_handle(cx);
28937 move |_window, cx| {
28938 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28939 }
28940 })
28941 .on_click({
28942 let editor = editor.clone();
28943 move |_event, window, cx| {
28944 editor.update(cx, |editor, cx| {
28945 let snapshot = editor.snapshot(window, cx);
28946 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28947 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28948 });
28949 }
28950 })
28951 .disabled(is_created_file),
28952 )
28953 .when(
28954 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28955 |el| {
28956 el.child(
28957 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28958 .shape(IconButtonShape::Square)
28959 .icon_size(IconSize::Small)
28960 // .disabled(!has_multiple_hunks)
28961 .tooltip({
28962 let focus_handle = editor.focus_handle(cx);
28963 move |_window, cx| {
28964 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28965 }
28966 })
28967 .on_click({
28968 let editor = editor.clone();
28969 move |_event, window, cx| {
28970 editor.update(cx, |editor, cx| {
28971 let snapshot = editor.snapshot(window, cx);
28972 let position =
28973 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28974 editor.go_to_hunk_before_or_after_position(
28975 &snapshot,
28976 position,
28977 Direction::Next,
28978 window,
28979 cx,
28980 );
28981 editor.expand_selected_diff_hunks(cx);
28982 });
28983 }
28984 }),
28985 )
28986 .child(
28987 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28988 .shape(IconButtonShape::Square)
28989 .icon_size(IconSize::Small)
28990 // .disabled(!has_multiple_hunks)
28991 .tooltip({
28992 let focus_handle = editor.focus_handle(cx);
28993 move |_window, cx| {
28994 Tooltip::for_action_in(
28995 "Previous Hunk",
28996 &GoToPreviousHunk,
28997 &focus_handle,
28998 cx,
28999 )
29000 }
29001 })
29002 .on_click({
29003 let editor = editor.clone();
29004 move |_event, window, cx| {
29005 editor.update(cx, |editor, cx| {
29006 let snapshot = editor.snapshot(window, cx);
29007 let point =
29008 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29009 editor.go_to_hunk_before_or_after_position(
29010 &snapshot,
29011 point,
29012 Direction::Prev,
29013 window,
29014 cx,
29015 );
29016 editor.expand_selected_diff_hunks(cx);
29017 });
29018 }
29019 }),
29020 )
29021 },
29022 )
29023 .into_any_element()
29024}
29025
29026pub fn multibuffer_context_lines(cx: &App) -> u32 {
29027 EditorSettings::try_get(cx)
29028 .map(|settings| settings.excerpt_context_lines)
29029 .unwrap_or(2)
29030 .min(32)
29031}