1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod semantic_tokens;
42mod split;
43pub mod split_editor_view;
44pub mod tasks;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
65 ShowMinimap,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
80 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
81 ToPoint,
82};
83pub use split::{SplittableEditor, ToggleSplitDiff};
84pub use split_editor_view::SplitEditorView;
85pub use text::Bias;
86
87use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
88use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
89use anyhow::{Context as _, Result, anyhow, bail};
90use blink_manager::BlinkManager;
91use buffer_diff::DiffHunkStatus;
92use client::{Collaborator, ParticipantIndex, parse_zed_link};
93use clock::ReplicaId;
94use code_context_menus::{
95 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
96 CompletionsMenu, ContextMenuOrigin,
97};
98use collections::{BTreeMap, HashMap, HashSet, VecDeque};
99use convert_case::{Case, Casing};
100use dap::TelemetrySpawnLocation;
101use display_map::*;
102use document_colors::LspColorData;
103use edit_prediction_types::{
104 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
105 EditPredictionGranularity, SuggestionDisplayType,
106};
107use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
108use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
109use futures::{
110 FutureExt,
111 future::{self, Shared, join},
112};
113use fuzzy::{StringMatch, StringMatchCandidate};
114use git::blame::{GitBlame, GlobalBlameRenderer};
115use gpui::{
116 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
117 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
118 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
119 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
120 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
121 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
122 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
123 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
124 pulsating_between, px, relative, size,
125};
126use hover_links::{HoverLink, HoveredLinkState, find_file};
127use hover_popover::{HoverState, hide_hover};
128use indent_guides::ActiveIndentGuidesState;
129use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
130use itertools::{Either, Itertools};
131use language::{
132 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
133 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
134 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
135 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, LocalFile, OffsetRangeExt,
136 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
137 TreeSitterOptions, WordsQuery,
138 language_settings::{
139 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
140 all_language_settings, language_settings,
141 },
142 point_from_lsp, point_to_lsp, text_diff_with_options,
143};
144use linked_editing_ranges::refresh_linked_ranges;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId,
148};
149use markdown::Markdown;
150use mouse_context_menu::MouseContextMenu;
151use movement::TextLayoutDetails;
152use multi_buffer::{
153 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
154};
155use parking_lot::Mutex;
156use persistence::DB;
157use project::{
158 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
159 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
160 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
161 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
162 debugger::{
163 breakpoint_store::{
164 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
165 BreakpointStore, BreakpointStoreEvent,
166 },
167 session::{Session, SessionEvent},
168 },
169 git_store::GitStoreEvent,
170 lsp_store::{
171 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
172 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
173 },
174 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
175};
176use rand::seq::SliceRandom;
177use regex::Regex;
178use rpc::{ErrorCode, ErrorExt, proto::PeerId};
179use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
180use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
181use serde::{Deserialize, Serialize};
182use settings::{
183 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
184 update_settings_file,
185};
186use smallvec::{SmallVec, smallvec};
187use snippet::Snippet;
188use std::{
189 any::{Any, TypeId},
190 borrow::Cow,
191 cell::{OnceCell, RefCell},
192 cmp::{self, Ordering, Reverse},
193 collections::hash_map,
194 iter::{self, Peekable},
195 mem,
196 num::NonZeroU32,
197 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
198 path::{Path, PathBuf},
199 rc::Rc,
200 sync::Arc,
201 time::{Duration, Instant},
202};
203use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
204use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
205use theme::{
206 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
207 ThemeSettings, observe_buffer_font_size_adjustment,
208};
209use ui::{
210 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
211 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
212};
213use ui_input::ErasedEditor;
214use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
215use workspace::{
216 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
217 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
218 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
219 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
220 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
221 searchable::SearchEvent,
222};
223use zed_actions::editor::{MoveDown, MoveUp};
224
225use crate::{
226 code_context_menus::CompletionsMenuSource,
227 editor_settings::MultiCursorModifier,
228 hover_links::{find_url, find_url_from_range},
229 inlays::{
230 InlineValueCache,
231 inlay_hints::{LspInlayHintData, inlay_hint_settings},
232 },
233 scroll::{ScrollOffset, ScrollPixelOffset},
234 selections_collection::resolve_selections_wrapping_blocks,
235 semantic_tokens::SemanticTokenState,
236 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
237};
238
239pub const FILE_HEADER_HEIGHT: u32 = 2;
240pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
241pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
242const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
243const MAX_LINE_LEN: usize = 1024;
244const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
245const MAX_SELECTION_HISTORY_LEN: usize = 1024;
246pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
247#[doc(hidden)]
248pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
249pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
250
251pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
252pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
253pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
254pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
255
256pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
257pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
258pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
259
260pub type RenderDiffHunkControlsFn = Arc<
261 dyn Fn(
262 u32,
263 &DiffHunkStatus,
264 Range<Anchor>,
265 bool,
266 Pixels,
267 &Entity<Editor>,
268 &mut Window,
269 &mut App,
270 ) -> AnyElement,
271>;
272
273enum ReportEditorEvent {
274 Saved { auto_saved: bool },
275 EditorOpened,
276 Closed,
277}
278
279impl ReportEditorEvent {
280 pub fn event_type(&self) -> &'static str {
281 match self {
282 Self::Saved { .. } => "Editor Saved",
283 Self::EditorOpened => "Editor Opened",
284 Self::Closed => "Editor Closed",
285 }
286 }
287}
288
289pub enum ActiveDebugLine {}
290pub enum DebugStackFrameLine {}
291
292pub enum ConflictsOuter {}
293pub enum ConflictsOurs {}
294pub enum ConflictsTheirs {}
295pub enum ConflictsOursMarker {}
296pub enum ConflictsTheirsMarker {}
297
298pub struct HunkAddedColor;
299pub struct HunkRemovedColor;
300
301#[derive(Debug, Copy, Clone, PartialEq, Eq)]
302pub enum Navigated {
303 Yes,
304 No,
305}
306
307impl Navigated {
308 pub fn from_bool(yes: bool) -> Navigated {
309 if yes { Navigated::Yes } else { Navigated::No }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
314enum DisplayDiffHunk {
315 Folded {
316 display_row: DisplayRow,
317 },
318 Unfolded {
319 is_created_file: bool,
320 diff_base_byte_range: Range<usize>,
321 display_row_range: Range<DisplayRow>,
322 multi_buffer_range: Range<Anchor>,
323 status: DiffHunkStatus,
324 word_diffs: Vec<Range<MultiBufferOffset>>,
325 },
326}
327
328pub enum HideMouseCursorOrigin {
329 TypingAction,
330 MovementAction,
331}
332
333pub fn init(cx: &mut App) {
334 cx.set_global(GlobalBlameRenderer(Arc::new(())));
335 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
336
337 workspace::register_project_item::<Editor>(cx);
338 workspace::FollowableViewRegistry::register::<Editor>(cx);
339 workspace::register_serializable_item::<Editor>(cx);
340
341 cx.observe_new(
342 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
343 workspace.register_action(Editor::new_file);
344 workspace.register_action(Editor::new_file_split);
345 workspace.register_action(Editor::new_file_vertical);
346 workspace.register_action(Editor::new_file_horizontal);
347 workspace.register_action(Editor::cancel_language_server_work);
348 workspace.register_action(Editor::toggle_focus);
349 },
350 )
351 .detach();
352
353 cx.on_action(move |_: &workspace::NewFile, cx| {
354 let app_state = workspace::AppState::global(cx);
355 if let Some(app_state) = app_state.upgrade() {
356 workspace::open_new(
357 Default::default(),
358 app_state,
359 cx,
360 |workspace, window, cx| {
361 Editor::new_file(workspace, &Default::default(), window, cx)
362 },
363 )
364 .detach_and_log_err(cx);
365 }
366 })
367 .on_action(move |_: &workspace::NewWindow, cx| {
368 let app_state = workspace::AppState::global(cx);
369 if let Some(app_state) = app_state.upgrade() {
370 workspace::open_new(
371 Default::default(),
372 app_state,
373 cx,
374 |workspace, window, cx| {
375 cx.activate(true);
376 Editor::new_file(workspace, &Default::default(), window, cx)
377 },
378 )
379 .detach_and_log_err(cx);
380 }
381 });
382 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
383 Arc::new(ErasedEditorImpl(
384 cx.new(|cx| Editor::single_line(window, cx)),
385 )) as Arc<dyn ErasedEditor>
386 });
387 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 language_registry: Option<Arc<LanguageRegistry>>,
402 cx: &mut App,
403 ) -> Vec<BlockProperties<Anchor>>;
404
405 fn render_hover(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 range: Range<Point>,
409 buffer_id: BufferId,
410 language_registry: Option<Arc<LanguageRegistry>>,
411 cx: &mut App,
412 ) -> Option<Entity<markdown::Markdown>>;
413
414 fn open_link(
415 &self,
416 editor: &mut Editor,
417 link: SharedString,
418 window: &mut Window,
419 cx: &mut Context<Editor>,
420 );
421}
422
423pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
424
425impl GlobalDiagnosticRenderer {
426 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
427 cx.try_global::<Self>().map(|g| g.0.clone())
428 }
429}
430
431impl gpui::Global for GlobalDiagnosticRenderer {}
432pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
433 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
434}
435
436pub struct SearchWithinRange;
437
438trait InvalidationRegion {
439 fn ranges(&self) -> &[Range<Anchor>];
440}
441
442#[derive(Clone, Debug, PartialEq)]
443pub enum SelectPhase {
444 Begin {
445 position: DisplayPoint,
446 add: bool,
447 click_count: usize,
448 },
449 BeginColumnar {
450 position: DisplayPoint,
451 reset: bool,
452 mode: ColumnarMode,
453 goal_column: u32,
454 },
455 Extend {
456 position: DisplayPoint,
457 click_count: usize,
458 },
459 Update {
460 position: DisplayPoint,
461 goal_column: u32,
462 scroll_delta: gpui::Point<f32>,
463 },
464 End,
465}
466
467#[derive(Clone, Debug, PartialEq)]
468pub enum ColumnarMode {
469 FromMouse,
470 FromSelection,
471}
472
473#[derive(Clone, Debug)]
474pub enum SelectMode {
475 Character,
476 Word(Range<Anchor>),
477 Line(Range<Anchor>),
478 All,
479}
480
481#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
482pub enum SizingBehavior {
483 /// The editor will layout itself using `size_full` and will include the vertical
484 /// scroll margin as requested by user settings.
485 #[default]
486 Default,
487 /// The editor will layout itself using `size_full`, but will not have any
488 /// vertical overscroll.
489 ExcludeOverscrollMargin,
490 /// The editor will request a vertical size according to its content and will be
491 /// layouted without a vertical scroll margin.
492 SizeByContent,
493}
494
495#[derive(Clone, PartialEq, Eq, Debug)]
496pub enum EditorMode {
497 SingleLine,
498 AutoHeight {
499 min_lines: usize,
500 max_lines: Option<usize>,
501 },
502 Full {
503 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
504 scale_ui_elements_with_buffer_font_size: bool,
505 /// When set to `true`, the editor will render a background for the active line.
506 show_active_line_background: bool,
507 /// Determines the sizing behavior for this editor
508 sizing_behavior: SizingBehavior,
509 },
510 Minimap {
511 parent: WeakEntity<Editor>,
512 },
513}
514
515impl EditorMode {
516 pub fn full() -> Self {
517 Self::Full {
518 scale_ui_elements_with_buffer_font_size: true,
519 show_active_line_background: true,
520 sizing_behavior: SizingBehavior::Default,
521 }
522 }
523
524 #[inline]
525 pub fn is_full(&self) -> bool {
526 matches!(self, Self::Full { .. })
527 }
528
529 #[inline]
530 pub fn is_single_line(&self) -> bool {
531 matches!(self, Self::SingleLine { .. })
532 }
533
534 #[inline]
535 fn is_minimap(&self) -> bool {
536 matches!(self, Self::Minimap { .. })
537 }
538}
539
540#[derive(Copy, Clone, Debug)]
541pub enum SoftWrap {
542 /// Prefer not to wrap at all.
543 ///
544 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
545 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
546 GitDiff,
547 /// Prefer a single line generally, unless an overly long line is encountered.
548 None,
549 /// Soft wrap lines that exceed the editor width.
550 EditorWidth,
551 /// Soft wrap lines at the preferred line length.
552 Column(u32),
553 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
554 Bounded(u32),
555}
556
557#[derive(Clone)]
558pub struct EditorStyle {
559 pub background: Hsla,
560 pub border: Hsla,
561 pub local_player: PlayerColor,
562 pub text: TextStyle,
563 pub scrollbar_width: Pixels,
564 pub syntax: Arc<SyntaxTheme>,
565 pub status: StatusColors,
566 pub inlay_hints_style: HighlightStyle,
567 pub edit_prediction_styles: EditPredictionStyles,
568 pub unnecessary_code_fade: f32,
569 pub show_underlines: bool,
570}
571
572impl Default for EditorStyle {
573 fn default() -> Self {
574 Self {
575 background: Hsla::default(),
576 border: Hsla::default(),
577 local_player: PlayerColor::default(),
578 text: TextStyle::default(),
579 scrollbar_width: Pixels::default(),
580 syntax: Default::default(),
581 // HACK: Status colors don't have a real default.
582 // We should look into removing the status colors from the editor
583 // style and retrieve them directly from the theme.
584 status: StatusColors::dark(),
585 inlay_hints_style: HighlightStyle::default(),
586 edit_prediction_styles: EditPredictionStyles {
587 insertion: HighlightStyle::default(),
588 whitespace: HighlightStyle::default(),
589 },
590 unnecessary_code_fade: Default::default(),
591 show_underlines: true,
592 }
593 }
594}
595
596pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
597 let show_background = language_settings::language_settings(None, None, cx)
598 .inlay_hints
599 .show_background;
600
601 let mut style = cx.theme().syntax().get("hint");
602
603 if style.color.is_none() {
604 style.color = Some(cx.theme().status().hint);
605 }
606
607 if !show_background {
608 style.background_color = None;
609 return style;
610 }
611
612 if style.background_color.is_none() {
613 style.background_color = Some(cx.theme().status().hint_background);
614 }
615
616 style
617}
618
619pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
620 EditPredictionStyles {
621 insertion: HighlightStyle {
622 color: Some(cx.theme().status().predictive),
623 ..HighlightStyle::default()
624 },
625 whitespace: HighlightStyle {
626 background_color: Some(cx.theme().status().created_background),
627 ..HighlightStyle::default()
628 },
629 }
630}
631
632type CompletionId = usize;
633
634pub(crate) enum EditDisplayMode {
635 TabAccept,
636 DiffPopover,
637 Inline,
638}
639
640enum EditPrediction {
641 Edit {
642 edits: Vec<(Range<Anchor>, Arc<str>)>,
643 /// Predicted cursor position as (anchor, offset_from_anchor).
644 /// The anchor is in multibuffer coordinates; after applying edits,
645 /// resolve the anchor and add the offset to get the final cursor position.
646 cursor_position: Option<(Anchor, usize)>,
647 edit_preview: Option<EditPreview>,
648 display_mode: EditDisplayMode,
649 snapshot: BufferSnapshot,
650 },
651 /// Move to a specific location in the active editor
652 MoveWithin {
653 target: Anchor,
654 snapshot: BufferSnapshot,
655 },
656 /// Move to a specific location in a different editor (not the active one)
657 MoveOutside {
658 target: language::Anchor,
659 snapshot: BufferSnapshot,
660 },
661}
662
663struct EditPredictionState {
664 inlay_ids: Vec<InlayId>,
665 completion: EditPrediction,
666 completion_id: Option<SharedString>,
667 invalidation_range: Option<Range<Anchor>>,
668}
669
670enum EditPredictionSettings {
671 Disabled,
672 Enabled {
673 show_in_menu: bool,
674 preview_requires_modifier: bool,
675 },
676}
677
678#[derive(Debug, Clone)]
679struct InlineDiagnostic {
680 message: SharedString,
681 group_id: usize,
682 is_primary: bool,
683 start: Point,
684 severity: lsp::DiagnosticSeverity,
685}
686
687pub enum MenuEditPredictionsPolicy {
688 Never,
689 ByProvider,
690}
691
692pub enum EditPredictionPreview {
693 /// Modifier is not pressed
694 Inactive { released_too_fast: bool },
695 /// Modifier pressed
696 Active {
697 since: Instant,
698 previous_scroll_position: Option<SharedScrollAnchor>,
699 },
700}
701
702impl EditPredictionPreview {
703 pub fn released_too_fast(&self) -> bool {
704 match self {
705 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
706 EditPredictionPreview::Active { .. } => false,
707 }
708 }
709
710 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
711 if let EditPredictionPreview::Active {
712 previous_scroll_position,
713 ..
714 } = self
715 {
716 *previous_scroll_position = scroll_position;
717 }
718 }
719}
720
721pub struct ContextMenuOptions {
722 pub min_entries_visible: usize,
723 pub max_entries_visible: usize,
724 pub placement: Option<ContextMenuPlacement>,
725}
726
727#[derive(Debug, Clone, PartialEq, Eq)]
728pub enum ContextMenuPlacement {
729 Above,
730 Below,
731}
732
733#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
734struct EditorActionId(usize);
735
736impl EditorActionId {
737 pub fn post_inc(&mut self) -> Self {
738 let answer = self.0;
739
740 *self = Self(answer + 1);
741
742 Self(answer)
743 }
744}
745
746// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
747// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
748
749type BackgroundHighlight = (
750 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
751 Arc<[Range<Anchor>]>,
752);
753type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
754
755#[derive(Default)]
756struct ScrollbarMarkerState {
757 scrollbar_size: Size<Pixels>,
758 dirty: bool,
759 markers: Arc<[PaintQuad]>,
760 pending_refresh: Option<Task<Result<()>>>,
761}
762
763impl ScrollbarMarkerState {
764 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
765 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
766 }
767}
768
769#[derive(Clone, Copy, PartialEq, Eq)]
770pub enum MinimapVisibility {
771 Disabled,
772 Enabled {
773 /// The configuration currently present in the users settings.
774 setting_configuration: bool,
775 /// Whether to override the currently set visibility from the users setting.
776 toggle_override: bool,
777 },
778}
779
780impl MinimapVisibility {
781 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
782 if mode.is_full() {
783 Self::Enabled {
784 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
785 toggle_override: false,
786 }
787 } else {
788 Self::Disabled
789 }
790 }
791
792 fn hidden(&self) -> Self {
793 match *self {
794 Self::Enabled {
795 setting_configuration,
796 ..
797 } => Self::Enabled {
798 setting_configuration,
799 toggle_override: setting_configuration,
800 },
801 Self::Disabled => Self::Disabled,
802 }
803 }
804
805 fn disabled(&self) -> bool {
806 matches!(*self, Self::Disabled)
807 }
808
809 fn settings_visibility(&self) -> bool {
810 match *self {
811 Self::Enabled {
812 setting_configuration,
813 ..
814 } => setting_configuration,
815 _ => false,
816 }
817 }
818
819 fn visible(&self) -> bool {
820 match *self {
821 Self::Enabled {
822 setting_configuration,
823 toggle_override,
824 } => setting_configuration ^ toggle_override,
825 _ => false,
826 }
827 }
828
829 fn toggle_visibility(&self) -> Self {
830 match *self {
831 Self::Enabled {
832 toggle_override,
833 setting_configuration,
834 } => Self::Enabled {
835 setting_configuration,
836 toggle_override: !toggle_override,
837 },
838 Self::Disabled => Self::Disabled,
839 }
840 }
841}
842
843#[derive(Debug, Clone, Copy, PartialEq, Eq)]
844pub enum BufferSerialization {
845 All,
846 NonDirtyBuffers,
847}
848
849impl BufferSerialization {
850 fn new(restore_unsaved_buffers: bool) -> Self {
851 if restore_unsaved_buffers {
852 Self::All
853 } else {
854 Self::NonDirtyBuffers
855 }
856 }
857}
858
859#[derive(Clone, Debug)]
860struct RunnableTasks {
861 templates: Vec<(TaskSourceKind, TaskTemplate)>,
862 offset: multi_buffer::Anchor,
863 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
864 column: u32,
865 // Values of all named captures, including those starting with '_'
866 extra_variables: HashMap<String, String>,
867 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
868 context_range: Range<BufferOffset>,
869}
870
871impl RunnableTasks {
872 fn resolve<'a>(
873 &'a self,
874 cx: &'a task::TaskContext,
875 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
876 self.templates.iter().filter_map(|(kind, template)| {
877 template
878 .resolve_task(&kind.to_id_base(), cx)
879 .map(|task| (kind.clone(), task))
880 })
881 }
882}
883
884#[derive(Clone)]
885pub struct ResolvedTasks {
886 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
887 position: Anchor,
888}
889
890/// Addons allow storing per-editor state in other crates (e.g. Vim)
891pub trait Addon: 'static {
892 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
893
894 fn render_buffer_header_controls(
895 &self,
896 _: &ExcerptInfo,
897 _: &Window,
898 _: &App,
899 ) -> Option<AnyElement> {
900 None
901 }
902
903 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
904 None
905 }
906
907 fn to_any(&self) -> &dyn std::any::Any;
908
909 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
910 None
911 }
912}
913
914struct ChangeLocation {
915 current: Option<Vec<Anchor>>,
916 original: Vec<Anchor>,
917}
918impl ChangeLocation {
919 fn locations(&self) -> &[Anchor] {
920 self.current.as_ref().unwrap_or(&self.original)
921 }
922}
923
924/// A set of caret positions, registered when the editor was edited.
925pub struct ChangeList {
926 changes: Vec<ChangeLocation>,
927 /// Currently "selected" change.
928 position: Option<usize>,
929}
930
931impl ChangeList {
932 pub fn new() -> Self {
933 Self {
934 changes: Vec::new(),
935 position: None,
936 }
937 }
938
939 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
940 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
941 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
942 if self.changes.is_empty() {
943 return None;
944 }
945
946 let prev = self.position.unwrap_or(self.changes.len());
947 let next = if direction == Direction::Prev {
948 prev.saturating_sub(count)
949 } else {
950 (prev + count).min(self.changes.len() - 1)
951 };
952 self.position = Some(next);
953 self.changes.get(next).map(|change| change.locations())
954 }
955
956 /// Adds a new change to the list, resetting the change list position.
957 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
958 self.position.take();
959 if let Some(last) = self.changes.last_mut()
960 && group
961 {
962 last.current = Some(new_positions)
963 } else {
964 self.changes.push(ChangeLocation {
965 original: new_positions,
966 current: None,
967 });
968 }
969 }
970
971 pub fn last(&self) -> Option<&[Anchor]> {
972 self.changes.last().map(|change| change.locations())
973 }
974
975 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
976 self.changes.last().map(|change| change.original.as_slice())
977 }
978
979 pub fn invert_last_group(&mut self) {
980 if let Some(last) = self.changes.last_mut()
981 && let Some(current) = last.current.as_mut()
982 {
983 mem::swap(&mut last.original, current);
984 }
985 }
986}
987
988#[derive(Clone)]
989struct InlineBlamePopoverState {
990 scroll_handle: ScrollHandle,
991 commit_message: Option<ParsedCommitMessage>,
992 markdown: Entity<Markdown>,
993}
994
995struct InlineBlamePopover {
996 position: gpui::Point<Pixels>,
997 hide_task: Option<Task<()>>,
998 popover_bounds: Option<Bounds<Pixels>>,
999 popover_state: InlineBlamePopoverState,
1000 keyboard_grace: bool,
1001}
1002
1003enum SelectionDragState {
1004 /// State when no drag related activity is detected.
1005 None,
1006 /// State when the mouse is down on a selection that is about to be dragged.
1007 ReadyToDrag {
1008 selection: Selection<Anchor>,
1009 click_position: gpui::Point<Pixels>,
1010 mouse_down_time: Instant,
1011 },
1012 /// State when the mouse is dragging the selection in the editor.
1013 Dragging {
1014 selection: Selection<Anchor>,
1015 drop_cursor: Selection<Anchor>,
1016 hide_drop_cursor: bool,
1017 },
1018}
1019
1020enum ColumnarSelectionState {
1021 FromMouse {
1022 selection_tail: Anchor,
1023 display_point: Option<DisplayPoint>,
1024 },
1025 FromSelection {
1026 selection_tail: Anchor,
1027 },
1028}
1029
1030/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1031/// a breakpoint on them.
1032#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1033struct PhantomBreakpointIndicator {
1034 display_row: DisplayRow,
1035 /// There's a small debounce between hovering over the line and showing the indicator.
1036 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1037 is_active: bool,
1038 collides_with_existing_breakpoint: bool,
1039}
1040
1041/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1042/// in diff view mode.
1043#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1044pub(crate) struct PhantomDiffReviewIndicator {
1045 /// The starting anchor of the selection (or the only row if not dragging).
1046 pub start: Anchor,
1047 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1048 pub end: Anchor,
1049 /// There's a small debounce between hovering over the line and showing the indicator.
1050 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1051 pub is_active: bool,
1052}
1053
1054#[derive(Clone, Debug)]
1055pub(crate) struct DiffReviewDragState {
1056 pub start_anchor: Anchor,
1057 pub current_anchor: Anchor,
1058}
1059
1060impl DiffReviewDragState {
1061 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1062 let start = self.start_anchor.to_display_point(snapshot).row();
1063 let current = self.current_anchor.to_display_point(snapshot).row();
1064
1065 (start..=current).sorted()
1066 }
1067}
1068
1069/// Identifies a specific hunk in the diff buffer.
1070/// Used as a key to group comments by their location.
1071#[derive(Clone, Debug)]
1072pub struct DiffHunkKey {
1073 /// The file path (relative to worktree) this hunk belongs to.
1074 pub file_path: Arc<util::rel_path::RelPath>,
1075 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1076 pub hunk_start_anchor: Anchor,
1077}
1078
1079/// A review comment stored locally before being sent to the Agent panel.
1080#[derive(Clone)]
1081pub struct StoredReviewComment {
1082 /// Unique identifier for this comment (for edit/delete operations).
1083 pub id: usize,
1084 /// The comment text entered by the user.
1085 pub comment: String,
1086 /// Anchors for the code range being reviewed.
1087 pub range: Range<Anchor>,
1088 /// Timestamp when the comment was created (for chronological ordering).
1089 pub created_at: Instant,
1090 /// Whether this comment is currently being edited inline.
1091 pub is_editing: bool,
1092}
1093
1094impl StoredReviewComment {
1095 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1096 Self {
1097 id,
1098 comment,
1099 range: anchor_range,
1100 created_at: Instant::now(),
1101 is_editing: false,
1102 }
1103 }
1104}
1105
1106/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1107pub(crate) struct DiffReviewOverlay {
1108 pub anchor_range: Range<Anchor>,
1109 /// The block ID for the overlay.
1110 pub block_id: CustomBlockId,
1111 /// The editor entity for the review input.
1112 pub prompt_editor: Entity<Editor>,
1113 /// The hunk key this overlay belongs to.
1114 pub hunk_key: DiffHunkKey,
1115 /// Whether the comments section is expanded.
1116 pub comments_expanded: bool,
1117 /// Editors for comments currently being edited inline.
1118 /// Key: comment ID, Value: Editor entity for inline editing.
1119 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1120 /// Subscriptions for inline edit editors' action handlers.
1121 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1122 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1123 /// The current user's avatar URI for display in comment rows.
1124 pub user_avatar_uri: Option<SharedUri>,
1125 /// Subscription to keep the action handler alive.
1126 _subscription: Subscription,
1127}
1128
1129/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1130///
1131/// See the [module level documentation](self) for more information.
1132pub struct Editor {
1133 focus_handle: FocusHandle,
1134 last_focused_descendant: Option<WeakFocusHandle>,
1135 /// The text buffer being edited
1136 buffer: Entity<MultiBuffer>,
1137 /// Map of how text in the buffer should be displayed.
1138 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1139 pub display_map: Entity<DisplayMap>,
1140 placeholder_display_map: Option<Entity<DisplayMap>>,
1141 pub selections: SelectionsCollection,
1142 pub scroll_manager: ScrollManager,
1143 /// When inline assist editors are linked, they all render cursors because
1144 /// typing enters text into each of them, even the ones that aren't focused.
1145 pub(crate) show_cursor_when_unfocused: bool,
1146 columnar_selection_state: Option<ColumnarSelectionState>,
1147 add_selections_state: Option<AddSelectionsState>,
1148 select_next_state: Option<SelectNextState>,
1149 select_prev_state: Option<SelectNextState>,
1150 selection_history: SelectionHistory,
1151 defer_selection_effects: bool,
1152 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1153 autoclose_regions: Vec<AutocloseRegion>,
1154 snippet_stack: InvalidationStack<SnippetState>,
1155 select_syntax_node_history: SelectSyntaxNodeHistory,
1156 ime_transaction: Option<TransactionId>,
1157 pub diagnostics_max_severity: DiagnosticSeverity,
1158 active_diagnostics: ActiveDiagnostic,
1159 show_inline_diagnostics: bool,
1160 inline_diagnostics_update: Task<()>,
1161 inline_diagnostics_enabled: bool,
1162 diagnostics_enabled: bool,
1163 word_completions_enabled: bool,
1164 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1165 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1166 hard_wrap: Option<usize>,
1167 project: Option<Entity<Project>>,
1168 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1169 completion_provider: Option<Rc<dyn CompletionProvider>>,
1170 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1171 blink_manager: Entity<BlinkManager>,
1172 show_cursor_names: bool,
1173 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1174 pub show_local_selections: bool,
1175 mode: EditorMode,
1176 show_breadcrumbs: bool,
1177 show_gutter: bool,
1178 show_scrollbars: ScrollbarAxes,
1179 minimap_visibility: MinimapVisibility,
1180 offset_content: bool,
1181 disable_expand_excerpt_buttons: bool,
1182 delegate_expand_excerpts: bool,
1183 delegate_stage_and_restore: bool,
1184 delegate_open_excerpts: bool,
1185 enable_lsp_data: bool,
1186 enable_runnables: bool,
1187 show_line_numbers: Option<bool>,
1188 use_relative_line_numbers: Option<bool>,
1189 show_git_diff_gutter: Option<bool>,
1190 show_code_actions: Option<bool>,
1191 show_runnables: Option<bool>,
1192 show_breakpoints: Option<bool>,
1193 show_diff_review_button: bool,
1194 show_wrap_guides: Option<bool>,
1195 show_indent_guides: Option<bool>,
1196 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1197 highlight_order: usize,
1198 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1199 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1200 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1201 scrollbar_marker_state: ScrollbarMarkerState,
1202 active_indent_guides_state: ActiveIndentGuidesState,
1203 nav_history: Option<ItemNavHistory>,
1204 context_menu: RefCell<Option<CodeContextMenu>>,
1205 context_menu_options: Option<ContextMenuOptions>,
1206 mouse_context_menu: Option<MouseContextMenu>,
1207 completion_tasks: Vec<(CompletionId, Task<()>)>,
1208 inline_blame_popover: Option<InlineBlamePopover>,
1209 inline_blame_popover_show_task: Option<Task<()>>,
1210 signature_help_state: SignatureHelpState,
1211 auto_signature_help: Option<bool>,
1212 find_all_references_task_sources: Vec<Anchor>,
1213 next_completion_id: CompletionId,
1214 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1215 code_actions_task: Option<Task<Result<()>>>,
1216 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1217 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1218 debounced_selection_highlight_complete: bool,
1219 document_highlights_task: Option<Task<()>>,
1220 linked_editing_range_task: Option<Task<Option<()>>>,
1221 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1222 pending_rename: Option<RenameState>,
1223 searchable: bool,
1224 cursor_shape: CursorShape,
1225 /// Whether the cursor is offset one character to the left when something is
1226 /// selected (needed for vim visual mode)
1227 cursor_offset_on_selection: bool,
1228 current_line_highlight: Option<CurrentLineHighlight>,
1229 /// Whether to collapse search match ranges to just their start position.
1230 /// When true, navigating to a match positions the cursor at the match
1231 /// without selecting the matched text.
1232 collapse_matches: bool,
1233 autoindent_mode: Option<AutoindentMode>,
1234 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1235 input_enabled: bool,
1236 use_modal_editing: bool,
1237 read_only: bool,
1238 leader_id: Option<CollaboratorId>,
1239 remote_id: Option<ViewId>,
1240 pub hover_state: HoverState,
1241 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1242 prev_pressure_stage: Option<PressureStage>,
1243 gutter_hovered: bool,
1244 hovered_link_state: Option<HoveredLinkState>,
1245 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1246 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1247 active_edit_prediction: Option<EditPredictionState>,
1248 /// Used to prevent flickering as the user types while the menu is open
1249 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1250 edit_prediction_settings: EditPredictionSettings,
1251 edit_predictions_hidden_for_vim_mode: bool,
1252 show_edit_predictions_override: Option<bool>,
1253 show_completions_on_input_override: Option<bool>,
1254 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1255 edit_prediction_preview: EditPredictionPreview,
1256 edit_prediction_indent_conflict: bool,
1257 edit_prediction_requires_modifier_in_indent_conflict: bool,
1258 next_inlay_id: usize,
1259 next_color_inlay_id: usize,
1260 _subscriptions: Vec<Subscription>,
1261 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1262 gutter_dimensions: GutterDimensions,
1263 style: Option<EditorStyle>,
1264 text_style_refinement: Option<TextStyleRefinement>,
1265 next_editor_action_id: EditorActionId,
1266 editor_actions: Rc<
1267 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1268 >,
1269 use_autoclose: bool,
1270 use_auto_surround: bool,
1271 auto_replace_emoji_shortcode: bool,
1272 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1273 show_git_blame_gutter: bool,
1274 show_git_blame_inline: bool,
1275 show_git_blame_inline_delay_task: Option<Task<()>>,
1276 git_blame_inline_enabled: bool,
1277 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1278 buffer_serialization: Option<BufferSerialization>,
1279 show_selection_menu: Option<bool>,
1280 blame: Option<Entity<GitBlame>>,
1281 blame_subscription: Option<Subscription>,
1282 custom_context_menu: Option<
1283 Box<
1284 dyn 'static
1285 + Fn(
1286 &mut Self,
1287 DisplayPoint,
1288 &mut Window,
1289 &mut Context<Self>,
1290 ) -> Option<Entity<ui::ContextMenu>>,
1291 >,
1292 >,
1293 last_bounds: Option<Bounds<Pixels>>,
1294 last_position_map: Option<Rc<PositionMap>>,
1295 expect_bounds_change: Option<Bounds<Pixels>>,
1296 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1297 tasks_update_task: Option<Task<()>>,
1298 breakpoint_store: Option<Entity<BreakpointStore>>,
1299 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1300 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1301 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1302 /// Active diff review overlays. Multiple overlays can be open simultaneously
1303 /// when hunks have comments stored.
1304 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1305 /// Stored review comments grouped by hunk.
1306 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1307 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1308 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1309 /// Counter for generating unique comment IDs.
1310 next_review_comment_id: usize,
1311 hovered_diff_hunk_row: Option<DisplayRow>,
1312 pull_diagnostics_task: Task<()>,
1313 in_project_search: bool,
1314 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1315 breadcrumb_header: Option<String>,
1316 focused_block: Option<FocusedBlock>,
1317 next_scroll_position: NextScrollCursorCenterTopBottom,
1318 addons: HashMap<TypeId, Box<dyn Addon>>,
1319 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1320 load_diff_task: Option<Shared<Task<()>>>,
1321 /// Whether we are temporarily displaying a diff other than git's
1322 temporary_diff_override: bool,
1323 selection_mark_mode: bool,
1324 toggle_fold_multiple_buffers: Task<()>,
1325 _scroll_cursor_center_top_bottom_task: Task<()>,
1326 serialize_selections: Task<()>,
1327 serialize_folds: Task<()>,
1328 mouse_cursor_hidden: bool,
1329 minimap: Option<Entity<Self>>,
1330 hide_mouse_mode: HideMouseMode,
1331 pub change_list: ChangeList,
1332 inline_value_cache: InlineValueCache,
1333 number_deleted_lines: bool,
1334
1335 selection_drag_state: SelectionDragState,
1336 colors: Option<LspColorData>,
1337 post_scroll_update: Task<()>,
1338 refresh_colors_task: Task<()>,
1339 use_document_folding_ranges: bool,
1340 refresh_folding_ranges_task: Task<()>,
1341 inlay_hints: Option<LspInlayHintData>,
1342 folding_newlines: Task<()>,
1343 select_next_is_case_sensitive: Option<bool>,
1344 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1345 on_local_selections_changed:
1346 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1347 suppress_selection_callback: bool,
1348 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1349 accent_data: Option<AccentData>,
1350 bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1351 semantic_token_state: SemanticTokenState,
1352 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1353 refresh_document_symbols_task: Shared<Task<()>>,
1354 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1355 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1356 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1357 sticky_headers_task: Task<()>,
1358 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1359 pub(crate) colorize_brackets_task: Task<()>,
1360}
1361
1362#[derive(Debug, PartialEq)]
1363struct AccentData {
1364 colors: AccentColors,
1365 overrides: Vec<SharedString>,
1366}
1367
1368fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1369 if debounce_ms > 0 {
1370 Some(Duration::from_millis(debounce_ms))
1371 } else {
1372 None
1373 }
1374}
1375
1376#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1377enum NextScrollCursorCenterTopBottom {
1378 #[default]
1379 Center,
1380 Top,
1381 Bottom,
1382}
1383
1384impl NextScrollCursorCenterTopBottom {
1385 fn next(&self) -> Self {
1386 match self {
1387 Self::Center => Self::Top,
1388 Self::Top => Self::Bottom,
1389 Self::Bottom => Self::Center,
1390 }
1391 }
1392}
1393
1394#[derive(Clone)]
1395pub struct EditorSnapshot {
1396 pub mode: EditorMode,
1397 show_gutter: bool,
1398 offset_content: bool,
1399 show_line_numbers: Option<bool>,
1400 number_deleted_lines: bool,
1401 show_git_diff_gutter: Option<bool>,
1402 show_code_actions: Option<bool>,
1403 show_runnables: Option<bool>,
1404 show_breakpoints: Option<bool>,
1405 git_blame_gutter_max_author_length: Option<usize>,
1406 pub display_snapshot: DisplaySnapshot,
1407 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1408 is_focused: bool,
1409 scroll_anchor: SharedScrollAnchor,
1410 ongoing_scroll: OngoingScroll,
1411 current_line_highlight: CurrentLineHighlight,
1412 gutter_hovered: bool,
1413 semantic_tokens_enabled: bool,
1414}
1415
1416#[derive(Default, Debug, Clone, Copy)]
1417pub struct GutterDimensions {
1418 pub left_padding: Pixels,
1419 pub right_padding: Pixels,
1420 pub width: Pixels,
1421 pub margin: Pixels,
1422 pub git_blame_entries_width: Option<Pixels>,
1423}
1424
1425impl GutterDimensions {
1426 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1427 Self {
1428 margin: Self::default_gutter_margin(font_id, font_size, cx),
1429 ..Default::default()
1430 }
1431 }
1432
1433 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1434 -cx.text_system().descent(font_id, font_size)
1435 }
1436 /// The full width of the space taken up by the gutter.
1437 pub fn full_width(&self) -> Pixels {
1438 self.margin + self.width
1439 }
1440
1441 /// The width of the space reserved for the fold indicators,
1442 /// use alongside 'justify_end' and `gutter_width` to
1443 /// right align content with the line numbers
1444 pub fn fold_area_width(&self) -> Pixels {
1445 self.margin + self.right_padding
1446 }
1447}
1448
1449struct CharacterDimensions {
1450 em_width: Pixels,
1451 em_advance: Pixels,
1452 line_height: Pixels,
1453}
1454
1455#[derive(Debug)]
1456pub struct RemoteSelection {
1457 pub replica_id: ReplicaId,
1458 pub selection: Selection<Anchor>,
1459 pub cursor_shape: CursorShape,
1460 pub collaborator_id: CollaboratorId,
1461 pub line_mode: bool,
1462 pub user_name: Option<SharedString>,
1463 pub color: PlayerColor,
1464}
1465
1466#[derive(Clone, Debug)]
1467struct SelectionHistoryEntry {
1468 selections: Arc<[Selection<Anchor>]>,
1469 select_next_state: Option<SelectNextState>,
1470 select_prev_state: Option<SelectNextState>,
1471 add_selections_state: Option<AddSelectionsState>,
1472}
1473
1474#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1475enum SelectionHistoryMode {
1476 #[default]
1477 Normal,
1478 Undoing,
1479 Redoing,
1480 Skipping,
1481}
1482
1483#[derive(Clone, PartialEq, Eq, Hash)]
1484struct HoveredCursor {
1485 replica_id: ReplicaId,
1486 selection_id: usize,
1487}
1488
1489#[derive(Debug)]
1490/// SelectionEffects controls the side-effects of updating the selection.
1491///
1492/// The default behaviour does "what you mostly want":
1493/// - it pushes to the nav history if the cursor moved by >10 lines
1494/// - it re-triggers completion requests
1495/// - it scrolls to fit
1496///
1497/// You might want to modify these behaviours. For example when doing a "jump"
1498/// like go to definition, we always want to add to nav history; but when scrolling
1499/// in vim mode we never do.
1500///
1501/// Similarly, you might want to disable scrolling if you don't want the viewport to
1502/// move.
1503#[derive(Clone)]
1504pub struct SelectionEffects {
1505 nav_history: Option<bool>,
1506 completions: bool,
1507 scroll: Option<Autoscroll>,
1508}
1509
1510impl Default for SelectionEffects {
1511 fn default() -> Self {
1512 Self {
1513 nav_history: None,
1514 completions: true,
1515 scroll: Some(Autoscroll::fit()),
1516 }
1517 }
1518}
1519impl SelectionEffects {
1520 pub fn scroll(scroll: Autoscroll) -> Self {
1521 Self {
1522 scroll: Some(scroll),
1523 ..Default::default()
1524 }
1525 }
1526
1527 pub fn no_scroll() -> Self {
1528 Self {
1529 scroll: None,
1530 ..Default::default()
1531 }
1532 }
1533
1534 pub fn completions(self, completions: bool) -> Self {
1535 Self {
1536 completions,
1537 ..self
1538 }
1539 }
1540
1541 pub fn nav_history(self, nav_history: bool) -> Self {
1542 Self {
1543 nav_history: Some(nav_history),
1544 ..self
1545 }
1546 }
1547}
1548
1549struct DeferredSelectionEffectsState {
1550 changed: bool,
1551 effects: SelectionEffects,
1552 old_cursor_position: Anchor,
1553 history_entry: SelectionHistoryEntry,
1554}
1555
1556#[derive(Default)]
1557struct SelectionHistory {
1558 #[allow(clippy::type_complexity)]
1559 selections_by_transaction:
1560 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1561 mode: SelectionHistoryMode,
1562 undo_stack: VecDeque<SelectionHistoryEntry>,
1563 redo_stack: VecDeque<SelectionHistoryEntry>,
1564}
1565
1566impl SelectionHistory {
1567 #[track_caller]
1568 fn insert_transaction(
1569 &mut self,
1570 transaction_id: TransactionId,
1571 selections: Arc<[Selection<Anchor>]>,
1572 ) {
1573 if selections.is_empty() {
1574 log::error!(
1575 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1576 std::panic::Location::caller()
1577 );
1578 return;
1579 }
1580 self.selections_by_transaction
1581 .insert(transaction_id, (selections, None));
1582 }
1583
1584 #[allow(clippy::type_complexity)]
1585 fn transaction(
1586 &self,
1587 transaction_id: TransactionId,
1588 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1589 self.selections_by_transaction.get(&transaction_id)
1590 }
1591
1592 #[allow(clippy::type_complexity)]
1593 fn transaction_mut(
1594 &mut self,
1595 transaction_id: TransactionId,
1596 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1597 self.selections_by_transaction.get_mut(&transaction_id)
1598 }
1599
1600 fn push(&mut self, entry: SelectionHistoryEntry) {
1601 if !entry.selections.is_empty() {
1602 match self.mode {
1603 SelectionHistoryMode::Normal => {
1604 self.push_undo(entry);
1605 self.redo_stack.clear();
1606 }
1607 SelectionHistoryMode::Undoing => self.push_redo(entry),
1608 SelectionHistoryMode::Redoing => self.push_undo(entry),
1609 SelectionHistoryMode::Skipping => {}
1610 }
1611 }
1612 }
1613
1614 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1615 if self
1616 .undo_stack
1617 .back()
1618 .is_none_or(|e| e.selections != entry.selections)
1619 {
1620 self.undo_stack.push_back(entry);
1621 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1622 self.undo_stack.pop_front();
1623 }
1624 }
1625 }
1626
1627 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1628 if self
1629 .redo_stack
1630 .back()
1631 .is_none_or(|e| e.selections != entry.selections)
1632 {
1633 self.redo_stack.push_back(entry);
1634 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1635 self.redo_stack.pop_front();
1636 }
1637 }
1638 }
1639}
1640
1641#[derive(Clone, Copy)]
1642pub struct RowHighlightOptions {
1643 pub autoscroll: bool,
1644 pub include_gutter: bool,
1645}
1646
1647impl Default for RowHighlightOptions {
1648 fn default() -> Self {
1649 Self {
1650 autoscroll: Default::default(),
1651 include_gutter: true,
1652 }
1653 }
1654}
1655
1656struct RowHighlight {
1657 index: usize,
1658 range: Range<Anchor>,
1659 color: Hsla,
1660 options: RowHighlightOptions,
1661 type_id: TypeId,
1662}
1663
1664#[derive(Clone, Debug)]
1665struct AddSelectionsState {
1666 groups: Vec<AddSelectionsGroup>,
1667}
1668
1669#[derive(Clone, Debug)]
1670struct AddSelectionsGroup {
1671 above: bool,
1672 stack: Vec<usize>,
1673}
1674
1675#[derive(Clone)]
1676struct SelectNextState {
1677 query: AhoCorasick,
1678 wordwise: bool,
1679 done: bool,
1680}
1681
1682impl std::fmt::Debug for SelectNextState {
1683 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1684 f.debug_struct(std::any::type_name::<Self>())
1685 .field("wordwise", &self.wordwise)
1686 .field("done", &self.done)
1687 .finish()
1688 }
1689}
1690
1691#[derive(Debug)]
1692struct AutocloseRegion {
1693 selection_id: usize,
1694 range: Range<Anchor>,
1695 pair: BracketPair,
1696}
1697
1698#[derive(Debug)]
1699struct SnippetState {
1700 ranges: Vec<Vec<Range<Anchor>>>,
1701 active_index: usize,
1702 choices: Vec<Option<Vec<String>>>,
1703}
1704
1705#[doc(hidden)]
1706pub struct RenameState {
1707 pub range: Range<Anchor>,
1708 pub old_name: Arc<str>,
1709 pub editor: Entity<Editor>,
1710 block_id: CustomBlockId,
1711}
1712
1713struct InvalidationStack<T>(Vec<T>);
1714
1715struct RegisteredEditPredictionDelegate {
1716 provider: Arc<dyn EditPredictionDelegateHandle>,
1717 _subscription: Subscription,
1718}
1719
1720#[derive(Debug, PartialEq, Eq)]
1721pub struct ActiveDiagnosticGroup {
1722 pub active_range: Range<Anchor>,
1723 pub active_message: String,
1724 pub group_id: usize,
1725 pub blocks: HashSet<CustomBlockId>,
1726}
1727
1728#[derive(Debug, PartialEq, Eq)]
1729
1730pub(crate) enum ActiveDiagnostic {
1731 None,
1732 All,
1733 Group(ActiveDiagnosticGroup),
1734}
1735
1736#[derive(Serialize, Deserialize, Clone, Debug)]
1737pub struct ClipboardSelection {
1738 /// The number of bytes in this selection.
1739 pub len: usize,
1740 /// Whether this was a full-line selection.
1741 pub is_entire_line: bool,
1742 /// The indentation of the first line when this content was originally copied.
1743 pub first_line_indent: u32,
1744 #[serde(default)]
1745 pub file_path: Option<PathBuf>,
1746 #[serde(default)]
1747 pub line_range: Option<RangeInclusive<u32>>,
1748}
1749
1750impl ClipboardSelection {
1751 pub fn for_buffer(
1752 len: usize,
1753 is_entire_line: bool,
1754 range: Range<Point>,
1755 buffer: &MultiBufferSnapshot,
1756 project: Option<&Entity<Project>>,
1757 cx: &App,
1758 ) -> Self {
1759 let first_line_indent = buffer
1760 .indent_size_for_line(MultiBufferRow(range.start.row))
1761 .len;
1762
1763 let file_path = util::maybe!({
1764 let project = project?.read(cx);
1765 let file = buffer.file_at(range.start)?;
1766 let project_path = ProjectPath {
1767 worktree_id: file.worktree_id(cx),
1768 path: file.path().clone(),
1769 };
1770 project.absolute_path(&project_path, cx)
1771 });
1772
1773 let line_range = file_path.as_ref().and_then(|_| {
1774 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1775 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1776 if start_excerpt_id == end_excerpt_id {
1777 Some(start_point.row..=end_point.row)
1778 } else {
1779 None
1780 }
1781 });
1782
1783 Self {
1784 len,
1785 is_entire_line,
1786 first_line_indent,
1787 file_path,
1788 line_range,
1789 }
1790 }
1791}
1792
1793// selections, scroll behavior, was newest selection reversed
1794type SelectSyntaxNodeHistoryState = (
1795 Box<[Selection<Anchor>]>,
1796 SelectSyntaxNodeScrollBehavior,
1797 bool,
1798);
1799
1800#[derive(Default)]
1801struct SelectSyntaxNodeHistory {
1802 stack: Vec<SelectSyntaxNodeHistoryState>,
1803 // disable temporarily to allow changing selections without losing the stack
1804 pub disable_clearing: bool,
1805}
1806
1807impl SelectSyntaxNodeHistory {
1808 pub fn try_clear(&mut self) {
1809 if !self.disable_clearing {
1810 self.stack.clear();
1811 }
1812 }
1813
1814 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1815 self.stack.push(selection);
1816 }
1817
1818 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1819 self.stack.pop()
1820 }
1821}
1822
1823enum SelectSyntaxNodeScrollBehavior {
1824 CursorTop,
1825 FitSelection,
1826 CursorBottom,
1827}
1828
1829#[derive(Debug, Clone, Copy)]
1830pub(crate) struct NavigationData {
1831 cursor_anchor: Anchor,
1832 cursor_position: Point,
1833 scroll_anchor: ScrollAnchor,
1834 scroll_top_row: u32,
1835}
1836
1837#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1838pub enum GotoDefinitionKind {
1839 Symbol,
1840 Declaration,
1841 Type,
1842 Implementation,
1843}
1844
1845pub enum FormatTarget {
1846 Buffers(HashSet<Entity<Buffer>>),
1847 Ranges(Vec<Range<MultiBufferPoint>>),
1848}
1849
1850pub(crate) struct FocusedBlock {
1851 id: BlockId,
1852 focus_handle: WeakFocusHandle,
1853}
1854
1855#[derive(Clone, Debug)]
1856pub enum JumpData {
1857 MultiBufferRow {
1858 row: MultiBufferRow,
1859 line_offset_from_top: u32,
1860 },
1861 MultiBufferPoint {
1862 excerpt_id: ExcerptId,
1863 position: Point,
1864 anchor: text::Anchor,
1865 line_offset_from_top: u32,
1866 },
1867}
1868
1869pub enum MultibufferSelectionMode {
1870 First,
1871 All,
1872}
1873
1874#[derive(Clone, Copy, Debug, Default)]
1875pub struct RewrapOptions {
1876 pub override_language_settings: bool,
1877 pub preserve_existing_whitespace: bool,
1878}
1879
1880impl Editor {
1881 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1882 let buffer = cx.new(|cx| Buffer::local("", cx));
1883 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1884 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1885 }
1886
1887 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1888 let buffer = cx.new(|cx| Buffer::local("", cx));
1889 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1890 Self::new(EditorMode::full(), buffer, None, window, cx)
1891 }
1892
1893 pub fn auto_height(
1894 min_lines: usize,
1895 max_lines: usize,
1896 window: &mut Window,
1897 cx: &mut Context<Self>,
1898 ) -> Self {
1899 let buffer = cx.new(|cx| Buffer::local("", cx));
1900 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1901 Self::new(
1902 EditorMode::AutoHeight {
1903 min_lines,
1904 max_lines: Some(max_lines),
1905 },
1906 buffer,
1907 None,
1908 window,
1909 cx,
1910 )
1911 }
1912
1913 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1914 /// The editor grows as tall as needed to fit its content.
1915 pub fn auto_height_unbounded(
1916 min_lines: usize,
1917 window: &mut Window,
1918 cx: &mut Context<Self>,
1919 ) -> Self {
1920 let buffer = cx.new(|cx| Buffer::local("", cx));
1921 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1922 Self::new(
1923 EditorMode::AutoHeight {
1924 min_lines,
1925 max_lines: None,
1926 },
1927 buffer,
1928 None,
1929 window,
1930 cx,
1931 )
1932 }
1933
1934 pub fn for_buffer(
1935 buffer: Entity<Buffer>,
1936 project: Option<Entity<Project>>,
1937 window: &mut Window,
1938 cx: &mut Context<Self>,
1939 ) -> Self {
1940 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1941 Self::new(EditorMode::full(), buffer, project, window, cx)
1942 }
1943
1944 pub fn for_multibuffer(
1945 buffer: Entity<MultiBuffer>,
1946 project: Option<Entity<Project>>,
1947 window: &mut Window,
1948 cx: &mut Context<Self>,
1949 ) -> Self {
1950 Self::new(EditorMode::full(), buffer, project, window, cx)
1951 }
1952
1953 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1954 let mut clone = Self::new(
1955 self.mode.clone(),
1956 self.buffer.clone(),
1957 self.project.clone(),
1958 window,
1959 cx,
1960 );
1961 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1962 let snapshot = display_map.snapshot(cx);
1963 clone.display_map.update(cx, |display_map, cx| {
1964 display_map.set_state(&snapshot, cx);
1965 });
1966 snapshot
1967 });
1968 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1969 clone.folds_did_change(cx);
1970 clone.selections.clone_state(&self.selections);
1971 clone
1972 .scroll_manager
1973 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1974 clone.searchable = self.searchable;
1975 clone.read_only = self.read_only;
1976 clone.buffers_with_disabled_indent_guides =
1977 self.buffers_with_disabled_indent_guides.clone();
1978 clone
1979 }
1980
1981 pub fn new(
1982 mode: EditorMode,
1983 buffer: Entity<MultiBuffer>,
1984 project: Option<Entity<Project>>,
1985 window: &mut Window,
1986 cx: &mut Context<Self>,
1987 ) -> Self {
1988 Editor::new_internal(mode, buffer, project, None, window, cx)
1989 }
1990
1991 pub fn refresh_sticky_headers(
1992 &mut self,
1993 display_snapshot: &DisplaySnapshot,
1994 cx: &mut Context<Editor>,
1995 ) {
1996 if !self.mode.is_full() {
1997 return;
1998 }
1999 let multi_buffer = display_snapshot.buffer_snapshot();
2000 let scroll_anchor = self
2001 .scroll_manager
2002 .native_anchor(display_snapshot, cx)
2003 .anchor;
2004 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2005 return;
2006 };
2007 let buffer = buffer.clone();
2008
2009 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2010 let max_row = buffer.max_point().row;
2011 let start_row = buffer_visible_start.row.min(max_row);
2012 let end_row = (buffer_visible_start.row + 10).min(max_row);
2013
2014 let syntax = self.style(cx).syntax.clone();
2015 let background_task = cx.background_spawn(async move {
2016 buffer
2017 .outline_items_containing(
2018 Point::new(start_row, 0)..Point::new(end_row, 0),
2019 true,
2020 Some(syntax.as_ref()),
2021 )
2022 .into_iter()
2023 .map(|outline_item| OutlineItem {
2024 depth: outline_item.depth,
2025 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2026 source_range_for_text: Anchor::range_in_buffer(
2027 excerpt_id,
2028 outline_item.source_range_for_text,
2029 ),
2030 text: outline_item.text,
2031 highlight_ranges: outline_item.highlight_ranges,
2032 name_ranges: outline_item.name_ranges,
2033 body_range: outline_item
2034 .body_range
2035 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2036 annotation_range: outline_item
2037 .annotation_range
2038 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2039 })
2040 .collect()
2041 });
2042 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2043 let sticky_headers = background_task.await;
2044 this.update(cx, |this, cx| {
2045 this.sticky_headers = Some(sticky_headers);
2046 cx.notify();
2047 })
2048 .ok();
2049 });
2050 }
2051
2052 fn new_internal(
2053 mode: EditorMode,
2054 multi_buffer: Entity<MultiBuffer>,
2055 project: Option<Entity<Project>>,
2056 display_map: Option<Entity<DisplayMap>>,
2057 window: &mut Window,
2058 cx: &mut Context<Self>,
2059 ) -> Self {
2060 debug_assert!(
2061 display_map.is_none() || mode.is_minimap(),
2062 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2063 );
2064
2065 let full_mode = mode.is_full();
2066 let is_minimap = mode.is_minimap();
2067 let diagnostics_max_severity = if full_mode {
2068 EditorSettings::get_global(cx)
2069 .diagnostics_max_severity
2070 .unwrap_or(DiagnosticSeverity::Hint)
2071 } else {
2072 DiagnosticSeverity::Off
2073 };
2074 let style = window.text_style();
2075 let font_size = style.font_size.to_pixels(window.rem_size());
2076 let editor = cx.entity().downgrade();
2077 let fold_placeholder = FoldPlaceholder {
2078 constrain_width: false,
2079 render: Arc::new(move |fold_id, fold_range, cx| {
2080 let editor = editor.clone();
2081 FoldPlaceholder::fold_element(fold_id, cx)
2082 .cursor_pointer()
2083 .child("⋯")
2084 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2085 .on_click(move |_, _window, cx| {
2086 editor
2087 .update(cx, |editor, cx| {
2088 editor.unfold_ranges(
2089 &[fold_range.start..fold_range.end],
2090 true,
2091 false,
2092 cx,
2093 );
2094 cx.stop_propagation();
2095 })
2096 .ok();
2097 })
2098 .into_any()
2099 }),
2100 merge_adjacent: true,
2101 ..FoldPlaceholder::default()
2102 };
2103 let display_map = display_map.unwrap_or_else(|| {
2104 cx.new(|cx| {
2105 DisplayMap::new(
2106 multi_buffer.clone(),
2107 style.font(),
2108 font_size,
2109 None,
2110 FILE_HEADER_HEIGHT,
2111 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2112 fold_placeholder,
2113 diagnostics_max_severity,
2114 cx,
2115 )
2116 })
2117 });
2118
2119 let selections = SelectionsCollection::new();
2120
2121 let blink_manager = cx.new(|cx| {
2122 let mut blink_manager = BlinkManager::new(
2123 CURSOR_BLINK_INTERVAL,
2124 |cx| EditorSettings::get_global(cx).cursor_blink,
2125 cx,
2126 );
2127 if is_minimap {
2128 blink_manager.disable(cx);
2129 }
2130 blink_manager
2131 });
2132
2133 let soft_wrap_mode_override =
2134 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2135
2136 let mut project_subscriptions = Vec::new();
2137 if full_mode && let Some(project) = project.as_ref() {
2138 project_subscriptions.push(cx.subscribe_in(
2139 project,
2140 window,
2141 |editor, _, event, window, cx| match event {
2142 project::Event::RefreshCodeLens => {
2143 // we always query lens with actions, without storing them, always refreshing them
2144 }
2145 project::Event::RefreshInlayHints {
2146 server_id,
2147 request_id,
2148 } => {
2149 editor.refresh_inlay_hints(
2150 InlayHintRefreshReason::RefreshRequested {
2151 server_id: *server_id,
2152 request_id: *request_id,
2153 },
2154 cx,
2155 );
2156 }
2157 project::Event::RefreshSemanticTokens {
2158 server_id,
2159 request_id,
2160 } => {
2161 editor.refresh_semantic_tokens(
2162 None,
2163 Some(RefreshForServer {
2164 server_id: *server_id,
2165 request_id: *request_id,
2166 }),
2167 cx,
2168 );
2169 }
2170 project::Event::LanguageServerRemoved(_) => {
2171 editor.registered_buffers.clear();
2172 editor.register_visible_buffers(cx);
2173 editor.invalidate_semantic_tokens(None);
2174 editor.update_lsp_data(None, window, cx);
2175 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2176 if editor.tasks_update_task.is_none() {
2177 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2178 }
2179 }
2180 project::Event::LanguageServerAdded(..) => {
2181 if editor.tasks_update_task.is_none() {
2182 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2183 }
2184 }
2185 project::Event::SnippetEdit(id, snippet_edits) => {
2186 // todo(lw): Non singletons
2187 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2188 let snapshot = buffer.read(cx).snapshot();
2189 let focus_handle = editor.focus_handle(cx);
2190 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2191 for (range, snippet) in snippet_edits {
2192 let buffer_range =
2193 language::range_from_lsp(*range).to_offset(&snapshot);
2194 editor
2195 .insert_snippet(
2196 &[MultiBufferOffset(buffer_range.start)
2197 ..MultiBufferOffset(buffer_range.end)],
2198 snippet.clone(),
2199 window,
2200 cx,
2201 )
2202 .ok();
2203 }
2204 }
2205 }
2206 }
2207 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2208 let buffer_id = *buffer_id;
2209 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2210 editor.register_buffer(buffer_id, cx);
2211 editor.update_lsp_data(Some(buffer_id), window, cx);
2212 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2213 refresh_linked_ranges(editor, window, cx);
2214 editor.refresh_code_actions(window, cx);
2215 editor.refresh_document_highlights(cx);
2216 }
2217 }
2218
2219 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2220 let Some(workspace) = editor.workspace() else {
2221 return;
2222 };
2223 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2224 else {
2225 return;
2226 };
2227
2228 if active_editor.entity_id() == cx.entity_id() {
2229 let entity_id = cx.entity_id();
2230 workspace.update(cx, |this, cx| {
2231 this.panes_mut()
2232 .iter_mut()
2233 .filter(|pane| pane.entity_id() != entity_id)
2234 .for_each(|p| {
2235 p.update(cx, |pane, _| {
2236 pane.nav_history_mut().rename_item(
2237 entity_id,
2238 project_path.clone(),
2239 abs_path.clone().into(),
2240 );
2241 })
2242 });
2243 });
2244
2245 Self::open_transaction_for_hidden_buffers(
2246 workspace,
2247 transaction.clone(),
2248 "Rename".to_string(),
2249 window,
2250 cx,
2251 );
2252 }
2253 }
2254
2255 project::Event::WorkspaceEditApplied(transaction) => {
2256 let Some(workspace) = editor.workspace() else {
2257 return;
2258 };
2259 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2260 else {
2261 return;
2262 };
2263
2264 if active_editor.entity_id() == cx.entity_id() {
2265 Self::open_transaction_for_hidden_buffers(
2266 workspace,
2267 transaction.clone(),
2268 "LSP Edit".to_string(),
2269 window,
2270 cx,
2271 );
2272 }
2273 }
2274
2275 _ => {}
2276 },
2277 ));
2278 if let Some(task_inventory) = project
2279 .read(cx)
2280 .task_store()
2281 .read(cx)
2282 .task_inventory()
2283 .cloned()
2284 {
2285 project_subscriptions.push(cx.observe_in(
2286 &task_inventory,
2287 window,
2288 |editor, _, window, cx| {
2289 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2290 },
2291 ));
2292 };
2293
2294 project_subscriptions.push(cx.subscribe_in(
2295 &project.read(cx).breakpoint_store(),
2296 window,
2297 |editor, _, event, window, cx| match event {
2298 BreakpointStoreEvent::ClearDebugLines => {
2299 editor.clear_row_highlights::<ActiveDebugLine>();
2300 editor.refresh_inline_values(cx);
2301 }
2302 BreakpointStoreEvent::SetDebugLine => {
2303 if editor.go_to_active_debug_line(window, cx) {
2304 cx.stop_propagation();
2305 }
2306
2307 editor.refresh_inline_values(cx);
2308 }
2309 _ => {}
2310 },
2311 ));
2312 let git_store = project.read(cx).git_store().clone();
2313 let project = project.clone();
2314 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2315 if let GitStoreEvent::RepositoryAdded = event {
2316 this.load_diff_task = Some(
2317 update_uncommitted_diff_for_buffer(
2318 cx.entity(),
2319 &project,
2320 this.buffer.read(cx).all_buffers(),
2321 this.buffer.clone(),
2322 cx,
2323 )
2324 .shared(),
2325 );
2326 }
2327 }));
2328 }
2329
2330 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2331
2332 let inlay_hint_settings =
2333 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2334 let focus_handle = cx.focus_handle();
2335 if !is_minimap {
2336 cx.on_focus(&focus_handle, window, Self::handle_focus)
2337 .detach();
2338 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2339 .detach();
2340 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2341 .detach();
2342 cx.on_blur(&focus_handle, window, Self::handle_blur)
2343 .detach();
2344 cx.observe_pending_input(window, Self::observe_pending_input)
2345 .detach();
2346 }
2347
2348 let show_indent_guides =
2349 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2350 Some(false)
2351 } else {
2352 None
2353 };
2354
2355 let breakpoint_store = match (&mode, project.as_ref()) {
2356 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2357 _ => None,
2358 };
2359
2360 let mut code_action_providers = Vec::new();
2361 let mut load_uncommitted_diff = None;
2362 if let Some(project) = project.clone() {
2363 load_uncommitted_diff = Some(
2364 update_uncommitted_diff_for_buffer(
2365 cx.entity(),
2366 &project,
2367 multi_buffer.read(cx).all_buffers(),
2368 multi_buffer.clone(),
2369 cx,
2370 )
2371 .shared(),
2372 );
2373 code_action_providers.push(Rc::new(project) as Rc<_>);
2374 }
2375
2376 let mut editor = Self {
2377 focus_handle,
2378 show_cursor_when_unfocused: false,
2379 last_focused_descendant: None,
2380 buffer: multi_buffer.clone(),
2381 display_map: display_map.clone(),
2382 placeholder_display_map: None,
2383 selections,
2384 scroll_manager: ScrollManager::new(cx),
2385 columnar_selection_state: None,
2386 add_selections_state: None,
2387 select_next_state: None,
2388 select_prev_state: None,
2389 selection_history: SelectionHistory::default(),
2390 defer_selection_effects: false,
2391 deferred_selection_effects_state: None,
2392 autoclose_regions: Vec::new(),
2393 snippet_stack: InvalidationStack::default(),
2394 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2395 ime_transaction: None,
2396 active_diagnostics: ActiveDiagnostic::None,
2397 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2398 inline_diagnostics_update: Task::ready(()),
2399 inline_diagnostics: Vec::new(),
2400 soft_wrap_mode_override,
2401 diagnostics_max_severity,
2402 hard_wrap: None,
2403 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2404 semantics_provider: project
2405 .as_ref()
2406 .map(|project| Rc::new(project.downgrade()) as _),
2407 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2408 project,
2409 blink_manager: blink_manager.clone(),
2410 show_local_selections: true,
2411 show_scrollbars: ScrollbarAxes {
2412 horizontal: full_mode,
2413 vertical: full_mode,
2414 },
2415 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2416 offset_content: !matches!(mode, EditorMode::SingleLine),
2417 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2418 show_gutter: full_mode,
2419 show_line_numbers: (!full_mode).then_some(false),
2420 use_relative_line_numbers: None,
2421 disable_expand_excerpt_buttons: !full_mode,
2422 delegate_expand_excerpts: false,
2423 delegate_stage_and_restore: false,
2424 delegate_open_excerpts: false,
2425 enable_lsp_data: true,
2426 enable_runnables: true,
2427 show_git_diff_gutter: None,
2428 show_code_actions: None,
2429 show_runnables: None,
2430 show_breakpoints: None,
2431 show_diff_review_button: false,
2432 show_wrap_guides: None,
2433 show_indent_guides,
2434 buffers_with_disabled_indent_guides: HashSet::default(),
2435 highlight_order: 0,
2436 highlighted_rows: HashMap::default(),
2437 background_highlights: HashMap::default(),
2438 gutter_highlights: HashMap::default(),
2439 scrollbar_marker_state: ScrollbarMarkerState::default(),
2440 active_indent_guides_state: ActiveIndentGuidesState::default(),
2441 nav_history: None,
2442 context_menu: RefCell::new(None),
2443 context_menu_options: None,
2444 mouse_context_menu: None,
2445 completion_tasks: Vec::new(),
2446 inline_blame_popover: None,
2447 inline_blame_popover_show_task: None,
2448 signature_help_state: SignatureHelpState::default(),
2449 auto_signature_help: None,
2450 find_all_references_task_sources: Vec::new(),
2451 next_completion_id: 0,
2452 next_inlay_id: 0,
2453 code_action_providers,
2454 available_code_actions: None,
2455 code_actions_task: None,
2456 quick_selection_highlight_task: None,
2457 debounced_selection_highlight_task: None,
2458 debounced_selection_highlight_complete: false,
2459 document_highlights_task: None,
2460 linked_editing_range_task: None,
2461 pending_rename: None,
2462 searchable: !is_minimap,
2463 cursor_shape: EditorSettings::get_global(cx)
2464 .cursor_shape
2465 .unwrap_or_default(),
2466 cursor_offset_on_selection: false,
2467 current_line_highlight: None,
2468 autoindent_mode: Some(AutoindentMode::EachLine),
2469 collapse_matches: false,
2470 workspace: None,
2471 input_enabled: !is_minimap,
2472 use_modal_editing: full_mode,
2473 read_only: is_minimap,
2474 use_autoclose: true,
2475 use_auto_surround: true,
2476 auto_replace_emoji_shortcode: false,
2477 jsx_tag_auto_close_enabled_in_any_buffer: false,
2478 leader_id: None,
2479 remote_id: None,
2480 hover_state: HoverState::default(),
2481 pending_mouse_down: None,
2482 prev_pressure_stage: None,
2483 hovered_link_state: None,
2484 edit_prediction_provider: None,
2485 active_edit_prediction: None,
2486 stale_edit_prediction_in_menu: None,
2487 edit_prediction_preview: EditPredictionPreview::Inactive {
2488 released_too_fast: false,
2489 },
2490 inline_diagnostics_enabled: full_mode,
2491 diagnostics_enabled: full_mode,
2492 word_completions_enabled: full_mode,
2493 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2494 gutter_hovered: false,
2495 pixel_position_of_newest_cursor: None,
2496 last_bounds: None,
2497 last_position_map: None,
2498 expect_bounds_change: None,
2499 gutter_dimensions: GutterDimensions::default(),
2500 style: None,
2501 show_cursor_names: false,
2502 hovered_cursors: HashMap::default(),
2503 next_editor_action_id: EditorActionId::default(),
2504 editor_actions: Rc::default(),
2505 edit_predictions_hidden_for_vim_mode: false,
2506 show_edit_predictions_override: None,
2507 show_completions_on_input_override: None,
2508 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2509 edit_prediction_settings: EditPredictionSettings::Disabled,
2510 edit_prediction_indent_conflict: false,
2511 edit_prediction_requires_modifier_in_indent_conflict: true,
2512 custom_context_menu: None,
2513 show_git_blame_gutter: false,
2514 show_git_blame_inline: false,
2515 show_selection_menu: None,
2516 show_git_blame_inline_delay_task: None,
2517 git_blame_inline_enabled: full_mode
2518 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2519 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2520 buffer_serialization: is_minimap.not().then(|| {
2521 BufferSerialization::new(
2522 ProjectSettings::get_global(cx)
2523 .session
2524 .restore_unsaved_buffers,
2525 )
2526 }),
2527 blame: None,
2528 blame_subscription: None,
2529 tasks: BTreeMap::default(),
2530
2531 breakpoint_store,
2532 gutter_breakpoint_indicator: (None, None),
2533 gutter_diff_review_indicator: (None, None),
2534 diff_review_drag_state: None,
2535 diff_review_overlays: Vec::new(),
2536 stored_review_comments: Vec::new(),
2537 next_review_comment_id: 0,
2538 hovered_diff_hunk_row: None,
2539 _subscriptions: (!is_minimap)
2540 .then(|| {
2541 vec![
2542 cx.observe(&multi_buffer, Self::on_buffer_changed),
2543 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2544 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2545 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2546 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2547 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2548 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2549 cx.observe_window_activation(window, |editor, window, cx| {
2550 let active = window.is_window_active();
2551 editor.blink_manager.update(cx, |blink_manager, cx| {
2552 if active {
2553 blink_manager.enable(cx);
2554 } else {
2555 blink_manager.disable(cx);
2556 }
2557 });
2558 if active {
2559 editor.show_mouse_cursor(cx);
2560 }
2561 }),
2562 ]
2563 })
2564 .unwrap_or_default(),
2565 tasks_update_task: None,
2566 pull_diagnostics_task: Task::ready(()),
2567 colors: None,
2568 refresh_colors_task: Task::ready(()),
2569 use_document_folding_ranges: false,
2570 refresh_folding_ranges_task: Task::ready(()),
2571 inlay_hints: None,
2572 next_color_inlay_id: 0,
2573 post_scroll_update: Task::ready(()),
2574 linked_edit_ranges: Default::default(),
2575 in_project_search: false,
2576 previous_search_ranges: None,
2577 breadcrumb_header: None,
2578 focused_block: None,
2579 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2580 addons: HashMap::default(),
2581 registered_buffers: HashMap::default(),
2582 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2583 selection_mark_mode: false,
2584 toggle_fold_multiple_buffers: Task::ready(()),
2585 serialize_selections: Task::ready(()),
2586 serialize_folds: Task::ready(()),
2587 text_style_refinement: None,
2588 load_diff_task: load_uncommitted_diff,
2589 temporary_diff_override: false,
2590 mouse_cursor_hidden: false,
2591 minimap: None,
2592 hide_mouse_mode: EditorSettings::get_global(cx)
2593 .hide_mouse
2594 .unwrap_or_default(),
2595 change_list: ChangeList::new(),
2596 mode,
2597 selection_drag_state: SelectionDragState::None,
2598 folding_newlines: Task::ready(()),
2599 lookup_key: None,
2600 select_next_is_case_sensitive: None,
2601 on_local_selections_changed: None,
2602 suppress_selection_callback: false,
2603 applicable_language_settings: HashMap::default(),
2604 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2605 accent_data: None,
2606 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2607 number_deleted_lines: false,
2608 refresh_matching_bracket_highlights_task: Task::ready(()),
2609 refresh_document_symbols_task: Task::ready(()).shared(),
2610 lsp_document_symbols: HashMap::default(),
2611 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2612 outline_symbols_at_cursor: None,
2613 sticky_headers_task: Task::ready(()),
2614 sticky_headers: None,
2615 colorize_brackets_task: Task::ready(()),
2616 };
2617
2618 if is_minimap {
2619 return editor;
2620 }
2621
2622 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2623 editor.accent_data = editor.fetch_accent_data(cx);
2624
2625 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2626 editor
2627 ._subscriptions
2628 .push(cx.observe(breakpoints, |_, _, cx| {
2629 cx.notify();
2630 }));
2631 }
2632 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2633 editor._subscriptions.extend(project_subscriptions);
2634
2635 editor._subscriptions.push(cx.subscribe_in(
2636 &cx.entity(),
2637 window,
2638 |editor, _, e: &EditorEvent, window, cx| match e {
2639 EditorEvent::ScrollPositionChanged { local, .. } => {
2640 if *local {
2641 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2642 editor.inline_blame_popover.take();
2643 let snapshot = editor.snapshot(window, cx);
2644 let new_anchor = editor
2645 .scroll_manager
2646 .native_anchor(&snapshot.display_snapshot, cx);
2647 editor.update_restoration_data(cx, move |data| {
2648 data.scroll_position = (
2649 new_anchor.top_row(snapshot.buffer_snapshot()),
2650 new_anchor.offset,
2651 );
2652 });
2653
2654 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2655 cx.background_executor()
2656 .timer(Duration::from_millis(50))
2657 .await;
2658 editor
2659 .update_in(cx, |editor, window, cx| {
2660 editor.register_visible_buffers(cx);
2661 editor.colorize_brackets(false, cx);
2662 editor.refresh_inlay_hints(
2663 InlayHintRefreshReason::NewLinesShown,
2664 cx,
2665 );
2666 if !editor.buffer().read(cx).is_singleton() {
2667 editor.update_lsp_data(None, window, cx);
2668 }
2669 })
2670 .ok();
2671 });
2672 }
2673 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2674 }
2675 EditorEvent::Edited { .. } => {
2676 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2677 .map(|vim_mode| vim_mode.0)
2678 .unwrap_or(false);
2679 if !vim_mode {
2680 let display_map = editor.display_snapshot(cx);
2681 let selections = editor.selections.all_adjusted_display(&display_map);
2682 let pop_state = editor
2683 .change_list
2684 .last()
2685 .map(|previous| {
2686 previous.len() == selections.len()
2687 && previous.iter().enumerate().all(|(ix, p)| {
2688 p.to_display_point(&display_map).row()
2689 == selections[ix].head().row()
2690 })
2691 })
2692 .unwrap_or(false);
2693 let new_positions = selections
2694 .into_iter()
2695 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2696 .collect();
2697 editor
2698 .change_list
2699 .push_to_change_list(pop_state, new_positions);
2700 }
2701 }
2702 _ => (),
2703 },
2704 ));
2705
2706 if let Some(dap_store) = editor
2707 .project
2708 .as_ref()
2709 .map(|project| project.read(cx).dap_store())
2710 {
2711 let weak_editor = cx.weak_entity();
2712
2713 editor
2714 ._subscriptions
2715 .push(
2716 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2717 let session_entity = cx.entity();
2718 weak_editor
2719 .update(cx, |editor, cx| {
2720 editor._subscriptions.push(
2721 cx.subscribe(&session_entity, Self::on_debug_session_event),
2722 );
2723 })
2724 .ok();
2725 }),
2726 );
2727
2728 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2729 editor
2730 ._subscriptions
2731 .push(cx.subscribe(&session, Self::on_debug_session_event));
2732 }
2733 }
2734
2735 // skip adding the initial selection to selection history
2736 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2737 editor.end_selection(window, cx);
2738 editor.selection_history.mode = SelectionHistoryMode::Normal;
2739
2740 editor.scroll_manager.show_scrollbars(window, cx);
2741 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2742
2743 if full_mode {
2744 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2745 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2746
2747 if editor.git_blame_inline_enabled {
2748 editor.start_git_blame_inline(false, window, cx);
2749 }
2750
2751 editor.go_to_active_debug_line(window, cx);
2752
2753 editor.minimap =
2754 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2755 editor.colors = Some(LspColorData::new(cx));
2756 editor.use_document_folding_ranges = true;
2757 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2758
2759 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2760 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2761 }
2762 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2763 }
2764
2765 editor
2766 }
2767
2768 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2769 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2770 }
2771
2772 pub fn deploy_mouse_context_menu(
2773 &mut self,
2774 position: gpui::Point<Pixels>,
2775 context_menu: Entity<ContextMenu>,
2776 window: &mut Window,
2777 cx: &mut Context<Self>,
2778 ) {
2779 self.mouse_context_menu = Some(MouseContextMenu::new(
2780 self,
2781 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2782 context_menu,
2783 window,
2784 cx,
2785 ));
2786 }
2787
2788 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2789 self.mouse_context_menu
2790 .as_ref()
2791 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2792 }
2793
2794 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2795 if self
2796 .selections
2797 .pending_anchor()
2798 .is_some_and(|pending_selection| {
2799 let snapshot = self.buffer().read(cx).snapshot(cx);
2800 pending_selection.range().includes(range, &snapshot)
2801 })
2802 {
2803 return true;
2804 }
2805
2806 self.selections
2807 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2808 .into_iter()
2809 .any(|selection| {
2810 // This is needed to cover a corner case, if we just check for an existing
2811 // selection in the fold range, having a cursor at the start of the fold
2812 // marks it as selected. Non-empty selections don't cause this.
2813 let length = selection.end - selection.start;
2814 length > 0
2815 })
2816 }
2817
2818 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2819 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2820 }
2821
2822 fn key_context_internal(
2823 &self,
2824 has_active_edit_prediction: bool,
2825 window: &mut Window,
2826 cx: &mut App,
2827 ) -> KeyContext {
2828 let mut key_context = KeyContext::new_with_defaults();
2829 key_context.add("Editor");
2830 let mode = match self.mode {
2831 EditorMode::SingleLine => "single_line",
2832 EditorMode::AutoHeight { .. } => "auto_height",
2833 EditorMode::Minimap { .. } => "minimap",
2834 EditorMode::Full { .. } => "full",
2835 };
2836
2837 if EditorSettings::jupyter_enabled(cx) {
2838 key_context.add("jupyter");
2839 }
2840
2841 key_context.set("mode", mode);
2842 if self.pending_rename.is_some() {
2843 key_context.add("renaming");
2844 }
2845
2846 if let Some(snippet_stack) = self.snippet_stack.last() {
2847 key_context.add("in_snippet");
2848
2849 if snippet_stack.active_index > 0 {
2850 key_context.add("has_previous_tabstop");
2851 }
2852
2853 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2854 key_context.add("has_next_tabstop");
2855 }
2856 }
2857
2858 match self.context_menu.borrow().as_ref() {
2859 Some(CodeContextMenu::Completions(menu)) => {
2860 if menu.visible() {
2861 key_context.add("menu");
2862 key_context.add("showing_completions");
2863 }
2864 }
2865 Some(CodeContextMenu::CodeActions(menu)) => {
2866 if menu.visible() {
2867 key_context.add("menu");
2868 key_context.add("showing_code_actions")
2869 }
2870 }
2871 None => {}
2872 }
2873
2874 if self.signature_help_state.has_multiple_signatures() {
2875 key_context.add("showing_signature_help");
2876 }
2877
2878 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2879 if !self.focus_handle(cx).contains_focused(window, cx)
2880 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2881 {
2882 for addon in self.addons.values() {
2883 addon.extend_key_context(&mut key_context, cx)
2884 }
2885 }
2886
2887 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2888 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2889 Some(
2890 file.full_path(cx)
2891 .extension()?
2892 .to_string_lossy()
2893 .to_lowercase(),
2894 )
2895 }) {
2896 key_context.set("extension", extension);
2897 }
2898 } else {
2899 key_context.add("multibuffer");
2900 }
2901
2902 if has_active_edit_prediction {
2903 if self.edit_prediction_in_conflict() {
2904 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2905 } else {
2906 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2907 key_context.add("copilot_suggestion");
2908 }
2909 }
2910
2911 if self.selection_mark_mode {
2912 key_context.add("selection_mode");
2913 }
2914
2915 let disjoint = self.selections.disjoint_anchors();
2916 let snapshot = self.snapshot(window, cx);
2917 let snapshot = snapshot.buffer_snapshot();
2918 if self.mode == EditorMode::SingleLine
2919 && let [selection] = disjoint
2920 && selection.start == selection.end
2921 && selection.end.to_offset(snapshot) == snapshot.len()
2922 {
2923 key_context.add("end_of_input");
2924 }
2925
2926 if self.has_any_expanded_diff_hunks(cx) {
2927 key_context.add("diffs_expanded");
2928 }
2929
2930 key_context
2931 }
2932
2933 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2934 self.last_bounds.as_ref()
2935 }
2936
2937 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2938 if self.mouse_cursor_hidden {
2939 self.mouse_cursor_hidden = false;
2940 cx.notify();
2941 }
2942 }
2943
2944 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2945 let hide_mouse_cursor = match origin {
2946 HideMouseCursorOrigin::TypingAction => {
2947 matches!(
2948 self.hide_mouse_mode,
2949 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2950 )
2951 }
2952 HideMouseCursorOrigin::MovementAction => {
2953 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2954 }
2955 };
2956 if self.mouse_cursor_hidden != hide_mouse_cursor {
2957 self.mouse_cursor_hidden = hide_mouse_cursor;
2958 cx.notify();
2959 }
2960 }
2961
2962 pub fn edit_prediction_in_conflict(&self) -> bool {
2963 if !self.show_edit_predictions_in_menu() {
2964 return false;
2965 }
2966
2967 let showing_completions = self
2968 .context_menu
2969 .borrow()
2970 .as_ref()
2971 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2972
2973 showing_completions
2974 || self.edit_prediction_requires_modifier()
2975 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2976 // bindings to insert tab characters.
2977 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2978 }
2979
2980 pub fn accept_edit_prediction_keybind(
2981 &self,
2982 granularity: EditPredictionGranularity,
2983 window: &mut Window,
2984 cx: &mut App,
2985 ) -> AcceptEditPredictionBinding {
2986 let key_context = self.key_context_internal(true, window, cx);
2987 let in_conflict = self.edit_prediction_in_conflict();
2988
2989 let bindings =
2990 match granularity {
2991 EditPredictionGranularity::Word => window
2992 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2993 EditPredictionGranularity::Line => window
2994 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2995 EditPredictionGranularity::Full => {
2996 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2997 }
2998 };
2999
3000 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
3001 !in_conflict
3002 || binding
3003 .keystrokes()
3004 .first()
3005 .is_some_and(|keystroke| keystroke.modifiers().modified())
3006 }))
3007 }
3008
3009 pub fn new_file(
3010 workspace: &mut Workspace,
3011 _: &workspace::NewFile,
3012 window: &mut Window,
3013 cx: &mut Context<Workspace>,
3014 ) {
3015 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3016 "Failed to create buffer",
3017 window,
3018 cx,
3019 |e, _, _| match e.error_code() {
3020 ErrorCode::RemoteUpgradeRequired => Some(format!(
3021 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3022 e.error_tag("required").unwrap_or("the latest version")
3023 )),
3024 _ => None,
3025 },
3026 );
3027 }
3028
3029 pub fn new_in_workspace(
3030 workspace: &mut Workspace,
3031 window: &mut Window,
3032 cx: &mut Context<Workspace>,
3033 ) -> Task<Result<Entity<Editor>>> {
3034 let project = workspace.project().clone();
3035 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3036
3037 cx.spawn_in(window, async move |workspace, cx| {
3038 let buffer = create.await?;
3039 workspace.update_in(cx, |workspace, window, cx| {
3040 let editor =
3041 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3042 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3043 editor
3044 })
3045 })
3046 }
3047
3048 fn new_file_vertical(
3049 workspace: &mut Workspace,
3050 _: &workspace::NewFileSplitVertical,
3051 window: &mut Window,
3052 cx: &mut Context<Workspace>,
3053 ) {
3054 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3055 }
3056
3057 fn new_file_horizontal(
3058 workspace: &mut Workspace,
3059 _: &workspace::NewFileSplitHorizontal,
3060 window: &mut Window,
3061 cx: &mut Context<Workspace>,
3062 ) {
3063 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3064 }
3065
3066 fn new_file_split(
3067 workspace: &mut Workspace,
3068 action: &workspace::NewFileSplit,
3069 window: &mut Window,
3070 cx: &mut Context<Workspace>,
3071 ) {
3072 Self::new_file_in_direction(workspace, action.0, window, cx)
3073 }
3074
3075 fn new_file_in_direction(
3076 workspace: &mut Workspace,
3077 direction: SplitDirection,
3078 window: &mut Window,
3079 cx: &mut Context<Workspace>,
3080 ) {
3081 let project = workspace.project().clone();
3082 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3083
3084 cx.spawn_in(window, async move |workspace, cx| {
3085 let buffer = create.await?;
3086 workspace.update_in(cx, move |workspace, window, cx| {
3087 workspace.split_item(
3088 direction,
3089 Box::new(
3090 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3091 ),
3092 window,
3093 cx,
3094 )
3095 })?;
3096 anyhow::Ok(())
3097 })
3098 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3099 match e.error_code() {
3100 ErrorCode::RemoteUpgradeRequired => Some(format!(
3101 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3102 e.error_tag("required").unwrap_or("the latest version")
3103 )),
3104 _ => None,
3105 }
3106 });
3107 }
3108
3109 pub fn leader_id(&self) -> Option<CollaboratorId> {
3110 self.leader_id
3111 }
3112
3113 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3114 &self.buffer
3115 }
3116
3117 pub fn project(&self) -> Option<&Entity<Project>> {
3118 self.project.as_ref()
3119 }
3120
3121 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3122 self.workspace.as_ref()?.0.upgrade()
3123 }
3124
3125 /// Detaches a task and shows an error notification in the workspace if available,
3126 /// otherwise just logs the error.
3127 pub fn detach_and_notify_err<R, E>(
3128 &self,
3129 task: Task<Result<R, E>>,
3130 window: &mut Window,
3131 cx: &mut App,
3132 ) where
3133 E: std::fmt::Debug + std::fmt::Display + 'static,
3134 R: 'static,
3135 {
3136 if let Some(workspace) = self.workspace() {
3137 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3138 } else {
3139 task.detach_and_log_err(cx);
3140 }
3141 }
3142
3143 /// Returns the workspace serialization ID if this editor should be serialized.
3144 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3145 self.workspace
3146 .as_ref()
3147 .filter(|_| self.should_serialize_buffer())
3148 .and_then(|workspace| workspace.1)
3149 }
3150
3151 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3152 self.buffer().read(cx).title(cx)
3153 }
3154
3155 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3156 let git_blame_gutter_max_author_length = self
3157 .render_git_blame_gutter(cx)
3158 .then(|| {
3159 if let Some(blame) = self.blame.as_ref() {
3160 let max_author_length =
3161 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3162 Some(max_author_length)
3163 } else {
3164 None
3165 }
3166 })
3167 .flatten();
3168
3169 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3170
3171 EditorSnapshot {
3172 mode: self.mode.clone(),
3173 show_gutter: self.show_gutter,
3174 offset_content: self.offset_content,
3175 show_line_numbers: self.show_line_numbers,
3176 number_deleted_lines: self.number_deleted_lines,
3177 show_git_diff_gutter: self.show_git_diff_gutter,
3178 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3179 show_code_actions: self.show_code_actions,
3180 show_runnables: self.show_runnables,
3181 show_breakpoints: self.show_breakpoints,
3182 git_blame_gutter_max_author_length,
3183 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3184 display_snapshot,
3185 placeholder_display_snapshot: self
3186 .placeholder_display_map
3187 .as_ref()
3188 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3189 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3190 is_focused: self.focus_handle.is_focused(window),
3191 current_line_highlight: self
3192 .current_line_highlight
3193 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3194 gutter_hovered: self.gutter_hovered,
3195 }
3196 }
3197
3198 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3199 self.buffer.read(cx).language_at(point, cx)
3200 }
3201
3202 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3203 self.buffer.read(cx).read(cx).file_at(point).cloned()
3204 }
3205
3206 pub fn active_excerpt(
3207 &self,
3208 cx: &App,
3209 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3210 self.buffer
3211 .read(cx)
3212 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3213 }
3214
3215 pub fn mode(&self) -> &EditorMode {
3216 &self.mode
3217 }
3218
3219 pub fn set_mode(&mut self, mode: EditorMode) {
3220 self.mode = mode;
3221 }
3222
3223 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3224 self.collaboration_hub.as_deref()
3225 }
3226
3227 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3228 self.collaboration_hub = Some(hub);
3229 }
3230
3231 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3232 self.in_project_search = in_project_search;
3233 }
3234
3235 pub fn set_custom_context_menu(
3236 &mut self,
3237 f: impl 'static
3238 + Fn(
3239 &mut Self,
3240 DisplayPoint,
3241 &mut Window,
3242 &mut Context<Self>,
3243 ) -> Option<Entity<ui::ContextMenu>>,
3244 ) {
3245 self.custom_context_menu = Some(Box::new(f))
3246 }
3247
3248 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3249 self.completion_provider = provider;
3250 }
3251
3252 #[cfg(any(test, feature = "test-support"))]
3253 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3254 self.completion_provider.clone()
3255 }
3256
3257 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3258 self.semantics_provider.clone()
3259 }
3260
3261 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3262 self.semantics_provider = provider;
3263 }
3264
3265 pub fn set_edit_prediction_provider<T>(
3266 &mut self,
3267 provider: Option<Entity<T>>,
3268 window: &mut Window,
3269 cx: &mut Context<Self>,
3270 ) where
3271 T: EditPredictionDelegate,
3272 {
3273 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3274 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3275 if this.focus_handle.is_focused(window) {
3276 this.update_visible_edit_prediction(window, cx);
3277 }
3278 }),
3279 provider: Arc::new(provider),
3280 });
3281 self.update_edit_prediction_settings(cx);
3282 self.refresh_edit_prediction(false, false, window, cx);
3283 }
3284
3285 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3286 self.placeholder_display_map
3287 .as_ref()
3288 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3289 }
3290
3291 pub fn set_placeholder_text(
3292 &mut self,
3293 placeholder_text: &str,
3294 window: &mut Window,
3295 cx: &mut Context<Self>,
3296 ) {
3297 let multibuffer = cx
3298 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3299
3300 let style = window.text_style();
3301
3302 self.placeholder_display_map = Some(cx.new(|cx| {
3303 DisplayMap::new(
3304 multibuffer,
3305 style.font(),
3306 style.font_size.to_pixels(window.rem_size()),
3307 None,
3308 FILE_HEADER_HEIGHT,
3309 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3310 Default::default(),
3311 DiagnosticSeverity::Off,
3312 cx,
3313 )
3314 }));
3315 cx.notify();
3316 }
3317
3318 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3319 self.cursor_shape = cursor_shape;
3320
3321 // Disrupt blink for immediate user feedback that the cursor shape has changed
3322 self.blink_manager.update(cx, BlinkManager::show_cursor);
3323
3324 cx.notify();
3325 }
3326
3327 pub fn cursor_shape(&self) -> CursorShape {
3328 self.cursor_shape
3329 }
3330
3331 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3332 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3333 }
3334
3335 pub fn set_current_line_highlight(
3336 &mut self,
3337 current_line_highlight: Option<CurrentLineHighlight>,
3338 ) {
3339 self.current_line_highlight = current_line_highlight;
3340 }
3341
3342 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3343 self.collapse_matches = collapse_matches;
3344 }
3345
3346 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3347 if self.collapse_matches {
3348 return range.start..range.start;
3349 }
3350 range.clone()
3351 }
3352
3353 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3354 self.display_map.read(cx).clip_at_line_ends
3355 }
3356
3357 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3358 if self.display_map.read(cx).clip_at_line_ends != clip {
3359 self.display_map
3360 .update(cx, |map, _| map.clip_at_line_ends = clip);
3361 }
3362 }
3363
3364 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3365 self.input_enabled = input_enabled;
3366 }
3367
3368 pub fn set_edit_predictions_hidden_for_vim_mode(
3369 &mut self,
3370 hidden: bool,
3371 window: &mut Window,
3372 cx: &mut Context<Self>,
3373 ) {
3374 if hidden != self.edit_predictions_hidden_for_vim_mode {
3375 self.edit_predictions_hidden_for_vim_mode = hidden;
3376 if hidden {
3377 self.update_visible_edit_prediction(window, cx);
3378 } else {
3379 self.refresh_edit_prediction(true, false, window, cx);
3380 }
3381 }
3382 }
3383
3384 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3385 self.menu_edit_predictions_policy = value;
3386 }
3387
3388 pub fn set_autoindent(&mut self, autoindent: bool) {
3389 if autoindent {
3390 self.autoindent_mode = Some(AutoindentMode::EachLine);
3391 } else {
3392 self.autoindent_mode = None;
3393 }
3394 }
3395
3396 pub fn capability(&self, cx: &App) -> Capability {
3397 if self.read_only {
3398 Capability::ReadOnly
3399 } else {
3400 self.buffer.read(cx).capability()
3401 }
3402 }
3403
3404 pub fn read_only(&self, cx: &App) -> bool {
3405 self.read_only || self.buffer.read(cx).read_only()
3406 }
3407
3408 pub fn set_read_only(&mut self, read_only: bool) {
3409 self.read_only = read_only;
3410 }
3411
3412 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3413 self.use_autoclose = autoclose;
3414 }
3415
3416 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3417 self.use_auto_surround = auto_surround;
3418 }
3419
3420 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3421 self.auto_replace_emoji_shortcode = auto_replace;
3422 }
3423
3424 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3425 self.buffer_serialization = should_serialize.then(|| {
3426 BufferSerialization::new(
3427 ProjectSettings::get_global(cx)
3428 .session
3429 .restore_unsaved_buffers,
3430 )
3431 })
3432 }
3433
3434 fn should_serialize_buffer(&self) -> bool {
3435 self.buffer_serialization.is_some()
3436 }
3437
3438 pub fn toggle_edit_predictions(
3439 &mut self,
3440 _: &ToggleEditPrediction,
3441 window: &mut Window,
3442 cx: &mut Context<Self>,
3443 ) {
3444 if self.show_edit_predictions_override.is_some() {
3445 self.set_show_edit_predictions(None, window, cx);
3446 } else {
3447 let show_edit_predictions = !self.edit_predictions_enabled();
3448 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3449 }
3450 }
3451
3452 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3453 self.show_completions_on_input_override = show_completions_on_input;
3454 }
3455
3456 pub fn set_show_edit_predictions(
3457 &mut self,
3458 show_edit_predictions: Option<bool>,
3459 window: &mut Window,
3460 cx: &mut Context<Self>,
3461 ) {
3462 self.show_edit_predictions_override = show_edit_predictions;
3463 self.update_edit_prediction_settings(cx);
3464
3465 if let Some(false) = show_edit_predictions {
3466 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3467 } else {
3468 self.refresh_edit_prediction(false, true, window, cx);
3469 }
3470 }
3471
3472 fn edit_predictions_disabled_in_scope(
3473 &self,
3474 buffer: &Entity<Buffer>,
3475 buffer_position: language::Anchor,
3476 cx: &App,
3477 ) -> bool {
3478 let snapshot = buffer.read(cx).snapshot();
3479 let settings = snapshot.settings_at(buffer_position, cx);
3480
3481 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3482 return false;
3483 };
3484
3485 scope.override_name().is_some_and(|scope_name| {
3486 settings
3487 .edit_predictions_disabled_in
3488 .iter()
3489 .any(|s| s == scope_name)
3490 })
3491 }
3492
3493 pub fn set_use_modal_editing(&mut self, to: bool) {
3494 self.use_modal_editing = to;
3495 }
3496
3497 pub fn use_modal_editing(&self) -> bool {
3498 self.use_modal_editing
3499 }
3500
3501 fn selections_did_change(
3502 &mut self,
3503 local: bool,
3504 old_cursor_position: &Anchor,
3505 effects: SelectionEffects,
3506 window: &mut Window,
3507 cx: &mut Context<Self>,
3508 ) {
3509 window.invalidate_character_coordinates();
3510
3511 // Copy selections to primary selection buffer
3512 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3513 if local {
3514 let selections = self
3515 .selections
3516 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3517 let buffer_handle = self.buffer.read(cx).read(cx);
3518
3519 let mut text = String::new();
3520 for (index, selection) in selections.iter().enumerate() {
3521 let text_for_selection = buffer_handle
3522 .text_for_range(selection.start..selection.end)
3523 .collect::<String>();
3524
3525 text.push_str(&text_for_selection);
3526 if index != selections.len() - 1 {
3527 text.push('\n');
3528 }
3529 }
3530
3531 if !text.is_empty() {
3532 cx.write_to_primary(ClipboardItem::new_string(text));
3533 }
3534 }
3535
3536 let selection_anchors = self.selections.disjoint_anchors_arc();
3537
3538 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3539 self.buffer.update(cx, |buffer, cx| {
3540 buffer.set_active_selections(
3541 &selection_anchors,
3542 self.selections.line_mode(),
3543 self.cursor_shape,
3544 cx,
3545 )
3546 });
3547 }
3548 let display_map = self
3549 .display_map
3550 .update(cx, |display_map, cx| display_map.snapshot(cx));
3551 let buffer = display_map.buffer_snapshot();
3552 if self.selections.count() == 1 {
3553 self.add_selections_state = None;
3554 }
3555 self.select_next_state = None;
3556 self.select_prev_state = None;
3557 self.select_syntax_node_history.try_clear();
3558 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3559 self.snippet_stack.invalidate(&selection_anchors, buffer);
3560 self.take_rename(false, window, cx);
3561
3562 let newest_selection = self.selections.newest_anchor();
3563 let new_cursor_position = newest_selection.head();
3564 let selection_start = newest_selection.start;
3565
3566 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3567 self.push_to_nav_history(
3568 *old_cursor_position,
3569 Some(new_cursor_position.to_point(buffer)),
3570 false,
3571 effects.nav_history == Some(true),
3572 cx,
3573 );
3574 }
3575
3576 if local {
3577 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3578 self.register_buffer(buffer_id, cx);
3579 }
3580
3581 let mut context_menu = self.context_menu.borrow_mut();
3582 let completion_menu = match context_menu.as_ref() {
3583 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3584 Some(CodeContextMenu::CodeActions(_)) => {
3585 *context_menu = None;
3586 None
3587 }
3588 None => None,
3589 };
3590 let completion_position = completion_menu.map(|menu| menu.initial_position);
3591 drop(context_menu);
3592
3593 if effects.completions
3594 && let Some(completion_position) = completion_position
3595 {
3596 let start_offset = selection_start.to_offset(buffer);
3597 let position_matches = start_offset == completion_position.to_offset(buffer);
3598 let continue_showing = if let Some((snap, ..)) =
3599 buffer.point_to_buffer_offset(completion_position)
3600 && !snap.capability.editable()
3601 {
3602 false
3603 } else if position_matches {
3604 if self.snippet_stack.is_empty() {
3605 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3606 == Some(CharKind::Word)
3607 } else {
3608 // Snippet choices can be shown even when the cursor is in whitespace.
3609 // Dismissing the menu with actions like backspace is handled by
3610 // invalidation regions.
3611 true
3612 }
3613 } else {
3614 false
3615 };
3616
3617 if continue_showing {
3618 self.open_or_update_completions_menu(None, None, false, window, cx);
3619 } else {
3620 self.hide_context_menu(window, cx);
3621 }
3622 }
3623
3624 hide_hover(self, cx);
3625
3626 if old_cursor_position.to_display_point(&display_map).row()
3627 != new_cursor_position.to_display_point(&display_map).row()
3628 {
3629 self.available_code_actions.take();
3630 }
3631 self.refresh_code_actions(window, cx);
3632 self.refresh_document_highlights(cx);
3633 refresh_linked_ranges(self, window, cx);
3634
3635 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3636 self.refresh_matching_bracket_highlights(&display_map, cx);
3637 self.refresh_outline_symbols_at_cursor(cx);
3638 self.update_visible_edit_prediction(window, cx);
3639 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3640 self.inline_blame_popover.take();
3641 if self.git_blame_inline_enabled {
3642 self.start_inline_blame_timer(window, cx);
3643 }
3644 }
3645
3646 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3647
3648 if local && !self.suppress_selection_callback {
3649 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3650 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3651 callback(cursor_position, window, cx);
3652 }
3653 }
3654
3655 cx.emit(EditorEvent::SelectionsChanged { local });
3656
3657 let selections = &self.selections.disjoint_anchors_arc();
3658 if selections.len() == 1 {
3659 cx.emit(SearchEvent::ActiveMatchChanged)
3660 }
3661 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3662 let inmemory_selections = selections
3663 .iter()
3664 .map(|s| {
3665 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3666 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3667 })
3668 .collect();
3669 self.update_restoration_data(cx, |data| {
3670 data.selections = inmemory_selections;
3671 });
3672
3673 if WorkspaceSettings::get(None, cx).restore_on_startup
3674 != RestoreOnStartupBehavior::EmptyTab
3675 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3676 {
3677 let snapshot = self.buffer().read(cx).snapshot(cx);
3678 let selections = selections.clone();
3679 let background_executor = cx.background_executor().clone();
3680 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3681 self.serialize_selections = cx.background_spawn(async move {
3682 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3683 let db_selections = selections
3684 .iter()
3685 .map(|selection| {
3686 (
3687 selection.start.to_offset(&snapshot).0,
3688 selection.end.to_offset(&snapshot).0,
3689 )
3690 })
3691 .collect();
3692
3693 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3694 .await
3695 .with_context(|| {
3696 format!(
3697 "persisting editor selections for editor {editor_id}, \
3698 workspace {workspace_id:?}"
3699 )
3700 })
3701 .log_err();
3702 });
3703 }
3704 }
3705
3706 cx.notify();
3707 }
3708
3709 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3710 use text::ToOffset as _;
3711 use text::ToPoint as _;
3712
3713 if self.mode.is_minimap()
3714 || WorkspaceSettings::get(None, cx).restore_on_startup
3715 == RestoreOnStartupBehavior::EmptyTab
3716 {
3717 return;
3718 }
3719
3720 if !self.buffer().read(cx).is_singleton() {
3721 return;
3722 }
3723
3724 let display_snapshot = self
3725 .display_map
3726 .update(cx, |display_map, cx| display_map.snapshot(cx));
3727 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3728 return;
3729 };
3730 let inmemory_folds = display_snapshot
3731 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3732 .map(|fold| {
3733 fold.range.start.text_anchor.to_point(&snapshot)
3734 ..fold.range.end.text_anchor.to_point(&snapshot)
3735 })
3736 .collect();
3737 self.update_restoration_data(cx, |data| {
3738 data.folds = inmemory_folds;
3739 });
3740
3741 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3742 return;
3743 };
3744
3745 // Get file path for path-based fold storage (survives tab close)
3746 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3747 project::File::from_dyn(buffer.read(cx).file())
3748 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3749 }) else {
3750 return;
3751 };
3752
3753 let background_executor = cx.background_executor().clone();
3754 const FINGERPRINT_LEN: usize = 32;
3755 let db_folds = display_snapshot
3756 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3757 .map(|fold| {
3758 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3759 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3760
3761 // Extract fingerprints - content at fold boundaries for validation on restore
3762 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3763 // content that might change independently.
3764 // start_fp: first min(32, fold_len) bytes of fold content
3765 // end_fp: last min(32, fold_len) bytes of fold content
3766 // Clip to character boundaries to handle multibyte UTF-8 characters.
3767 let fold_len = end - start;
3768 let start_fp_end = snapshot
3769 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3770 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3771 let end_fp_start = snapshot
3772 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3773 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3774
3775 (start, end, start_fp, end_fp)
3776 })
3777 .collect::<Vec<_>>();
3778 self.serialize_folds = cx.background_spawn(async move {
3779 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3780 if db_folds.is_empty() {
3781 // No folds - delete any persisted folds for this file
3782 DB.delete_file_folds(workspace_id, file_path)
3783 .await
3784 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3785 .log_err();
3786 } else {
3787 DB.save_file_folds(workspace_id, file_path, db_folds)
3788 .await
3789 .with_context(|| {
3790 format!("persisting file folds for workspace {workspace_id:?}")
3791 })
3792 .log_err();
3793 }
3794 });
3795 }
3796
3797 pub fn sync_selections(
3798 &mut self,
3799 other: Entity<Editor>,
3800 cx: &mut Context<Self>,
3801 ) -> gpui::Subscription {
3802 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3803 if !other_selections.is_empty() {
3804 self.selections
3805 .change_with(&self.display_snapshot(cx), |selections| {
3806 selections.select_anchors(other_selections);
3807 });
3808 }
3809
3810 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3811 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3812 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3813 if other_selections.is_empty() {
3814 return;
3815 }
3816 let snapshot = this.display_snapshot(cx);
3817 this.selections.change_with(&snapshot, |selections| {
3818 selections.select_anchors(other_selections);
3819 });
3820 }
3821 });
3822
3823 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3824 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3825 let these_selections = this.selections.disjoint_anchors().to_vec();
3826 if these_selections.is_empty() {
3827 return;
3828 }
3829 other.update(cx, |other_editor, cx| {
3830 let snapshot = other_editor.display_snapshot(cx);
3831 other_editor
3832 .selections
3833 .change_with(&snapshot, |selections| {
3834 selections.select_anchors(these_selections);
3835 })
3836 });
3837 }
3838 });
3839
3840 Subscription::join(other_subscription, this_subscription)
3841 }
3842
3843 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3844 if self.buffer().read(cx).is_singleton() {
3845 return;
3846 }
3847 let snapshot = self.buffer.read(cx).snapshot(cx);
3848 let buffer_ids: HashSet<BufferId> = self
3849 .selections
3850 .disjoint_anchor_ranges()
3851 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3852 .collect();
3853 for buffer_id in buffer_ids {
3854 self.unfold_buffer(buffer_id, cx);
3855 }
3856 }
3857
3858 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3859 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3860 /// effects of selection change occur at the end of the transaction.
3861 pub fn change_selections<R>(
3862 &mut self,
3863 effects: SelectionEffects,
3864 window: &mut Window,
3865 cx: &mut Context<Self>,
3866 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3867 ) -> R {
3868 let snapshot = self.display_snapshot(cx);
3869 if let Some(state) = &mut self.deferred_selection_effects_state {
3870 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3871 state.effects.completions = effects.completions;
3872 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3873 let (changed, result) = self.selections.change_with(&snapshot, change);
3874 state.changed |= changed;
3875 return result;
3876 }
3877 let mut state = DeferredSelectionEffectsState {
3878 changed: false,
3879 effects,
3880 old_cursor_position: self.selections.newest_anchor().head(),
3881 history_entry: SelectionHistoryEntry {
3882 selections: self.selections.disjoint_anchors_arc(),
3883 select_next_state: self.select_next_state.clone(),
3884 select_prev_state: self.select_prev_state.clone(),
3885 add_selections_state: self.add_selections_state.clone(),
3886 },
3887 };
3888 let (changed, result) = self.selections.change_with(&snapshot, change);
3889 state.changed = state.changed || changed;
3890 if self.defer_selection_effects {
3891 self.deferred_selection_effects_state = Some(state);
3892 } else {
3893 self.apply_selection_effects(state, window, cx);
3894 }
3895 result
3896 }
3897
3898 /// Defers the effects of selection change, so that the effects of multiple calls to
3899 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3900 /// to selection history and the state of popovers based on selection position aren't
3901 /// erroneously updated.
3902 pub fn with_selection_effects_deferred<R>(
3903 &mut self,
3904 window: &mut Window,
3905 cx: &mut Context<Self>,
3906 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3907 ) -> R {
3908 let already_deferred = self.defer_selection_effects;
3909 self.defer_selection_effects = true;
3910 let result = update(self, window, cx);
3911 if !already_deferred {
3912 self.defer_selection_effects = false;
3913 if let Some(state) = self.deferred_selection_effects_state.take() {
3914 self.apply_selection_effects(state, window, cx);
3915 }
3916 }
3917 result
3918 }
3919
3920 fn apply_selection_effects(
3921 &mut self,
3922 state: DeferredSelectionEffectsState,
3923 window: &mut Window,
3924 cx: &mut Context<Self>,
3925 ) {
3926 if state.changed {
3927 self.selection_history.push(state.history_entry);
3928
3929 if let Some(autoscroll) = state.effects.scroll {
3930 self.request_autoscroll(autoscroll, cx);
3931 }
3932
3933 let old_cursor_position = &state.old_cursor_position;
3934
3935 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3936
3937 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3938 self.show_signature_help_auto(window, cx);
3939 }
3940 }
3941 }
3942
3943 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3944 where
3945 I: IntoIterator<Item = (Range<S>, T)>,
3946 S: ToOffset,
3947 T: Into<Arc<str>>,
3948 {
3949 if self.read_only(cx) {
3950 return;
3951 }
3952
3953 self.buffer
3954 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3955 }
3956
3957 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3958 where
3959 I: IntoIterator<Item = (Range<S>, T)>,
3960 S: ToOffset,
3961 T: Into<Arc<str>>,
3962 {
3963 if self.read_only(cx) {
3964 return;
3965 }
3966
3967 self.buffer.update(cx, |buffer, cx| {
3968 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3969 });
3970 }
3971
3972 pub fn edit_with_block_indent<I, S, T>(
3973 &mut self,
3974 edits: I,
3975 original_indent_columns: Vec<Option<u32>>,
3976 cx: &mut Context<Self>,
3977 ) where
3978 I: IntoIterator<Item = (Range<S>, T)>,
3979 S: ToOffset,
3980 T: Into<Arc<str>>,
3981 {
3982 if self.read_only(cx) {
3983 return;
3984 }
3985
3986 self.buffer.update(cx, |buffer, cx| {
3987 buffer.edit(
3988 edits,
3989 Some(AutoindentMode::Block {
3990 original_indent_columns,
3991 }),
3992 cx,
3993 )
3994 });
3995 }
3996
3997 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3998 self.hide_context_menu(window, cx);
3999
4000 match phase {
4001 SelectPhase::Begin {
4002 position,
4003 add,
4004 click_count,
4005 } => self.begin_selection(position, add, click_count, window, cx),
4006 SelectPhase::BeginColumnar {
4007 position,
4008 goal_column,
4009 reset,
4010 mode,
4011 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4012 SelectPhase::Extend {
4013 position,
4014 click_count,
4015 } => self.extend_selection(position, click_count, window, cx),
4016 SelectPhase::Update {
4017 position,
4018 goal_column,
4019 scroll_delta,
4020 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4021 SelectPhase::End => self.end_selection(window, cx),
4022 }
4023 }
4024
4025 fn extend_selection(
4026 &mut self,
4027 position: DisplayPoint,
4028 click_count: usize,
4029 window: &mut Window,
4030 cx: &mut Context<Self>,
4031 ) {
4032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4033 let tail = self
4034 .selections
4035 .newest::<MultiBufferOffset>(&display_map)
4036 .tail();
4037 let click_count = click_count.max(match self.selections.select_mode() {
4038 SelectMode::Character => 1,
4039 SelectMode::Word(_) => 2,
4040 SelectMode::Line(_) => 3,
4041 SelectMode::All => 4,
4042 });
4043 self.begin_selection(position, false, click_count, window, cx);
4044
4045 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4046
4047 let current_selection = match self.selections.select_mode() {
4048 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4049 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4050 };
4051
4052 let mut pending_selection = self
4053 .selections
4054 .pending_anchor()
4055 .cloned()
4056 .expect("extend_selection not called with pending selection");
4057
4058 if pending_selection
4059 .start
4060 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4061 == Ordering::Greater
4062 {
4063 pending_selection.start = current_selection.start;
4064 }
4065 if pending_selection
4066 .end
4067 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4068 == Ordering::Less
4069 {
4070 pending_selection.end = current_selection.end;
4071 pending_selection.reversed = true;
4072 }
4073
4074 let mut pending_mode = self.selections.pending_mode().unwrap();
4075 match &mut pending_mode {
4076 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4077 _ => {}
4078 }
4079
4080 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4081 SelectionEffects::scroll(Autoscroll::fit())
4082 } else {
4083 SelectionEffects::no_scroll()
4084 };
4085
4086 self.change_selections(effects, window, cx, |s| {
4087 s.set_pending(pending_selection.clone(), pending_mode);
4088 s.set_is_extending(true);
4089 });
4090 }
4091
4092 fn begin_selection(
4093 &mut self,
4094 position: DisplayPoint,
4095 add: bool,
4096 click_count: usize,
4097 window: &mut Window,
4098 cx: &mut Context<Self>,
4099 ) {
4100 if !self.focus_handle.is_focused(window) {
4101 self.last_focused_descendant = None;
4102 window.focus(&self.focus_handle, cx);
4103 }
4104
4105 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4106 let buffer = display_map.buffer_snapshot();
4107 let position = display_map.clip_point(position, Bias::Left);
4108
4109 let start;
4110 let end;
4111 let mode;
4112 let mut auto_scroll;
4113 match click_count {
4114 1 => {
4115 start = buffer.anchor_before(position.to_point(&display_map));
4116 end = start;
4117 mode = SelectMode::Character;
4118 auto_scroll = true;
4119 }
4120 2 => {
4121 let position = display_map
4122 .clip_point(position, Bias::Left)
4123 .to_offset(&display_map, Bias::Left);
4124 let (range, _) = buffer.surrounding_word(position, None);
4125 start = buffer.anchor_before(range.start);
4126 end = buffer.anchor_before(range.end);
4127 mode = SelectMode::Word(start..end);
4128 auto_scroll = true;
4129 }
4130 3 => {
4131 let position = display_map
4132 .clip_point(position, Bias::Left)
4133 .to_point(&display_map);
4134 let line_start = display_map.prev_line_boundary(position).0;
4135 let next_line_start = buffer.clip_point(
4136 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4137 Bias::Left,
4138 );
4139 start = buffer.anchor_before(line_start);
4140 end = buffer.anchor_before(next_line_start);
4141 mode = SelectMode::Line(start..end);
4142 auto_scroll = true;
4143 }
4144 _ => {
4145 start = buffer.anchor_before(MultiBufferOffset(0));
4146 end = buffer.anchor_before(buffer.len());
4147 mode = SelectMode::All;
4148 auto_scroll = false;
4149 }
4150 }
4151 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4152
4153 let point_to_delete: Option<usize> = {
4154 let selected_points: Vec<Selection<Point>> =
4155 self.selections.disjoint_in_range(start..end, &display_map);
4156
4157 if !add || click_count > 1 {
4158 None
4159 } else if !selected_points.is_empty() {
4160 Some(selected_points[0].id)
4161 } else {
4162 let clicked_point_already_selected =
4163 self.selections.disjoint_anchors().iter().find(|selection| {
4164 selection.start.to_point(buffer) == start.to_point(buffer)
4165 || selection.end.to_point(buffer) == end.to_point(buffer)
4166 });
4167
4168 clicked_point_already_selected.map(|selection| selection.id)
4169 }
4170 };
4171
4172 let selections_count = self.selections.count();
4173 let effects = if auto_scroll {
4174 SelectionEffects::default()
4175 } else {
4176 SelectionEffects::no_scroll()
4177 };
4178
4179 self.change_selections(effects, window, cx, |s| {
4180 if let Some(point_to_delete) = point_to_delete {
4181 s.delete(point_to_delete);
4182
4183 if selections_count == 1 {
4184 s.set_pending_anchor_range(start..end, mode);
4185 }
4186 } else {
4187 if !add {
4188 s.clear_disjoint();
4189 }
4190
4191 s.set_pending_anchor_range(start..end, mode);
4192 }
4193 });
4194 }
4195
4196 fn begin_columnar_selection(
4197 &mut self,
4198 position: DisplayPoint,
4199 goal_column: u32,
4200 reset: bool,
4201 mode: ColumnarMode,
4202 window: &mut Window,
4203 cx: &mut Context<Self>,
4204 ) {
4205 if !self.focus_handle.is_focused(window) {
4206 self.last_focused_descendant = None;
4207 window.focus(&self.focus_handle, cx);
4208 }
4209
4210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4211
4212 if reset {
4213 let pointer_position = display_map
4214 .buffer_snapshot()
4215 .anchor_before(position.to_point(&display_map));
4216
4217 self.change_selections(
4218 SelectionEffects::scroll(Autoscroll::newest()),
4219 window,
4220 cx,
4221 |s| {
4222 s.clear_disjoint();
4223 s.set_pending_anchor_range(
4224 pointer_position..pointer_position,
4225 SelectMode::Character,
4226 );
4227 },
4228 );
4229 };
4230
4231 let tail = self.selections.newest::<Point>(&display_map).tail();
4232 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4233 self.columnar_selection_state = match mode {
4234 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4235 selection_tail: selection_anchor,
4236 display_point: if reset {
4237 if position.column() != goal_column {
4238 Some(DisplayPoint::new(position.row(), goal_column))
4239 } else {
4240 None
4241 }
4242 } else {
4243 None
4244 },
4245 }),
4246 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4247 selection_tail: selection_anchor,
4248 }),
4249 };
4250
4251 if !reset {
4252 self.select_columns(position, goal_column, &display_map, window, cx);
4253 }
4254 }
4255
4256 fn update_selection(
4257 &mut self,
4258 position: DisplayPoint,
4259 goal_column: u32,
4260 scroll_delta: gpui::Point<f32>,
4261 window: &mut Window,
4262 cx: &mut Context<Self>,
4263 ) {
4264 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4265
4266 if self.columnar_selection_state.is_some() {
4267 self.select_columns(position, goal_column, &display_map, window, cx);
4268 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4269 let buffer = display_map.buffer_snapshot();
4270 let head;
4271 let tail;
4272 let mode = self.selections.pending_mode().unwrap();
4273 match &mode {
4274 SelectMode::Character => {
4275 head = position.to_point(&display_map);
4276 tail = pending.tail().to_point(buffer);
4277 }
4278 SelectMode::Word(original_range) => {
4279 let offset = display_map
4280 .clip_point(position, Bias::Left)
4281 .to_offset(&display_map, Bias::Left);
4282 let original_range = original_range.to_offset(buffer);
4283
4284 let head_offset = if buffer.is_inside_word(offset, None)
4285 || original_range.contains(&offset)
4286 {
4287 let (word_range, _) = buffer.surrounding_word(offset, None);
4288 if word_range.start < original_range.start {
4289 word_range.start
4290 } else {
4291 word_range.end
4292 }
4293 } else {
4294 offset
4295 };
4296
4297 head = head_offset.to_point(buffer);
4298 if head_offset <= original_range.start {
4299 tail = original_range.end.to_point(buffer);
4300 } else {
4301 tail = original_range.start.to_point(buffer);
4302 }
4303 }
4304 SelectMode::Line(original_range) => {
4305 let original_range = original_range.to_point(display_map.buffer_snapshot());
4306
4307 let position = display_map
4308 .clip_point(position, Bias::Left)
4309 .to_point(&display_map);
4310 let line_start = display_map.prev_line_boundary(position).0;
4311 let next_line_start = buffer.clip_point(
4312 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4313 Bias::Left,
4314 );
4315
4316 if line_start < original_range.start {
4317 head = line_start
4318 } else {
4319 head = next_line_start
4320 }
4321
4322 if head <= original_range.start {
4323 tail = original_range.end;
4324 } else {
4325 tail = original_range.start;
4326 }
4327 }
4328 SelectMode::All => {
4329 return;
4330 }
4331 };
4332
4333 if head < tail {
4334 pending.start = buffer.anchor_before(head);
4335 pending.end = buffer.anchor_before(tail);
4336 pending.reversed = true;
4337 } else {
4338 pending.start = buffer.anchor_before(tail);
4339 pending.end = buffer.anchor_before(head);
4340 pending.reversed = false;
4341 }
4342
4343 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4344 s.set_pending(pending.clone(), mode);
4345 });
4346 } else {
4347 log::error!("update_selection dispatched with no pending selection");
4348 return;
4349 }
4350
4351 self.apply_scroll_delta(scroll_delta, window, cx);
4352 cx.notify();
4353 }
4354
4355 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4356 self.columnar_selection_state.take();
4357 if let Some(pending_mode) = self.selections.pending_mode() {
4358 let selections = self
4359 .selections
4360 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4361 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4362 s.select(selections);
4363 s.clear_pending();
4364 if s.is_extending() {
4365 s.set_is_extending(false);
4366 } else {
4367 s.set_select_mode(pending_mode);
4368 }
4369 });
4370 }
4371 }
4372
4373 fn select_columns(
4374 &mut self,
4375 head: DisplayPoint,
4376 goal_column: u32,
4377 display_map: &DisplaySnapshot,
4378 window: &mut Window,
4379 cx: &mut Context<Self>,
4380 ) {
4381 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4382 return;
4383 };
4384
4385 let tail = match columnar_state {
4386 ColumnarSelectionState::FromMouse {
4387 selection_tail,
4388 display_point,
4389 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4390 ColumnarSelectionState::FromSelection { selection_tail } => {
4391 selection_tail.to_display_point(display_map)
4392 }
4393 };
4394
4395 let start_row = cmp::min(tail.row(), head.row());
4396 let end_row = cmp::max(tail.row(), head.row());
4397 let start_column = cmp::min(tail.column(), goal_column);
4398 let end_column = cmp::max(tail.column(), goal_column);
4399 let reversed = start_column < tail.column();
4400
4401 let selection_ranges = (start_row.0..=end_row.0)
4402 .map(DisplayRow)
4403 .filter_map(|row| {
4404 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4405 || start_column <= display_map.line_len(row))
4406 && !display_map.is_block_line(row)
4407 {
4408 let start = display_map
4409 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4410 .to_point(display_map);
4411 let end = display_map
4412 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4413 .to_point(display_map);
4414 if reversed {
4415 Some(end..start)
4416 } else {
4417 Some(start..end)
4418 }
4419 } else {
4420 None
4421 }
4422 })
4423 .collect::<Vec<_>>();
4424 if selection_ranges.is_empty() {
4425 return;
4426 }
4427
4428 let ranges = match columnar_state {
4429 ColumnarSelectionState::FromMouse { .. } => {
4430 let mut non_empty_ranges = selection_ranges
4431 .iter()
4432 .filter(|selection_range| selection_range.start != selection_range.end)
4433 .peekable();
4434 if non_empty_ranges.peek().is_some() {
4435 non_empty_ranges.cloned().collect()
4436 } else {
4437 selection_ranges
4438 }
4439 }
4440 _ => selection_ranges,
4441 };
4442
4443 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4444 s.select_ranges(ranges);
4445 });
4446 cx.notify();
4447 }
4448
4449 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4450 self.selections
4451 .all_adjusted(snapshot)
4452 .iter()
4453 .any(|selection| !selection.is_empty())
4454 }
4455
4456 pub fn has_pending_nonempty_selection(&self) -> bool {
4457 let pending_nonempty_selection = match self.selections.pending_anchor() {
4458 Some(Selection { start, end, .. }) => start != end,
4459 None => false,
4460 };
4461
4462 pending_nonempty_selection
4463 || (self.columnar_selection_state.is_some()
4464 && self.selections.disjoint_anchors().len() > 1)
4465 }
4466
4467 pub fn has_pending_selection(&self) -> bool {
4468 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4469 }
4470
4471 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4472 self.selection_mark_mode = false;
4473 self.selection_drag_state = SelectionDragState::None;
4474
4475 if self.dismiss_menus_and_popups(true, window, cx) {
4476 cx.notify();
4477 return;
4478 }
4479 if self.clear_expanded_diff_hunks(cx) {
4480 cx.notify();
4481 return;
4482 }
4483 if self.show_git_blame_gutter {
4484 self.show_git_blame_gutter = false;
4485 cx.notify();
4486 return;
4487 }
4488
4489 if self.mode.is_full()
4490 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4491 {
4492 cx.notify();
4493 return;
4494 }
4495
4496 cx.propagate();
4497 }
4498
4499 pub fn dismiss_menus_and_popups(
4500 &mut self,
4501 is_user_requested: bool,
4502 window: &mut Window,
4503 cx: &mut Context<Self>,
4504 ) -> bool {
4505 let mut dismissed = false;
4506
4507 dismissed |= self.take_rename(false, window, cx).is_some();
4508 dismissed |= self.hide_blame_popover(true, cx);
4509 dismissed |= hide_hover(self, cx);
4510 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4511 dismissed |= self.hide_context_menu(window, cx).is_some();
4512 dismissed |= self.mouse_context_menu.take().is_some();
4513 dismissed |= is_user_requested
4514 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4515 dismissed |= self.snippet_stack.pop().is_some();
4516 if self.diff_review_drag_state.is_some() {
4517 self.cancel_diff_review_drag(cx);
4518 dismissed = true;
4519 }
4520 if !self.diff_review_overlays.is_empty() {
4521 self.dismiss_all_diff_review_overlays(cx);
4522 dismissed = true;
4523 }
4524
4525 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4526 self.dismiss_diagnostics(cx);
4527 dismissed = true;
4528 }
4529
4530 dismissed
4531 }
4532
4533 fn linked_editing_ranges_for(
4534 &self,
4535 selection: Range<text::Anchor>,
4536 cx: &App,
4537 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4538 if self.linked_edit_ranges.is_empty() {
4539 return None;
4540 }
4541 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4542 selection.end.buffer_id.and_then(|end_buffer_id| {
4543 if selection.start.buffer_id != Some(end_buffer_id) {
4544 return None;
4545 }
4546 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4547 let snapshot = buffer.read(cx).snapshot();
4548 self.linked_edit_ranges
4549 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4550 .map(|ranges| (ranges, snapshot, buffer))
4551 })?;
4552 use text::ToOffset as TO;
4553 // find offset from the start of current range to current cursor position
4554 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4555
4556 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4557 let start_difference = start_offset - start_byte_offset;
4558 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4559 let end_difference = end_offset - start_byte_offset;
4560
4561 // Current range has associated linked ranges.
4562 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4563 for range in linked_ranges.iter() {
4564 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4565 let end_offset = start_offset + end_difference;
4566 let start_offset = start_offset + start_difference;
4567 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4568 continue;
4569 }
4570 if self.selections.disjoint_anchor_ranges().any(|s| {
4571 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4572 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4573 {
4574 return false;
4575 }
4576 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4577 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4578 }) {
4579 continue;
4580 }
4581 let start = buffer_snapshot.anchor_after(start_offset);
4582 let end = buffer_snapshot.anchor_after(end_offset);
4583 linked_edits
4584 .entry(buffer.clone())
4585 .or_default()
4586 .push(start..end);
4587 }
4588 Some(linked_edits)
4589 }
4590
4591 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4592 let text: Arc<str> = text.into();
4593
4594 if self.read_only(cx) {
4595 return;
4596 }
4597
4598 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4599
4600 self.unfold_buffers_with_selections(cx);
4601
4602 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4603 let mut bracket_inserted = false;
4604 let mut edits = Vec::new();
4605 let mut linked_edits = LinkedEdits::new();
4606 let mut new_selections = Vec::with_capacity(selections.len());
4607 let mut new_autoclose_regions = Vec::new();
4608 let snapshot = self.buffer.read(cx).read(cx);
4609 let mut clear_linked_edit_ranges = false;
4610 let mut all_selections_read_only = true;
4611 let mut has_adjacent_edits = false;
4612 let mut in_adjacent_group = false;
4613
4614 let mut regions = self
4615 .selections_with_autoclose_regions(selections, &snapshot)
4616 .peekable();
4617
4618 while let Some((selection, autoclose_region)) = regions.next() {
4619 if snapshot
4620 .point_to_buffer_point(selection.head())
4621 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4622 {
4623 continue;
4624 }
4625 if snapshot
4626 .point_to_buffer_point(selection.tail())
4627 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4628 {
4629 // note, ideally we'd clip the tail to the closest writeable region towards the head
4630 continue;
4631 }
4632 all_selections_read_only = false;
4633
4634 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4635 // Determine if the inserted text matches the opening or closing
4636 // bracket of any of this language's bracket pairs.
4637 let mut bracket_pair = None;
4638 let mut is_bracket_pair_start = false;
4639 let mut is_bracket_pair_end = false;
4640 if !text.is_empty() {
4641 let mut bracket_pair_matching_end = None;
4642 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4643 // and they are removing the character that triggered IME popup.
4644 for (pair, enabled) in scope.brackets() {
4645 if !pair.close && !pair.surround {
4646 continue;
4647 }
4648
4649 if enabled && pair.start.ends_with(text.as_ref()) {
4650 let prefix_len = pair.start.len() - text.len();
4651 let preceding_text_matches_prefix = prefix_len == 0
4652 || (selection.start.column >= (prefix_len as u32)
4653 && snapshot.contains_str_at(
4654 Point::new(
4655 selection.start.row,
4656 selection.start.column - (prefix_len as u32),
4657 ),
4658 &pair.start[..prefix_len],
4659 ));
4660 if preceding_text_matches_prefix {
4661 bracket_pair = Some(pair.clone());
4662 is_bracket_pair_start = true;
4663 break;
4664 }
4665 }
4666 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4667 {
4668 // take first bracket pair matching end, but don't break in case a later bracket
4669 // pair matches start
4670 bracket_pair_matching_end = Some(pair.clone());
4671 }
4672 }
4673 if let Some(end) = bracket_pair_matching_end
4674 && bracket_pair.is_none()
4675 {
4676 bracket_pair = Some(end);
4677 is_bracket_pair_end = true;
4678 }
4679 }
4680
4681 if let Some(bracket_pair) = bracket_pair {
4682 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4683 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4684 let auto_surround =
4685 self.use_auto_surround && snapshot_settings.use_auto_surround;
4686 if selection.is_empty() {
4687 if is_bracket_pair_start {
4688 // If the inserted text is a suffix of an opening bracket and the
4689 // selection is preceded by the rest of the opening bracket, then
4690 // insert the closing bracket.
4691 let following_text_allows_autoclose = snapshot
4692 .chars_at(selection.start)
4693 .next()
4694 .is_none_or(|c| scope.should_autoclose_before(c));
4695
4696 let preceding_text_allows_autoclose = selection.start.column == 0
4697 || snapshot
4698 .reversed_chars_at(selection.start)
4699 .next()
4700 .is_none_or(|c| {
4701 bracket_pair.start != bracket_pair.end
4702 || !snapshot
4703 .char_classifier_at(selection.start)
4704 .is_word(c)
4705 });
4706
4707 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4708 && bracket_pair.start.len() == 1
4709 {
4710 let target = bracket_pair.start.chars().next().unwrap();
4711 let mut byte_offset = 0u32;
4712 let current_line_count = snapshot
4713 .reversed_chars_at(selection.start)
4714 .take_while(|&c| c != '\n')
4715 .filter(|c| {
4716 byte_offset += c.len_utf8() as u32;
4717 if *c != target {
4718 return false;
4719 }
4720
4721 let point = Point::new(
4722 selection.start.row,
4723 selection.start.column.saturating_sub(byte_offset),
4724 );
4725
4726 let is_enabled = snapshot
4727 .language_scope_at(point)
4728 .and_then(|scope| {
4729 scope
4730 .brackets()
4731 .find(|(pair, _)| {
4732 pair.start == bracket_pair.start
4733 })
4734 .map(|(_, enabled)| enabled)
4735 })
4736 .unwrap_or(true);
4737
4738 let is_delimiter = snapshot
4739 .language_scope_at(Point::new(
4740 point.row,
4741 point.column + 1,
4742 ))
4743 .and_then(|scope| {
4744 scope
4745 .brackets()
4746 .find(|(pair, _)| {
4747 pair.start == bracket_pair.start
4748 })
4749 .map(|(_, enabled)| !enabled)
4750 })
4751 .unwrap_or(false);
4752
4753 is_enabled && !is_delimiter
4754 })
4755 .count();
4756 current_line_count % 2 == 1
4757 } else {
4758 false
4759 };
4760
4761 if autoclose
4762 && bracket_pair.close
4763 && following_text_allows_autoclose
4764 && preceding_text_allows_autoclose
4765 && !is_closing_quote
4766 {
4767 let anchor = snapshot.anchor_before(selection.end);
4768 new_selections.push((selection.map(|_| anchor), text.len()));
4769 new_autoclose_regions.push((
4770 anchor,
4771 text.len(),
4772 selection.id,
4773 bracket_pair.clone(),
4774 ));
4775 edits.push((
4776 selection.range(),
4777 format!("{}{}", text, bracket_pair.end).into(),
4778 ));
4779 bracket_inserted = true;
4780 continue;
4781 }
4782 }
4783
4784 if let Some(region) = autoclose_region {
4785 // If the selection is followed by an auto-inserted closing bracket,
4786 // then don't insert that closing bracket again; just move the selection
4787 // past the closing bracket.
4788 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4789 && text.as_ref() == region.pair.end.as_str()
4790 && snapshot.contains_str_at(region.range.end, text.as_ref());
4791 if should_skip {
4792 let anchor = snapshot.anchor_after(selection.end);
4793 new_selections
4794 .push((selection.map(|_| anchor), region.pair.end.len()));
4795 continue;
4796 }
4797 }
4798
4799 let always_treat_brackets_as_autoclosed = snapshot
4800 .language_settings_at(selection.start, cx)
4801 .always_treat_brackets_as_autoclosed;
4802 if always_treat_brackets_as_autoclosed
4803 && is_bracket_pair_end
4804 && snapshot.contains_str_at(selection.end, text.as_ref())
4805 {
4806 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4807 // and the inserted text is a closing bracket and the selection is followed
4808 // by the closing bracket then move the selection past the closing bracket.
4809 let anchor = snapshot.anchor_after(selection.end);
4810 new_selections.push((selection.map(|_| anchor), text.len()));
4811 continue;
4812 }
4813 }
4814 // If an opening bracket is 1 character long and is typed while
4815 // text is selected, then surround that text with the bracket pair.
4816 else if auto_surround
4817 && bracket_pair.surround
4818 && is_bracket_pair_start
4819 && bracket_pair.start.chars().count() == 1
4820 {
4821 edits.push((selection.start..selection.start, text.clone()));
4822 edits.push((
4823 selection.end..selection.end,
4824 bracket_pair.end.as_str().into(),
4825 ));
4826 bracket_inserted = true;
4827 new_selections.push((
4828 Selection {
4829 id: selection.id,
4830 start: snapshot.anchor_after(selection.start),
4831 end: snapshot.anchor_before(selection.end),
4832 reversed: selection.reversed,
4833 goal: selection.goal,
4834 },
4835 0,
4836 ));
4837 continue;
4838 }
4839 }
4840 }
4841
4842 if self.auto_replace_emoji_shortcode
4843 && selection.is_empty()
4844 && text.as_ref().ends_with(':')
4845 && let Some(possible_emoji_short_code) =
4846 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4847 && !possible_emoji_short_code.is_empty()
4848 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4849 {
4850 let emoji_shortcode_start = Point::new(
4851 selection.start.row,
4852 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4853 );
4854
4855 // Remove shortcode from buffer
4856 edits.push((
4857 emoji_shortcode_start..selection.start,
4858 "".to_string().into(),
4859 ));
4860 new_selections.push((
4861 Selection {
4862 id: selection.id,
4863 start: snapshot.anchor_after(emoji_shortcode_start),
4864 end: snapshot.anchor_before(selection.start),
4865 reversed: selection.reversed,
4866 goal: selection.goal,
4867 },
4868 0,
4869 ));
4870
4871 // Insert emoji
4872 let selection_start_anchor = snapshot.anchor_after(selection.start);
4873 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4874 edits.push((selection.start..selection.end, emoji.to_string().into()));
4875
4876 continue;
4877 }
4878
4879 let next_is_adjacent = regions
4880 .peek()
4881 .is_some_and(|(next, _)| selection.end == next.start);
4882
4883 // If not handling any auto-close operation, then just replace the selected
4884 // text with the given input and move the selection to the end of the
4885 // newly inserted text.
4886 let anchor = if in_adjacent_group || next_is_adjacent {
4887 // After edits the right bias would shift those anchor to the next visible fragment
4888 // but we want to resolve to the previous one
4889 snapshot.anchor_before(selection.end)
4890 } else {
4891 snapshot.anchor_after(selection.end)
4892 };
4893
4894 if !self.linked_edit_ranges.is_empty() {
4895 let start_anchor = snapshot.anchor_before(selection.start);
4896
4897 let is_word_char = text.chars().next().is_none_or(|char| {
4898 let classifier = snapshot
4899 .char_classifier_at(start_anchor.to_offset(&snapshot))
4900 .scope_context(Some(CharScopeContext::LinkedEdit));
4901 classifier.is_word(char)
4902 });
4903 let is_dot = text.as_ref() == ".";
4904 let should_apply_linked_edit = is_word_char || is_dot;
4905
4906 if should_apply_linked_edit {
4907 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
4908 linked_edits.push(&self, anchor_range, text.clone(), cx);
4909 } else {
4910 clear_linked_edit_ranges = true;
4911 }
4912 }
4913
4914 new_selections.push((selection.map(|_| anchor), 0));
4915 edits.push((selection.start..selection.end, text.clone()));
4916
4917 has_adjacent_edits |= next_is_adjacent;
4918 in_adjacent_group = next_is_adjacent;
4919 }
4920
4921 if all_selections_read_only {
4922 return;
4923 }
4924
4925 drop(regions);
4926 drop(snapshot);
4927
4928 self.transact(window, cx, |this, window, cx| {
4929 if clear_linked_edit_ranges {
4930 this.linked_edit_ranges.clear();
4931 }
4932 let initial_buffer_versions =
4933 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4934
4935 this.buffer.update(cx, |buffer, cx| {
4936 if has_adjacent_edits {
4937 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4938 } else {
4939 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4940 }
4941 });
4942 linked_edits.apply(cx);
4943 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4944 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4945 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4946 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4947 new_anchor_selections,
4948 &map,
4949 )
4950 .zip(new_selection_deltas)
4951 .map(|(selection, delta)| Selection {
4952 id: selection.id,
4953 start: selection.start + delta,
4954 end: selection.end + delta,
4955 reversed: selection.reversed,
4956 goal: SelectionGoal::None,
4957 })
4958 .collect::<Vec<_>>();
4959
4960 let mut i = 0;
4961 for (position, delta, selection_id, pair) in new_autoclose_regions {
4962 let position = position.to_offset(map.buffer_snapshot()) + delta;
4963 let start = map.buffer_snapshot().anchor_before(position);
4964 let end = map.buffer_snapshot().anchor_after(position);
4965 while let Some(existing_state) = this.autoclose_regions.get(i) {
4966 match existing_state
4967 .range
4968 .start
4969 .cmp(&start, map.buffer_snapshot())
4970 {
4971 Ordering::Less => i += 1,
4972 Ordering::Greater => break,
4973 Ordering::Equal => {
4974 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4975 Ordering::Less => i += 1,
4976 Ordering::Equal => break,
4977 Ordering::Greater => break,
4978 }
4979 }
4980 }
4981 }
4982 this.autoclose_regions.insert(
4983 i,
4984 AutocloseRegion {
4985 selection_id,
4986 range: start..end,
4987 pair,
4988 },
4989 );
4990 }
4991
4992 let had_active_edit_prediction = this.has_active_edit_prediction();
4993 this.change_selections(
4994 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4995 window,
4996 cx,
4997 |s| s.select(new_selections),
4998 );
4999
5000 if !bracket_inserted
5001 && let Some(on_type_format_task) =
5002 this.trigger_on_type_formatting(text.to_string(), window, cx)
5003 {
5004 on_type_format_task.detach_and_log_err(cx);
5005 }
5006
5007 let editor_settings = EditorSettings::get_global(cx);
5008 if bracket_inserted
5009 && (editor_settings.auto_signature_help
5010 || editor_settings.show_signature_help_after_edits)
5011 {
5012 this.show_signature_help(&ShowSignatureHelp, window, cx);
5013 }
5014
5015 let trigger_in_words =
5016 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5017 if this.hard_wrap.is_some() {
5018 let latest: Range<Point> = this.selections.newest(&map).range();
5019 if latest.is_empty()
5020 && this
5021 .buffer()
5022 .read(cx)
5023 .snapshot(cx)
5024 .line_len(MultiBufferRow(latest.start.row))
5025 == latest.start.column
5026 {
5027 this.rewrap_impl(
5028 RewrapOptions {
5029 override_language_settings: true,
5030 preserve_existing_whitespace: true,
5031 },
5032 cx,
5033 )
5034 }
5035 }
5036 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5037 refresh_linked_ranges(this, window, cx);
5038 this.refresh_edit_prediction(true, false, window, cx);
5039 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5040 });
5041 }
5042
5043 fn find_possible_emoji_shortcode_at_position(
5044 snapshot: &MultiBufferSnapshot,
5045 position: Point,
5046 ) -> Option<String> {
5047 let mut chars = Vec::new();
5048 let mut found_colon = false;
5049 for char in snapshot.reversed_chars_at(position).take(100) {
5050 // Found a possible emoji shortcode in the middle of the buffer
5051 if found_colon {
5052 if char.is_whitespace() {
5053 chars.reverse();
5054 return Some(chars.iter().collect());
5055 }
5056 // If the previous character is not a whitespace, we are in the middle of a word
5057 // and we only want to complete the shortcode if the word is made up of other emojis
5058 let mut containing_word = String::new();
5059 for ch in snapshot
5060 .reversed_chars_at(position)
5061 .skip(chars.len() + 1)
5062 .take(100)
5063 {
5064 if ch.is_whitespace() {
5065 break;
5066 }
5067 containing_word.push(ch);
5068 }
5069 let containing_word = containing_word.chars().rev().collect::<String>();
5070 if util::word_consists_of_emojis(containing_word.as_str()) {
5071 chars.reverse();
5072 return Some(chars.iter().collect());
5073 }
5074 }
5075
5076 if char.is_whitespace() || !char.is_ascii() {
5077 return None;
5078 }
5079 if char == ':' {
5080 found_colon = true;
5081 } else {
5082 chars.push(char);
5083 }
5084 }
5085 // Found a possible emoji shortcode at the beginning of the buffer
5086 chars.reverse();
5087 Some(chars.iter().collect())
5088 }
5089
5090 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5092 self.transact(window, cx, |this, window, cx| {
5093 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5094 let selections = this
5095 .selections
5096 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5097 let multi_buffer = this.buffer.read(cx);
5098 let buffer = multi_buffer.snapshot(cx);
5099 selections
5100 .iter()
5101 .map(|selection| {
5102 let start_point = selection.start.to_point(&buffer);
5103 let mut existing_indent =
5104 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5105 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5106 let start = selection.start;
5107 let end = selection.end;
5108 let selection_is_empty = start == end;
5109 let language_scope = buffer.language_scope_at(start);
5110 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5111 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5112 &buffer,
5113 start..end,
5114 language,
5115 )
5116 || NewlineConfig::insert_extra_newline_tree_sitter(
5117 &buffer,
5118 start..end,
5119 );
5120
5121 let mut newline_config = NewlineConfig::Newline {
5122 additional_indent: IndentSize::spaces(0),
5123 extra_line_additional_indent: if needs_extra_newline {
5124 Some(IndentSize::spaces(0))
5125 } else {
5126 None
5127 },
5128 prevent_auto_indent: false,
5129 };
5130
5131 let comment_delimiter = maybe!({
5132 if !selection_is_empty {
5133 return None;
5134 }
5135
5136 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5137 return None;
5138 }
5139
5140 return comment_delimiter_for_newline(
5141 &start_point,
5142 &buffer,
5143 language,
5144 );
5145 });
5146
5147 let doc_delimiter = maybe!({
5148 if !selection_is_empty {
5149 return None;
5150 }
5151
5152 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5153 return None;
5154 }
5155
5156 return documentation_delimiter_for_newline(
5157 &start_point,
5158 &buffer,
5159 language,
5160 &mut newline_config,
5161 );
5162 });
5163
5164 let list_delimiter = maybe!({
5165 if !selection_is_empty {
5166 return None;
5167 }
5168
5169 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5170 return None;
5171 }
5172
5173 return list_delimiter_for_newline(
5174 &start_point,
5175 &buffer,
5176 language,
5177 &mut newline_config,
5178 );
5179 });
5180
5181 (
5182 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5183 newline_config,
5184 )
5185 } else {
5186 (
5187 None,
5188 NewlineConfig::Newline {
5189 additional_indent: IndentSize::spaces(0),
5190 extra_line_additional_indent: None,
5191 prevent_auto_indent: false,
5192 },
5193 )
5194 };
5195
5196 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5197 NewlineConfig::ClearCurrentLine => {
5198 let row_start =
5199 buffer.point_to_offset(Point::new(start_point.row, 0));
5200 (row_start, String::new(), false)
5201 }
5202 NewlineConfig::UnindentCurrentLine { continuation } => {
5203 let row_start =
5204 buffer.point_to_offset(Point::new(start_point.row, 0));
5205 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5206 let tab_size_indent = IndentSize::spaces(tab_size.get());
5207 let reduced_indent =
5208 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5209 let mut new_text = String::new();
5210 new_text.extend(reduced_indent.chars());
5211 new_text.push_str(continuation);
5212 (row_start, new_text, true)
5213 }
5214 NewlineConfig::Newline {
5215 additional_indent,
5216 extra_line_additional_indent,
5217 prevent_auto_indent,
5218 } => {
5219 let capacity_for_delimiter =
5220 delimiter.as_deref().map(str::len).unwrap_or_default();
5221 let extra_line_len = extra_line_additional_indent
5222 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5223 .unwrap_or(0);
5224 let mut new_text = String::with_capacity(
5225 1 + capacity_for_delimiter
5226 + existing_indent.len as usize
5227 + additional_indent.len as usize
5228 + extra_line_len,
5229 );
5230 new_text.push('\n');
5231 new_text.extend(existing_indent.chars());
5232 new_text.extend(additional_indent.chars());
5233 if let Some(delimiter) = &delimiter {
5234 new_text.push_str(delimiter);
5235 }
5236 if let Some(extra_indent) = extra_line_additional_indent {
5237 new_text.push('\n');
5238 new_text.extend(existing_indent.chars());
5239 new_text.extend(extra_indent.chars());
5240 }
5241 (start, new_text, *prevent_auto_indent)
5242 }
5243 };
5244
5245 let anchor = buffer.anchor_after(end);
5246 let new_selection = selection.map(|_| anchor);
5247 (
5248 ((edit_start..end, new_text), prevent_auto_indent),
5249 (newline_config.has_extra_line(), new_selection),
5250 )
5251 })
5252 .unzip()
5253 };
5254
5255 let mut auto_indent_edits = Vec::new();
5256 let mut edits = Vec::new();
5257 for (edit, prevent_auto_indent) in edits_with_flags {
5258 if prevent_auto_indent {
5259 edits.push(edit);
5260 } else {
5261 auto_indent_edits.push(edit);
5262 }
5263 }
5264 if !edits.is_empty() {
5265 this.edit(edits, cx);
5266 }
5267 if !auto_indent_edits.is_empty() {
5268 this.edit_with_autoindent(auto_indent_edits, cx);
5269 }
5270
5271 let buffer = this.buffer.read(cx).snapshot(cx);
5272 let new_selections = selection_info
5273 .into_iter()
5274 .map(|(extra_newline_inserted, new_selection)| {
5275 let mut cursor = new_selection.end.to_point(&buffer);
5276 if extra_newline_inserted {
5277 cursor.row -= 1;
5278 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5279 }
5280 new_selection.map(|_| cursor)
5281 })
5282 .collect();
5283
5284 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5285 this.refresh_edit_prediction(true, false, window, cx);
5286 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5287 task.detach_and_log_err(cx);
5288 }
5289 });
5290 }
5291
5292 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5293 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5294
5295 let buffer = self.buffer.read(cx);
5296 let snapshot = buffer.snapshot(cx);
5297
5298 let mut edits = Vec::new();
5299 let mut rows = Vec::new();
5300
5301 for (rows_inserted, selection) in self
5302 .selections
5303 .all_adjusted(&self.display_snapshot(cx))
5304 .into_iter()
5305 .enumerate()
5306 {
5307 let cursor = selection.head();
5308 let row = cursor.row;
5309
5310 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5311
5312 let newline = "\n".to_string();
5313 edits.push((start_of_line..start_of_line, newline));
5314
5315 rows.push(row + rows_inserted as u32);
5316 }
5317
5318 self.transact(window, cx, |editor, window, cx| {
5319 editor.edit(edits, cx);
5320
5321 editor.change_selections(Default::default(), window, cx, |s| {
5322 let mut index = 0;
5323 s.move_cursors_with(&mut |map, _, _| {
5324 let row = rows[index];
5325 index += 1;
5326
5327 let point = Point::new(row, 0);
5328 let boundary = map.next_line_boundary(point).1;
5329 let clipped = map.clip_point(boundary, Bias::Left);
5330
5331 (clipped, SelectionGoal::None)
5332 });
5333 });
5334
5335 let mut indent_edits = Vec::new();
5336 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5337 for row in rows {
5338 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5339 for (row, indent) in indents {
5340 if indent.len == 0 {
5341 continue;
5342 }
5343
5344 let text = match indent.kind {
5345 IndentKind::Space => " ".repeat(indent.len as usize),
5346 IndentKind::Tab => "\t".repeat(indent.len as usize),
5347 };
5348 let point = Point::new(row.0, 0);
5349 indent_edits.push((point..point, text));
5350 }
5351 }
5352 editor.edit(indent_edits, cx);
5353 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5354 format.detach_and_log_err(cx);
5355 }
5356 });
5357 }
5358
5359 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5360 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5361
5362 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5363 let mut rows = Vec::new();
5364 let mut rows_inserted = 0;
5365
5366 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5367 let cursor = selection.head();
5368 let row = cursor.row;
5369
5370 let point = Point::new(row, 0);
5371 let Some((buffer_handle, buffer_point, _)) =
5372 self.buffer.read(cx).point_to_buffer_point(point, cx)
5373 else {
5374 continue;
5375 };
5376
5377 buffer_edits
5378 .entry(buffer_handle.entity_id())
5379 .or_insert_with(|| (buffer_handle, Vec::new()))
5380 .1
5381 .push(buffer_point);
5382
5383 rows_inserted += 1;
5384 rows.push(row + rows_inserted);
5385 }
5386
5387 self.transact(window, cx, |editor, window, cx| {
5388 for (_, (buffer_handle, points)) in &buffer_edits {
5389 buffer_handle.update(cx, |buffer, cx| {
5390 let edits: Vec<_> = points
5391 .iter()
5392 .map(|point| {
5393 let target = Point::new(point.row + 1, 0);
5394 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5395 (start_of_line..start_of_line, "\n")
5396 })
5397 .collect();
5398 buffer.edit(edits, None, cx);
5399 });
5400 }
5401
5402 editor.change_selections(Default::default(), window, cx, |s| {
5403 let mut index = 0;
5404 s.move_cursors_with(&mut |map, _, _| {
5405 let row = rows[index];
5406 index += 1;
5407
5408 let point = Point::new(row, 0);
5409 let boundary = map.next_line_boundary(point).1;
5410 let clipped = map.clip_point(boundary, Bias::Left);
5411
5412 (clipped, SelectionGoal::None)
5413 });
5414 });
5415
5416 let mut indent_edits = Vec::new();
5417 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5418 for row in rows {
5419 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5420 for (row, indent) in indents {
5421 if indent.len == 0 {
5422 continue;
5423 }
5424
5425 let text = match indent.kind {
5426 IndentKind::Space => " ".repeat(indent.len as usize),
5427 IndentKind::Tab => "\t".repeat(indent.len as usize),
5428 };
5429 let point = Point::new(row.0, 0);
5430 indent_edits.push((point..point, text));
5431 }
5432 }
5433 editor.edit(indent_edits, cx);
5434 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5435 format.detach_and_log_err(cx);
5436 }
5437 });
5438 }
5439
5440 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5441 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5442 original_indent_columns: Vec::new(),
5443 });
5444 self.replace_selections(text, autoindent, window, cx, false);
5445 }
5446
5447 /// Replaces the editor's selections with the provided `text`, applying the
5448 /// given `autoindent_mode` (`None` will skip autoindentation).
5449 ///
5450 /// Early returns if the editor is in read-only mode, without applying any
5451 /// edits.
5452 fn replace_selections(
5453 &mut self,
5454 text: &str,
5455 autoindent_mode: Option<AutoindentMode>,
5456 window: &mut Window,
5457 cx: &mut Context<Self>,
5458 apply_linked_edits: bool,
5459 ) {
5460 if self.read_only(cx) {
5461 return;
5462 }
5463
5464 let text: Arc<str> = text.into();
5465 self.transact(window, cx, |this, window, cx| {
5466 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5467 let linked_edits = if apply_linked_edits {
5468 this.linked_edits_for_selections(text.clone(), cx)
5469 } else {
5470 LinkedEdits::new()
5471 };
5472
5473 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5474 let anchors = {
5475 let snapshot = buffer.read(cx);
5476 old_selections
5477 .iter()
5478 .map(|s| {
5479 let anchor = snapshot.anchor_after(s.head());
5480 s.map(|_| anchor)
5481 })
5482 .collect::<Vec<_>>()
5483 };
5484 buffer.edit(
5485 old_selections
5486 .iter()
5487 .map(|s| (s.start..s.end, text.clone())),
5488 autoindent_mode,
5489 cx,
5490 );
5491 anchors
5492 });
5493
5494 linked_edits.apply(cx);
5495
5496 this.change_selections(Default::default(), window, cx, |s| {
5497 s.select_anchors(selection_anchors);
5498 });
5499
5500 if apply_linked_edits {
5501 refresh_linked_ranges(this, window, cx);
5502 }
5503
5504 cx.notify();
5505 });
5506 }
5507
5508 /// Collects linked edits for the current selections, pairing each linked
5509 /// range with `text`.
5510 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5511 let mut linked_edits = LinkedEdits::new();
5512 if !self.linked_edit_ranges.is_empty() {
5513 for selection in self.selections.disjoint_anchors() {
5514 let start = selection.start.text_anchor;
5515 let end = selection.end.text_anchor;
5516 linked_edits.push(self, start..end, text.clone(), cx);
5517 }
5518 }
5519 linked_edits
5520 }
5521
5522 /// Deletes the content covered by the current selections and applies
5523 /// linked edits.
5524 pub fn delete_selections_with_linked_edits(
5525 &mut self,
5526 window: &mut Window,
5527 cx: &mut Context<Self>,
5528 ) {
5529 self.replace_selections("", None, window, cx, true);
5530 }
5531
5532 #[cfg(any(test, feature = "test-support"))]
5533 pub fn set_linked_edit_ranges_for_testing(
5534 &mut self,
5535 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5536 cx: &mut Context<Self>,
5537 ) -> Option<()> {
5538 let Some((buffer, _)) = self
5539 .buffer
5540 .read(cx)
5541 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5542 else {
5543 return None;
5544 };
5545 let buffer = buffer.read(cx);
5546 let buffer_id = buffer.remote_id();
5547 let mut linked_ranges = Vec::with_capacity(ranges.len());
5548 for (base_range, linked_ranges_points) in ranges {
5549 let base_anchor =
5550 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5551 let linked_anchors = linked_ranges_points
5552 .into_iter()
5553 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5554 .collect();
5555 linked_ranges.push((base_anchor, linked_anchors));
5556 }
5557 let mut map = HashMap::default();
5558 map.insert(buffer_id, linked_ranges);
5559 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5560 Some(())
5561 }
5562
5563 fn trigger_completion_on_input(
5564 &mut self,
5565 text: &str,
5566 trigger_in_words: bool,
5567 window: &mut Window,
5568 cx: &mut Context<Self>,
5569 ) {
5570 let completions_source = self
5571 .context_menu
5572 .borrow()
5573 .as_ref()
5574 .and_then(|menu| match menu {
5575 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5576 CodeContextMenu::CodeActions(_) => None,
5577 });
5578
5579 match completions_source {
5580 Some(CompletionsMenuSource::Words { .. }) => {
5581 self.open_or_update_completions_menu(
5582 Some(CompletionsMenuSource::Words {
5583 ignore_threshold: false,
5584 }),
5585 None,
5586 trigger_in_words,
5587 window,
5588 cx,
5589 );
5590 }
5591 _ => self.open_or_update_completions_menu(
5592 None,
5593 Some(text.to_owned()).filter(|x| !x.is_empty()),
5594 true,
5595 window,
5596 cx,
5597 ),
5598 }
5599 }
5600
5601 /// If any empty selections is touching the start of its innermost containing autoclose
5602 /// region, expand it to select the brackets.
5603 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5604 let selections = self
5605 .selections
5606 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5607 let buffer = self.buffer.read(cx).read(cx);
5608 let new_selections = self
5609 .selections_with_autoclose_regions(selections, &buffer)
5610 .map(|(mut selection, region)| {
5611 if !selection.is_empty() {
5612 return selection;
5613 }
5614
5615 if let Some(region) = region {
5616 let mut range = region.range.to_offset(&buffer);
5617 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5618 range.start -= region.pair.start.len();
5619 if buffer.contains_str_at(range.start, ®ion.pair.start)
5620 && buffer.contains_str_at(range.end, ®ion.pair.end)
5621 {
5622 range.end += region.pair.end.len();
5623 selection.start = range.start;
5624 selection.end = range.end;
5625
5626 return selection;
5627 }
5628 }
5629 }
5630
5631 let always_treat_brackets_as_autoclosed = buffer
5632 .language_settings_at(selection.start, cx)
5633 .always_treat_brackets_as_autoclosed;
5634
5635 if !always_treat_brackets_as_autoclosed {
5636 return selection;
5637 }
5638
5639 if let Some(scope) = buffer.language_scope_at(selection.start) {
5640 for (pair, enabled) in scope.brackets() {
5641 if !enabled || !pair.close {
5642 continue;
5643 }
5644
5645 if buffer.contains_str_at(selection.start, &pair.end) {
5646 let pair_start_len = pair.start.len();
5647 if buffer.contains_str_at(
5648 selection.start.saturating_sub_usize(pair_start_len),
5649 &pair.start,
5650 ) {
5651 selection.start -= pair_start_len;
5652 selection.end += pair.end.len();
5653
5654 return selection;
5655 }
5656 }
5657 }
5658 }
5659
5660 selection
5661 })
5662 .collect();
5663
5664 drop(buffer);
5665 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5666 selections.select(new_selections)
5667 });
5668 }
5669
5670 /// Iterate the given selections, and for each one, find the smallest surrounding
5671 /// autoclose region. This uses the ordering of the selections and the autoclose
5672 /// regions to avoid repeated comparisons.
5673 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5674 &'a self,
5675 selections: impl IntoIterator<Item = Selection<D>>,
5676 buffer: &'a MultiBufferSnapshot,
5677 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5678 let mut i = 0;
5679 let mut regions = self.autoclose_regions.as_slice();
5680 selections.into_iter().map(move |selection| {
5681 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5682
5683 let mut enclosing = None;
5684 while let Some(pair_state) = regions.get(i) {
5685 if pair_state.range.end.to_offset(buffer) < range.start {
5686 regions = ®ions[i + 1..];
5687 i = 0;
5688 } else if pair_state.range.start.to_offset(buffer) > range.end {
5689 break;
5690 } else {
5691 if pair_state.selection_id == selection.id {
5692 enclosing = Some(pair_state);
5693 }
5694 i += 1;
5695 }
5696 }
5697
5698 (selection, enclosing)
5699 })
5700 }
5701
5702 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5703 fn invalidate_autoclose_regions(
5704 &mut self,
5705 mut selections: &[Selection<Anchor>],
5706 buffer: &MultiBufferSnapshot,
5707 ) {
5708 self.autoclose_regions.retain(|state| {
5709 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5710 return false;
5711 }
5712
5713 let mut i = 0;
5714 while let Some(selection) = selections.get(i) {
5715 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5716 selections = &selections[1..];
5717 continue;
5718 }
5719 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5720 break;
5721 }
5722 if selection.id == state.selection_id {
5723 return true;
5724 } else {
5725 i += 1;
5726 }
5727 }
5728 false
5729 });
5730 }
5731
5732 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5733 let offset = position.to_offset(buffer);
5734 let (word_range, kind) =
5735 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5736 if offset > word_range.start && kind == Some(CharKind::Word) {
5737 Some(
5738 buffer
5739 .text_for_range(word_range.start..offset)
5740 .collect::<String>(),
5741 )
5742 } else {
5743 None
5744 }
5745 }
5746
5747 pub fn visible_excerpts(
5748 &self,
5749 lsp_related_only: bool,
5750 cx: &mut Context<Editor>,
5751 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5752 let project = self.project().cloned();
5753 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5754 let multi_buffer = self.buffer().read(cx);
5755 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5756 let multi_buffer_visible_start = self
5757 .scroll_manager
5758 .native_anchor(&display_snapshot, cx)
5759 .anchor
5760 .to_point(&multi_buffer_snapshot);
5761 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5762 multi_buffer_visible_start
5763 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5764 Bias::Left,
5765 );
5766 multi_buffer_snapshot
5767 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5768 .into_iter()
5769 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5770 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5771 if !lsp_related_only {
5772 return Some((
5773 excerpt_id,
5774 (
5775 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5776 buffer.version().clone(),
5777 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5778 ),
5779 ));
5780 }
5781
5782 let project = project.as_ref()?.read(cx);
5783 let buffer_file = project::File::from_dyn(buffer.file())?;
5784 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5785 let worktree_entry = buffer_worktree
5786 .read(cx)
5787 .entry_for_id(buffer_file.project_entry_id()?)?;
5788 if worktree_entry.is_ignored {
5789 None
5790 } else {
5791 Some((
5792 excerpt_id,
5793 (
5794 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5795 buffer.version().clone(),
5796 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5797 ),
5798 ))
5799 }
5800 })
5801 .collect()
5802 }
5803
5804 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5805 TextLayoutDetails {
5806 text_system: window.text_system().clone(),
5807 editor_style: self.style.clone().unwrap(),
5808 rem_size: window.rem_size(),
5809 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5810 visible_rows: self.visible_line_count(),
5811 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5812 }
5813 }
5814
5815 fn trigger_on_type_formatting(
5816 &self,
5817 input: String,
5818 window: &mut Window,
5819 cx: &mut Context<Self>,
5820 ) -> Option<Task<Result<()>>> {
5821 if input.chars().count() != 1 {
5822 return None;
5823 }
5824
5825 let project = self.project()?;
5826 let position = self.selections.newest_anchor().head();
5827 let (buffer, buffer_position) = self
5828 .buffer
5829 .read(cx)
5830 .text_anchor_for_position(position, cx)?;
5831
5832 let settings = language_settings::language_settings(
5833 buffer
5834 .read(cx)
5835 .language_at(buffer_position)
5836 .map(|l| l.name()),
5837 buffer.read(cx).file(),
5838 cx,
5839 );
5840 if !settings.use_on_type_format {
5841 return None;
5842 }
5843
5844 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5845 // hence we do LSP request & edit on host side only — add formats to host's history.
5846 let push_to_lsp_host_history = true;
5847 // If this is not the host, append its history with new edits.
5848 let push_to_client_history = project.read(cx).is_via_collab();
5849
5850 let on_type_formatting = project.update(cx, |project, cx| {
5851 project.on_type_format(
5852 buffer.clone(),
5853 buffer_position,
5854 input,
5855 push_to_lsp_host_history,
5856 cx,
5857 )
5858 });
5859 Some(cx.spawn_in(window, async move |editor, cx| {
5860 if let Some(transaction) = on_type_formatting.await? {
5861 if push_to_client_history {
5862 buffer.update(cx, |buffer, _| {
5863 buffer.push_transaction(transaction, Instant::now());
5864 buffer.finalize_last_transaction();
5865 });
5866 }
5867 editor.update(cx, |editor, cx| {
5868 editor.refresh_document_highlights(cx);
5869 })?;
5870 }
5871 Ok(())
5872 }))
5873 }
5874
5875 pub fn show_word_completions(
5876 &mut self,
5877 _: &ShowWordCompletions,
5878 window: &mut Window,
5879 cx: &mut Context<Self>,
5880 ) {
5881 self.open_or_update_completions_menu(
5882 Some(CompletionsMenuSource::Words {
5883 ignore_threshold: true,
5884 }),
5885 None,
5886 false,
5887 window,
5888 cx,
5889 );
5890 }
5891
5892 pub fn show_completions(
5893 &mut self,
5894 _: &ShowCompletions,
5895 window: &mut Window,
5896 cx: &mut Context<Self>,
5897 ) {
5898 self.open_or_update_completions_menu(None, None, false, window, cx);
5899 }
5900
5901 fn open_or_update_completions_menu(
5902 &mut self,
5903 requested_source: Option<CompletionsMenuSource>,
5904 trigger: Option<String>,
5905 trigger_in_words: bool,
5906 window: &mut Window,
5907 cx: &mut Context<Self>,
5908 ) {
5909 if self.pending_rename.is_some() {
5910 return;
5911 }
5912
5913 let completions_source = self
5914 .context_menu
5915 .borrow()
5916 .as_ref()
5917 .and_then(|menu| match menu {
5918 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5919 CodeContextMenu::CodeActions(_) => None,
5920 });
5921
5922 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5923
5924 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5925 // inserted and selected. To handle that case, the start of the selection is used so that
5926 // the menu starts with all choices.
5927 let position = self
5928 .selections
5929 .newest_anchor()
5930 .start
5931 .bias_right(&multibuffer_snapshot);
5932 if position.diff_base_anchor.is_some() {
5933 return;
5934 }
5935 let buffer_position = multibuffer_snapshot.anchor_before(position);
5936 let Some(buffer) = buffer_position
5937 .text_anchor
5938 .buffer_id
5939 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5940 else {
5941 return;
5942 };
5943 let buffer_snapshot = buffer.read(cx).snapshot();
5944
5945 let menu_is_open = matches!(
5946 self.context_menu.borrow().as_ref(),
5947 Some(CodeContextMenu::Completions(_))
5948 );
5949
5950 let language = buffer_snapshot
5951 .language_at(buffer_position.text_anchor)
5952 .map(|language| language.name());
5953
5954 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5955 let completion_settings = language_settings.completions.clone();
5956
5957 let show_completions_on_input = self
5958 .show_completions_on_input_override
5959 .unwrap_or(language_settings.show_completions_on_input);
5960 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5961 return;
5962 }
5963
5964 let query: Option<Arc<String>> =
5965 Self::completion_query(&multibuffer_snapshot, buffer_position)
5966 .map(|query| query.into());
5967
5968 drop(multibuffer_snapshot);
5969
5970 // Hide the current completions menu when query is empty. Without this, cached
5971 // completions from before the trigger char may be reused (#32774).
5972 if query.is_none() && menu_is_open {
5973 self.hide_context_menu(window, cx);
5974 }
5975
5976 let mut ignore_word_threshold = false;
5977 let provider = match requested_source {
5978 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5979 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5980 ignore_word_threshold = ignore_threshold;
5981 None
5982 }
5983 Some(CompletionsMenuSource::SnippetChoices)
5984 | Some(CompletionsMenuSource::SnippetsOnly) => {
5985 log::error!("bug: SnippetChoices requested_source is not handled");
5986 None
5987 }
5988 };
5989
5990 let sort_completions = provider
5991 .as_ref()
5992 .is_some_and(|provider| provider.sort_completions());
5993
5994 let filter_completions = provider
5995 .as_ref()
5996 .is_none_or(|provider| provider.filter_completions());
5997
5998 let was_snippets_only = matches!(
5999 completions_source,
6000 Some(CompletionsMenuSource::SnippetsOnly)
6001 );
6002
6003 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6004 if filter_completions {
6005 menu.filter(
6006 query.clone().unwrap_or_default(),
6007 buffer_position.text_anchor,
6008 &buffer,
6009 provider.clone(),
6010 window,
6011 cx,
6012 );
6013 }
6014 // When `is_incomplete` is false, no need to re-query completions when the current query
6015 // is a suffix of the initial query.
6016 let was_complete = !menu.is_incomplete;
6017 if was_complete && !was_snippets_only {
6018 // If the new query is a suffix of the old query (typing more characters) and
6019 // the previous result was complete, the existing completions can be filtered.
6020 //
6021 // Note that snippet completions are always complete.
6022 let query_matches = match (&menu.initial_query, &query) {
6023 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6024 (None, _) => true,
6025 _ => false,
6026 };
6027 if query_matches {
6028 let position_matches = if menu.initial_position == position {
6029 true
6030 } else {
6031 let snapshot = self.buffer.read(cx).read(cx);
6032 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6033 };
6034 if position_matches {
6035 return;
6036 }
6037 }
6038 }
6039 };
6040
6041 let Anchor {
6042 excerpt_id: buffer_excerpt_id,
6043 text_anchor: buffer_position,
6044 ..
6045 } = buffer_position;
6046
6047 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6048 buffer_snapshot.surrounding_word(buffer_position, None)
6049 {
6050 let word_to_exclude = buffer_snapshot
6051 .text_for_range(word_range.clone())
6052 .collect::<String>();
6053 (
6054 buffer_snapshot.anchor_before(word_range.start)
6055 ..buffer_snapshot.anchor_after(buffer_position),
6056 Some(word_to_exclude),
6057 )
6058 } else {
6059 (buffer_position..buffer_position, None)
6060 };
6061
6062 let show_completion_documentation = buffer_snapshot
6063 .settings_at(buffer_position, cx)
6064 .show_completion_documentation;
6065
6066 // The document can be large, so stay in reasonable bounds when searching for words,
6067 // otherwise completion pop-up might be slow to appear.
6068 const WORD_LOOKUP_ROWS: u32 = 5_000;
6069 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6070 let min_word_search = buffer_snapshot.clip_point(
6071 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6072 Bias::Left,
6073 );
6074 let max_word_search = buffer_snapshot.clip_point(
6075 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6076 Bias::Right,
6077 );
6078 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6079 ..buffer_snapshot.point_to_offset(max_word_search);
6080
6081 let skip_digits = query
6082 .as_ref()
6083 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6084
6085 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6086 trigger.as_ref().is_none_or(|trigger| {
6087 provider.is_completion_trigger(
6088 &buffer,
6089 position.text_anchor,
6090 trigger,
6091 trigger_in_words,
6092 cx,
6093 )
6094 })
6095 });
6096
6097 let provider_responses = if let Some(provider) = &provider
6098 && load_provider_completions
6099 {
6100 let trigger_character =
6101 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6102 let completion_context = CompletionContext {
6103 trigger_kind: match &trigger_character {
6104 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6105 None => CompletionTriggerKind::INVOKED,
6106 },
6107 trigger_character,
6108 };
6109
6110 provider.completions(
6111 buffer_excerpt_id,
6112 &buffer,
6113 buffer_position,
6114 completion_context,
6115 window,
6116 cx,
6117 )
6118 } else {
6119 Task::ready(Ok(Vec::new()))
6120 };
6121
6122 let load_word_completions = if !self.word_completions_enabled {
6123 false
6124 } else if requested_source
6125 == Some(CompletionsMenuSource::Words {
6126 ignore_threshold: true,
6127 })
6128 {
6129 true
6130 } else {
6131 load_provider_completions
6132 && completion_settings.words != WordsCompletionMode::Disabled
6133 && (ignore_word_threshold || {
6134 let words_min_length = completion_settings.words_min_length;
6135 // check whether word has at least `words_min_length` characters
6136 let query_chars = query.iter().flat_map(|q| q.chars());
6137 query_chars.take(words_min_length).count() == words_min_length
6138 })
6139 };
6140
6141 let mut words = if load_word_completions {
6142 cx.background_spawn({
6143 let buffer_snapshot = buffer_snapshot.clone();
6144 async move {
6145 buffer_snapshot.words_in_range(WordsQuery {
6146 fuzzy_contents: None,
6147 range: word_search_range,
6148 skip_digits,
6149 })
6150 }
6151 })
6152 } else {
6153 Task::ready(BTreeMap::default())
6154 };
6155
6156 let snippets = if let Some(provider) = &provider
6157 && provider.show_snippets()
6158 && let Some(project) = self.project()
6159 {
6160 let char_classifier = buffer_snapshot
6161 .char_classifier_at(buffer_position)
6162 .scope_context(Some(CharScopeContext::Completion));
6163 project.update(cx, |project, cx| {
6164 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6165 })
6166 } else {
6167 Task::ready(Ok(CompletionResponse {
6168 completions: Vec::new(),
6169 display_options: Default::default(),
6170 is_incomplete: false,
6171 }))
6172 };
6173
6174 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6175
6176 let id = post_inc(&mut self.next_completion_id);
6177 let task = cx.spawn_in(window, async move |editor, cx| {
6178 let Ok(()) = editor.update(cx, |this, _| {
6179 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6180 }) else {
6181 return;
6182 };
6183
6184 // TODO: Ideally completions from different sources would be selectively re-queried, so
6185 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6186 let mut completions = Vec::new();
6187 let mut is_incomplete = false;
6188 let mut display_options: Option<CompletionDisplayOptions> = None;
6189 if let Some(provider_responses) = provider_responses.await.log_err()
6190 && !provider_responses.is_empty()
6191 {
6192 for response in provider_responses {
6193 completions.extend(response.completions);
6194 is_incomplete = is_incomplete || response.is_incomplete;
6195 match display_options.as_mut() {
6196 None => {
6197 display_options = Some(response.display_options);
6198 }
6199 Some(options) => options.merge(&response.display_options),
6200 }
6201 }
6202 if completion_settings.words == WordsCompletionMode::Fallback {
6203 words = Task::ready(BTreeMap::default());
6204 }
6205 }
6206 let display_options = display_options.unwrap_or_default();
6207
6208 let mut words = words.await;
6209 if let Some(word_to_exclude) = &word_to_exclude {
6210 words.remove(word_to_exclude);
6211 }
6212 for lsp_completion in &completions {
6213 words.remove(&lsp_completion.new_text);
6214 }
6215 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6216 replace_range: word_replace_range.clone(),
6217 new_text: word.clone(),
6218 label: CodeLabel::plain(word, None),
6219 match_start: None,
6220 snippet_deduplication_key: None,
6221 icon_path: None,
6222 documentation: None,
6223 source: CompletionSource::BufferWord {
6224 word_range,
6225 resolved: false,
6226 },
6227 insert_text_mode: Some(InsertTextMode::AS_IS),
6228 confirm: None,
6229 }));
6230
6231 completions.extend(
6232 snippets
6233 .await
6234 .into_iter()
6235 .flat_map(|response| response.completions),
6236 );
6237
6238 let menu = if completions.is_empty() {
6239 None
6240 } else {
6241 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6242 let languages = editor
6243 .workspace
6244 .as_ref()
6245 .and_then(|(workspace, _)| workspace.upgrade())
6246 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6247 let menu = CompletionsMenu::new(
6248 id,
6249 requested_source.unwrap_or(if load_provider_completions {
6250 CompletionsMenuSource::Normal
6251 } else {
6252 CompletionsMenuSource::SnippetsOnly
6253 }),
6254 sort_completions,
6255 show_completion_documentation,
6256 position,
6257 query.clone(),
6258 is_incomplete,
6259 buffer.clone(),
6260 completions.into(),
6261 editor
6262 .context_menu()
6263 .borrow_mut()
6264 .as_ref()
6265 .map(|menu| menu.primary_scroll_handle()),
6266 display_options,
6267 snippet_sort_order,
6268 languages,
6269 language,
6270 cx,
6271 );
6272
6273 let query = if filter_completions { query } else { None };
6274 let matches_task = menu.do_async_filtering(
6275 query.unwrap_or_default(),
6276 buffer_position,
6277 &buffer,
6278 cx,
6279 );
6280 (menu, matches_task)
6281 }) else {
6282 return;
6283 };
6284
6285 let matches = matches_task.await;
6286
6287 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6288 // Newer menu already set, so exit.
6289 if let Some(CodeContextMenu::Completions(prev_menu)) =
6290 editor.context_menu.borrow().as_ref()
6291 && prev_menu.id > id
6292 {
6293 return;
6294 };
6295
6296 // Only valid to take prev_menu because either the new menu is immediately set
6297 // below, or the menu is hidden.
6298 if let Some(CodeContextMenu::Completions(prev_menu)) =
6299 editor.context_menu.borrow_mut().take()
6300 {
6301 let position_matches =
6302 if prev_menu.initial_position == menu.initial_position {
6303 true
6304 } else {
6305 let snapshot = editor.buffer.read(cx).read(cx);
6306 prev_menu.initial_position.to_offset(&snapshot)
6307 == menu.initial_position.to_offset(&snapshot)
6308 };
6309 if position_matches {
6310 // Preserve markdown cache before `set_filter_results` because it will
6311 // try to populate the documentation cache.
6312 menu.preserve_markdown_cache(prev_menu);
6313 }
6314 };
6315
6316 menu.set_filter_results(matches, provider, window, cx);
6317 }) else {
6318 return;
6319 };
6320
6321 menu.visible().then_some(menu)
6322 };
6323
6324 editor
6325 .update_in(cx, |editor, window, cx| {
6326 if editor.focus_handle.is_focused(window)
6327 && let Some(menu) = menu
6328 {
6329 *editor.context_menu.borrow_mut() =
6330 Some(CodeContextMenu::Completions(menu));
6331
6332 crate::hover_popover::hide_hover(editor, cx);
6333 if editor.show_edit_predictions_in_menu() {
6334 editor.update_visible_edit_prediction(window, cx);
6335 } else {
6336 editor
6337 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6338 }
6339
6340 cx.notify();
6341 return;
6342 }
6343
6344 if editor.completion_tasks.len() <= 1 {
6345 // If there are no more completion tasks and the last menu was empty, we should hide it.
6346 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6347 // If it was already hidden and we don't show edit predictions in the menu,
6348 // we should also show the edit prediction when available.
6349 if was_hidden && editor.show_edit_predictions_in_menu() {
6350 editor.update_visible_edit_prediction(window, cx);
6351 }
6352 }
6353 })
6354 .ok();
6355 });
6356
6357 self.completion_tasks.push((id, task));
6358 }
6359
6360 #[cfg(any(test, feature = "test-support"))]
6361 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6362 let menu = self.context_menu.borrow();
6363 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6364 let completions = menu.completions.borrow();
6365 Some(completions.to_vec())
6366 } else {
6367 None
6368 }
6369 }
6370
6371 pub fn with_completions_menu_matching_id<R>(
6372 &self,
6373 id: CompletionId,
6374 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6375 ) -> R {
6376 let mut context_menu = self.context_menu.borrow_mut();
6377 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6378 return f(None);
6379 };
6380 if completions_menu.id != id {
6381 return f(None);
6382 }
6383 f(Some(completions_menu))
6384 }
6385
6386 pub fn confirm_completion(
6387 &mut self,
6388 action: &ConfirmCompletion,
6389 window: &mut Window,
6390 cx: &mut Context<Self>,
6391 ) -> Option<Task<Result<()>>> {
6392 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6393 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6394 }
6395
6396 pub fn confirm_completion_insert(
6397 &mut self,
6398 _: &ConfirmCompletionInsert,
6399 window: &mut Window,
6400 cx: &mut Context<Self>,
6401 ) -> Option<Task<Result<()>>> {
6402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6403 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6404 }
6405
6406 pub fn confirm_completion_replace(
6407 &mut self,
6408 _: &ConfirmCompletionReplace,
6409 window: &mut Window,
6410 cx: &mut Context<Self>,
6411 ) -> Option<Task<Result<()>>> {
6412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6413 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6414 }
6415
6416 pub fn compose_completion(
6417 &mut self,
6418 action: &ComposeCompletion,
6419 window: &mut Window,
6420 cx: &mut Context<Self>,
6421 ) -> Option<Task<Result<()>>> {
6422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6423 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6424 }
6425
6426 fn do_completion(
6427 &mut self,
6428 item_ix: Option<usize>,
6429 intent: CompletionIntent,
6430 window: &mut Window,
6431 cx: &mut Context<Editor>,
6432 ) -> Option<Task<Result<()>>> {
6433 use language::ToOffset as _;
6434
6435 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6436 else {
6437 return None;
6438 };
6439
6440 let candidate_id = {
6441 let entries = completions_menu.entries.borrow();
6442 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6443 if self.show_edit_predictions_in_menu() {
6444 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6445 }
6446 mat.candidate_id
6447 };
6448
6449 let completion = completions_menu
6450 .completions
6451 .borrow()
6452 .get(candidate_id)?
6453 .clone();
6454 cx.stop_propagation();
6455
6456 let buffer_handle = completions_menu.buffer.clone();
6457
6458 let CompletionEdit {
6459 new_text,
6460 snippet,
6461 replace_range,
6462 } = process_completion_for_edit(
6463 &completion,
6464 intent,
6465 &buffer_handle,
6466 &completions_menu.initial_position.text_anchor,
6467 cx,
6468 );
6469
6470 let buffer = buffer_handle.read(cx);
6471 let snapshot = self.buffer.read(cx).snapshot(cx);
6472 let newest_anchor = self.selections.newest_anchor();
6473 let replace_range_multibuffer = {
6474 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6475 excerpt.map_range_from_buffer(replace_range.clone())
6476 };
6477 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6478 return None;
6479 }
6480
6481 let old_text = buffer
6482 .text_for_range(replace_range.clone())
6483 .collect::<String>();
6484 let lookbehind = newest_anchor
6485 .start
6486 .text_anchor
6487 .to_offset(buffer)
6488 .saturating_sub(replace_range.start.0);
6489 let lookahead = replace_range
6490 .end
6491 .0
6492 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6493 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6494 let suffix = &old_text[lookbehind.min(old_text.len())..];
6495
6496 let selections = self
6497 .selections
6498 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6499 let mut ranges = Vec::new();
6500 let mut linked_edits = LinkedEdits::new();
6501
6502 let text: Arc<str> = new_text.clone().into();
6503 for selection in &selections {
6504 let range = if selection.id == newest_anchor.id {
6505 replace_range_multibuffer.clone()
6506 } else {
6507 let mut range = selection.range();
6508
6509 // if prefix is present, don't duplicate it
6510 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6511 range.start = range.start.saturating_sub_usize(lookbehind);
6512
6513 // if suffix is also present, mimic the newest cursor and replace it
6514 if selection.id != newest_anchor.id
6515 && snapshot.contains_str_at(range.end, suffix)
6516 {
6517 range.end += lookahead;
6518 }
6519 }
6520 range
6521 };
6522
6523 ranges.push(range.clone());
6524
6525 if !self.linked_edit_ranges.is_empty() {
6526 let start_anchor = snapshot.anchor_before(range.start);
6527 let end_anchor = snapshot.anchor_after(range.end);
6528 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6529 linked_edits.push(&self, anchor_range, text.clone(), cx);
6530 }
6531 }
6532
6533 let common_prefix_len = old_text
6534 .chars()
6535 .zip(new_text.chars())
6536 .take_while(|(a, b)| a == b)
6537 .map(|(a, _)| a.len_utf8())
6538 .sum::<usize>();
6539
6540 cx.emit(EditorEvent::InputHandled {
6541 utf16_range_to_replace: None,
6542 text: new_text[common_prefix_len..].into(),
6543 });
6544
6545 self.transact(window, cx, |editor, window, cx| {
6546 if let Some(mut snippet) = snippet {
6547 snippet.text = new_text.to_string();
6548 editor
6549 .insert_snippet(&ranges, snippet, window, cx)
6550 .log_err();
6551 } else {
6552 editor.buffer.update(cx, |multi_buffer, cx| {
6553 let auto_indent = match completion.insert_text_mode {
6554 Some(InsertTextMode::AS_IS) => None,
6555 _ => editor.autoindent_mode.clone(),
6556 };
6557 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6558 multi_buffer.edit(edits, auto_indent, cx);
6559 });
6560 }
6561 linked_edits.apply(cx);
6562 editor.refresh_edit_prediction(true, false, window, cx);
6563 });
6564 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6565
6566 let show_new_completions_on_confirm = completion
6567 .confirm
6568 .as_ref()
6569 .is_some_and(|confirm| confirm(intent, window, cx));
6570 if show_new_completions_on_confirm {
6571 self.open_or_update_completions_menu(None, None, false, window, cx);
6572 }
6573
6574 let provider = self.completion_provider.as_ref()?;
6575
6576 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6577 let command = lsp_store.as_ref().and_then(|lsp_store| {
6578 let CompletionSource::Lsp {
6579 lsp_completion,
6580 server_id,
6581 ..
6582 } = &completion.source
6583 else {
6584 return None;
6585 };
6586 let lsp_command = lsp_completion.command.as_ref()?;
6587 let available_commands = lsp_store
6588 .read(cx)
6589 .lsp_server_capabilities
6590 .get(server_id)
6591 .and_then(|server_capabilities| {
6592 server_capabilities
6593 .execute_command_provider
6594 .as_ref()
6595 .map(|options| options.commands.as_slice())
6596 })?;
6597 if available_commands.contains(&lsp_command.command) {
6598 Some(CodeAction {
6599 server_id: *server_id,
6600 range: language::Anchor::MIN..language::Anchor::MIN,
6601 lsp_action: LspAction::Command(lsp_command.clone()),
6602 resolved: false,
6603 })
6604 } else {
6605 None
6606 }
6607 });
6608
6609 drop(completion);
6610 let apply_edits = provider.apply_additional_edits_for_completion(
6611 buffer_handle.clone(),
6612 completions_menu.completions.clone(),
6613 candidate_id,
6614 true,
6615 cx,
6616 );
6617
6618 let editor_settings = EditorSettings::get_global(cx);
6619 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6620 // After the code completion is finished, users often want to know what signatures are needed.
6621 // so we should automatically call signature_help
6622 self.show_signature_help(&ShowSignatureHelp, window, cx);
6623 }
6624
6625 Some(cx.spawn_in(window, async move |editor, cx| {
6626 apply_edits.await?;
6627
6628 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6629 let title = command.lsp_action.title().to_owned();
6630 let project_transaction = lsp_store
6631 .update(cx, |lsp_store, cx| {
6632 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6633 })
6634 .await
6635 .context("applying post-completion command")?;
6636 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6637 Self::open_project_transaction(
6638 &editor,
6639 workspace.downgrade(),
6640 project_transaction,
6641 title,
6642 cx,
6643 )
6644 .await?;
6645 }
6646 }
6647
6648 Ok(())
6649 }))
6650 }
6651
6652 pub fn toggle_code_actions(
6653 &mut self,
6654 action: &ToggleCodeActions,
6655 window: &mut Window,
6656 cx: &mut Context<Self>,
6657 ) {
6658 let quick_launch = action.quick_launch;
6659 let mut context_menu = self.context_menu.borrow_mut();
6660 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6661 if code_actions.deployed_from == action.deployed_from {
6662 // Toggle if we're selecting the same one
6663 *context_menu = None;
6664 cx.notify();
6665 return;
6666 } else {
6667 // Otherwise, clear it and start a new one
6668 *context_menu = None;
6669 cx.notify();
6670 }
6671 }
6672 drop(context_menu);
6673 let snapshot = self.snapshot(window, cx);
6674 let deployed_from = action.deployed_from.clone();
6675 let action = action.clone();
6676 self.completion_tasks.clear();
6677 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6678
6679 let multibuffer_point = match &action.deployed_from {
6680 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6681 DisplayPoint::new(*row, 0).to_point(&snapshot)
6682 }
6683 _ => self
6684 .selections
6685 .newest::<Point>(&snapshot.display_snapshot)
6686 .head(),
6687 };
6688 let Some((buffer, buffer_row)) = snapshot
6689 .buffer_snapshot()
6690 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6691 .and_then(|(buffer_snapshot, range)| {
6692 self.buffer()
6693 .read(cx)
6694 .buffer(buffer_snapshot.remote_id())
6695 .map(|buffer| (buffer, range.start.row))
6696 })
6697 else {
6698 return;
6699 };
6700 let buffer_id = buffer.read(cx).remote_id();
6701 let tasks = self
6702 .tasks
6703 .get(&(buffer_id, buffer_row))
6704 .map(|t| Arc::new(t.to_owned()));
6705
6706 if !self.focus_handle.is_focused(window) {
6707 return;
6708 }
6709 let project = self.project.clone();
6710
6711 let code_actions_task = match deployed_from {
6712 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6713 _ => self.code_actions(buffer_row, window, cx),
6714 };
6715
6716 let runnable_task = match deployed_from {
6717 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6718 _ => {
6719 let mut task_context_task = Task::ready(None);
6720 if let Some(tasks) = &tasks
6721 && let Some(project) = project
6722 {
6723 task_context_task =
6724 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6725 }
6726
6727 cx.spawn_in(window, {
6728 let buffer = buffer.clone();
6729 async move |editor, cx| {
6730 let task_context = task_context_task.await;
6731
6732 let resolved_tasks =
6733 tasks
6734 .zip(task_context.clone())
6735 .map(|(tasks, task_context)| ResolvedTasks {
6736 templates: tasks.resolve(&task_context).collect(),
6737 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6738 multibuffer_point.row,
6739 tasks.column,
6740 )),
6741 });
6742 let debug_scenarios = editor
6743 .update(cx, |editor, cx| {
6744 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6745 })?
6746 .await;
6747 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6748 }
6749 })
6750 }
6751 };
6752
6753 cx.spawn_in(window, async move |editor, cx| {
6754 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6755 let code_actions = code_actions_task.await;
6756 let spawn_straight_away = quick_launch
6757 && resolved_tasks
6758 .as_ref()
6759 .is_some_and(|tasks| tasks.templates.len() == 1)
6760 && code_actions
6761 .as_ref()
6762 .is_none_or(|actions| actions.is_empty())
6763 && debug_scenarios.is_empty();
6764
6765 editor.update_in(cx, |editor, window, cx| {
6766 crate::hover_popover::hide_hover(editor, cx);
6767 let actions = CodeActionContents::new(
6768 resolved_tasks,
6769 code_actions,
6770 debug_scenarios,
6771 task_context.unwrap_or_default(),
6772 );
6773
6774 // Don't show the menu if there are no actions available
6775 if actions.is_empty() {
6776 cx.notify();
6777 return Task::ready(Ok(()));
6778 }
6779
6780 *editor.context_menu.borrow_mut() =
6781 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6782 buffer,
6783 actions,
6784 selected_item: Default::default(),
6785 scroll_handle: UniformListScrollHandle::default(),
6786 deployed_from,
6787 }));
6788 cx.notify();
6789 if spawn_straight_away
6790 && let Some(task) = editor.confirm_code_action(
6791 &ConfirmCodeAction { item_ix: Some(0) },
6792 window,
6793 cx,
6794 )
6795 {
6796 return task;
6797 }
6798
6799 Task::ready(Ok(()))
6800 })
6801 })
6802 .detach_and_log_err(cx);
6803 }
6804
6805 fn debug_scenarios(
6806 &mut self,
6807 resolved_tasks: &Option<ResolvedTasks>,
6808 buffer: &Entity<Buffer>,
6809 cx: &mut App,
6810 ) -> Task<Vec<task::DebugScenario>> {
6811 maybe!({
6812 let project = self.project()?;
6813 let dap_store = project.read(cx).dap_store();
6814 let mut scenarios = vec![];
6815 let resolved_tasks = resolved_tasks.as_ref()?;
6816 let buffer = buffer.read(cx);
6817 let language = buffer.language()?;
6818 let file = buffer.file();
6819 let debug_adapter = language_settings(language.name().into(), file, cx)
6820 .debuggers
6821 .first()
6822 .map(SharedString::from)
6823 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6824
6825 dap_store.update(cx, |dap_store, cx| {
6826 for (_, task) in &resolved_tasks.templates {
6827 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6828 task.original_task().clone(),
6829 debug_adapter.clone().into(),
6830 task.display_label().to_owned().into(),
6831 cx,
6832 );
6833 scenarios.push(maybe_scenario);
6834 }
6835 });
6836 Some(cx.background_spawn(async move {
6837 futures::future::join_all(scenarios)
6838 .await
6839 .into_iter()
6840 .flatten()
6841 .collect::<Vec<_>>()
6842 }))
6843 })
6844 .unwrap_or_else(|| Task::ready(vec![]))
6845 }
6846
6847 fn code_actions(
6848 &mut self,
6849 buffer_row: u32,
6850 window: &mut Window,
6851 cx: &mut Context<Self>,
6852 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6853 let mut task = self.code_actions_task.take();
6854 cx.spawn_in(window, async move |editor, cx| {
6855 while let Some(prev_task) = task {
6856 prev_task.await.log_err();
6857 task = editor
6858 .update(cx, |this, _| this.code_actions_task.take())
6859 .ok()?;
6860 }
6861
6862 editor
6863 .update(cx, |editor, cx| {
6864 editor
6865 .available_code_actions
6866 .clone()
6867 .and_then(|(location, code_actions)| {
6868 let snapshot = location.buffer.read(cx).snapshot();
6869 let point_range = location.range.to_point(&snapshot);
6870 let point_range = point_range.start.row..=point_range.end.row;
6871 if point_range.contains(&buffer_row) {
6872 Some(code_actions)
6873 } else {
6874 None
6875 }
6876 })
6877 })
6878 .ok()
6879 .flatten()
6880 })
6881 }
6882
6883 pub fn confirm_code_action(
6884 &mut self,
6885 action: &ConfirmCodeAction,
6886 window: &mut Window,
6887 cx: &mut Context<Self>,
6888 ) -> Option<Task<Result<()>>> {
6889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6890
6891 let actions_menu =
6892 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6893 menu
6894 } else {
6895 return None;
6896 };
6897
6898 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6899 let action = actions_menu.actions.get(action_ix)?;
6900 let title = action.label();
6901 let buffer = actions_menu.buffer;
6902 let workspace = self.workspace()?;
6903
6904 match action {
6905 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6906 workspace.update(cx, |workspace, cx| {
6907 workspace.schedule_resolved_task(
6908 task_source_kind,
6909 resolved_task,
6910 false,
6911 window,
6912 cx,
6913 );
6914
6915 Some(Task::ready(Ok(())))
6916 })
6917 }
6918 CodeActionsItem::CodeAction {
6919 excerpt_id,
6920 action,
6921 provider,
6922 } => {
6923 let apply_code_action =
6924 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6925 let workspace = workspace.downgrade();
6926 Some(cx.spawn_in(window, async move |editor, cx| {
6927 let project_transaction = apply_code_action.await?;
6928 Self::open_project_transaction(
6929 &editor,
6930 workspace,
6931 project_transaction,
6932 title,
6933 cx,
6934 )
6935 .await
6936 }))
6937 }
6938 CodeActionsItem::DebugScenario(scenario) => {
6939 let context = actions_menu.actions.context.into();
6940
6941 workspace.update(cx, |workspace, cx| {
6942 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6943 workspace.start_debug_session(
6944 scenario,
6945 context,
6946 Some(buffer),
6947 None,
6948 window,
6949 cx,
6950 );
6951 });
6952 Some(Task::ready(Ok(())))
6953 }
6954 }
6955 }
6956
6957 fn open_transaction_for_hidden_buffers(
6958 workspace: Entity<Workspace>,
6959 transaction: ProjectTransaction,
6960 title: String,
6961 window: &mut Window,
6962 cx: &mut Context<Self>,
6963 ) {
6964 if transaction.0.is_empty() {
6965 return;
6966 }
6967
6968 let edited_buffers_already_open = {
6969 let other_editors: Vec<Entity<Editor>> = workspace
6970 .read(cx)
6971 .panes()
6972 .iter()
6973 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6974 .filter(|editor| editor.entity_id() != cx.entity_id())
6975 .collect();
6976
6977 transaction.0.keys().all(|buffer| {
6978 other_editors.iter().any(|editor| {
6979 let multi_buffer = editor.read(cx).buffer();
6980 multi_buffer.read(cx).is_singleton()
6981 && multi_buffer
6982 .read(cx)
6983 .as_singleton()
6984 .map_or(false, |singleton| {
6985 singleton.entity_id() == buffer.entity_id()
6986 })
6987 })
6988 })
6989 };
6990 if !edited_buffers_already_open {
6991 let workspace = workspace.downgrade();
6992 cx.defer_in(window, move |_, window, cx| {
6993 cx.spawn_in(window, async move |editor, cx| {
6994 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6995 .await
6996 .ok()
6997 })
6998 .detach();
6999 });
7000 }
7001 }
7002
7003 pub async fn open_project_transaction(
7004 editor: &WeakEntity<Editor>,
7005 workspace: WeakEntity<Workspace>,
7006 transaction: ProjectTransaction,
7007 title: String,
7008 cx: &mut AsyncWindowContext,
7009 ) -> Result<()> {
7010 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7011 cx.update(|_, cx| {
7012 entries.sort_unstable_by_key(|(buffer, _)| {
7013 buffer.read(cx).file().map(|f| f.path().clone())
7014 });
7015 })?;
7016 if entries.is_empty() {
7017 return Ok(());
7018 }
7019
7020 // If the project transaction's edits are all contained within this editor, then
7021 // avoid opening a new editor to display them.
7022
7023 if let [(buffer, transaction)] = &*entries {
7024 let excerpt = editor.update(cx, |editor, cx| {
7025 editor
7026 .buffer()
7027 .read(cx)
7028 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7029 })?;
7030 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7031 && excerpted_buffer == *buffer
7032 {
7033 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7034 let excerpt_range = excerpt_range.to_offset(buffer);
7035 buffer
7036 .edited_ranges_for_transaction::<usize>(transaction)
7037 .all(|range| {
7038 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7039 })
7040 });
7041
7042 if all_edits_within_excerpt {
7043 return Ok(());
7044 }
7045 }
7046 }
7047
7048 let mut ranges_to_highlight = Vec::new();
7049 let excerpt_buffer = cx.new(|cx| {
7050 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7051 for (buffer_handle, transaction) in &entries {
7052 let edited_ranges = buffer_handle
7053 .read(cx)
7054 .edited_ranges_for_transaction::<Point>(transaction)
7055 .collect::<Vec<_>>();
7056 let (ranges, _) = multibuffer.set_excerpts_for_path(
7057 PathKey::for_buffer(buffer_handle, cx),
7058 buffer_handle.clone(),
7059 edited_ranges,
7060 multibuffer_context_lines(cx),
7061 cx,
7062 );
7063
7064 ranges_to_highlight.extend(ranges);
7065 }
7066 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7067 multibuffer
7068 });
7069
7070 workspace.update_in(cx, |workspace, window, cx| {
7071 let project = workspace.project().clone();
7072 let editor =
7073 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7074 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7075 editor.update(cx, |editor, cx| {
7076 editor.highlight_background(
7077 HighlightKey::Editor,
7078 &ranges_to_highlight,
7079 |_, theme| theme.colors().editor_highlighted_line_background,
7080 cx,
7081 );
7082 });
7083 })?;
7084
7085 Ok(())
7086 }
7087
7088 pub fn clear_code_action_providers(&mut self) {
7089 self.code_action_providers.clear();
7090 self.available_code_actions.take();
7091 }
7092
7093 pub fn add_code_action_provider(
7094 &mut self,
7095 provider: Rc<dyn CodeActionProvider>,
7096 window: &mut Window,
7097 cx: &mut Context<Self>,
7098 ) {
7099 if self
7100 .code_action_providers
7101 .iter()
7102 .any(|existing_provider| existing_provider.id() == provider.id())
7103 {
7104 return;
7105 }
7106
7107 self.code_action_providers.push(provider);
7108 self.refresh_code_actions(window, cx);
7109 }
7110
7111 pub fn remove_code_action_provider(
7112 &mut self,
7113 id: Arc<str>,
7114 window: &mut Window,
7115 cx: &mut Context<Self>,
7116 ) {
7117 self.code_action_providers
7118 .retain(|provider| provider.id() != id);
7119 self.refresh_code_actions(window, cx);
7120 }
7121
7122 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7123 !self.code_action_providers.is_empty()
7124 && EditorSettings::get_global(cx).toolbar.code_actions
7125 }
7126
7127 pub fn has_available_code_actions(&self) -> bool {
7128 self.available_code_actions
7129 .as_ref()
7130 .is_some_and(|(_, actions)| !actions.is_empty())
7131 }
7132
7133 fn render_inline_code_actions(
7134 &self,
7135 icon_size: ui::IconSize,
7136 display_row: DisplayRow,
7137 is_active: bool,
7138 cx: &mut Context<Self>,
7139 ) -> AnyElement {
7140 let show_tooltip = !self.context_menu_visible();
7141 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7142 .icon_size(icon_size)
7143 .shape(ui::IconButtonShape::Square)
7144 .icon_color(ui::Color::Hidden)
7145 .toggle_state(is_active)
7146 .when(show_tooltip, |this| {
7147 this.tooltip({
7148 let focus_handle = self.focus_handle.clone();
7149 move |_window, cx| {
7150 Tooltip::for_action_in(
7151 "Toggle Code Actions",
7152 &ToggleCodeActions {
7153 deployed_from: None,
7154 quick_launch: false,
7155 },
7156 &focus_handle,
7157 cx,
7158 )
7159 }
7160 })
7161 })
7162 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7163 window.focus(&editor.focus_handle(cx), cx);
7164 editor.toggle_code_actions(
7165 &crate::actions::ToggleCodeActions {
7166 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7167 display_row,
7168 )),
7169 quick_launch: false,
7170 },
7171 window,
7172 cx,
7173 );
7174 }))
7175 .into_any_element()
7176 }
7177
7178 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7179 &self.context_menu
7180 }
7181
7182 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7183 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7184 cx.background_executor()
7185 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7186 .await;
7187
7188 let (start_buffer, start, _, end, newest_selection) = this
7189 .update(cx, |this, cx| {
7190 let newest_selection = this.selections.newest_anchor().clone();
7191 if newest_selection.head().diff_base_anchor.is_some() {
7192 return None;
7193 }
7194 let display_snapshot = this.display_snapshot(cx);
7195 let newest_selection_adjusted =
7196 this.selections.newest_adjusted(&display_snapshot);
7197 let buffer = this.buffer.read(cx);
7198
7199 let (start_buffer, start) =
7200 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7201 let (end_buffer, end) =
7202 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7203
7204 Some((start_buffer, start, end_buffer, end, newest_selection))
7205 })?
7206 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7207 .context(
7208 "Expected selection to lie in a single buffer when refreshing code actions",
7209 )?;
7210 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7211 let providers = this.code_action_providers.clone();
7212 let tasks = this
7213 .code_action_providers
7214 .iter()
7215 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7216 .collect::<Vec<_>>();
7217 (providers, tasks)
7218 })?;
7219
7220 let mut actions = Vec::new();
7221 for (provider, provider_actions) in
7222 providers.into_iter().zip(future::join_all(tasks).await)
7223 {
7224 if let Some(provider_actions) = provider_actions.log_err() {
7225 actions.extend(provider_actions.into_iter().map(|action| {
7226 AvailableCodeAction {
7227 excerpt_id: newest_selection.start.excerpt_id,
7228 action,
7229 provider: provider.clone(),
7230 }
7231 }));
7232 }
7233 }
7234
7235 this.update(cx, |this, cx| {
7236 this.available_code_actions = if actions.is_empty() {
7237 None
7238 } else {
7239 Some((
7240 Location {
7241 buffer: start_buffer,
7242 range: start..end,
7243 },
7244 actions.into(),
7245 ))
7246 };
7247 cx.notify();
7248 })
7249 }));
7250 }
7251
7252 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7253 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7254 self.show_git_blame_inline = false;
7255
7256 self.show_git_blame_inline_delay_task =
7257 Some(cx.spawn_in(window, async move |this, cx| {
7258 cx.background_executor().timer(delay).await;
7259
7260 this.update(cx, |this, cx| {
7261 this.show_git_blame_inline = true;
7262 cx.notify();
7263 })
7264 .log_err();
7265 }));
7266 }
7267 }
7268
7269 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7270 let snapshot = self.snapshot(window, cx);
7271 let cursor = self
7272 .selections
7273 .newest::<Point>(&snapshot.display_snapshot)
7274 .head();
7275 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7276 else {
7277 return;
7278 };
7279
7280 if self.blame.is_none() {
7281 self.start_git_blame(true, window, cx);
7282 }
7283 let Some(blame) = self.blame.as_ref() else {
7284 return;
7285 };
7286
7287 let row_info = RowInfo {
7288 buffer_id: Some(buffer.remote_id()),
7289 buffer_row: Some(point.row),
7290 ..Default::default()
7291 };
7292 let Some((buffer, blame_entry)) = blame
7293 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7294 .flatten()
7295 else {
7296 return;
7297 };
7298
7299 let anchor = self.selections.newest_anchor().head();
7300 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7301 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7302 self.show_blame_popover(
7303 buffer,
7304 &blame_entry,
7305 position + last_bounds.origin,
7306 true,
7307 cx,
7308 );
7309 };
7310 }
7311
7312 fn show_blame_popover(
7313 &mut self,
7314 buffer: BufferId,
7315 blame_entry: &BlameEntry,
7316 position: gpui::Point<Pixels>,
7317 ignore_timeout: bool,
7318 cx: &mut Context<Self>,
7319 ) {
7320 if let Some(state) = &mut self.inline_blame_popover {
7321 state.hide_task.take();
7322 } else {
7323 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7324 let blame_entry = blame_entry.clone();
7325 let show_task = cx.spawn(async move |editor, cx| {
7326 if !ignore_timeout {
7327 cx.background_executor()
7328 .timer(std::time::Duration::from_millis(blame_popover_delay))
7329 .await;
7330 }
7331 editor
7332 .update(cx, |editor, cx| {
7333 editor.inline_blame_popover_show_task.take();
7334 let Some(blame) = editor.blame.as_ref() else {
7335 return;
7336 };
7337 let blame = blame.read(cx);
7338 let details = blame.details_for_entry(buffer, &blame_entry);
7339 let markdown = cx.new(|cx| {
7340 Markdown::new(
7341 details
7342 .as_ref()
7343 .map(|message| message.message.clone())
7344 .unwrap_or_default(),
7345 None,
7346 None,
7347 cx,
7348 )
7349 });
7350 editor.inline_blame_popover = Some(InlineBlamePopover {
7351 position,
7352 hide_task: None,
7353 popover_bounds: None,
7354 popover_state: InlineBlamePopoverState {
7355 scroll_handle: ScrollHandle::new(),
7356 commit_message: details,
7357 markdown,
7358 },
7359 keyboard_grace: ignore_timeout,
7360 });
7361 cx.notify();
7362 })
7363 .ok();
7364 });
7365 self.inline_blame_popover_show_task = Some(show_task);
7366 }
7367 }
7368
7369 pub fn has_mouse_context_menu(&self) -> bool {
7370 self.mouse_context_menu.is_some()
7371 }
7372
7373 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7374 self.inline_blame_popover_show_task.take();
7375 if let Some(state) = &mut self.inline_blame_popover {
7376 let hide_task = cx.spawn(async move |editor, cx| {
7377 if !ignore_timeout {
7378 cx.background_executor()
7379 .timer(std::time::Duration::from_millis(100))
7380 .await;
7381 }
7382 editor
7383 .update(cx, |editor, cx| {
7384 editor.inline_blame_popover.take();
7385 cx.notify();
7386 })
7387 .ok();
7388 });
7389 state.hide_task = Some(hide_task);
7390 true
7391 } else {
7392 false
7393 }
7394 }
7395
7396 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7397 if self.pending_rename.is_some() {
7398 return None;
7399 }
7400
7401 let provider = self.semantics_provider.clone()?;
7402 let buffer = self.buffer.read(cx);
7403 let newest_selection = self.selections.newest_anchor().clone();
7404 let cursor_position = newest_selection.head();
7405 let (cursor_buffer, cursor_buffer_position) =
7406 buffer.text_anchor_for_position(cursor_position, cx)?;
7407 let (tail_buffer, tail_buffer_position) =
7408 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7409 if cursor_buffer != tail_buffer {
7410 return None;
7411 }
7412
7413 let snapshot = cursor_buffer.read(cx).snapshot();
7414 let word_ranges = cx.background_spawn(async move {
7415 // this might look odd to put on the background thread, but
7416 // `surrounding_word` can be quite expensive as it calls into
7417 // tree-sitter language scopes
7418 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7419 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7420 (start_word_range, end_word_range)
7421 });
7422
7423 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7424 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7425 let (start_word_range, end_word_range) = word_ranges.await;
7426 if start_word_range != end_word_range {
7427 this.update(cx, |this, cx| {
7428 this.document_highlights_task.take();
7429 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7430 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7431 })
7432 .ok();
7433 return;
7434 }
7435 cx.background_executor()
7436 .timer(Duration::from_millis(debounce))
7437 .await;
7438
7439 let highlights = if let Some(highlights) = cx.update(|cx| {
7440 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7441 }) {
7442 highlights.await.log_err()
7443 } else {
7444 None
7445 };
7446
7447 if let Some(highlights) = highlights {
7448 this.update(cx, |this, cx| {
7449 if this.pending_rename.is_some() {
7450 return;
7451 }
7452
7453 let buffer = this.buffer.read(cx);
7454 if buffer
7455 .text_anchor_for_position(cursor_position, cx)
7456 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7457 {
7458 return;
7459 }
7460
7461 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7462 let mut write_ranges = Vec::new();
7463 let mut read_ranges = Vec::new();
7464 for highlight in highlights {
7465 let buffer_id = cursor_buffer.read(cx).remote_id();
7466 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7467 {
7468 let start = highlight
7469 .range
7470 .start
7471 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7472 let end = highlight
7473 .range
7474 .end
7475 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7476 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7477 continue;
7478 }
7479
7480 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7481 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7482 write_ranges.push(range);
7483 } else {
7484 read_ranges.push(range);
7485 }
7486 }
7487 }
7488
7489 this.highlight_background(
7490 HighlightKey::DocumentHighlightRead,
7491 &read_ranges,
7492 |_, theme| theme.colors().editor_document_highlight_read_background,
7493 cx,
7494 );
7495 this.highlight_background(
7496 HighlightKey::DocumentHighlightWrite,
7497 &write_ranges,
7498 |_, theme| theme.colors().editor_document_highlight_write_background,
7499 cx,
7500 );
7501 cx.notify();
7502 })
7503 .log_err();
7504 }
7505 }));
7506 None
7507 }
7508
7509 fn prepare_highlight_query_from_selection(
7510 &mut self,
7511 snapshot: &DisplaySnapshot,
7512 cx: &mut Context<Editor>,
7513 ) -> Option<(String, Range<Anchor>)> {
7514 if matches!(self.mode, EditorMode::SingleLine) {
7515 return None;
7516 }
7517 if !EditorSettings::get_global(cx).selection_highlight {
7518 return None;
7519 }
7520 if self.selections.count() != 1 || self.selections.line_mode() {
7521 return None;
7522 }
7523 let selection = self.selections.newest::<Point>(&snapshot);
7524 // If the selection spans multiple rows OR it is empty
7525 if selection.start.row != selection.end.row
7526 || selection.start.column == selection.end.column
7527 {
7528 return None;
7529 }
7530 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7531 let query = snapshot
7532 .buffer_snapshot()
7533 .text_for_range(selection_anchor_range.clone())
7534 .collect::<String>();
7535 if query.trim().is_empty() {
7536 return None;
7537 }
7538 Some((query, selection_anchor_range))
7539 }
7540
7541 #[ztracing::instrument(skip_all)]
7542 fn update_selection_occurrence_highlights(
7543 &mut self,
7544 multi_buffer_snapshot: MultiBufferSnapshot,
7545 query_text: String,
7546 query_range: Range<Anchor>,
7547 multi_buffer_range_to_query: Range<Point>,
7548 use_debounce: bool,
7549 window: &mut Window,
7550 cx: &mut Context<Editor>,
7551 ) -> Task<()> {
7552 cx.spawn_in(window, async move |editor, cx| {
7553 if use_debounce {
7554 cx.background_executor()
7555 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7556 .await;
7557 }
7558 let match_task = cx.background_spawn(async move {
7559 let buffer_ranges = multi_buffer_snapshot
7560 .range_to_buffer_ranges(
7561 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7562 )
7563 .into_iter()
7564 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7565 let mut match_ranges = Vec::new();
7566 let Ok(regex) = project::search::SearchQuery::text(
7567 query_text,
7568 false,
7569 false,
7570 false,
7571 Default::default(),
7572 Default::default(),
7573 false,
7574 None,
7575 ) else {
7576 return Vec::default();
7577 };
7578 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7579 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7580 match_ranges.extend(
7581 regex
7582 .search(
7583 buffer_snapshot,
7584 Some(search_range.start.0..search_range.end.0),
7585 )
7586 .await
7587 .into_iter()
7588 .filter_map(|match_range| {
7589 let match_start = buffer_snapshot
7590 .anchor_after(search_range.start + match_range.start);
7591 let match_end = buffer_snapshot
7592 .anchor_before(search_range.start + match_range.end);
7593 let match_anchor_range =
7594 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7595 (match_anchor_range != query_range).then_some(match_anchor_range)
7596 }),
7597 );
7598 }
7599 match_ranges
7600 });
7601 let match_ranges = match_task.await;
7602 editor
7603 .update_in(cx, |editor, _, cx| {
7604 if use_debounce {
7605 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7606 editor.debounced_selection_highlight_complete = true;
7607 } else if editor.debounced_selection_highlight_complete {
7608 return;
7609 }
7610 if !match_ranges.is_empty() {
7611 editor.highlight_background(
7612 HighlightKey::SelectedTextHighlight,
7613 &match_ranges,
7614 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7615 cx,
7616 )
7617 }
7618 })
7619 .log_err();
7620 })
7621 }
7622
7623 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7624 struct NewlineFold;
7625 let type_id = std::any::TypeId::of::<NewlineFold>();
7626 if !self.mode.is_single_line() {
7627 return;
7628 }
7629 let snapshot = self.snapshot(window, cx);
7630 if snapshot.buffer_snapshot().max_point().row == 0 {
7631 return;
7632 }
7633 let task = cx.background_spawn(async move {
7634 let new_newlines = snapshot
7635 .buffer_chars_at(MultiBufferOffset(0))
7636 .filter_map(|(c, i)| {
7637 if c == '\n' {
7638 Some(
7639 snapshot.buffer_snapshot().anchor_after(i)
7640 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7641 )
7642 } else {
7643 None
7644 }
7645 })
7646 .collect::<Vec<_>>();
7647 let existing_newlines = snapshot
7648 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7649 .filter_map(|fold| {
7650 if fold.placeholder.type_tag == Some(type_id) {
7651 Some(fold.range.start..fold.range.end)
7652 } else {
7653 None
7654 }
7655 })
7656 .collect::<Vec<_>>();
7657
7658 (new_newlines, existing_newlines)
7659 });
7660 self.folding_newlines = cx.spawn(async move |this, cx| {
7661 let (new_newlines, existing_newlines) = task.await;
7662 if new_newlines == existing_newlines {
7663 return;
7664 }
7665 let placeholder = FoldPlaceholder {
7666 render: Arc::new(move |_, _, cx| {
7667 div()
7668 .bg(cx.theme().status().hint_background)
7669 .border_b_1()
7670 .size_full()
7671 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7672 .border_color(cx.theme().status().hint)
7673 .child("\\n")
7674 .into_any()
7675 }),
7676 constrain_width: false,
7677 merge_adjacent: false,
7678 type_tag: Some(type_id),
7679 collapsed_text: None,
7680 };
7681 let creases = new_newlines
7682 .into_iter()
7683 .map(|range| Crease::simple(range, placeholder.clone()))
7684 .collect();
7685 this.update(cx, |this, cx| {
7686 this.display_map.update(cx, |display_map, cx| {
7687 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7688 display_map.fold(creases, cx);
7689 });
7690 })
7691 .ok();
7692 });
7693 }
7694
7695 #[ztracing::instrument(skip_all)]
7696 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7697 if !self.mode.is_full() {
7698 return;
7699 }
7700 let cursor = self.selections.newest_anchor().head();
7701 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7702
7703 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7704 self.outline_symbols_at_cursor =
7705 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7706 cx.emit(EditorEvent::OutlineSymbolsChanged);
7707 cx.notify();
7708 } else {
7709 let syntax = cx.theme().syntax().clone();
7710 let background_task = cx.background_spawn(async move {
7711 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7712 });
7713 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7714 cx.spawn(async move |this, cx| {
7715 let symbols = background_task.await;
7716 this.update(cx, |this, cx| {
7717 this.outline_symbols_at_cursor = symbols;
7718 cx.emit(EditorEvent::OutlineSymbolsChanged);
7719 cx.notify();
7720 })
7721 .ok();
7722 });
7723 }
7724 }
7725
7726 #[ztracing::instrument(skip_all)]
7727 fn refresh_selected_text_highlights(
7728 &mut self,
7729 snapshot: &DisplaySnapshot,
7730 on_buffer_edit: bool,
7731 window: &mut Window,
7732 cx: &mut Context<Editor>,
7733 ) {
7734 let Some((query_text, query_range)) =
7735 self.prepare_highlight_query_from_selection(snapshot, cx)
7736 else {
7737 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7738 self.quick_selection_highlight_task.take();
7739 self.debounced_selection_highlight_task.take();
7740 self.debounced_selection_highlight_complete = false;
7741 return;
7742 };
7743 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7744 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7745 let query_changed = self
7746 .quick_selection_highlight_task
7747 .as_ref()
7748 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7749 if query_changed {
7750 self.debounced_selection_highlight_complete = false;
7751 }
7752 if on_buffer_edit || query_changed {
7753 let multi_buffer_visible_start = self
7754 .scroll_manager
7755 .native_anchor(&display_snapshot, cx)
7756 .anchor
7757 .to_point(&multi_buffer_snapshot);
7758 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7759 multi_buffer_visible_start
7760 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7761 Bias::Left,
7762 );
7763 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7764 self.quick_selection_highlight_task = Some((
7765 query_range.clone(),
7766 self.update_selection_occurrence_highlights(
7767 snapshot.buffer.clone(),
7768 query_text.clone(),
7769 query_range.clone(),
7770 multi_buffer_visible_range,
7771 false,
7772 window,
7773 cx,
7774 ),
7775 ));
7776 }
7777 if on_buffer_edit
7778 || self
7779 .debounced_selection_highlight_task
7780 .as_ref()
7781 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7782 {
7783 let multi_buffer_start = multi_buffer_snapshot
7784 .anchor_before(MultiBufferOffset(0))
7785 .to_point(&multi_buffer_snapshot);
7786 let multi_buffer_end = multi_buffer_snapshot
7787 .anchor_after(multi_buffer_snapshot.len())
7788 .to_point(&multi_buffer_snapshot);
7789 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7790 self.debounced_selection_highlight_task = Some((
7791 query_range.clone(),
7792 self.update_selection_occurrence_highlights(
7793 snapshot.buffer.clone(),
7794 query_text,
7795 query_range,
7796 multi_buffer_full_range,
7797 true,
7798 window,
7799 cx,
7800 ),
7801 ));
7802 }
7803 }
7804
7805 pub fn refresh_edit_prediction(
7806 &mut self,
7807 debounce: bool,
7808 user_requested: bool,
7809 window: &mut Window,
7810 cx: &mut Context<Self>,
7811 ) -> Option<()> {
7812 let provider = self.edit_prediction_provider()?;
7813 let cursor = self.selections.newest_anchor().head();
7814 let (buffer, cursor_buffer_position) =
7815 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7816
7817 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7818 return None;
7819 }
7820
7821 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7822 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7823 return None;
7824 }
7825
7826 self.update_visible_edit_prediction(window, cx);
7827
7828 if !user_requested
7829 && (!self.should_show_edit_predictions()
7830 || !self.is_focused(window)
7831 || buffer.read(cx).is_empty())
7832 {
7833 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7834 return None;
7835 }
7836
7837 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7838 Some(())
7839 }
7840
7841 fn show_edit_predictions_in_menu(&self) -> bool {
7842 match self.edit_prediction_settings {
7843 EditPredictionSettings::Disabled => false,
7844 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7845 }
7846 }
7847
7848 pub fn edit_predictions_enabled(&self) -> bool {
7849 match self.edit_prediction_settings {
7850 EditPredictionSettings::Disabled => false,
7851 EditPredictionSettings::Enabled { .. } => true,
7852 }
7853 }
7854
7855 fn edit_prediction_requires_modifier(&self) -> bool {
7856 match self.edit_prediction_settings {
7857 EditPredictionSettings::Disabled => false,
7858 EditPredictionSettings::Enabled {
7859 preview_requires_modifier,
7860 ..
7861 } => preview_requires_modifier,
7862 }
7863 }
7864
7865 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7866 if self.edit_prediction_provider.is_none() {
7867 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7868 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7869 return;
7870 }
7871
7872 let selection = self.selections.newest_anchor();
7873 let cursor = selection.head();
7874
7875 if let Some((buffer, cursor_buffer_position)) =
7876 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7877 {
7878 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7879 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7880 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7881 return;
7882 }
7883 self.edit_prediction_settings =
7884 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7885 }
7886 }
7887
7888 fn edit_prediction_settings_at_position(
7889 &self,
7890 buffer: &Entity<Buffer>,
7891 buffer_position: language::Anchor,
7892 cx: &App,
7893 ) -> EditPredictionSettings {
7894 if !self.mode.is_full()
7895 || !self.show_edit_predictions_override.unwrap_or(true)
7896 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7897 {
7898 return EditPredictionSettings::Disabled;
7899 }
7900
7901 let buffer = buffer.read(cx);
7902
7903 let file = buffer.file();
7904
7905 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7906 return EditPredictionSettings::Disabled;
7907 };
7908
7909 let by_provider = matches!(
7910 self.menu_edit_predictions_policy,
7911 MenuEditPredictionsPolicy::ByProvider
7912 );
7913
7914 let show_in_menu = by_provider
7915 && self
7916 .edit_prediction_provider
7917 .as_ref()
7918 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7919
7920 let preview_requires_modifier =
7921 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7922
7923 EditPredictionSettings::Enabled {
7924 show_in_menu,
7925 preview_requires_modifier,
7926 }
7927 }
7928
7929 fn should_show_edit_predictions(&self) -> bool {
7930 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7931 }
7932
7933 pub fn edit_prediction_preview_is_active(&self) -> bool {
7934 matches!(
7935 self.edit_prediction_preview,
7936 EditPredictionPreview::Active { .. }
7937 )
7938 }
7939
7940 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7941 let cursor = self.selections.newest_anchor().head();
7942 if let Some((buffer, cursor_position)) =
7943 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7944 {
7945 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7946 } else {
7947 false
7948 }
7949 }
7950
7951 pub fn supports_minimap(&self, cx: &App) -> bool {
7952 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7953 }
7954
7955 fn edit_predictions_enabled_in_buffer(
7956 &self,
7957 buffer: &Entity<Buffer>,
7958 buffer_position: language::Anchor,
7959 cx: &App,
7960 ) -> bool {
7961 maybe!({
7962 if self.read_only(cx) {
7963 return Some(false);
7964 }
7965 let provider = self.edit_prediction_provider()?;
7966 if !provider.is_enabled(buffer, buffer_position, cx) {
7967 return Some(false);
7968 }
7969 let buffer = buffer.read(cx);
7970 let Some(file) = buffer.file() else {
7971 return Some(true);
7972 };
7973 let settings = all_language_settings(Some(file), cx);
7974 Some(settings.edit_predictions_enabled_for_file(file, cx))
7975 })
7976 .unwrap_or(false)
7977 }
7978
7979 pub fn show_edit_prediction(
7980 &mut self,
7981 _: &ShowEditPrediction,
7982 window: &mut Window,
7983 cx: &mut Context<Self>,
7984 ) {
7985 if !self.has_active_edit_prediction() {
7986 self.refresh_edit_prediction(false, true, window, cx);
7987 return;
7988 }
7989
7990 self.update_visible_edit_prediction(window, cx);
7991 }
7992
7993 pub fn display_cursor_names(
7994 &mut self,
7995 _: &DisplayCursorNames,
7996 window: &mut Window,
7997 cx: &mut Context<Self>,
7998 ) {
7999 self.show_cursor_names(window, cx);
8000 }
8001
8002 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8003 self.show_cursor_names = true;
8004 cx.notify();
8005 cx.spawn_in(window, async move |this, cx| {
8006 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8007 this.update(cx, |this, cx| {
8008 this.show_cursor_names = false;
8009 cx.notify()
8010 })
8011 .ok()
8012 })
8013 .detach();
8014 }
8015
8016 pub fn accept_partial_edit_prediction(
8017 &mut self,
8018 granularity: EditPredictionGranularity,
8019 window: &mut Window,
8020 cx: &mut Context<Self>,
8021 ) {
8022 if self.show_edit_predictions_in_menu() {
8023 self.hide_context_menu(window, cx);
8024 }
8025
8026 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8027 return;
8028 };
8029
8030 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8031 return;
8032 }
8033
8034 match &active_edit_prediction.completion {
8035 EditPrediction::MoveWithin { target, .. } => {
8036 let target = *target;
8037
8038 if matches!(granularity, EditPredictionGranularity::Full) {
8039 if let Some(position_map) = &self.last_position_map {
8040 let target_row = target.to_display_point(&position_map.snapshot).row();
8041 let is_visible = position_map.visible_row_range.contains(&target_row);
8042
8043 if is_visible || !self.edit_prediction_requires_modifier() {
8044 self.unfold_ranges(&[target..target], true, false, cx);
8045 self.change_selections(
8046 SelectionEffects::scroll(Autoscroll::newest()),
8047 window,
8048 cx,
8049 |selections| {
8050 selections.select_anchor_ranges([target..target]);
8051 },
8052 );
8053 self.clear_row_highlights::<EditPredictionPreview>();
8054 self.edit_prediction_preview
8055 .set_previous_scroll_position(None);
8056 } else {
8057 // Highlight and request scroll
8058 self.edit_prediction_preview
8059 .set_previous_scroll_position(Some(
8060 position_map.snapshot.scroll_anchor,
8061 ));
8062 self.highlight_rows::<EditPredictionPreview>(
8063 target..target,
8064 cx.theme().colors().editor_highlighted_line_background,
8065 RowHighlightOptions {
8066 autoscroll: true,
8067 ..Default::default()
8068 },
8069 cx,
8070 );
8071 self.request_autoscroll(Autoscroll::fit(), cx);
8072 }
8073 }
8074 } else {
8075 self.change_selections(
8076 SelectionEffects::scroll(Autoscroll::newest()),
8077 window,
8078 cx,
8079 |selections| {
8080 selections.select_anchor_ranges([target..target]);
8081 },
8082 );
8083 }
8084 }
8085 EditPrediction::MoveOutside { snapshot, target } => {
8086 if let Some(workspace) = self.workspace() {
8087 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8088 .detach_and_log_err(cx);
8089 }
8090 }
8091 EditPrediction::Edit {
8092 edits,
8093 cursor_position,
8094 ..
8095 } => {
8096 self.report_edit_prediction_event(
8097 active_edit_prediction.completion_id.clone(),
8098 true,
8099 cx,
8100 );
8101
8102 match granularity {
8103 EditPredictionGranularity::Full => {
8104 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8105
8106 // Compute fallback cursor position BEFORE applying the edit,
8107 // so the anchor tracks through the edit correctly
8108 let fallback_cursor_target = {
8109 let snapshot = self.buffer.read(cx).snapshot(cx);
8110 edits.last().unwrap().0.end.bias_right(&snapshot)
8111 };
8112
8113 self.buffer.update(cx, |buffer, cx| {
8114 buffer.edit(edits.iter().cloned(), None, cx)
8115 });
8116
8117 if let Some(provider) = self.edit_prediction_provider() {
8118 provider.accept(cx);
8119 }
8120
8121 // Resolve cursor position after the edit is applied
8122 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8123 // The anchor tracks through the edit, then we add the offset
8124 let snapshot = self.buffer.read(cx).snapshot(cx);
8125 let base_offset = anchor.to_offset(&snapshot).0;
8126 let target_offset =
8127 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8128 snapshot.anchor_after(target_offset)
8129 } else {
8130 fallback_cursor_target
8131 };
8132
8133 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8134 s.select_anchor_ranges([cursor_target..cursor_target]);
8135 });
8136
8137 let selections = self.selections.disjoint_anchors_arc();
8138 if let Some(transaction_id_now) =
8139 self.buffer.read(cx).last_transaction_id(cx)
8140 {
8141 if transaction_id_prev != Some(transaction_id_now) {
8142 self.selection_history
8143 .insert_transaction(transaction_id_now, selections);
8144 }
8145 }
8146
8147 self.update_visible_edit_prediction(window, cx);
8148 if self.active_edit_prediction.is_none() {
8149 self.refresh_edit_prediction(true, true, window, cx);
8150 }
8151 cx.notify();
8152 }
8153 _ => {
8154 let snapshot = self.buffer.read(cx).snapshot(cx);
8155 let cursor_offset = self
8156 .selections
8157 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8158 .head();
8159
8160 let insertion = edits.iter().find_map(|(range, text)| {
8161 let range = range.to_offset(&snapshot);
8162 if range.is_empty() && range.start == cursor_offset {
8163 Some(text)
8164 } else {
8165 None
8166 }
8167 });
8168
8169 if let Some(text) = insertion {
8170 let text_to_insert = match granularity {
8171 EditPredictionGranularity::Word => {
8172 let mut partial = text
8173 .chars()
8174 .by_ref()
8175 .take_while(|c| c.is_alphabetic())
8176 .collect::<String>();
8177 if partial.is_empty() {
8178 partial = text
8179 .chars()
8180 .by_ref()
8181 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8182 .collect::<String>();
8183 }
8184 partial
8185 }
8186 EditPredictionGranularity::Line => {
8187 if let Some(line) = text.split_inclusive('\n').next() {
8188 line.to_string()
8189 } else {
8190 text.to_string()
8191 }
8192 }
8193 EditPredictionGranularity::Full => unreachable!(),
8194 };
8195
8196 cx.emit(EditorEvent::InputHandled {
8197 utf16_range_to_replace: None,
8198 text: text_to_insert.clone().into(),
8199 });
8200
8201 self.replace_selections(&text_to_insert, None, window, cx, false);
8202 self.refresh_edit_prediction(true, true, window, cx);
8203 cx.notify();
8204 } else {
8205 self.accept_partial_edit_prediction(
8206 EditPredictionGranularity::Full,
8207 window,
8208 cx,
8209 );
8210 }
8211 }
8212 }
8213 }
8214 }
8215
8216 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8217 }
8218
8219 pub fn accept_next_word_edit_prediction(
8220 &mut self,
8221 _: &AcceptNextWordEditPrediction,
8222 window: &mut Window,
8223 cx: &mut Context<Self>,
8224 ) {
8225 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8226 }
8227
8228 pub fn accept_next_line_edit_prediction(
8229 &mut self,
8230 _: &AcceptNextLineEditPrediction,
8231 window: &mut Window,
8232 cx: &mut Context<Self>,
8233 ) {
8234 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8235 }
8236
8237 pub fn accept_edit_prediction(
8238 &mut self,
8239 _: &AcceptEditPrediction,
8240 window: &mut Window,
8241 cx: &mut Context<Self>,
8242 ) {
8243 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8244 }
8245
8246 fn discard_edit_prediction(
8247 &mut self,
8248 reason: EditPredictionDiscardReason,
8249 cx: &mut Context<Self>,
8250 ) -> bool {
8251 if reason == EditPredictionDiscardReason::Rejected {
8252 let completion_id = self
8253 .active_edit_prediction
8254 .as_ref()
8255 .and_then(|active_completion| active_completion.completion_id.clone());
8256
8257 self.report_edit_prediction_event(completion_id, false, cx);
8258 }
8259
8260 if let Some(provider) = self.edit_prediction_provider() {
8261 provider.discard(reason, cx);
8262 }
8263
8264 self.take_active_edit_prediction(cx)
8265 }
8266
8267 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8268 let Some(provider) = self.edit_prediction_provider() else {
8269 return;
8270 };
8271
8272 let Some((_, buffer, _)) = self
8273 .buffer
8274 .read(cx)
8275 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8276 else {
8277 return;
8278 };
8279
8280 let extension = buffer
8281 .read(cx)
8282 .file()
8283 .and_then(|file| Some(file.path().extension()?.to_string()));
8284
8285 let event_type = match accepted {
8286 true => "Edit Prediction Accepted",
8287 false => "Edit Prediction Discarded",
8288 };
8289 telemetry::event!(
8290 event_type,
8291 provider = provider.name(),
8292 prediction_id = id,
8293 suggestion_accepted = accepted,
8294 file_extension = extension,
8295 );
8296 }
8297
8298 fn open_editor_at_anchor(
8299 snapshot: &language::BufferSnapshot,
8300 target: language::Anchor,
8301 workspace: &Entity<Workspace>,
8302 window: &mut Window,
8303 cx: &mut App,
8304 ) -> Task<Result<()>> {
8305 workspace.update(cx, |workspace, cx| {
8306 let path = snapshot.file().map(|file| file.full_path(cx));
8307 let Some(path) =
8308 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8309 else {
8310 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8311 };
8312 let target = text::ToPoint::to_point(&target, snapshot);
8313 let item = workspace.open_path(path, None, true, window, cx);
8314 window.spawn(cx, async move |cx| {
8315 let Some(editor) = item.await?.downcast::<Editor>() else {
8316 return Ok(());
8317 };
8318 editor
8319 .update_in(cx, |editor, window, cx| {
8320 editor.go_to_singleton_buffer_point(target, window, cx);
8321 })
8322 .ok();
8323 anyhow::Ok(())
8324 })
8325 })
8326 }
8327
8328 pub fn has_active_edit_prediction(&self) -> bool {
8329 self.active_edit_prediction.is_some()
8330 }
8331
8332 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8333 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8334 return false;
8335 };
8336
8337 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8338 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8339 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8340 true
8341 }
8342
8343 /// Returns true when we're displaying the edit prediction popover below the cursor
8344 /// like we are not previewing and the LSP autocomplete menu is visible
8345 /// or we are in `when_holding_modifier` mode.
8346 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8347 if self.edit_prediction_preview_is_active()
8348 || !self.show_edit_predictions_in_menu()
8349 || !self.edit_predictions_enabled()
8350 {
8351 return false;
8352 }
8353
8354 if self.has_visible_completions_menu() {
8355 return true;
8356 }
8357
8358 has_completion && self.edit_prediction_requires_modifier()
8359 }
8360
8361 fn handle_modifiers_changed(
8362 &mut self,
8363 modifiers: Modifiers,
8364 position_map: &PositionMap,
8365 window: &mut Window,
8366 cx: &mut Context<Self>,
8367 ) {
8368 // Ensure that the edit prediction preview is updated, even when not
8369 // enabled, if there's an active edit prediction preview.
8370 if self.show_edit_predictions_in_menu()
8371 || matches!(
8372 self.edit_prediction_preview,
8373 EditPredictionPreview::Active { .. }
8374 )
8375 {
8376 self.update_edit_prediction_preview(&modifiers, window, cx);
8377 }
8378
8379 self.update_selection_mode(&modifiers, position_map, window, cx);
8380
8381 let mouse_position = window.mouse_position();
8382 if !position_map.text_hitbox.is_hovered(window) {
8383 return;
8384 }
8385
8386 self.update_hovered_link(
8387 position_map.point_for_position(mouse_position),
8388 &position_map.snapshot,
8389 modifiers,
8390 window,
8391 cx,
8392 )
8393 }
8394
8395 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8396 match EditorSettings::get_global(cx).multi_cursor_modifier {
8397 MultiCursorModifier::Alt => modifiers.secondary(),
8398 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8399 }
8400 }
8401
8402 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8403 match EditorSettings::get_global(cx).multi_cursor_modifier {
8404 MultiCursorModifier::Alt => modifiers.alt,
8405 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8406 }
8407 }
8408
8409 fn columnar_selection_mode(
8410 modifiers: &Modifiers,
8411 cx: &mut Context<Self>,
8412 ) -> Option<ColumnarMode> {
8413 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8414 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8415 Some(ColumnarMode::FromMouse)
8416 } else if Self::is_alt_pressed(modifiers, cx) {
8417 Some(ColumnarMode::FromSelection)
8418 } else {
8419 None
8420 }
8421 } else {
8422 None
8423 }
8424 }
8425
8426 fn update_selection_mode(
8427 &mut self,
8428 modifiers: &Modifiers,
8429 position_map: &PositionMap,
8430 window: &mut Window,
8431 cx: &mut Context<Self>,
8432 ) {
8433 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8434 return;
8435 };
8436 if self.selections.pending_anchor().is_none() {
8437 return;
8438 }
8439
8440 let mouse_position = window.mouse_position();
8441 let point_for_position = position_map.point_for_position(mouse_position);
8442 let position = point_for_position.previous_valid;
8443
8444 self.select(
8445 SelectPhase::BeginColumnar {
8446 position,
8447 reset: false,
8448 mode,
8449 goal_column: point_for_position.exact_unclipped.column(),
8450 },
8451 window,
8452 cx,
8453 );
8454 }
8455
8456 fn update_edit_prediction_preview(
8457 &mut self,
8458 modifiers: &Modifiers,
8459 window: &mut Window,
8460 cx: &mut Context<Self>,
8461 ) {
8462 let mut modifiers_held = false;
8463
8464 // Check bindings for all granularities.
8465 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8466 let granularities = [
8467 EditPredictionGranularity::Full,
8468 EditPredictionGranularity::Line,
8469 EditPredictionGranularity::Word,
8470 ];
8471
8472 for granularity in granularities {
8473 if let Some(keystroke) = self
8474 .accept_edit_prediction_keybind(granularity, window, cx)
8475 .keystroke()
8476 {
8477 modifiers_held = modifiers_held
8478 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8479 }
8480 }
8481
8482 if modifiers_held {
8483 if matches!(
8484 self.edit_prediction_preview,
8485 EditPredictionPreview::Inactive { .. }
8486 ) {
8487 self.edit_prediction_preview = EditPredictionPreview::Active {
8488 previous_scroll_position: None,
8489 since: Instant::now(),
8490 };
8491
8492 self.update_visible_edit_prediction(window, cx);
8493 cx.notify();
8494 }
8495 } else if let EditPredictionPreview::Active {
8496 previous_scroll_position,
8497 since,
8498 } = self.edit_prediction_preview
8499 {
8500 if let (Some(previous_scroll_position), Some(position_map)) =
8501 (previous_scroll_position, self.last_position_map.as_ref())
8502 {
8503 self.set_scroll_position(
8504 previous_scroll_position
8505 .scroll_position(&position_map.snapshot.display_snapshot),
8506 window,
8507 cx,
8508 );
8509 }
8510
8511 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8512 released_too_fast: since.elapsed() < Duration::from_millis(200),
8513 };
8514 self.clear_row_highlights::<EditPredictionPreview>();
8515 self.update_visible_edit_prediction(window, cx);
8516 cx.notify();
8517 }
8518 }
8519
8520 fn update_visible_edit_prediction(
8521 &mut self,
8522 _window: &mut Window,
8523 cx: &mut Context<Self>,
8524 ) -> Option<()> {
8525 if self.ime_transaction.is_some() {
8526 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8527 return None;
8528 }
8529
8530 let selection = self.selections.newest_anchor();
8531 let cursor = selection.head();
8532 let multibuffer = self.buffer.read(cx).snapshot(cx);
8533
8534 // Check project-level disable_ai setting for the current buffer
8535 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8536 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8537 return None;
8538 }
8539 }
8540 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8541 let excerpt_id = cursor.excerpt_id;
8542
8543 let show_in_menu = self.show_edit_predictions_in_menu();
8544 let completions_menu_has_precedence = !show_in_menu
8545 && (self.context_menu.borrow().is_some()
8546 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8547
8548 if completions_menu_has_precedence
8549 || !offset_selection.is_empty()
8550 || self
8551 .active_edit_prediction
8552 .as_ref()
8553 .is_some_and(|completion| {
8554 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8555 return false;
8556 };
8557 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8558 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8559 !invalidation_range.contains(&offset_selection.head())
8560 })
8561 {
8562 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8563 return None;
8564 }
8565
8566 self.take_active_edit_prediction(cx);
8567 let Some(provider) = self.edit_prediction_provider() else {
8568 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8569 return None;
8570 };
8571
8572 let (buffer, cursor_buffer_position) =
8573 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8574
8575 self.edit_prediction_settings =
8576 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8577
8578 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8579
8580 if self.edit_prediction_indent_conflict {
8581 let cursor_point = cursor.to_point(&multibuffer);
8582 let mut suggested_indent = None;
8583 multibuffer.suggested_indents_callback(
8584 cursor_point.row..cursor_point.row + 1,
8585 &mut |_, indent| {
8586 suggested_indent = Some(indent);
8587 ControlFlow::Break(())
8588 },
8589 cx,
8590 );
8591
8592 if let Some(indent) = suggested_indent
8593 && indent.len == cursor_point.column
8594 {
8595 self.edit_prediction_indent_conflict = false;
8596 }
8597 }
8598
8599 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8600
8601 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8602 {
8603 edit_prediction_types::EditPrediction::Local {
8604 id,
8605 edits,
8606 cursor_position,
8607 edit_preview,
8608 } => (id, edits, cursor_position, edit_preview),
8609 edit_prediction_types::EditPrediction::Jump {
8610 id,
8611 snapshot,
8612 target,
8613 } => {
8614 if let Some(provider) = &self.edit_prediction_provider {
8615 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8616 }
8617 self.stale_edit_prediction_in_menu = None;
8618 self.active_edit_prediction = Some(EditPredictionState {
8619 inlay_ids: vec![],
8620 completion: EditPrediction::MoveOutside { snapshot, target },
8621 completion_id: id,
8622 invalidation_range: None,
8623 });
8624 cx.notify();
8625 return Some(());
8626 }
8627 };
8628
8629 let edits = edits
8630 .into_iter()
8631 .flat_map(|(range, new_text)| {
8632 Some((
8633 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8634 new_text,
8635 ))
8636 })
8637 .collect::<Vec<_>>();
8638 if edits.is_empty() {
8639 return None;
8640 }
8641
8642 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8643 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8644 Some((anchor, predicted.offset))
8645 });
8646
8647 let first_edit_start = edits.first().unwrap().0.start;
8648 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8649 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8650
8651 let last_edit_end = edits.last().unwrap().0.end;
8652 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8653 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8654
8655 let cursor_row = cursor.to_point(&multibuffer).row;
8656
8657 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8658
8659 let mut inlay_ids = Vec::new();
8660 let invalidation_row_range;
8661 let move_invalidation_row_range = if cursor_row < edit_start_row {
8662 Some(cursor_row..edit_end_row)
8663 } else if cursor_row > edit_end_row {
8664 Some(edit_start_row..cursor_row)
8665 } else {
8666 None
8667 };
8668 let supports_jump = self
8669 .edit_prediction_provider
8670 .as_ref()
8671 .map(|provider| provider.provider.supports_jump_to_edit())
8672 .unwrap_or(true);
8673
8674 let is_move = supports_jump
8675 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8676 let completion = if is_move {
8677 if let Some(provider) = &self.edit_prediction_provider {
8678 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8679 }
8680 invalidation_row_range =
8681 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8682 let target = first_edit_start;
8683 EditPrediction::MoveWithin { target, snapshot }
8684 } else {
8685 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8686 && !self.edit_predictions_hidden_for_vim_mode;
8687
8688 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8689 if provider.show_tab_accept_marker() {
8690 EditDisplayMode::TabAccept
8691 } else {
8692 EditDisplayMode::Inline
8693 }
8694 } else {
8695 EditDisplayMode::DiffPopover
8696 };
8697
8698 if show_completions_in_buffer {
8699 if let Some(provider) = &self.edit_prediction_provider {
8700 let suggestion_display_type = match display_mode {
8701 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8702 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8703 SuggestionDisplayType::GhostText
8704 }
8705 };
8706 provider.provider.did_show(suggestion_display_type, cx);
8707 }
8708 if edits
8709 .iter()
8710 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8711 {
8712 let mut inlays = Vec::new();
8713 for (range, new_text) in &edits {
8714 let inlay = Inlay::edit_prediction(
8715 post_inc(&mut self.next_inlay_id),
8716 range.start,
8717 new_text.as_ref(),
8718 );
8719 inlay_ids.push(inlay.id);
8720 inlays.push(inlay);
8721 }
8722
8723 self.splice_inlays(&[], inlays, cx);
8724 } else {
8725 let background_color = cx.theme().status().deleted_background;
8726 self.highlight_text(
8727 HighlightKey::EditPredictionHighlight,
8728 edits.iter().map(|(range, _)| range.clone()).collect(),
8729 HighlightStyle {
8730 background_color: Some(background_color),
8731 ..Default::default()
8732 },
8733 cx,
8734 );
8735 }
8736 }
8737
8738 invalidation_row_range = edit_start_row..edit_end_row;
8739
8740 EditPrediction::Edit {
8741 edits,
8742 cursor_position,
8743 edit_preview,
8744 display_mode,
8745 snapshot,
8746 }
8747 };
8748
8749 let invalidation_range = multibuffer
8750 .anchor_before(Point::new(invalidation_row_range.start, 0))
8751 ..multibuffer.anchor_after(Point::new(
8752 invalidation_row_range.end,
8753 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8754 ));
8755
8756 self.stale_edit_prediction_in_menu = None;
8757 self.active_edit_prediction = Some(EditPredictionState {
8758 inlay_ids,
8759 completion,
8760 completion_id,
8761 invalidation_range: Some(invalidation_range),
8762 });
8763
8764 cx.notify();
8765
8766 Some(())
8767 }
8768
8769 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8770 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8771 }
8772
8773 fn clear_tasks(&mut self) {
8774 self.tasks.clear()
8775 }
8776
8777 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8778 if self.tasks.insert(key, value).is_some() {
8779 // This case should hopefully be rare, but just in case...
8780 log::error!(
8781 "multiple different run targets found on a single line, only the last target will be rendered"
8782 )
8783 }
8784 }
8785
8786 /// Get all display points of breakpoints that will be rendered within editor
8787 ///
8788 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8789 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8790 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8791 fn active_breakpoints(
8792 &self,
8793 range: Range<DisplayRow>,
8794 window: &mut Window,
8795 cx: &mut Context<Self>,
8796 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8797 let mut breakpoint_display_points = HashMap::default();
8798
8799 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8800 return breakpoint_display_points;
8801 };
8802
8803 let snapshot = self.snapshot(window, cx);
8804
8805 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8806 let Some(project) = self.project() else {
8807 return breakpoint_display_points;
8808 };
8809
8810 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8811 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8812
8813 for (buffer_snapshot, range, excerpt_id) in
8814 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8815 {
8816 let Some(buffer) = project
8817 .read(cx)
8818 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8819 else {
8820 continue;
8821 };
8822 let breakpoints = breakpoint_store.read(cx).breakpoints(
8823 &buffer,
8824 Some(
8825 buffer_snapshot.anchor_before(range.start)
8826 ..buffer_snapshot.anchor_after(range.end),
8827 ),
8828 buffer_snapshot,
8829 cx,
8830 );
8831 for (breakpoint, state) in breakpoints {
8832 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8833 let position = multi_buffer_anchor
8834 .to_point(&multi_buffer_snapshot)
8835 .to_display_point(&snapshot);
8836
8837 breakpoint_display_points.insert(
8838 position.row(),
8839 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8840 );
8841 }
8842 }
8843
8844 breakpoint_display_points
8845 }
8846
8847 fn breakpoint_context_menu(
8848 &self,
8849 anchor: Anchor,
8850 window: &mut Window,
8851 cx: &mut Context<Self>,
8852 ) -> Entity<ui::ContextMenu> {
8853 let weak_editor = cx.weak_entity();
8854 let focus_handle = self.focus_handle(cx);
8855
8856 let row = self
8857 .buffer
8858 .read(cx)
8859 .snapshot(cx)
8860 .summary_for_anchor::<Point>(&anchor)
8861 .row;
8862
8863 let breakpoint = self
8864 .breakpoint_at_row(row, window, cx)
8865 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8866
8867 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8868 "Edit Log Breakpoint"
8869 } else {
8870 "Set Log Breakpoint"
8871 };
8872
8873 let condition_breakpoint_msg = if breakpoint
8874 .as_ref()
8875 .is_some_and(|bp| bp.1.condition.is_some())
8876 {
8877 "Edit Condition Breakpoint"
8878 } else {
8879 "Set Condition Breakpoint"
8880 };
8881
8882 let hit_condition_breakpoint_msg = if breakpoint
8883 .as_ref()
8884 .is_some_and(|bp| bp.1.hit_condition.is_some())
8885 {
8886 "Edit Hit Condition Breakpoint"
8887 } else {
8888 "Set Hit Condition Breakpoint"
8889 };
8890
8891 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8892 "Unset Breakpoint"
8893 } else {
8894 "Set Breakpoint"
8895 };
8896
8897 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8898
8899 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8900 BreakpointState::Enabled => Some("Disable"),
8901 BreakpointState::Disabled => Some("Enable"),
8902 });
8903
8904 let (anchor, breakpoint) =
8905 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8906
8907 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8908 menu.on_blur_subscription(Subscription::new(|| {}))
8909 .context(focus_handle)
8910 .when(run_to_cursor, |this| {
8911 let weak_editor = weak_editor.clone();
8912 this.entry("Run to Cursor", None, move |window, cx| {
8913 weak_editor
8914 .update(cx, |editor, cx| {
8915 editor.change_selections(
8916 SelectionEffects::no_scroll(),
8917 window,
8918 cx,
8919 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8920 );
8921 })
8922 .ok();
8923
8924 window.dispatch_action(Box::new(RunToCursor), cx);
8925 })
8926 .separator()
8927 })
8928 .when_some(toggle_state_msg, |this, msg| {
8929 this.entry(msg, None, {
8930 let weak_editor = weak_editor.clone();
8931 let breakpoint = breakpoint.clone();
8932 move |_window, cx| {
8933 weak_editor
8934 .update(cx, |this, cx| {
8935 this.edit_breakpoint_at_anchor(
8936 anchor,
8937 breakpoint.as_ref().clone(),
8938 BreakpointEditAction::InvertState,
8939 cx,
8940 );
8941 })
8942 .log_err();
8943 }
8944 })
8945 })
8946 .entry(set_breakpoint_msg, None, {
8947 let weak_editor = weak_editor.clone();
8948 let breakpoint = breakpoint.clone();
8949 move |_window, cx| {
8950 weak_editor
8951 .update(cx, |this, cx| {
8952 this.edit_breakpoint_at_anchor(
8953 anchor,
8954 breakpoint.as_ref().clone(),
8955 BreakpointEditAction::Toggle,
8956 cx,
8957 );
8958 })
8959 .log_err();
8960 }
8961 })
8962 .entry(log_breakpoint_msg, None, {
8963 let breakpoint = breakpoint.clone();
8964 let weak_editor = weak_editor.clone();
8965 move |window, cx| {
8966 weak_editor
8967 .update(cx, |this, cx| {
8968 this.add_edit_breakpoint_block(
8969 anchor,
8970 breakpoint.as_ref(),
8971 BreakpointPromptEditAction::Log,
8972 window,
8973 cx,
8974 );
8975 })
8976 .log_err();
8977 }
8978 })
8979 .entry(condition_breakpoint_msg, None, {
8980 let breakpoint = breakpoint.clone();
8981 let weak_editor = weak_editor.clone();
8982 move |window, cx| {
8983 weak_editor
8984 .update(cx, |this, cx| {
8985 this.add_edit_breakpoint_block(
8986 anchor,
8987 breakpoint.as_ref(),
8988 BreakpointPromptEditAction::Condition,
8989 window,
8990 cx,
8991 );
8992 })
8993 .log_err();
8994 }
8995 })
8996 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8997 weak_editor
8998 .update(cx, |this, cx| {
8999 this.add_edit_breakpoint_block(
9000 anchor,
9001 breakpoint.as_ref(),
9002 BreakpointPromptEditAction::HitCondition,
9003 window,
9004 cx,
9005 );
9006 })
9007 .log_err();
9008 })
9009 })
9010 }
9011
9012 fn render_breakpoint(
9013 &self,
9014 position: Anchor,
9015 row: DisplayRow,
9016 breakpoint: &Breakpoint,
9017 state: Option<BreakpointSessionState>,
9018 cx: &mut Context<Self>,
9019 ) -> IconButton {
9020 let is_rejected = state.is_some_and(|s| !s.verified);
9021 // Is it a breakpoint that shows up when hovering over gutter?
9022 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9023 (false, false),
9024 |PhantomBreakpointIndicator {
9025 is_active,
9026 display_row,
9027 collides_with_existing_breakpoint,
9028 }| {
9029 (
9030 is_active && display_row == row,
9031 collides_with_existing_breakpoint,
9032 )
9033 },
9034 );
9035
9036 let (color, icon) = {
9037 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9038 (false, false) => ui::IconName::DebugBreakpoint,
9039 (true, false) => ui::IconName::DebugLogBreakpoint,
9040 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9041 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9042 };
9043
9044 let theme_colors = cx.theme().colors();
9045
9046 let color = if is_phantom {
9047 if collides_with_existing {
9048 Color::Custom(
9049 theme_colors
9050 .debugger_accent
9051 .blend(theme_colors.text.opacity(0.6)),
9052 )
9053 } else {
9054 Color::Hint
9055 }
9056 } else if is_rejected {
9057 Color::Disabled
9058 } else {
9059 Color::Debugger
9060 };
9061
9062 (color, icon)
9063 };
9064
9065 let breakpoint = Arc::from(breakpoint.clone());
9066
9067 let alt_as_text = gpui::Keystroke {
9068 modifiers: Modifiers::secondary_key(),
9069 ..Default::default()
9070 };
9071 let primary_action_text = if breakpoint.is_disabled() {
9072 "Enable breakpoint"
9073 } else if is_phantom && !collides_with_existing {
9074 "Set breakpoint"
9075 } else {
9076 "Unset breakpoint"
9077 };
9078 let focus_handle = self.focus_handle.clone();
9079
9080 let meta = if is_rejected {
9081 SharedString::from("No executable code is associated with this line.")
9082 } else if collides_with_existing && !breakpoint.is_disabled() {
9083 SharedString::from(format!(
9084 "{alt_as_text}-click to disable,\nright-click for more options."
9085 ))
9086 } else {
9087 SharedString::from("Right-click for more options.")
9088 };
9089 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9090 .icon_size(IconSize::XSmall)
9091 .size(ui::ButtonSize::None)
9092 .when(is_rejected, |this| {
9093 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9094 })
9095 .icon_color(color)
9096 .style(ButtonStyle::Transparent)
9097 .on_click(cx.listener({
9098 move |editor, event: &ClickEvent, window, cx| {
9099 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9100 BreakpointEditAction::InvertState
9101 } else {
9102 BreakpointEditAction::Toggle
9103 };
9104
9105 window.focus(&editor.focus_handle(cx), cx);
9106 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9107 editor.edit_breakpoint_at_anchor(
9108 position,
9109 breakpoint.as_ref().clone(),
9110 edit_action,
9111 cx,
9112 );
9113 }
9114 }))
9115 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9116 editor.set_breakpoint_context_menu(
9117 row,
9118 Some(position),
9119 event.position(),
9120 window,
9121 cx,
9122 );
9123 }))
9124 .tooltip(move |_window, cx| {
9125 Tooltip::with_meta_in(
9126 primary_action_text,
9127 Some(&ToggleBreakpoint),
9128 meta.clone(),
9129 &focus_handle,
9130 cx,
9131 )
9132 })
9133 }
9134
9135 fn build_tasks_context(
9136 project: &Entity<Project>,
9137 buffer: &Entity<Buffer>,
9138 buffer_row: u32,
9139 tasks: &Arc<RunnableTasks>,
9140 cx: &mut Context<Self>,
9141 ) -> Task<Option<task::TaskContext>> {
9142 let position = Point::new(buffer_row, tasks.column);
9143 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9144 let location = Location {
9145 buffer: buffer.clone(),
9146 range: range_start..range_start,
9147 };
9148 // Fill in the environmental variables from the tree-sitter captures
9149 let mut captured_task_variables = TaskVariables::default();
9150 for (capture_name, value) in tasks.extra_variables.clone() {
9151 captured_task_variables.insert(
9152 task::VariableName::Custom(capture_name.into()),
9153 value.clone(),
9154 );
9155 }
9156 project.update(cx, |project, cx| {
9157 project.task_store().update(cx, |task_store, cx| {
9158 task_store.task_context_for_location(captured_task_variables, location, cx)
9159 })
9160 })
9161 }
9162
9163 pub fn spawn_nearest_task(
9164 &mut self,
9165 action: &SpawnNearestTask,
9166 window: &mut Window,
9167 cx: &mut Context<Self>,
9168 ) {
9169 let Some((workspace, _)) = self.workspace.clone() else {
9170 return;
9171 };
9172 let Some(project) = self.project.clone() else {
9173 return;
9174 };
9175
9176 // Try to find a closest, enclosing node using tree-sitter that has a task
9177 let Some((buffer, buffer_row, tasks)) = self
9178 .find_enclosing_node_task(cx)
9179 // Or find the task that's closest in row-distance.
9180 .or_else(|| self.find_closest_task(cx))
9181 else {
9182 return;
9183 };
9184
9185 let reveal_strategy = action.reveal;
9186 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
9187 cx.spawn_in(window, async move |_, cx| {
9188 let context = task_context.await?;
9189 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
9190
9191 let resolved = &mut resolved_task.resolved;
9192 resolved.reveal = reveal_strategy;
9193
9194 workspace
9195 .update_in(cx, |workspace, window, cx| {
9196 workspace.schedule_resolved_task(
9197 task_source_kind,
9198 resolved_task,
9199 false,
9200 window,
9201 cx,
9202 );
9203 })
9204 .ok()
9205 })
9206 .detach();
9207 }
9208
9209 fn find_closest_task(
9210 &mut self,
9211 cx: &mut Context<Self>,
9212 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9213 let cursor_row = self
9214 .selections
9215 .newest_adjusted(&self.display_snapshot(cx))
9216 .head()
9217 .row;
9218
9219 let ((buffer_id, row), tasks) = self
9220 .tasks
9221 .iter()
9222 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
9223
9224 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
9225 let tasks = Arc::new(tasks.to_owned());
9226 Some((buffer, *row, tasks))
9227 }
9228
9229 fn find_enclosing_node_task(
9230 &mut self,
9231 cx: &mut Context<Self>,
9232 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9233 let snapshot = self.buffer.read(cx).snapshot(cx);
9234 let offset = self
9235 .selections
9236 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
9237 .head();
9238 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
9239 let offset = excerpt.map_offset_to_buffer(offset);
9240 let buffer_id = excerpt.buffer().remote_id();
9241
9242 let layer = excerpt.buffer().syntax_layer_at(offset)?;
9243 let mut cursor = layer.node().walk();
9244
9245 while cursor.goto_first_child_for_byte(offset.0).is_some() {
9246 if cursor.node().end_byte() == offset.0 {
9247 cursor.goto_next_sibling();
9248 }
9249 }
9250
9251 // Ascend to the smallest ancestor that contains the range and has a task.
9252 loop {
9253 let node = cursor.node();
9254 let node_range = node.byte_range();
9255 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9256
9257 // Check if this node contains our offset
9258 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9259 // If it contains offset, check for task
9260 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9261 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9262 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9263 }
9264 }
9265
9266 if !cursor.goto_parent() {
9267 break;
9268 }
9269 }
9270 None
9271 }
9272
9273 fn render_run_indicator(
9274 &self,
9275 _style: &EditorStyle,
9276 is_active: bool,
9277 row: DisplayRow,
9278 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9279 cx: &mut Context<Self>,
9280 ) -> IconButton {
9281 let color = Color::Muted;
9282 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9283
9284 IconButton::new(
9285 ("run_indicator", row.0 as usize),
9286 ui::IconName::PlayOutlined,
9287 )
9288 .shape(ui::IconButtonShape::Square)
9289 .icon_size(IconSize::XSmall)
9290 .icon_color(color)
9291 .toggle_state(is_active)
9292 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9293 let quick_launch = match e {
9294 ClickEvent::Keyboard(_) => true,
9295 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9296 };
9297
9298 window.focus(&editor.focus_handle(cx), cx);
9299 editor.toggle_code_actions(
9300 &ToggleCodeActions {
9301 deployed_from: Some(CodeActionSource::RunMenu(row)),
9302 quick_launch,
9303 },
9304 window,
9305 cx,
9306 );
9307 }))
9308 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9309 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9310 }))
9311 }
9312
9313 pub fn context_menu_visible(&self) -> bool {
9314 !self.edit_prediction_preview_is_active()
9315 && self
9316 .context_menu
9317 .borrow()
9318 .as_ref()
9319 .is_some_and(|menu| menu.visible())
9320 }
9321
9322 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9323 self.context_menu
9324 .borrow()
9325 .as_ref()
9326 .map(|menu| menu.origin())
9327 }
9328
9329 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9330 self.context_menu_options = Some(options);
9331 }
9332
9333 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9334 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9335
9336 fn render_edit_prediction_popover(
9337 &mut self,
9338 text_bounds: &Bounds<Pixels>,
9339 content_origin: gpui::Point<Pixels>,
9340 right_margin: Pixels,
9341 editor_snapshot: &EditorSnapshot,
9342 visible_row_range: Range<DisplayRow>,
9343 scroll_top: ScrollOffset,
9344 scroll_bottom: ScrollOffset,
9345 line_layouts: &[LineWithInvisibles],
9346 line_height: Pixels,
9347 scroll_position: gpui::Point<ScrollOffset>,
9348 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9349 newest_selection_head: Option<DisplayPoint>,
9350 editor_width: Pixels,
9351 style: &EditorStyle,
9352 window: &mut Window,
9353 cx: &mut App,
9354 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9355 if self.mode().is_minimap() {
9356 return None;
9357 }
9358 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9359
9360 if self.edit_prediction_visible_in_cursor_popover(true) {
9361 return None;
9362 }
9363
9364 match &active_edit_prediction.completion {
9365 EditPrediction::MoveWithin { target, .. } => {
9366 let target_display_point = target.to_display_point(editor_snapshot);
9367
9368 if self.edit_prediction_requires_modifier() {
9369 if !self.edit_prediction_preview_is_active() {
9370 return None;
9371 }
9372
9373 self.render_edit_prediction_modifier_jump_popover(
9374 text_bounds,
9375 content_origin,
9376 visible_row_range,
9377 line_layouts,
9378 line_height,
9379 scroll_pixel_position,
9380 newest_selection_head,
9381 target_display_point,
9382 window,
9383 cx,
9384 )
9385 } else {
9386 self.render_edit_prediction_eager_jump_popover(
9387 text_bounds,
9388 content_origin,
9389 editor_snapshot,
9390 visible_row_range,
9391 scroll_top,
9392 scroll_bottom,
9393 line_height,
9394 scroll_pixel_position,
9395 target_display_point,
9396 editor_width,
9397 window,
9398 cx,
9399 )
9400 }
9401 }
9402 EditPrediction::Edit {
9403 display_mode: EditDisplayMode::Inline,
9404 ..
9405 } => None,
9406 EditPrediction::Edit {
9407 display_mode: EditDisplayMode::TabAccept,
9408 edits,
9409 ..
9410 } => {
9411 let range = &edits.first()?.0;
9412 let target_display_point = range.end.to_display_point(editor_snapshot);
9413
9414 self.render_edit_prediction_end_of_line_popover(
9415 "Accept",
9416 editor_snapshot,
9417 visible_row_range,
9418 target_display_point,
9419 line_height,
9420 scroll_pixel_position,
9421 content_origin,
9422 editor_width,
9423 window,
9424 cx,
9425 )
9426 }
9427 EditPrediction::Edit {
9428 edits,
9429 edit_preview,
9430 display_mode: EditDisplayMode::DiffPopover,
9431 snapshot,
9432 ..
9433 } => self.render_edit_prediction_diff_popover(
9434 text_bounds,
9435 content_origin,
9436 right_margin,
9437 editor_snapshot,
9438 visible_row_range,
9439 line_layouts,
9440 line_height,
9441 scroll_position,
9442 scroll_pixel_position,
9443 newest_selection_head,
9444 editor_width,
9445 style,
9446 edits,
9447 edit_preview,
9448 snapshot,
9449 window,
9450 cx,
9451 ),
9452 EditPrediction::MoveOutside { snapshot, .. } => {
9453 let mut element = self
9454 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9455 .into_any();
9456
9457 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9458 let origin_x = text_bounds.size.width - size.width - px(30.);
9459 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9460 element.prepaint_at(origin, window, cx);
9461
9462 Some((element, origin))
9463 }
9464 }
9465 }
9466
9467 fn render_edit_prediction_modifier_jump_popover(
9468 &mut self,
9469 text_bounds: &Bounds<Pixels>,
9470 content_origin: gpui::Point<Pixels>,
9471 visible_row_range: Range<DisplayRow>,
9472 line_layouts: &[LineWithInvisibles],
9473 line_height: Pixels,
9474 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9475 newest_selection_head: Option<DisplayPoint>,
9476 target_display_point: DisplayPoint,
9477 window: &mut Window,
9478 cx: &mut App,
9479 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9480 let scrolled_content_origin =
9481 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9482
9483 const SCROLL_PADDING_Y: Pixels = px(12.);
9484
9485 if target_display_point.row() < visible_row_range.start {
9486 return self.render_edit_prediction_scroll_popover(
9487 &|_| SCROLL_PADDING_Y,
9488 IconName::ArrowUp,
9489 visible_row_range,
9490 line_layouts,
9491 newest_selection_head,
9492 scrolled_content_origin,
9493 window,
9494 cx,
9495 );
9496 } else if target_display_point.row() >= visible_row_range.end {
9497 return self.render_edit_prediction_scroll_popover(
9498 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9499 IconName::ArrowDown,
9500 visible_row_range,
9501 line_layouts,
9502 newest_selection_head,
9503 scrolled_content_origin,
9504 window,
9505 cx,
9506 );
9507 }
9508
9509 const POLE_WIDTH: Pixels = px(2.);
9510
9511 let line_layout =
9512 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9513 let target_column = target_display_point.column() as usize;
9514
9515 let target_x = line_layout.x_for_index(target_column);
9516 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9517 - scroll_pixel_position.y;
9518
9519 let flag_on_right = target_x < text_bounds.size.width / 2.;
9520
9521 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9522 border_color.l += 0.001;
9523
9524 let mut element = v_flex()
9525 .items_end()
9526 .when(flag_on_right, |el| el.items_start())
9527 .child(if flag_on_right {
9528 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9529 .rounded_bl(px(0.))
9530 .rounded_tl(px(0.))
9531 .border_l_2()
9532 .border_color(border_color)
9533 } else {
9534 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9535 .rounded_br(px(0.))
9536 .rounded_tr(px(0.))
9537 .border_r_2()
9538 .border_color(border_color)
9539 })
9540 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9541 .into_any();
9542
9543 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9544
9545 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9546 - point(
9547 if flag_on_right {
9548 POLE_WIDTH
9549 } else {
9550 size.width - POLE_WIDTH
9551 },
9552 size.height - line_height,
9553 );
9554
9555 origin.x = origin.x.max(content_origin.x);
9556
9557 element.prepaint_at(origin, window, cx);
9558
9559 Some((element, origin))
9560 }
9561
9562 fn render_edit_prediction_scroll_popover(
9563 &mut self,
9564 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9565 scroll_icon: IconName,
9566 visible_row_range: Range<DisplayRow>,
9567 line_layouts: &[LineWithInvisibles],
9568 newest_selection_head: Option<DisplayPoint>,
9569 scrolled_content_origin: gpui::Point<Pixels>,
9570 window: &mut Window,
9571 cx: &mut App,
9572 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9573 let mut element = self
9574 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9575 .into_any();
9576
9577 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9578
9579 let cursor = newest_selection_head?;
9580 let cursor_row_layout =
9581 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9582 let cursor_column = cursor.column() as usize;
9583
9584 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9585
9586 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9587
9588 element.prepaint_at(origin, window, cx);
9589 Some((element, origin))
9590 }
9591
9592 fn render_edit_prediction_eager_jump_popover(
9593 &mut self,
9594 text_bounds: &Bounds<Pixels>,
9595 content_origin: gpui::Point<Pixels>,
9596 editor_snapshot: &EditorSnapshot,
9597 visible_row_range: Range<DisplayRow>,
9598 scroll_top: ScrollOffset,
9599 scroll_bottom: ScrollOffset,
9600 line_height: Pixels,
9601 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9602 target_display_point: DisplayPoint,
9603 editor_width: Pixels,
9604 window: &mut Window,
9605 cx: &mut App,
9606 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9607 if target_display_point.row().as_f64() < scroll_top {
9608 let mut element = self
9609 .render_edit_prediction_line_popover(
9610 "Jump to Edit",
9611 Some(IconName::ArrowUp),
9612 window,
9613 cx,
9614 )
9615 .into_any();
9616
9617 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9618 let offset = point(
9619 (text_bounds.size.width - size.width) / 2.,
9620 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9621 );
9622
9623 let origin = text_bounds.origin + offset;
9624 element.prepaint_at(origin, window, cx);
9625 Some((element, origin))
9626 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9627 let mut element = self
9628 .render_edit_prediction_line_popover(
9629 "Jump to Edit",
9630 Some(IconName::ArrowDown),
9631 window,
9632 cx,
9633 )
9634 .into_any();
9635
9636 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9637 let offset = point(
9638 (text_bounds.size.width - size.width) / 2.,
9639 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9640 );
9641
9642 let origin = text_bounds.origin + offset;
9643 element.prepaint_at(origin, window, cx);
9644 Some((element, origin))
9645 } else {
9646 self.render_edit_prediction_end_of_line_popover(
9647 "Jump to Edit",
9648 editor_snapshot,
9649 visible_row_range,
9650 target_display_point,
9651 line_height,
9652 scroll_pixel_position,
9653 content_origin,
9654 editor_width,
9655 window,
9656 cx,
9657 )
9658 }
9659 }
9660
9661 fn render_edit_prediction_end_of_line_popover(
9662 self: &mut Editor,
9663 label: &'static str,
9664 editor_snapshot: &EditorSnapshot,
9665 visible_row_range: Range<DisplayRow>,
9666 target_display_point: DisplayPoint,
9667 line_height: Pixels,
9668 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9669 content_origin: gpui::Point<Pixels>,
9670 editor_width: Pixels,
9671 window: &mut Window,
9672 cx: &mut App,
9673 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9674 let target_line_end = DisplayPoint::new(
9675 target_display_point.row(),
9676 editor_snapshot.line_len(target_display_point.row()),
9677 );
9678
9679 let mut element = self
9680 .render_edit_prediction_line_popover(label, None, window, cx)
9681 .into_any();
9682
9683 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9684
9685 let line_origin =
9686 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9687
9688 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9689 let mut origin = start_point
9690 + line_origin
9691 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9692 origin.x = origin.x.max(content_origin.x);
9693
9694 let max_x = content_origin.x + editor_width - size.width;
9695
9696 if origin.x > max_x {
9697 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9698
9699 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9700 origin.y += offset;
9701 IconName::ArrowUp
9702 } else {
9703 origin.y -= offset;
9704 IconName::ArrowDown
9705 };
9706
9707 element = self
9708 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9709 .into_any();
9710
9711 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9712
9713 origin.x = content_origin.x + editor_width - size.width - px(2.);
9714 }
9715
9716 element.prepaint_at(origin, window, cx);
9717 Some((element, origin))
9718 }
9719
9720 fn render_edit_prediction_diff_popover(
9721 self: &Editor,
9722 text_bounds: &Bounds<Pixels>,
9723 content_origin: gpui::Point<Pixels>,
9724 right_margin: Pixels,
9725 editor_snapshot: &EditorSnapshot,
9726 visible_row_range: Range<DisplayRow>,
9727 line_layouts: &[LineWithInvisibles],
9728 line_height: Pixels,
9729 scroll_position: gpui::Point<ScrollOffset>,
9730 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9731 newest_selection_head: Option<DisplayPoint>,
9732 editor_width: Pixels,
9733 style: &EditorStyle,
9734 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9735 edit_preview: &Option<language::EditPreview>,
9736 snapshot: &language::BufferSnapshot,
9737 window: &mut Window,
9738 cx: &mut App,
9739 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9740 let edit_start = edits
9741 .first()
9742 .unwrap()
9743 .0
9744 .start
9745 .to_display_point(editor_snapshot);
9746 let edit_end = edits
9747 .last()
9748 .unwrap()
9749 .0
9750 .end
9751 .to_display_point(editor_snapshot);
9752
9753 let is_visible = visible_row_range.contains(&edit_start.row())
9754 || visible_row_range.contains(&edit_end.row());
9755 if !is_visible {
9756 return None;
9757 }
9758
9759 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9760 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9761 } else {
9762 // Fallback for providers without edit_preview
9763 crate::edit_prediction_fallback_text(edits, cx)
9764 };
9765
9766 let styled_text = highlighted_edits.to_styled_text(&style.text);
9767 let line_count = highlighted_edits.text.lines().count();
9768
9769 const BORDER_WIDTH: Pixels = px(1.);
9770
9771 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9772 let has_keybind = keybind.is_some();
9773
9774 let mut element = h_flex()
9775 .items_start()
9776 .child(
9777 h_flex()
9778 .bg(cx.theme().colors().editor_background)
9779 .border(BORDER_WIDTH)
9780 .shadow_xs()
9781 .border_color(cx.theme().colors().border)
9782 .rounded_l_lg()
9783 .when(line_count > 1, |el| el.rounded_br_lg())
9784 .pr_1()
9785 .child(styled_text),
9786 )
9787 .child(
9788 h_flex()
9789 .h(line_height + BORDER_WIDTH * 2.)
9790 .px_1p5()
9791 .gap_1()
9792 // Workaround: For some reason, there's a gap if we don't do this
9793 .ml(-BORDER_WIDTH)
9794 .shadow(vec![gpui::BoxShadow {
9795 color: gpui::black().opacity(0.05),
9796 offset: point(px(1.), px(1.)),
9797 blur_radius: px(2.),
9798 spread_radius: px(0.),
9799 }])
9800 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9801 .border(BORDER_WIDTH)
9802 .border_color(cx.theme().colors().border)
9803 .rounded_r_lg()
9804 .id("edit_prediction_diff_popover_keybind")
9805 .when(!has_keybind, |el| {
9806 let status_colors = cx.theme().status();
9807
9808 el.bg(status_colors.error_background)
9809 .border_color(status_colors.error.opacity(0.6))
9810 .child(Icon::new(IconName::Info).color(Color::Error))
9811 .cursor_default()
9812 .hoverable_tooltip(move |_window, cx| {
9813 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9814 })
9815 })
9816 .children(keybind),
9817 )
9818 .into_any();
9819
9820 let longest_row =
9821 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9822 let longest_line_width = if visible_row_range.contains(&longest_row) {
9823 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9824 } else {
9825 layout_line(
9826 longest_row,
9827 editor_snapshot,
9828 style,
9829 editor_width,
9830 |_| false,
9831 window,
9832 cx,
9833 )
9834 .width
9835 };
9836
9837 let viewport_bounds =
9838 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9839 right: -right_margin,
9840 ..Default::default()
9841 });
9842
9843 let x_after_longest = Pixels::from(
9844 ScrollPixelOffset::from(
9845 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9846 ) - scroll_pixel_position.x,
9847 );
9848
9849 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9850
9851 // Fully visible if it can be displayed within the window (allow overlapping other
9852 // panes). However, this is only allowed if the popover starts within text_bounds.
9853 let can_position_to_the_right = x_after_longest < text_bounds.right()
9854 && x_after_longest + element_bounds.width < viewport_bounds.right();
9855
9856 let mut origin = if can_position_to_the_right {
9857 point(
9858 x_after_longest,
9859 text_bounds.origin.y
9860 + Pixels::from(
9861 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9862 - scroll_pixel_position.y,
9863 ),
9864 )
9865 } else {
9866 let cursor_row = newest_selection_head.map(|head| head.row());
9867 let above_edit = edit_start
9868 .row()
9869 .0
9870 .checked_sub(line_count as u32)
9871 .map(DisplayRow);
9872 let below_edit = Some(edit_end.row() + 1);
9873 let above_cursor =
9874 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9875 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9876
9877 // Place the edit popover adjacent to the edit if there is a location
9878 // available that is onscreen and does not obscure the cursor. Otherwise,
9879 // place it adjacent to the cursor.
9880 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9881 .into_iter()
9882 .flatten()
9883 .find(|&start_row| {
9884 let end_row = start_row + line_count as u32;
9885 visible_row_range.contains(&start_row)
9886 && visible_row_range.contains(&end_row)
9887 && cursor_row
9888 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9889 })?;
9890
9891 content_origin
9892 + point(
9893 Pixels::from(-scroll_pixel_position.x),
9894 Pixels::from(
9895 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9896 ),
9897 )
9898 };
9899
9900 origin.x -= BORDER_WIDTH;
9901
9902 window.with_content_mask(
9903 Some(gpui::ContentMask {
9904 bounds: *text_bounds,
9905 }),
9906 |window| {
9907 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9908 },
9909 );
9910
9911 // Do not return an element, since it will already be drawn due to defer_draw.
9912 None
9913 }
9914
9915 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9916 px(30.)
9917 }
9918
9919 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9920 if self.read_only(cx) {
9921 cx.theme().players().read_only()
9922 } else {
9923 self.style.as_ref().unwrap().local_player
9924 }
9925 }
9926
9927 fn render_edit_prediction_accept_keybind(
9928 &self,
9929 window: &mut Window,
9930 cx: &mut App,
9931 ) -> Option<AnyElement> {
9932 let accept_binding =
9933 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9934 let accept_keystroke = accept_binding.keystroke()?;
9935
9936 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9937
9938 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9939 Color::Accent
9940 } else {
9941 Color::Muted
9942 };
9943
9944 h_flex()
9945 .px_0p5()
9946 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9947 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9948 .text_size(TextSize::XSmall.rems(cx))
9949 .child(h_flex().children(ui::render_modifiers(
9950 accept_keystroke.modifiers(),
9951 PlatformStyle::platform(),
9952 Some(modifiers_color),
9953 Some(IconSize::XSmall.rems().into()),
9954 true,
9955 )))
9956 .when(is_platform_style_mac, |parent| {
9957 parent.child(accept_keystroke.key().to_string())
9958 })
9959 .when(!is_platform_style_mac, |parent| {
9960 parent.child(
9961 Key::new(
9962 util::capitalize(accept_keystroke.key()),
9963 Some(Color::Default),
9964 )
9965 .size(Some(IconSize::XSmall.rems().into())),
9966 )
9967 })
9968 .into_any()
9969 .into()
9970 }
9971
9972 fn render_edit_prediction_line_popover(
9973 &self,
9974 label: impl Into<SharedString>,
9975 icon: Option<IconName>,
9976 window: &mut Window,
9977 cx: &mut App,
9978 ) -> Stateful<Div> {
9979 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9980
9981 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9982 let has_keybind = keybind.is_some();
9983 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9984
9985 h_flex()
9986 .id("ep-line-popover")
9987 .py_0p5()
9988 .pl_1()
9989 .pr(padding_right)
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(label)
10011 .size(LabelSize::Small)
10012 .when(!has_keybind, |el| {
10013 el.color(cx.theme().status().error.into()).strikethrough()
10014 }),
10015 )
10016 .when(!has_keybind, |el| {
10017 el.child(
10018 h_flex().ml_1().child(
10019 Icon::new(IconName::Info)
10020 .size(IconSize::Small)
10021 .color(cx.theme().status().error.into()),
10022 ),
10023 )
10024 })
10025 .when_some(icon, |element, icon| {
10026 element.child(
10027 div()
10028 .mt(px(1.5))
10029 .child(Icon::new(icon).size(IconSize::Small)),
10030 )
10031 })
10032 }
10033
10034 fn render_edit_prediction_jump_outside_popover(
10035 &self,
10036 snapshot: &BufferSnapshot,
10037 window: &mut Window,
10038 cx: &mut App,
10039 ) -> Stateful<Div> {
10040 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
10041 let has_keybind = keybind.is_some();
10042 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10043
10044 let file_name = snapshot
10045 .file()
10046 .map(|file| SharedString::new(file.file_name(cx)))
10047 .unwrap_or(SharedString::new_static("untitled"));
10048
10049 h_flex()
10050 .id("ep-jump-outside-popover")
10051 .py_1()
10052 .px_2()
10053 .gap_1()
10054 .rounded_md()
10055 .border_1()
10056 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10057 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10058 .shadow_xs()
10059 .when(!has_keybind, |el| {
10060 let status_colors = cx.theme().status();
10061
10062 el.bg(status_colors.error_background)
10063 .border_color(status_colors.error.opacity(0.6))
10064 .pl_2()
10065 .child(Icon::new(icons.error).color(Color::Error))
10066 .cursor_default()
10067 .hoverable_tooltip(move |_window, cx| {
10068 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10069 })
10070 })
10071 .children(keybind)
10072 .child(
10073 Label::new(file_name)
10074 .size(LabelSize::Small)
10075 .buffer_font(cx)
10076 .when(!has_keybind, |el| {
10077 el.color(cx.theme().status().error.into()).strikethrough()
10078 }),
10079 )
10080 .when(!has_keybind, |el| {
10081 el.child(
10082 h_flex().ml_1().child(
10083 Icon::new(IconName::Info)
10084 .size(IconSize::Small)
10085 .color(cx.theme().status().error.into()),
10086 ),
10087 )
10088 })
10089 .child(
10090 div()
10091 .mt(px(1.5))
10092 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10093 )
10094 }
10095
10096 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10097 let accent_color = cx.theme().colors().text_accent;
10098 let editor_bg_color = cx.theme().colors().editor_background;
10099 editor_bg_color.blend(accent_color.opacity(0.1))
10100 }
10101
10102 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10103 let accent_color = cx.theme().colors().text_accent;
10104 let editor_bg_color = cx.theme().colors().editor_background;
10105 editor_bg_color.blend(accent_color.opacity(0.6))
10106 }
10107 fn get_prediction_provider_icons(
10108 provider: &Option<RegisteredEditPredictionDelegate>,
10109 cx: &App,
10110 ) -> edit_prediction_types::EditPredictionIconSet {
10111 match provider {
10112 Some(provider) => provider.provider.icons(cx),
10113 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10114 }
10115 }
10116
10117 fn render_edit_prediction_cursor_popover(
10118 &self,
10119 min_width: Pixels,
10120 max_width: Pixels,
10121 cursor_point: Point,
10122 style: &EditorStyle,
10123 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
10124 _window: &Window,
10125 cx: &mut Context<Editor>,
10126 ) -> Option<AnyElement> {
10127 let provider = self.edit_prediction_provider.as_ref()?;
10128 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10129
10130 let is_refreshing = provider.provider.is_refreshing(cx);
10131
10132 fn pending_completion_container(icon: IconName) -> Div {
10133 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10134 }
10135
10136 let completion = match &self.active_edit_prediction {
10137 Some(prediction) => {
10138 if !self.has_visible_completions_menu() {
10139 const RADIUS: Pixels = px(6.);
10140 const BORDER_WIDTH: Pixels = px(1.);
10141
10142 return Some(
10143 h_flex()
10144 .elevation_2(cx)
10145 .border(BORDER_WIDTH)
10146 .border_color(cx.theme().colors().border)
10147 .when(accept_keystroke.is_none(), |el| {
10148 el.border_color(cx.theme().status().error)
10149 })
10150 .rounded(RADIUS)
10151 .rounded_tl(px(0.))
10152 .overflow_hidden()
10153 .child(div().px_1p5().child(match &prediction.completion {
10154 EditPrediction::MoveWithin { target, snapshot } => {
10155 use text::ToPoint as _;
10156 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10157 {
10158 Icon::new(icons.down)
10159 } else {
10160 Icon::new(icons.up)
10161 }
10162 }
10163 EditPrediction::MoveOutside { .. } => {
10164 // TODO [zeta2] custom icon for external jump?
10165 Icon::new(icons.base)
10166 }
10167 EditPrediction::Edit { .. } => Icon::new(icons.base),
10168 }))
10169 .child(
10170 h_flex()
10171 .gap_1()
10172 .py_1()
10173 .px_2()
10174 .rounded_r(RADIUS - BORDER_WIDTH)
10175 .border_l_1()
10176 .border_color(cx.theme().colors().border)
10177 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10178 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10179 el.child(
10180 Label::new("Hold")
10181 .size(LabelSize::Small)
10182 .when(accept_keystroke.is_none(), |el| {
10183 el.strikethrough()
10184 })
10185 .line_height_style(LineHeightStyle::UiLabel),
10186 )
10187 })
10188 .id("edit_prediction_cursor_popover_keybind")
10189 .when(accept_keystroke.is_none(), |el| {
10190 let status_colors = cx.theme().status();
10191
10192 el.bg(status_colors.error_background)
10193 .border_color(status_colors.error.opacity(0.6))
10194 .child(Icon::new(IconName::Info).color(Color::Error))
10195 .cursor_default()
10196 .hoverable_tooltip(move |_window, cx| {
10197 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10198 .into()
10199 })
10200 })
10201 .when_some(
10202 accept_keystroke.as_ref(),
10203 |el, accept_keystroke| {
10204 el.child(h_flex().children(ui::render_modifiers(
10205 accept_keystroke.modifiers(),
10206 PlatformStyle::platform(),
10207 Some(Color::Default),
10208 Some(IconSize::XSmall.rems().into()),
10209 false,
10210 )))
10211 },
10212 ),
10213 )
10214 .into_any(),
10215 );
10216 }
10217
10218 self.render_edit_prediction_cursor_popover_preview(
10219 prediction,
10220 cursor_point,
10221 style,
10222 cx,
10223 )?
10224 }
10225
10226 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10227 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10228 stale_completion,
10229 cursor_point,
10230 style,
10231 cx,
10232 )?,
10233
10234 None => pending_completion_container(icons.base)
10235 .child(Label::new("...").size(LabelSize::Small)),
10236 },
10237
10238 None => pending_completion_container(icons.base)
10239 .child(Label::new("...").size(LabelSize::Small)),
10240 };
10241
10242 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10243 completion
10244 .with_animation(
10245 "loading-completion",
10246 Animation::new(Duration::from_secs(2))
10247 .repeat()
10248 .with_easing(pulsating_between(0.4, 0.8)),
10249 |label, delta| label.opacity(delta),
10250 )
10251 .into_any_element()
10252 } else {
10253 completion.into_any_element()
10254 };
10255
10256 let has_completion = self.active_edit_prediction.is_some();
10257
10258 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10259 Some(
10260 h_flex()
10261 .min_w(min_width)
10262 .max_w(max_width)
10263 .flex_1()
10264 .elevation_2(cx)
10265 .border_color(cx.theme().colors().border)
10266 .child(
10267 div()
10268 .flex_1()
10269 .py_1()
10270 .px_2()
10271 .overflow_hidden()
10272 .child(completion),
10273 )
10274 .when_some(accept_keystroke, |el, accept_keystroke| {
10275 if !accept_keystroke.modifiers().modified() {
10276 return el;
10277 }
10278
10279 el.child(
10280 h_flex()
10281 .h_full()
10282 .border_l_1()
10283 .rounded_r_lg()
10284 .border_color(cx.theme().colors().border)
10285 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10286 .gap_1()
10287 .py_1()
10288 .px_2()
10289 .child(
10290 h_flex()
10291 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10292 .when(is_platform_style_mac, |parent| parent.gap_1())
10293 .child(h_flex().children(ui::render_modifiers(
10294 accept_keystroke.modifiers(),
10295 PlatformStyle::platform(),
10296 Some(if !has_completion {
10297 Color::Muted
10298 } else {
10299 Color::Default
10300 }),
10301 None,
10302 false,
10303 ))),
10304 )
10305 .child(Label::new("Preview").into_any_element())
10306 .opacity(if has_completion { 1.0 } else { 0.4 }),
10307 )
10308 })
10309 .into_any(),
10310 )
10311 }
10312
10313 fn render_edit_prediction_cursor_popover_preview(
10314 &self,
10315 completion: &EditPredictionState,
10316 cursor_point: Point,
10317 style: &EditorStyle,
10318 cx: &mut Context<Editor>,
10319 ) -> Option<Div> {
10320 use text::ToPoint as _;
10321
10322 fn render_relative_row_jump(
10323 prefix: impl Into<String>,
10324 current_row: u32,
10325 target_row: u32,
10326 ) -> Div {
10327 let (row_diff, arrow) = if target_row < current_row {
10328 (current_row - target_row, IconName::ArrowUp)
10329 } else {
10330 (target_row - current_row, IconName::ArrowDown)
10331 };
10332
10333 h_flex()
10334 .child(
10335 Label::new(format!("{}{}", prefix.into(), row_diff))
10336 .color(Color::Muted)
10337 .size(LabelSize::Small),
10338 )
10339 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10340 }
10341
10342 let supports_jump = self
10343 .edit_prediction_provider
10344 .as_ref()
10345 .map(|provider| provider.provider.supports_jump_to_edit())
10346 .unwrap_or(true);
10347
10348 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10349
10350 match &completion.completion {
10351 EditPrediction::MoveWithin {
10352 target, snapshot, ..
10353 } => {
10354 if !supports_jump {
10355 return None;
10356 }
10357
10358 Some(
10359 h_flex()
10360 .px_2()
10361 .gap_2()
10362 .flex_1()
10363 .child(
10364 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10365 Icon::new(icons.down)
10366 } else {
10367 Icon::new(icons.up)
10368 },
10369 )
10370 .child(Label::new("Jump to Edit")),
10371 )
10372 }
10373 EditPrediction::MoveOutside { snapshot, .. } => {
10374 let file_name = snapshot
10375 .file()
10376 .map(|file| file.file_name(cx))
10377 .unwrap_or("untitled");
10378 Some(
10379 h_flex()
10380 .px_2()
10381 .gap_2()
10382 .flex_1()
10383 .child(Icon::new(icons.base))
10384 .child(Label::new(format!("Jump to {file_name}"))),
10385 )
10386 }
10387 EditPrediction::Edit {
10388 edits,
10389 edit_preview,
10390 snapshot,
10391 ..
10392 } => {
10393 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10394
10395 let (highlighted_edits, has_more_lines) =
10396 if let Some(edit_preview) = edit_preview.as_ref() {
10397 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10398 .first_line_preview()
10399 } else {
10400 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10401 };
10402
10403 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10404 .with_default_highlights(&style.text, highlighted_edits.highlights);
10405
10406 let preview = h_flex()
10407 .gap_1()
10408 .min_w_16()
10409 .child(styled_text)
10410 .when(has_more_lines, |parent| parent.child("…"));
10411
10412 let left = if supports_jump && first_edit_row != cursor_point.row {
10413 render_relative_row_jump("", cursor_point.row, first_edit_row)
10414 .into_any_element()
10415 } else {
10416 Icon::new(icons.base).into_any_element()
10417 };
10418
10419 Some(
10420 h_flex()
10421 .h_full()
10422 .flex_1()
10423 .gap_2()
10424 .pr_1()
10425 .overflow_x_hidden()
10426 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10427 .child(left)
10428 .child(preview),
10429 )
10430 }
10431 }
10432 }
10433
10434 pub fn render_context_menu(
10435 &mut self,
10436 max_height_in_lines: u32,
10437 window: &mut Window,
10438 cx: &mut Context<Editor>,
10439 ) -> Option<AnyElement> {
10440 let menu = self.context_menu.borrow();
10441 let menu = menu.as_ref()?;
10442 if !menu.visible() {
10443 return None;
10444 };
10445 self.style
10446 .as_ref()
10447 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10448 }
10449
10450 fn render_context_menu_aside(
10451 &mut self,
10452 max_size: Size<Pixels>,
10453 window: &mut Window,
10454 cx: &mut Context<Editor>,
10455 ) -> Option<AnyElement> {
10456 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10457 if menu.visible() {
10458 menu.render_aside(max_size, window, cx)
10459 } else {
10460 None
10461 }
10462 })
10463 }
10464
10465 fn hide_context_menu(
10466 &mut self,
10467 window: &mut Window,
10468 cx: &mut Context<Self>,
10469 ) -> Option<CodeContextMenu> {
10470 cx.notify();
10471 self.completion_tasks.clear();
10472 let context_menu = self.context_menu.borrow_mut().take();
10473 self.stale_edit_prediction_in_menu.take();
10474 self.update_visible_edit_prediction(window, cx);
10475 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10476 && let Some(completion_provider) = &self.completion_provider
10477 {
10478 completion_provider.selection_changed(None, window, cx);
10479 }
10480 context_menu
10481 }
10482
10483 fn show_snippet_choices(
10484 &mut self,
10485 choices: &Vec<String>,
10486 selection: Range<Anchor>,
10487 cx: &mut Context<Self>,
10488 ) {
10489 let Some((_, buffer, _)) = self
10490 .buffer()
10491 .read(cx)
10492 .excerpt_containing(selection.start, cx)
10493 else {
10494 return;
10495 };
10496 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10497 else {
10498 return;
10499 };
10500 if buffer != end_buffer {
10501 log::error!("expected anchor range to have matching buffer IDs");
10502 return;
10503 }
10504
10505 let id = post_inc(&mut self.next_completion_id);
10506 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10507 let mut context_menu = self.context_menu.borrow_mut();
10508 let old_menu = context_menu.take();
10509 *context_menu = Some(CodeContextMenu::Completions(
10510 CompletionsMenu::new_snippet_choices(
10511 id,
10512 true,
10513 choices,
10514 selection,
10515 buffer,
10516 old_menu.map(|menu| menu.primary_scroll_handle()),
10517 snippet_sort_order,
10518 ),
10519 ));
10520 }
10521
10522 pub fn insert_snippet(
10523 &mut self,
10524 insertion_ranges: &[Range<MultiBufferOffset>],
10525 snippet: Snippet,
10526 window: &mut Window,
10527 cx: &mut Context<Self>,
10528 ) -> Result<()> {
10529 struct Tabstop<T> {
10530 is_end_tabstop: bool,
10531 ranges: Vec<Range<T>>,
10532 choices: Option<Vec<String>>,
10533 }
10534
10535 let tabstops = self.buffer.update(cx, |buffer, cx| {
10536 let snippet_text: Arc<str> = snippet.text.clone().into();
10537 let edits = insertion_ranges
10538 .iter()
10539 .cloned()
10540 .map(|range| (range, snippet_text.clone()));
10541 let autoindent_mode = AutoindentMode::Block {
10542 original_indent_columns: Vec::new(),
10543 };
10544 buffer.edit(edits, Some(autoindent_mode), cx);
10545
10546 let snapshot = &*buffer.read(cx);
10547 let snippet = &snippet;
10548 snippet
10549 .tabstops
10550 .iter()
10551 .map(|tabstop| {
10552 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10553 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10554 });
10555 let mut tabstop_ranges = tabstop
10556 .ranges
10557 .iter()
10558 .flat_map(|tabstop_range| {
10559 let mut delta = 0_isize;
10560 insertion_ranges.iter().map(move |insertion_range| {
10561 let insertion_start = insertion_range.start + delta;
10562 delta += snippet.text.len() as isize
10563 - (insertion_range.end - insertion_range.start) as isize;
10564
10565 let start =
10566 (insertion_start + tabstop_range.start).min(snapshot.len());
10567 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10568 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10569 })
10570 })
10571 .collect::<Vec<_>>();
10572 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10573
10574 Tabstop {
10575 is_end_tabstop,
10576 ranges: tabstop_ranges,
10577 choices: tabstop.choices.clone(),
10578 }
10579 })
10580 .collect::<Vec<_>>()
10581 });
10582 if let Some(tabstop) = tabstops.first() {
10583 self.change_selections(Default::default(), window, cx, |s| {
10584 // Reverse order so that the first range is the newest created selection.
10585 // Completions will use it and autoscroll will prioritize it.
10586 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10587 });
10588
10589 if let Some(choices) = &tabstop.choices
10590 && let Some(selection) = tabstop.ranges.first()
10591 {
10592 self.show_snippet_choices(choices, selection.clone(), cx)
10593 }
10594
10595 // If we're already at the last tabstop and it's at the end of the snippet,
10596 // we're done, we don't need to keep the state around.
10597 if !tabstop.is_end_tabstop {
10598 let choices = tabstops
10599 .iter()
10600 .map(|tabstop| tabstop.choices.clone())
10601 .collect();
10602
10603 let ranges = tabstops
10604 .into_iter()
10605 .map(|tabstop| tabstop.ranges)
10606 .collect::<Vec<_>>();
10607
10608 self.snippet_stack.push(SnippetState {
10609 active_index: 0,
10610 ranges,
10611 choices,
10612 });
10613 }
10614
10615 // Check whether the just-entered snippet ends with an auto-closable bracket.
10616 if self.autoclose_regions.is_empty() {
10617 let snapshot = self.buffer.read(cx).snapshot(cx);
10618 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10619 let selection_head = selection.head();
10620 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10621 continue;
10622 };
10623
10624 let mut bracket_pair = None;
10625 let max_lookup_length = scope
10626 .brackets()
10627 .map(|(pair, _)| {
10628 pair.start
10629 .as_str()
10630 .chars()
10631 .count()
10632 .max(pair.end.as_str().chars().count())
10633 })
10634 .max();
10635 if let Some(max_lookup_length) = max_lookup_length {
10636 let next_text = snapshot
10637 .chars_at(selection_head)
10638 .take(max_lookup_length)
10639 .collect::<String>();
10640 let prev_text = snapshot
10641 .reversed_chars_at(selection_head)
10642 .take(max_lookup_length)
10643 .collect::<String>();
10644
10645 for (pair, enabled) in scope.brackets() {
10646 if enabled
10647 && pair.close
10648 && prev_text.starts_with(pair.start.as_str())
10649 && next_text.starts_with(pair.end.as_str())
10650 {
10651 bracket_pair = Some(pair.clone());
10652 break;
10653 }
10654 }
10655 }
10656
10657 if let Some(pair) = bracket_pair {
10658 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10659 let autoclose_enabled =
10660 self.use_autoclose && snapshot_settings.use_autoclose;
10661 if autoclose_enabled {
10662 let start = snapshot.anchor_after(selection_head);
10663 let end = snapshot.anchor_after(selection_head);
10664 self.autoclose_regions.push(AutocloseRegion {
10665 selection_id: selection.id,
10666 range: start..end,
10667 pair,
10668 });
10669 }
10670 }
10671 }
10672 }
10673 }
10674 Ok(())
10675 }
10676
10677 pub fn move_to_next_snippet_tabstop(
10678 &mut self,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) -> bool {
10682 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10683 }
10684
10685 pub fn move_to_prev_snippet_tabstop(
10686 &mut self,
10687 window: &mut Window,
10688 cx: &mut Context<Self>,
10689 ) -> bool {
10690 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10691 }
10692
10693 pub fn move_to_snippet_tabstop(
10694 &mut self,
10695 bias: Bias,
10696 window: &mut Window,
10697 cx: &mut Context<Self>,
10698 ) -> bool {
10699 if let Some(mut snippet) = self.snippet_stack.pop() {
10700 match bias {
10701 Bias::Left => {
10702 if snippet.active_index > 0 {
10703 snippet.active_index -= 1;
10704 } else {
10705 self.snippet_stack.push(snippet);
10706 return false;
10707 }
10708 }
10709 Bias::Right => {
10710 if snippet.active_index + 1 < snippet.ranges.len() {
10711 snippet.active_index += 1;
10712 } else {
10713 self.snippet_stack.push(snippet);
10714 return false;
10715 }
10716 }
10717 }
10718 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10719 self.change_selections(Default::default(), window, cx, |s| {
10720 // Reverse order so that the first range is the newest created selection.
10721 // Completions will use it and autoscroll will prioritize it.
10722 s.select_ranges(current_ranges.iter().rev().cloned())
10723 });
10724
10725 if let Some(choices) = &snippet.choices[snippet.active_index]
10726 && let Some(selection) = current_ranges.first()
10727 {
10728 self.show_snippet_choices(choices, selection.clone(), cx);
10729 }
10730
10731 // If snippet state is not at the last tabstop, push it back on the stack
10732 if snippet.active_index + 1 < snippet.ranges.len() {
10733 self.snippet_stack.push(snippet);
10734 }
10735 return true;
10736 }
10737 }
10738
10739 false
10740 }
10741
10742 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10743 self.transact(window, cx, |this, window, cx| {
10744 this.select_all(&SelectAll, window, cx);
10745 this.insert("", window, cx);
10746 });
10747 }
10748
10749 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10750 if self.read_only(cx) {
10751 return;
10752 }
10753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10754 self.transact(window, cx, |this, window, cx| {
10755 this.select_autoclose_pair(window, cx);
10756
10757 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10758
10759 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10760 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10761 for selection in &mut selections {
10762 if selection.is_empty() {
10763 let old_head = selection.head();
10764 let mut new_head =
10765 movement::left(&display_map, old_head.to_display_point(&display_map))
10766 .to_point(&display_map);
10767 if let Some((buffer, line_buffer_range)) = display_map
10768 .buffer_snapshot()
10769 .buffer_line_for_row(MultiBufferRow(old_head.row))
10770 {
10771 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10772 let indent_len = match indent_size.kind {
10773 IndentKind::Space => {
10774 buffer.settings_at(line_buffer_range.start, cx).tab_size
10775 }
10776 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10777 };
10778 if old_head.column <= indent_size.len && old_head.column > 0 {
10779 let indent_len = indent_len.get();
10780 new_head = cmp::min(
10781 new_head,
10782 MultiBufferPoint::new(
10783 old_head.row,
10784 ((old_head.column - 1) / indent_len) * indent_len,
10785 ),
10786 );
10787 }
10788 }
10789
10790 selection.set_head(new_head, SelectionGoal::None);
10791 }
10792 }
10793
10794 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10795 this.insert("", window, cx);
10796 linked_edits.apply_with_left_expansion(cx);
10797 this.refresh_edit_prediction(true, false, window, cx);
10798 refresh_linked_ranges(this, window, cx);
10799 });
10800 }
10801
10802 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10803 if self.read_only(cx) {
10804 return;
10805 }
10806 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10807 self.transact(window, cx, |this, window, cx| {
10808 this.change_selections(Default::default(), window, cx, |s| {
10809 s.move_with(&mut |map, selection| {
10810 if selection.is_empty() {
10811 let cursor = movement::right(map, selection.head());
10812 selection.end = cursor;
10813 selection.reversed = true;
10814 selection.goal = SelectionGoal::None;
10815 }
10816 })
10817 });
10818 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10819 this.insert("", window, cx);
10820 linked_edits.apply(cx);
10821 this.refresh_edit_prediction(true, false, window, cx);
10822 refresh_linked_ranges(this, window, cx);
10823 });
10824 }
10825
10826 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10827 if self.mode.is_single_line() {
10828 cx.propagate();
10829 return;
10830 }
10831
10832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10833 if self.move_to_prev_snippet_tabstop(window, cx) {
10834 return;
10835 }
10836 self.outdent(&Outdent, window, cx);
10837 }
10838
10839 pub fn next_snippet_tabstop(
10840 &mut self,
10841 _: &NextSnippetTabstop,
10842 window: &mut Window,
10843 cx: &mut Context<Self>,
10844 ) {
10845 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10846 cx.propagate();
10847 return;
10848 }
10849
10850 if self.move_to_next_snippet_tabstop(window, cx) {
10851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10852 return;
10853 }
10854 cx.propagate();
10855 }
10856
10857 pub fn previous_snippet_tabstop(
10858 &mut self,
10859 _: &PreviousSnippetTabstop,
10860 window: &mut Window,
10861 cx: &mut Context<Self>,
10862 ) {
10863 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10864 cx.propagate();
10865 return;
10866 }
10867
10868 if self.move_to_prev_snippet_tabstop(window, cx) {
10869 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10870 return;
10871 }
10872 cx.propagate();
10873 }
10874
10875 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10876 if self.mode.is_single_line() {
10877 cx.propagate();
10878 return;
10879 }
10880
10881 if self.move_to_next_snippet_tabstop(window, cx) {
10882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10883 return;
10884 }
10885 if self.read_only(cx) {
10886 return;
10887 }
10888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10889 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10890 let buffer = self.buffer.read(cx);
10891 let snapshot = buffer.snapshot(cx);
10892 let rows_iter = selections.iter().map(|s| s.head().row);
10893 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10894
10895 let has_some_cursor_in_whitespace = selections
10896 .iter()
10897 .filter(|selection| selection.is_empty())
10898 .any(|selection| {
10899 let cursor = selection.head();
10900 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10901 cursor.column < current_indent.len
10902 });
10903
10904 let mut edits = Vec::new();
10905 let mut prev_edited_row = 0;
10906 let mut row_delta = 0;
10907 for selection in &mut selections {
10908 if selection.start.row != prev_edited_row {
10909 row_delta = 0;
10910 }
10911 prev_edited_row = selection.end.row;
10912
10913 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10914 if selection.is_empty() {
10915 let cursor = selection.head();
10916 let settings = buffer.language_settings_at(cursor, cx);
10917 if settings.indent_list_on_tab {
10918 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10919 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10920 row_delta = Self::indent_selection(
10921 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10922 );
10923 continue;
10924 }
10925 }
10926 }
10927 }
10928
10929 // If the selection is non-empty, then increase the indentation of the selected lines.
10930 if !selection.is_empty() {
10931 row_delta =
10932 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10933 continue;
10934 }
10935
10936 let cursor = selection.head();
10937 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10938 if let Some(suggested_indent) =
10939 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10940 {
10941 // Don't do anything if already at suggested indent
10942 // and there is any other cursor which is not
10943 if has_some_cursor_in_whitespace
10944 && cursor.column == current_indent.len
10945 && current_indent.len == suggested_indent.len
10946 {
10947 continue;
10948 }
10949
10950 // Adjust line and move cursor to suggested indent
10951 // if cursor is not at suggested indent
10952 if cursor.column < suggested_indent.len
10953 && cursor.column <= current_indent.len
10954 && current_indent.len <= suggested_indent.len
10955 {
10956 selection.start = Point::new(cursor.row, suggested_indent.len);
10957 selection.end = selection.start;
10958 if row_delta == 0 {
10959 edits.extend(Buffer::edit_for_indent_size_adjustment(
10960 cursor.row,
10961 current_indent,
10962 suggested_indent,
10963 ));
10964 row_delta = suggested_indent.len - current_indent.len;
10965 }
10966 continue;
10967 }
10968
10969 // If current indent is more than suggested indent
10970 // only move cursor to current indent and skip indent
10971 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10972 selection.start = Point::new(cursor.row, current_indent.len);
10973 selection.end = selection.start;
10974 continue;
10975 }
10976 }
10977
10978 // Otherwise, insert a hard or soft tab.
10979 let settings = buffer.language_settings_at(cursor, cx);
10980 let tab_size = if settings.hard_tabs {
10981 IndentSize::tab()
10982 } else {
10983 let tab_size = settings.tab_size.get();
10984 let indent_remainder = snapshot
10985 .text_for_range(Point::new(cursor.row, 0)..cursor)
10986 .flat_map(str::chars)
10987 .fold(row_delta % tab_size, |counter: u32, c| {
10988 if c == '\t' {
10989 0
10990 } else {
10991 (counter + 1) % tab_size
10992 }
10993 });
10994
10995 let chars_to_next_tab_stop = tab_size - indent_remainder;
10996 IndentSize::spaces(chars_to_next_tab_stop)
10997 };
10998 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10999 selection.end = selection.start;
11000 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11001 row_delta += tab_size.len;
11002 }
11003
11004 self.transact(window, cx, |this, window, cx| {
11005 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11006 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11007 this.refresh_edit_prediction(true, false, window, cx);
11008 });
11009 }
11010
11011 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11012 if self.read_only(cx) {
11013 return;
11014 }
11015 if self.mode.is_single_line() {
11016 cx.propagate();
11017 return;
11018 }
11019
11020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11021 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11022 let mut prev_edited_row = 0;
11023 let mut row_delta = 0;
11024 let mut edits = Vec::new();
11025 let buffer = self.buffer.read(cx);
11026 let snapshot = buffer.snapshot(cx);
11027 for selection in &mut selections {
11028 if selection.start.row != prev_edited_row {
11029 row_delta = 0;
11030 }
11031 prev_edited_row = selection.end.row;
11032
11033 row_delta =
11034 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11035 }
11036
11037 self.transact(window, cx, |this, window, cx| {
11038 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11039 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11040 });
11041 }
11042
11043 fn indent_selection(
11044 buffer: &MultiBuffer,
11045 snapshot: &MultiBufferSnapshot,
11046 selection: &mut Selection<Point>,
11047 edits: &mut Vec<(Range<Point>, String)>,
11048 delta_for_start_row: u32,
11049 cx: &App,
11050 ) -> u32 {
11051 let settings = buffer.language_settings_at(selection.start, cx);
11052 let tab_size = settings.tab_size.get();
11053 let indent_kind = if settings.hard_tabs {
11054 IndentKind::Tab
11055 } else {
11056 IndentKind::Space
11057 };
11058 let mut start_row = selection.start.row;
11059 let mut end_row = selection.end.row + 1;
11060
11061 // If a selection ends at the beginning of a line, don't indent
11062 // that last line.
11063 if selection.end.column == 0 && selection.end.row > selection.start.row {
11064 end_row -= 1;
11065 }
11066
11067 // Avoid re-indenting a row that has already been indented by a
11068 // previous selection, but still update this selection's column
11069 // to reflect that indentation.
11070 if delta_for_start_row > 0 {
11071 start_row += 1;
11072 selection.start.column += delta_for_start_row;
11073 if selection.end.row == selection.start.row {
11074 selection.end.column += delta_for_start_row;
11075 }
11076 }
11077
11078 let mut delta_for_end_row = 0;
11079 let has_multiple_rows = start_row + 1 != end_row;
11080 for row in start_row..end_row {
11081 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11082 let indent_delta = match (current_indent.kind, indent_kind) {
11083 (IndentKind::Space, IndentKind::Space) => {
11084 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11085 IndentSize::spaces(columns_to_next_tab_stop)
11086 }
11087 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11088 (_, IndentKind::Tab) => IndentSize::tab(),
11089 };
11090
11091 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11092 0
11093 } else {
11094 selection.start.column
11095 };
11096 let row_start = Point::new(row, start);
11097 edits.push((
11098 row_start..row_start,
11099 indent_delta.chars().collect::<String>(),
11100 ));
11101
11102 // Update this selection's endpoints to reflect the indentation.
11103 if row == selection.start.row {
11104 selection.start.column += indent_delta.len;
11105 }
11106 if row == selection.end.row {
11107 selection.end.column += indent_delta.len;
11108 delta_for_end_row = indent_delta.len;
11109 }
11110 }
11111
11112 if selection.start.row == selection.end.row {
11113 delta_for_start_row + delta_for_end_row
11114 } else {
11115 delta_for_end_row
11116 }
11117 }
11118
11119 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11120 if self.read_only(cx) {
11121 return;
11122 }
11123 if self.mode.is_single_line() {
11124 cx.propagate();
11125 return;
11126 }
11127
11128 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11129 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11130 let selections = self.selections.all::<Point>(&display_map);
11131 let mut deletion_ranges = Vec::new();
11132 let mut last_outdent = None;
11133 {
11134 let buffer = self.buffer.read(cx);
11135 let snapshot = buffer.snapshot(cx);
11136 for selection in &selections {
11137 let settings = buffer.language_settings_at(selection.start, cx);
11138 let tab_size = settings.tab_size.get();
11139 let mut rows = selection.spanned_rows(false, &display_map);
11140
11141 // Avoid re-outdenting a row that has already been outdented by a
11142 // previous selection.
11143 if let Some(last_row) = last_outdent
11144 && last_row == rows.start
11145 {
11146 rows.start = rows.start.next_row();
11147 }
11148 let has_multiple_rows = rows.len() > 1;
11149 for row in rows.iter_rows() {
11150 let indent_size = snapshot.indent_size_for_line(row);
11151 if indent_size.len > 0 {
11152 let deletion_len = match indent_size.kind {
11153 IndentKind::Space => {
11154 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11155 if columns_to_prev_tab_stop == 0 {
11156 tab_size
11157 } else {
11158 columns_to_prev_tab_stop
11159 }
11160 }
11161 IndentKind::Tab => 1,
11162 };
11163 let start = if has_multiple_rows
11164 || deletion_len > selection.start.column
11165 || indent_size.len < selection.start.column
11166 {
11167 0
11168 } else {
11169 selection.start.column - deletion_len
11170 };
11171 deletion_ranges.push(
11172 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11173 );
11174 last_outdent = Some(row);
11175 }
11176 }
11177 }
11178 }
11179
11180 self.transact(window, cx, |this, window, cx| {
11181 this.buffer.update(cx, |buffer, cx| {
11182 let empty_str: Arc<str> = Arc::default();
11183 buffer.edit(
11184 deletion_ranges
11185 .into_iter()
11186 .map(|range| (range, empty_str.clone())),
11187 None,
11188 cx,
11189 );
11190 });
11191 let selections = this
11192 .selections
11193 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11194 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11195 });
11196 }
11197
11198 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11199 if self.read_only(cx) {
11200 return;
11201 }
11202 if self.mode.is_single_line() {
11203 cx.propagate();
11204 return;
11205 }
11206
11207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11208 let selections = self
11209 .selections
11210 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11211 .into_iter()
11212 .map(|s| s.range());
11213
11214 self.transact(window, cx, |this, window, cx| {
11215 this.buffer.update(cx, |buffer, cx| {
11216 buffer.autoindent_ranges(selections, cx);
11217 });
11218 let selections = this
11219 .selections
11220 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11221 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11222 });
11223 }
11224
11225 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11228 let selections = self.selections.all::<Point>(&display_map);
11229
11230 let mut new_cursors = Vec::new();
11231 let mut edit_ranges = Vec::new();
11232 let mut selections = selections.iter().peekable();
11233 while let Some(selection) = selections.next() {
11234 let mut rows = selection.spanned_rows(false, &display_map);
11235
11236 // Accumulate contiguous regions of rows that we want to delete.
11237 while let Some(next_selection) = selections.peek() {
11238 let next_rows = next_selection.spanned_rows(false, &display_map);
11239 if next_rows.start <= rows.end {
11240 rows.end = next_rows.end;
11241 selections.next().unwrap();
11242 } else {
11243 break;
11244 }
11245 }
11246
11247 let buffer = display_map.buffer_snapshot();
11248 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11249 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11250 // If there's a line after the range, delete the \n from the end of the row range
11251 (
11252 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11253 rows.end,
11254 )
11255 } else {
11256 // If there isn't a line after the range, delete the \n from the line before the
11257 // start of the row range
11258 edit_start = edit_start.saturating_sub_usize(1);
11259 (buffer.len(), rows.start.previous_row())
11260 };
11261
11262 let text_layout_details = self.text_layout_details(window, cx);
11263 let x = display_map.x_for_display_point(
11264 selection.head().to_display_point(&display_map),
11265 &text_layout_details,
11266 );
11267 let row = Point::new(target_row.0, 0)
11268 .to_display_point(&display_map)
11269 .row();
11270 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11271
11272 new_cursors.push((
11273 selection.id,
11274 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11275 SelectionGoal::None,
11276 ));
11277 edit_ranges.push(edit_start..edit_end);
11278 }
11279
11280 self.transact(window, cx, |this, window, cx| {
11281 let buffer = this.buffer.update(cx, |buffer, cx| {
11282 let empty_str: Arc<str> = Arc::default();
11283 buffer.edit(
11284 edit_ranges
11285 .into_iter()
11286 .map(|range| (range, empty_str.clone())),
11287 None,
11288 cx,
11289 );
11290 buffer.snapshot(cx)
11291 });
11292 let new_selections = new_cursors
11293 .into_iter()
11294 .map(|(id, cursor, goal)| {
11295 let cursor = cursor.to_point(&buffer);
11296 Selection {
11297 id,
11298 start: cursor,
11299 end: cursor,
11300 reversed: false,
11301 goal,
11302 }
11303 })
11304 .collect();
11305
11306 this.change_selections(Default::default(), window, cx, |s| {
11307 s.select(new_selections);
11308 });
11309 });
11310 }
11311
11312 pub fn join_lines_impl(
11313 &mut self,
11314 insert_whitespace: bool,
11315 window: &mut Window,
11316 cx: &mut Context<Self>,
11317 ) {
11318 if self.read_only(cx) {
11319 return;
11320 }
11321 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11322 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11323 let start = MultiBufferRow(selection.start.row);
11324 // Treat single line selections as if they include the next line. Otherwise this action
11325 // would do nothing for single line selections individual cursors.
11326 let end = if selection.start.row == selection.end.row {
11327 MultiBufferRow(selection.start.row + 1)
11328 } else if selection.end.column == 0 {
11329 // If the selection ends at the start of a line, it's logically at the end of the
11330 // previous line (plus its newline).
11331 // Don't include the end line unless there's only one line selected.
11332 if selection.start.row + 1 == selection.end.row {
11333 MultiBufferRow(selection.end.row)
11334 } else {
11335 MultiBufferRow(selection.end.row - 1)
11336 }
11337 } else {
11338 MultiBufferRow(selection.end.row)
11339 };
11340
11341 if let Some(last_row_range) = row_ranges.last_mut()
11342 && start <= last_row_range.end
11343 {
11344 last_row_range.end = end;
11345 continue;
11346 }
11347 row_ranges.push(start..end);
11348 }
11349
11350 let snapshot = self.buffer.read(cx).snapshot(cx);
11351 let mut cursor_positions = Vec::new();
11352 for row_range in &row_ranges {
11353 let anchor = snapshot.anchor_before(Point::new(
11354 row_range.end.previous_row().0,
11355 snapshot.line_len(row_range.end.previous_row()),
11356 ));
11357 cursor_positions.push(anchor..anchor);
11358 }
11359
11360 self.transact(window, cx, |this, window, cx| {
11361 for row_range in row_ranges.into_iter().rev() {
11362 for row in row_range.iter_rows().rev() {
11363 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11364 let next_line_row = row.next_row();
11365 let indent = snapshot.indent_size_for_line(next_line_row);
11366 let mut join_start_column = indent.len;
11367
11368 if let Some(language_scope) =
11369 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11370 {
11371 let line_end =
11372 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11373 let line_text_after_indent = snapshot
11374 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11375 .collect::<String>();
11376
11377 if !line_text_after_indent.is_empty() {
11378 let block_prefix = language_scope
11379 .block_comment()
11380 .map(|c| c.prefix.as_ref())
11381 .filter(|p| !p.is_empty());
11382 let doc_prefix = language_scope
11383 .documentation_comment()
11384 .map(|c| c.prefix.as_ref())
11385 .filter(|p| !p.is_empty());
11386 let all_prefixes = language_scope
11387 .line_comment_prefixes()
11388 .iter()
11389 .map(|p| p.as_ref())
11390 .chain(block_prefix)
11391 .chain(doc_prefix)
11392 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11393
11394 let mut longest_prefix_len = None;
11395 for prefix in all_prefixes {
11396 let trimmed = prefix.trim_end();
11397 if line_text_after_indent.starts_with(trimmed) {
11398 let candidate_len =
11399 if line_text_after_indent.starts_with(prefix) {
11400 prefix.len()
11401 } else {
11402 trimmed.len()
11403 };
11404 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11405 longest_prefix_len = Some(candidate_len);
11406 }
11407 }
11408 }
11409
11410 if let Some(prefix_len) = longest_prefix_len {
11411 join_start_column =
11412 join_start_column.saturating_add(prefix_len as u32);
11413 }
11414 }
11415 }
11416
11417 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11418
11419 let replace = if snapshot.line_len(next_line_row) > join_start_column
11420 && insert_whitespace
11421 {
11422 " "
11423 } else {
11424 ""
11425 };
11426
11427 this.buffer.update(cx, |buffer, cx| {
11428 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11429 });
11430 }
11431 }
11432
11433 this.change_selections(Default::default(), window, cx, |s| {
11434 s.select_anchor_ranges(cursor_positions)
11435 });
11436 });
11437 }
11438
11439 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11441 self.join_lines_impl(true, window, cx);
11442 }
11443
11444 pub fn sort_lines_case_sensitive(
11445 &mut self,
11446 _: &SortLinesCaseSensitive,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11451 }
11452
11453 pub fn sort_lines_by_length(
11454 &mut self,
11455 _: &SortLinesByLength,
11456 window: &mut Window,
11457 cx: &mut Context<Self>,
11458 ) {
11459 self.manipulate_immutable_lines(window, cx, |lines| {
11460 lines.sort_by_key(|&line| line.chars().count())
11461 })
11462 }
11463
11464 pub fn sort_lines_case_insensitive(
11465 &mut self,
11466 _: &SortLinesCaseInsensitive,
11467 window: &mut Window,
11468 cx: &mut Context<Self>,
11469 ) {
11470 self.manipulate_immutable_lines(window, cx, |lines| {
11471 lines.sort_by_key(|line| line.to_lowercase())
11472 })
11473 }
11474
11475 pub fn unique_lines_case_insensitive(
11476 &mut self,
11477 _: &UniqueLinesCaseInsensitive,
11478 window: &mut Window,
11479 cx: &mut Context<Self>,
11480 ) {
11481 self.manipulate_immutable_lines(window, cx, |lines| {
11482 let mut seen = HashSet::default();
11483 lines.retain(|line| seen.insert(line.to_lowercase()));
11484 })
11485 }
11486
11487 pub fn unique_lines_case_sensitive(
11488 &mut self,
11489 _: &UniqueLinesCaseSensitive,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 self.manipulate_immutable_lines(window, cx, |lines| {
11494 let mut seen = HashSet::default();
11495 lines.retain(|line| seen.insert(*line));
11496 })
11497 }
11498
11499 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11500 let snapshot = self.buffer.read(cx).snapshot(cx);
11501 for selection in self.selections.disjoint_anchors_arc().iter() {
11502 if snapshot
11503 .language_at(selection.start)
11504 .and_then(|lang| lang.config().wrap_characters.as_ref())
11505 .is_some()
11506 {
11507 return true;
11508 }
11509 }
11510 false
11511 }
11512
11513 fn wrap_selections_in_tag(
11514 &mut self,
11515 _: &WrapSelectionsInTag,
11516 window: &mut Window,
11517 cx: &mut Context<Self>,
11518 ) {
11519 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11520
11521 let snapshot = self.buffer.read(cx).snapshot(cx);
11522
11523 let mut edits = Vec::new();
11524 let mut boundaries = Vec::new();
11525
11526 for selection in self
11527 .selections
11528 .all_adjusted(&self.display_snapshot(cx))
11529 .iter()
11530 {
11531 let Some(wrap_config) = snapshot
11532 .language_at(selection.start)
11533 .and_then(|lang| lang.config().wrap_characters.clone())
11534 else {
11535 continue;
11536 };
11537
11538 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11539 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11540
11541 let start_before = snapshot.anchor_before(selection.start);
11542 let end_after = snapshot.anchor_after(selection.end);
11543
11544 edits.push((start_before..start_before, open_tag));
11545 edits.push((end_after..end_after, close_tag));
11546
11547 boundaries.push((
11548 start_before,
11549 end_after,
11550 wrap_config.start_prefix.len(),
11551 wrap_config.end_suffix.len(),
11552 ));
11553 }
11554
11555 if edits.is_empty() {
11556 return;
11557 }
11558
11559 self.transact(window, cx, |this, window, cx| {
11560 let buffer = this.buffer.update(cx, |buffer, cx| {
11561 buffer.edit(edits, None, cx);
11562 buffer.snapshot(cx)
11563 });
11564
11565 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11566 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11567 boundaries.into_iter()
11568 {
11569 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11570 let close_offset = end_after
11571 .to_offset(&buffer)
11572 .saturating_sub_usize(end_suffix_len);
11573 new_selections.push(open_offset..open_offset);
11574 new_selections.push(close_offset..close_offset);
11575 }
11576
11577 this.change_selections(Default::default(), window, cx, |s| {
11578 s.select_ranges(new_selections);
11579 });
11580
11581 this.request_autoscroll(Autoscroll::fit(), cx);
11582 });
11583 }
11584
11585 pub fn toggle_read_only(
11586 &mut self,
11587 _: &workspace::ToggleReadOnlyFile,
11588 _: &mut Window,
11589 cx: &mut Context<Self>,
11590 ) {
11591 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11592 buffer.update(cx, |buffer, cx| {
11593 buffer.set_capability(
11594 match buffer.capability() {
11595 Capability::ReadWrite => Capability::Read,
11596 Capability::Read => Capability::ReadWrite,
11597 Capability::ReadOnly => Capability::ReadOnly,
11598 },
11599 cx,
11600 );
11601 })
11602 }
11603 }
11604
11605 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11606 let Some(project) = self.project.clone() else {
11607 return;
11608 };
11609 let task = self.reload(project, window, cx);
11610 self.detach_and_notify_err(task, window, cx);
11611 }
11612
11613 pub fn restore_file(
11614 &mut self,
11615 _: &::git::RestoreFile,
11616 window: &mut Window,
11617 cx: &mut Context<Self>,
11618 ) {
11619 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11620 let mut buffer_ids = HashSet::default();
11621 let snapshot = self.buffer().read(cx).snapshot(cx);
11622 for selection in self
11623 .selections
11624 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11625 {
11626 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11627 }
11628
11629 let buffer = self.buffer().read(cx);
11630 let ranges = buffer_ids
11631 .into_iter()
11632 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11633 .collect::<Vec<_>>();
11634
11635 self.restore_hunks_in_ranges(ranges, window, cx);
11636 }
11637
11638 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11640 let selections = self
11641 .selections
11642 .all(&self.display_snapshot(cx))
11643 .into_iter()
11644 .map(|s| s.range())
11645 .collect();
11646 self.restore_hunks_in_ranges(selections, window, cx);
11647 }
11648
11649 pub fn restore_hunks_in_ranges(
11650 &mut self,
11651 ranges: Vec<Range<Point>>,
11652 window: &mut Window,
11653 cx: &mut Context<Editor>,
11654 ) {
11655 if self.delegate_stage_and_restore {
11656 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11657 if !hunks.is_empty() {
11658 cx.emit(EditorEvent::RestoreRequested { hunks });
11659 }
11660 return;
11661 }
11662 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11663 self.transact(window, cx, |editor, window, cx| {
11664 editor.restore_diff_hunks(hunks, cx);
11665 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11666 selections.refresh()
11667 });
11668 });
11669 }
11670
11671 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11672 let mut revert_changes = HashMap::default();
11673 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11674 for (buffer_id, hunks) in &chunk_by {
11675 let hunks = hunks.collect::<Vec<_>>();
11676 for hunk in &hunks {
11677 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11678 }
11679 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11680 }
11681 if !revert_changes.is_empty() {
11682 self.buffer().update(cx, |multi_buffer, cx| {
11683 for (buffer_id, changes) in revert_changes {
11684 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11685 buffer.update(cx, |buffer, cx| {
11686 buffer.edit(
11687 changes
11688 .into_iter()
11689 .map(|(range, text)| (range, text.to_string())),
11690 None,
11691 cx,
11692 );
11693 });
11694 }
11695 }
11696 });
11697 }
11698 }
11699
11700 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11701 if let Some(status) = self
11702 .addons
11703 .iter()
11704 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11705 {
11706 return Some(status);
11707 }
11708 self.project
11709 .as_ref()?
11710 .read(cx)
11711 .status_for_buffer_id(buffer_id, cx)
11712 }
11713
11714 pub fn open_active_item_in_terminal(
11715 &mut self,
11716 _: &OpenInTerminal,
11717 window: &mut Window,
11718 cx: &mut Context<Self>,
11719 ) {
11720 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11721 let project_path = buffer.read(cx).project_path(cx)?;
11722 let project = self.project()?.read(cx);
11723 let entry = project.entry_for_path(&project_path, cx)?;
11724 let parent = match &entry.canonical_path {
11725 Some(canonical_path) => canonical_path.to_path_buf(),
11726 None => project.absolute_path(&project_path, cx)?,
11727 }
11728 .parent()?
11729 .to_path_buf();
11730 Some(parent)
11731 }) {
11732 window.dispatch_action(
11733 OpenTerminal {
11734 working_directory,
11735 local: false,
11736 }
11737 .boxed_clone(),
11738 cx,
11739 );
11740 }
11741 }
11742
11743 fn set_breakpoint_context_menu(
11744 &mut self,
11745 display_row: DisplayRow,
11746 position: Option<Anchor>,
11747 clicked_point: gpui::Point<Pixels>,
11748 window: &mut Window,
11749 cx: &mut Context<Self>,
11750 ) {
11751 let source = self
11752 .buffer
11753 .read(cx)
11754 .snapshot(cx)
11755 .anchor_before(Point::new(display_row.0, 0u32));
11756
11757 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11758
11759 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11760 self,
11761 source,
11762 clicked_point,
11763 context_menu,
11764 window,
11765 cx,
11766 );
11767 }
11768
11769 fn add_edit_breakpoint_block(
11770 &mut self,
11771 anchor: Anchor,
11772 breakpoint: &Breakpoint,
11773 edit_action: BreakpointPromptEditAction,
11774 window: &mut Window,
11775 cx: &mut Context<Self>,
11776 ) {
11777 let weak_editor = cx.weak_entity();
11778 let bp_prompt = cx.new(|cx| {
11779 BreakpointPromptEditor::new(
11780 weak_editor,
11781 anchor,
11782 breakpoint.clone(),
11783 edit_action,
11784 window,
11785 cx,
11786 )
11787 });
11788
11789 let height = bp_prompt.update(cx, |this, cx| {
11790 this.prompt
11791 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11792 });
11793 let cloned_prompt = bp_prompt.clone();
11794 let blocks = vec![BlockProperties {
11795 style: BlockStyle::Sticky,
11796 placement: BlockPlacement::Above(anchor),
11797 height: Some(height),
11798 render: Arc::new(move |cx| {
11799 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11800 cloned_prompt.clone().into_any_element()
11801 }),
11802 priority: 0,
11803 }];
11804
11805 let focus_handle = bp_prompt.focus_handle(cx);
11806 window.focus(&focus_handle, cx);
11807
11808 let block_ids = self.insert_blocks(blocks, None, cx);
11809 bp_prompt.update(cx, |prompt, _| {
11810 prompt.add_block_ids(block_ids);
11811 });
11812 }
11813
11814 pub(crate) fn breakpoint_at_row(
11815 &self,
11816 row: u32,
11817 window: &mut Window,
11818 cx: &mut Context<Self>,
11819 ) -> Option<(Anchor, Breakpoint)> {
11820 let snapshot = self.snapshot(window, cx);
11821 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11822
11823 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11824 }
11825
11826 pub(crate) fn breakpoint_at_anchor(
11827 &self,
11828 breakpoint_position: Anchor,
11829 snapshot: &EditorSnapshot,
11830 cx: &mut Context<Self>,
11831 ) -> Option<(Anchor, Breakpoint)> {
11832 let buffer = self
11833 .buffer
11834 .read(cx)
11835 .buffer_for_anchor(breakpoint_position, cx)?;
11836
11837 let enclosing_excerpt = breakpoint_position.excerpt_id;
11838 let buffer_snapshot = buffer.read(cx).snapshot();
11839
11840 let row = buffer_snapshot
11841 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11842 .row;
11843
11844 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11845 let anchor_end = snapshot
11846 .buffer_snapshot()
11847 .anchor_after(Point::new(row, line_len));
11848
11849 self.breakpoint_store
11850 .as_ref()?
11851 .read_with(cx, |breakpoint_store, cx| {
11852 breakpoint_store
11853 .breakpoints(
11854 &buffer,
11855 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11856 &buffer_snapshot,
11857 cx,
11858 )
11859 .next()
11860 .and_then(|(bp, _)| {
11861 let breakpoint_row = buffer_snapshot
11862 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11863 .row;
11864
11865 if breakpoint_row == row {
11866 snapshot
11867 .buffer_snapshot()
11868 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11869 .map(|position| (position, bp.bp.clone()))
11870 } else {
11871 None
11872 }
11873 })
11874 })
11875 }
11876
11877 pub fn edit_log_breakpoint(
11878 &mut self,
11879 _: &EditLogBreakpoint,
11880 window: &mut Window,
11881 cx: &mut Context<Self>,
11882 ) {
11883 if self.breakpoint_store.is_none() {
11884 return;
11885 }
11886
11887 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11888 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11889 message: None,
11890 state: BreakpointState::Enabled,
11891 condition: None,
11892 hit_condition: None,
11893 });
11894
11895 self.add_edit_breakpoint_block(
11896 anchor,
11897 &breakpoint,
11898 BreakpointPromptEditAction::Log,
11899 window,
11900 cx,
11901 );
11902 }
11903 }
11904
11905 fn breakpoints_at_cursors(
11906 &self,
11907 window: &mut Window,
11908 cx: &mut Context<Self>,
11909 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11910 let snapshot = self.snapshot(window, cx);
11911 let cursors = self
11912 .selections
11913 .disjoint_anchors_arc()
11914 .iter()
11915 .map(|selection| {
11916 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11917
11918 let breakpoint_position = self
11919 .breakpoint_at_row(cursor_position.row, window, cx)
11920 .map(|bp| bp.0)
11921 .unwrap_or_else(|| {
11922 snapshot
11923 .display_snapshot
11924 .buffer_snapshot()
11925 .anchor_after(Point::new(cursor_position.row, 0))
11926 });
11927
11928 let breakpoint = self
11929 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11930 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11931
11932 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11933 })
11934 // 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.
11935 .collect::<HashMap<Anchor, _>>();
11936
11937 cursors.into_iter().collect()
11938 }
11939
11940 pub fn enable_breakpoint(
11941 &mut self,
11942 _: &crate::actions::EnableBreakpoint,
11943 window: &mut Window,
11944 cx: &mut Context<Self>,
11945 ) {
11946 if self.breakpoint_store.is_none() {
11947 return;
11948 }
11949
11950 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11951 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11952 continue;
11953 };
11954 self.edit_breakpoint_at_anchor(
11955 anchor,
11956 breakpoint,
11957 BreakpointEditAction::InvertState,
11958 cx,
11959 );
11960 }
11961 }
11962
11963 pub fn disable_breakpoint(
11964 &mut self,
11965 _: &crate::actions::DisableBreakpoint,
11966 window: &mut Window,
11967 cx: &mut Context<Self>,
11968 ) {
11969 if self.breakpoint_store.is_none() {
11970 return;
11971 }
11972
11973 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11974 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11975 continue;
11976 };
11977 self.edit_breakpoint_at_anchor(
11978 anchor,
11979 breakpoint,
11980 BreakpointEditAction::InvertState,
11981 cx,
11982 );
11983 }
11984 }
11985
11986 pub fn toggle_breakpoint(
11987 &mut self,
11988 _: &crate::actions::ToggleBreakpoint,
11989 window: &mut Window,
11990 cx: &mut Context<Self>,
11991 ) {
11992 if self.breakpoint_store.is_none() {
11993 return;
11994 }
11995
11996 let snapshot = self.snapshot(window, cx);
11997 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11998 if self.gutter_breakpoint_indicator.0.is_some() {
11999 let display_row = anchor
12000 .to_point(snapshot.buffer_snapshot())
12001 .to_display_point(&snapshot.display_snapshot)
12002 .row();
12003 self.update_breakpoint_collision_on_toggle(
12004 display_row,
12005 &BreakpointEditAction::Toggle,
12006 );
12007 }
12008
12009 if let Some(breakpoint) = breakpoint {
12010 self.edit_breakpoint_at_anchor(
12011 anchor,
12012 breakpoint,
12013 BreakpointEditAction::Toggle,
12014 cx,
12015 );
12016 } else {
12017 self.edit_breakpoint_at_anchor(
12018 anchor,
12019 Breakpoint::new_standard(),
12020 BreakpointEditAction::Toggle,
12021 cx,
12022 );
12023 }
12024 }
12025 }
12026
12027 fn update_breakpoint_collision_on_toggle(
12028 &mut self,
12029 display_row: DisplayRow,
12030 edit_action: &BreakpointEditAction,
12031 ) {
12032 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12033 if breakpoint_indicator.display_row == display_row
12034 && matches!(edit_action, BreakpointEditAction::Toggle)
12035 {
12036 breakpoint_indicator.collides_with_existing_breakpoint =
12037 !breakpoint_indicator.collides_with_existing_breakpoint;
12038 }
12039 }
12040 }
12041
12042 pub fn edit_breakpoint_at_anchor(
12043 &mut self,
12044 breakpoint_position: Anchor,
12045 breakpoint: Breakpoint,
12046 edit_action: BreakpointEditAction,
12047 cx: &mut Context<Self>,
12048 ) {
12049 let Some(breakpoint_store) = &self.breakpoint_store else {
12050 return;
12051 };
12052
12053 let Some(buffer) = self
12054 .buffer
12055 .read(cx)
12056 .buffer_for_anchor(breakpoint_position, cx)
12057 else {
12058 return;
12059 };
12060
12061 breakpoint_store.update(cx, |breakpoint_store, cx| {
12062 breakpoint_store.toggle_breakpoint(
12063 buffer,
12064 BreakpointWithPosition {
12065 position: breakpoint_position.text_anchor,
12066 bp: breakpoint,
12067 },
12068 edit_action,
12069 cx,
12070 );
12071 });
12072
12073 cx.notify();
12074 }
12075
12076 #[cfg(any(test, feature = "test-support"))]
12077 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12078 self.breakpoint_store.clone()
12079 }
12080
12081 pub fn prepare_restore_change(
12082 &self,
12083 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12084 hunk: &MultiBufferDiffHunk,
12085 cx: &mut App,
12086 ) -> Option<()> {
12087 if hunk.is_created_file() {
12088 return None;
12089 }
12090 let buffer = self.buffer.read(cx);
12091 let diff = buffer.diff_for(hunk.buffer_id)?;
12092 let buffer = buffer.buffer(hunk.buffer_id)?;
12093 let buffer = buffer.read(cx);
12094 let original_text = diff
12095 .read(cx)
12096 .base_text(cx)
12097 .as_rope()
12098 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12099 let buffer_snapshot = buffer.snapshot();
12100 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12101 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12102 probe
12103 .0
12104 .start
12105 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12106 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12107 }) {
12108 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12109 Some(())
12110 } else {
12111 None
12112 }
12113 }
12114
12115 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12116 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12117 }
12118
12119 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12120 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12121 }
12122
12123 pub fn rotate_selections_forward(
12124 &mut self,
12125 _: &RotateSelectionsForward,
12126 window: &mut Window,
12127 cx: &mut Context<Self>,
12128 ) {
12129 self.rotate_selections(window, cx, false)
12130 }
12131
12132 pub fn rotate_selections_backward(
12133 &mut self,
12134 _: &RotateSelectionsBackward,
12135 window: &mut Window,
12136 cx: &mut Context<Self>,
12137 ) {
12138 self.rotate_selections(window, cx, true)
12139 }
12140
12141 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12143 let display_snapshot = self.display_snapshot(cx);
12144 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12145
12146 if selections.len() < 2 {
12147 return;
12148 }
12149
12150 let (edits, new_selections) = {
12151 let buffer = self.buffer.read(cx).read(cx);
12152 let has_selections = selections.iter().any(|s| !s.is_empty());
12153 if has_selections {
12154 let mut selected_texts: Vec<String> = selections
12155 .iter()
12156 .map(|selection| {
12157 buffer
12158 .text_for_range(selection.start..selection.end)
12159 .collect()
12160 })
12161 .collect();
12162
12163 if reverse {
12164 selected_texts.rotate_left(1);
12165 } else {
12166 selected_texts.rotate_right(1);
12167 }
12168
12169 let mut offset_delta: i64 = 0;
12170 let mut new_selections = Vec::new();
12171 let edits: Vec<_> = selections
12172 .iter()
12173 .zip(selected_texts.iter())
12174 .map(|(selection, new_text)| {
12175 let old_len = (selection.end.0 - selection.start.0) as i64;
12176 let new_len = new_text.len() as i64;
12177 let adjusted_start =
12178 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12179 let adjusted_end =
12180 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12181
12182 new_selections.push(Selection {
12183 id: selection.id,
12184 start: adjusted_start,
12185 end: adjusted_end,
12186 reversed: selection.reversed,
12187 goal: selection.goal,
12188 });
12189
12190 offset_delta += new_len - old_len;
12191 (selection.start..selection.end, new_text.clone())
12192 })
12193 .collect();
12194 (edits, new_selections)
12195 } else {
12196 let mut all_rows: Vec<u32> = selections
12197 .iter()
12198 .map(|selection| buffer.offset_to_point(selection.start).row)
12199 .collect();
12200 all_rows.sort_unstable();
12201 all_rows.dedup();
12202
12203 if all_rows.len() < 2 {
12204 return;
12205 }
12206
12207 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12208 .iter()
12209 .map(|&row| {
12210 let start = Point::new(row, 0);
12211 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12212 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12213 })
12214 .collect();
12215
12216 let mut line_texts: Vec<String> = line_ranges
12217 .iter()
12218 .map(|range| buffer.text_for_range(range.clone()).collect())
12219 .collect();
12220
12221 if reverse {
12222 line_texts.rotate_left(1);
12223 } else {
12224 line_texts.rotate_right(1);
12225 }
12226
12227 let edits = line_ranges
12228 .iter()
12229 .zip(line_texts.iter())
12230 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12231 .collect();
12232
12233 let num_rows = all_rows.len();
12234 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12235 .iter()
12236 .enumerate()
12237 .map(|(i, &row)| (row, i))
12238 .collect();
12239
12240 // Compute new line start offsets after rotation (handles CRLF)
12241 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12242 let first_line_start = line_ranges[0].start.0;
12243 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12244 for text in line_texts.iter().take(num_rows - 1) {
12245 let prev_start = *new_line_starts.last().unwrap();
12246 new_line_starts.push(prev_start + text.len() + newline_len);
12247 }
12248
12249 let new_selections = selections
12250 .iter()
12251 .map(|selection| {
12252 let point = buffer.offset_to_point(selection.start);
12253 let old_index = row_to_index[&point.row];
12254 let new_index = if reverse {
12255 (old_index + num_rows - 1) % num_rows
12256 } else {
12257 (old_index + 1) % num_rows
12258 };
12259 let new_offset =
12260 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12261 Selection {
12262 id: selection.id,
12263 start: new_offset,
12264 end: new_offset,
12265 reversed: selection.reversed,
12266 goal: selection.goal,
12267 }
12268 })
12269 .collect();
12270
12271 (edits, new_selections)
12272 }
12273 };
12274
12275 self.transact(window, cx, |this, window, cx| {
12276 this.buffer.update(cx, |buffer, cx| {
12277 buffer.edit(edits, None, cx);
12278 });
12279 this.change_selections(Default::default(), window, cx, |s| {
12280 s.select(new_selections);
12281 });
12282 });
12283 }
12284
12285 fn manipulate_lines<M>(
12286 &mut self,
12287 window: &mut Window,
12288 cx: &mut Context<Self>,
12289 mut manipulate: M,
12290 ) where
12291 M: FnMut(&str) -> LineManipulationResult,
12292 {
12293 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12294
12295 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12296 let buffer = self.buffer.read(cx).snapshot(cx);
12297
12298 let mut edits = Vec::new();
12299
12300 let selections = self.selections.all::<Point>(&display_map);
12301 let mut selections = selections.iter().peekable();
12302 let mut contiguous_row_selections = Vec::new();
12303 let mut new_selections = Vec::new();
12304 let mut added_lines = 0;
12305 let mut removed_lines = 0;
12306
12307 while let Some(selection) = selections.next() {
12308 let (start_row, end_row) = consume_contiguous_rows(
12309 &mut contiguous_row_selections,
12310 selection,
12311 &display_map,
12312 &mut selections,
12313 );
12314
12315 let start_point = Point::new(start_row.0, 0);
12316 let end_point = Point::new(
12317 end_row.previous_row().0,
12318 buffer.line_len(end_row.previous_row()),
12319 );
12320 let text = buffer
12321 .text_for_range(start_point..end_point)
12322 .collect::<String>();
12323
12324 let LineManipulationResult {
12325 new_text,
12326 line_count_before,
12327 line_count_after,
12328 } = manipulate(&text);
12329
12330 edits.push((start_point..end_point, new_text));
12331
12332 // Selections must change based on added and removed line count
12333 let start_row =
12334 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12335 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12336 new_selections.push(Selection {
12337 id: selection.id,
12338 start: start_row,
12339 end: end_row,
12340 goal: SelectionGoal::None,
12341 reversed: selection.reversed,
12342 });
12343
12344 if line_count_after > line_count_before {
12345 added_lines += line_count_after - line_count_before;
12346 } else if line_count_before > line_count_after {
12347 removed_lines += line_count_before - line_count_after;
12348 }
12349 }
12350
12351 self.transact(window, cx, |this, window, cx| {
12352 let buffer = this.buffer.update(cx, |buffer, cx| {
12353 buffer.edit(edits, None, cx);
12354 buffer.snapshot(cx)
12355 });
12356
12357 // Recalculate offsets on newly edited buffer
12358 let new_selections = new_selections
12359 .iter()
12360 .map(|s| {
12361 let start_point = Point::new(s.start.0, 0);
12362 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12363 Selection {
12364 id: s.id,
12365 start: buffer.point_to_offset(start_point),
12366 end: buffer.point_to_offset(end_point),
12367 goal: s.goal,
12368 reversed: s.reversed,
12369 }
12370 })
12371 .collect();
12372
12373 this.change_selections(Default::default(), window, cx, |s| {
12374 s.select(new_selections);
12375 });
12376
12377 this.request_autoscroll(Autoscroll::fit(), cx);
12378 });
12379 }
12380
12381 fn manipulate_immutable_lines<Fn>(
12382 &mut self,
12383 window: &mut Window,
12384 cx: &mut Context<Self>,
12385 mut callback: Fn,
12386 ) where
12387 Fn: FnMut(&mut Vec<&str>),
12388 {
12389 self.manipulate_lines(window, cx, |text| {
12390 let mut lines: Vec<&str> = text.split('\n').collect();
12391 let line_count_before = lines.len();
12392
12393 callback(&mut lines);
12394
12395 LineManipulationResult {
12396 new_text: lines.join("\n"),
12397 line_count_before,
12398 line_count_after: lines.len(),
12399 }
12400 });
12401 }
12402
12403 fn manipulate_mutable_lines<Fn>(
12404 &mut self,
12405 window: &mut Window,
12406 cx: &mut Context<Self>,
12407 mut callback: Fn,
12408 ) where
12409 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12410 {
12411 self.manipulate_lines(window, cx, |text| {
12412 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12413 let line_count_before = lines.len();
12414
12415 callback(&mut lines);
12416
12417 LineManipulationResult {
12418 new_text: lines.join("\n"),
12419 line_count_before,
12420 line_count_after: lines.len(),
12421 }
12422 });
12423 }
12424
12425 pub fn convert_indentation_to_spaces(
12426 &mut self,
12427 _: &ConvertIndentationToSpaces,
12428 window: &mut Window,
12429 cx: &mut Context<Self>,
12430 ) {
12431 let settings = self.buffer.read(cx).language_settings(cx);
12432 let tab_size = settings.tab_size.get() as usize;
12433
12434 self.manipulate_mutable_lines(window, cx, |lines| {
12435 // Allocates a reasonably sized scratch buffer once for the whole loop
12436 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12437 // Avoids recomputing spaces that could be inserted many times
12438 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12439 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12440 .collect();
12441
12442 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12443 let mut chars = line.as_ref().chars();
12444 let mut col = 0;
12445 let mut changed = false;
12446
12447 for ch in chars.by_ref() {
12448 match ch {
12449 ' ' => {
12450 reindented_line.push(' ');
12451 col += 1;
12452 }
12453 '\t' => {
12454 // \t are converted to spaces depending on the current column
12455 let spaces_len = tab_size - (col % tab_size);
12456 reindented_line.extend(&space_cache[spaces_len - 1]);
12457 col += spaces_len;
12458 changed = true;
12459 }
12460 _ => {
12461 // If we dont append before break, the character is consumed
12462 reindented_line.push(ch);
12463 break;
12464 }
12465 }
12466 }
12467
12468 if !changed {
12469 reindented_line.clear();
12470 continue;
12471 }
12472 // Append the rest of the line and replace old reference with new one
12473 reindented_line.extend(chars);
12474 *line = Cow::Owned(reindented_line.clone());
12475 reindented_line.clear();
12476 }
12477 });
12478 }
12479
12480 pub fn convert_indentation_to_tabs(
12481 &mut self,
12482 _: &ConvertIndentationToTabs,
12483 window: &mut Window,
12484 cx: &mut Context<Self>,
12485 ) {
12486 let settings = self.buffer.read(cx).language_settings(cx);
12487 let tab_size = settings.tab_size.get() as usize;
12488
12489 self.manipulate_mutable_lines(window, cx, |lines| {
12490 // Allocates a reasonably sized buffer once for the whole loop
12491 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12492 // Avoids recomputing spaces that could be inserted many times
12493 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12494 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12495 .collect();
12496
12497 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12498 let mut chars = line.chars();
12499 let mut spaces_count = 0;
12500 let mut first_non_indent_char = None;
12501 let mut changed = false;
12502
12503 for ch in chars.by_ref() {
12504 match ch {
12505 ' ' => {
12506 // Keep track of spaces. Append \t when we reach tab_size
12507 spaces_count += 1;
12508 changed = true;
12509 if spaces_count == tab_size {
12510 reindented_line.push('\t');
12511 spaces_count = 0;
12512 }
12513 }
12514 '\t' => {
12515 reindented_line.push('\t');
12516 spaces_count = 0;
12517 }
12518 _ => {
12519 // Dont append it yet, we might have remaining spaces
12520 first_non_indent_char = Some(ch);
12521 break;
12522 }
12523 }
12524 }
12525
12526 if !changed {
12527 reindented_line.clear();
12528 continue;
12529 }
12530 // Remaining spaces that didn't make a full tab stop
12531 if spaces_count > 0 {
12532 reindented_line.extend(&space_cache[spaces_count - 1]);
12533 }
12534 // If we consume an extra character that was not indentation, add it back
12535 if let Some(extra_char) = first_non_indent_char {
12536 reindented_line.push(extra_char);
12537 }
12538 // Append the rest of the line and replace old reference with new one
12539 reindented_line.extend(chars);
12540 *line = Cow::Owned(reindented_line.clone());
12541 reindented_line.clear();
12542 }
12543 });
12544 }
12545
12546 pub fn convert_to_upper_case(
12547 &mut self,
12548 _: &ConvertToUpperCase,
12549 window: &mut Window,
12550 cx: &mut Context<Self>,
12551 ) {
12552 self.manipulate_text(window, cx, |text| text.to_uppercase())
12553 }
12554
12555 pub fn convert_to_lower_case(
12556 &mut self,
12557 _: &ConvertToLowerCase,
12558 window: &mut Window,
12559 cx: &mut Context<Self>,
12560 ) {
12561 self.manipulate_text(window, cx, |text| text.to_lowercase())
12562 }
12563
12564 pub fn convert_to_title_case(
12565 &mut self,
12566 _: &ConvertToTitleCase,
12567 window: &mut Window,
12568 cx: &mut Context<Self>,
12569 ) {
12570 self.manipulate_text(window, cx, |text| {
12571 text.split('\n')
12572 .map(|line| line.to_case(Case::Title))
12573 .join("\n")
12574 })
12575 }
12576
12577 pub fn convert_to_snake_case(
12578 &mut self,
12579 _: &ConvertToSnakeCase,
12580 window: &mut Window,
12581 cx: &mut Context<Self>,
12582 ) {
12583 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12584 }
12585
12586 pub fn convert_to_kebab_case(
12587 &mut self,
12588 _: &ConvertToKebabCase,
12589 window: &mut Window,
12590 cx: &mut Context<Self>,
12591 ) {
12592 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12593 }
12594
12595 pub fn convert_to_upper_camel_case(
12596 &mut self,
12597 _: &ConvertToUpperCamelCase,
12598 window: &mut Window,
12599 cx: &mut Context<Self>,
12600 ) {
12601 self.manipulate_text(window, cx, |text| {
12602 text.split('\n')
12603 .map(|line| line.to_case(Case::UpperCamel))
12604 .join("\n")
12605 })
12606 }
12607
12608 pub fn convert_to_lower_camel_case(
12609 &mut self,
12610 _: &ConvertToLowerCamelCase,
12611 window: &mut Window,
12612 cx: &mut Context<Self>,
12613 ) {
12614 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12615 }
12616
12617 pub fn convert_to_opposite_case(
12618 &mut self,
12619 _: &ConvertToOppositeCase,
12620 window: &mut Window,
12621 cx: &mut Context<Self>,
12622 ) {
12623 self.manipulate_text(window, cx, |text| {
12624 text.chars()
12625 .fold(String::with_capacity(text.len()), |mut t, c| {
12626 if c.is_uppercase() {
12627 t.extend(c.to_lowercase());
12628 } else {
12629 t.extend(c.to_uppercase());
12630 }
12631 t
12632 })
12633 })
12634 }
12635
12636 pub fn convert_to_sentence_case(
12637 &mut self,
12638 _: &ConvertToSentenceCase,
12639 window: &mut Window,
12640 cx: &mut Context<Self>,
12641 ) {
12642 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12643 }
12644
12645 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12646 self.manipulate_text(window, cx, |text| {
12647 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12648 if has_upper_case_characters {
12649 text.to_lowercase()
12650 } else {
12651 text.to_uppercase()
12652 }
12653 })
12654 }
12655
12656 pub fn convert_to_rot13(
12657 &mut self,
12658 _: &ConvertToRot13,
12659 window: &mut Window,
12660 cx: &mut Context<Self>,
12661 ) {
12662 self.manipulate_text(window, cx, |text| {
12663 text.chars()
12664 .map(|c| match c {
12665 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12666 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12667 _ => c,
12668 })
12669 .collect()
12670 })
12671 }
12672
12673 pub fn convert_to_rot47(
12674 &mut self,
12675 _: &ConvertToRot47,
12676 window: &mut Window,
12677 cx: &mut Context<Self>,
12678 ) {
12679 self.manipulate_text(window, cx, |text| {
12680 text.chars()
12681 .map(|c| {
12682 let code_point = c as u32;
12683 if code_point >= 33 && code_point <= 126 {
12684 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12685 }
12686 c
12687 })
12688 .collect()
12689 })
12690 }
12691
12692 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12693 where
12694 Fn: FnMut(&str) -> String,
12695 {
12696 let buffer = self.buffer.read(cx).snapshot(cx);
12697
12698 let mut new_selections = Vec::new();
12699 let mut edits = Vec::new();
12700 let mut selection_adjustment = 0isize;
12701
12702 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12703 let selection_is_empty = selection.is_empty();
12704
12705 let (start, end) = if selection_is_empty {
12706 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12707 (word_range.start, word_range.end)
12708 } else {
12709 (
12710 buffer.point_to_offset(selection.start),
12711 buffer.point_to_offset(selection.end),
12712 )
12713 };
12714
12715 let text = buffer.text_for_range(start..end).collect::<String>();
12716 let old_length = text.len() as isize;
12717 let text = callback(&text);
12718
12719 new_selections.push(Selection {
12720 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12721 end: MultiBufferOffset(
12722 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12723 ),
12724 goal: SelectionGoal::None,
12725 id: selection.id,
12726 reversed: selection.reversed,
12727 });
12728
12729 selection_adjustment += old_length - text.len() as isize;
12730
12731 edits.push((start..end, text));
12732 }
12733
12734 self.transact(window, cx, |this, window, cx| {
12735 this.buffer.update(cx, |buffer, cx| {
12736 buffer.edit(edits, None, cx);
12737 });
12738
12739 this.change_selections(Default::default(), window, cx, |s| {
12740 s.select(new_selections);
12741 });
12742
12743 this.request_autoscroll(Autoscroll::fit(), cx);
12744 });
12745 }
12746
12747 pub fn move_selection_on_drop(
12748 &mut self,
12749 selection: &Selection<Anchor>,
12750 target: DisplayPoint,
12751 is_cut: bool,
12752 window: &mut Window,
12753 cx: &mut Context<Self>,
12754 ) {
12755 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12756 let buffer = display_map.buffer_snapshot();
12757 let mut edits = Vec::new();
12758 let insert_point = display_map
12759 .clip_point(target, Bias::Left)
12760 .to_point(&display_map);
12761 let text = buffer
12762 .text_for_range(selection.start..selection.end)
12763 .collect::<String>();
12764 if is_cut {
12765 edits.push(((selection.start..selection.end), String::new()));
12766 }
12767 let insert_anchor = buffer.anchor_before(insert_point);
12768 edits.push(((insert_anchor..insert_anchor), text));
12769 let last_edit_start = insert_anchor.bias_left(buffer);
12770 let last_edit_end = insert_anchor.bias_right(buffer);
12771 self.transact(window, cx, |this, window, cx| {
12772 this.buffer.update(cx, |buffer, cx| {
12773 buffer.edit(edits, None, cx);
12774 });
12775 this.change_selections(Default::default(), window, cx, |s| {
12776 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12777 });
12778 });
12779 }
12780
12781 pub fn clear_selection_drag_state(&mut self) {
12782 self.selection_drag_state = SelectionDragState::None;
12783 }
12784
12785 pub fn duplicate(
12786 &mut self,
12787 upwards: bool,
12788 whole_lines: bool,
12789 window: &mut Window,
12790 cx: &mut Context<Self>,
12791 ) {
12792 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12793
12794 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12795 let buffer = display_map.buffer_snapshot();
12796 let selections = self.selections.all::<Point>(&display_map);
12797
12798 let mut edits = Vec::new();
12799 let mut selections_iter = selections.iter().peekable();
12800 while let Some(selection) = selections_iter.next() {
12801 let mut rows = selection.spanned_rows(false, &display_map);
12802 // duplicate line-wise
12803 if whole_lines || selection.start == selection.end {
12804 // Avoid duplicating the same lines twice.
12805 while let Some(next_selection) = selections_iter.peek() {
12806 let next_rows = next_selection.spanned_rows(false, &display_map);
12807 if next_rows.start < rows.end {
12808 rows.end = next_rows.end;
12809 selections_iter.next().unwrap();
12810 } else {
12811 break;
12812 }
12813 }
12814
12815 // Copy the text from the selected row region and splice it either at the start
12816 // or end of the region.
12817 let start = Point::new(rows.start.0, 0);
12818 let end = Point::new(
12819 rows.end.previous_row().0,
12820 buffer.line_len(rows.end.previous_row()),
12821 );
12822
12823 let mut text = buffer.text_for_range(start..end).collect::<String>();
12824
12825 let insert_location = if upwards {
12826 // When duplicating upward, we need to insert before the current line.
12827 // If we're on the last line and it doesn't end with a newline,
12828 // we need to add a newline before the duplicated content.
12829 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12830 && buffer.max_point().column > 0
12831 && !text.ends_with('\n');
12832
12833 if needs_leading_newline {
12834 text.insert(0, '\n');
12835 end
12836 } else {
12837 text.push('\n');
12838 Point::new(rows.start.0, 0)
12839 }
12840 } else {
12841 text.push('\n');
12842 start
12843 };
12844 edits.push((insert_location..insert_location, text));
12845 } else {
12846 // duplicate character-wise
12847 let start = selection.start;
12848 let end = selection.end;
12849 let text = buffer.text_for_range(start..end).collect::<String>();
12850 edits.push((selection.end..selection.end, text));
12851 }
12852 }
12853
12854 self.transact(window, cx, |this, window, cx| {
12855 this.buffer.update(cx, |buffer, cx| {
12856 buffer.edit(edits, None, cx);
12857 });
12858
12859 // When duplicating upward with whole lines, move the cursor to the duplicated line
12860 if upwards && whole_lines {
12861 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12862
12863 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12864 let mut new_ranges = Vec::new();
12865 let selections = s.all::<Point>(&display_map);
12866 let mut selections_iter = selections.iter().peekable();
12867
12868 while let Some(first_selection) = selections_iter.next() {
12869 // Group contiguous selections together to find the total row span
12870 let mut group_selections = vec![first_selection];
12871 let mut rows = first_selection.spanned_rows(false, &display_map);
12872
12873 while let Some(next_selection) = selections_iter.peek() {
12874 let next_rows = next_selection.spanned_rows(false, &display_map);
12875 if next_rows.start < rows.end {
12876 rows.end = next_rows.end;
12877 group_selections.push(selections_iter.next().unwrap());
12878 } else {
12879 break;
12880 }
12881 }
12882
12883 let row_count = rows.end.0 - rows.start.0;
12884
12885 // Move all selections in this group up by the total number of duplicated rows
12886 for selection in group_selections {
12887 let new_start = Point::new(
12888 selection.start.row.saturating_sub(row_count),
12889 selection.start.column,
12890 );
12891
12892 let new_end = Point::new(
12893 selection.end.row.saturating_sub(row_count),
12894 selection.end.column,
12895 );
12896
12897 new_ranges.push(new_start..new_end);
12898 }
12899 }
12900
12901 s.select_ranges(new_ranges);
12902 });
12903 }
12904
12905 this.request_autoscroll(Autoscroll::fit(), cx);
12906 });
12907 }
12908
12909 pub fn duplicate_line_up(
12910 &mut self,
12911 _: &DuplicateLineUp,
12912 window: &mut Window,
12913 cx: &mut Context<Self>,
12914 ) {
12915 self.duplicate(true, true, window, cx);
12916 }
12917
12918 pub fn duplicate_line_down(
12919 &mut self,
12920 _: &DuplicateLineDown,
12921 window: &mut Window,
12922 cx: &mut Context<Self>,
12923 ) {
12924 self.duplicate(false, true, window, cx);
12925 }
12926
12927 pub fn duplicate_selection(
12928 &mut self,
12929 _: &DuplicateSelection,
12930 window: &mut Window,
12931 cx: &mut Context<Self>,
12932 ) {
12933 self.duplicate(false, false, window, cx);
12934 }
12935
12936 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12937 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12938 if self.mode.is_single_line() {
12939 cx.propagate();
12940 return;
12941 }
12942
12943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12944 let buffer = self.buffer.read(cx).snapshot(cx);
12945
12946 let mut edits = Vec::new();
12947 let mut unfold_ranges = Vec::new();
12948 let mut refold_creases = Vec::new();
12949
12950 let selections = self.selections.all::<Point>(&display_map);
12951 let mut selections = selections.iter().peekable();
12952 let mut contiguous_row_selections = Vec::new();
12953 let mut new_selections = Vec::new();
12954
12955 while let Some(selection) = selections.next() {
12956 // Find all the selections that span a contiguous row range
12957 let (start_row, end_row) = consume_contiguous_rows(
12958 &mut contiguous_row_selections,
12959 selection,
12960 &display_map,
12961 &mut selections,
12962 );
12963
12964 // Move the text spanned by the row range to be before the line preceding the row range
12965 if start_row.0 > 0 {
12966 let range_to_move = Point::new(
12967 start_row.previous_row().0,
12968 buffer.line_len(start_row.previous_row()),
12969 )
12970 ..Point::new(
12971 end_row.previous_row().0,
12972 buffer.line_len(end_row.previous_row()),
12973 );
12974 let insertion_point = display_map
12975 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12976 .0;
12977
12978 // Don't move lines across excerpts
12979 if buffer
12980 .excerpt_containing(insertion_point..range_to_move.end)
12981 .is_some()
12982 {
12983 let text = buffer
12984 .text_for_range(range_to_move.clone())
12985 .flat_map(|s| s.chars())
12986 .skip(1)
12987 .chain(['\n'])
12988 .collect::<String>();
12989
12990 edits.push((
12991 buffer.anchor_after(range_to_move.start)
12992 ..buffer.anchor_before(range_to_move.end),
12993 String::new(),
12994 ));
12995 let insertion_anchor = buffer.anchor_after(insertion_point);
12996 edits.push((insertion_anchor..insertion_anchor, text));
12997
12998 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12999
13000 // Move selections up
13001 new_selections.extend(contiguous_row_selections.drain(..).map(
13002 |mut selection| {
13003 selection.start.row -= row_delta;
13004 selection.end.row -= row_delta;
13005 selection
13006 },
13007 ));
13008
13009 // Move folds up
13010 unfold_ranges.push(range_to_move.clone());
13011 for fold in display_map.folds_in_range(
13012 buffer.anchor_before(range_to_move.start)
13013 ..buffer.anchor_after(range_to_move.end),
13014 ) {
13015 let mut start = fold.range.start.to_point(&buffer);
13016 let mut end = fold.range.end.to_point(&buffer);
13017 start.row -= row_delta;
13018 end.row -= row_delta;
13019 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13020 }
13021 }
13022 }
13023
13024 // If we didn't move line(s), preserve the existing selections
13025 new_selections.append(&mut contiguous_row_selections);
13026 }
13027
13028 self.transact(window, cx, |this, window, cx| {
13029 this.unfold_ranges(&unfold_ranges, true, true, cx);
13030 this.buffer.update(cx, |buffer, cx| {
13031 for (range, text) in edits {
13032 buffer.edit([(range, text)], None, cx);
13033 }
13034 });
13035 this.fold_creases(refold_creases, true, window, cx);
13036 this.change_selections(Default::default(), window, cx, |s| {
13037 s.select(new_selections);
13038 })
13039 });
13040 }
13041
13042 pub fn move_line_down(
13043 &mut self,
13044 _: &MoveLineDown,
13045 window: &mut Window,
13046 cx: &mut Context<Self>,
13047 ) {
13048 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13049 if self.mode.is_single_line() {
13050 cx.propagate();
13051 return;
13052 }
13053
13054 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13055 let buffer = self.buffer.read(cx).snapshot(cx);
13056
13057 let mut edits = Vec::new();
13058 let mut unfold_ranges = Vec::new();
13059 let mut refold_creases = Vec::new();
13060
13061 let selections = self.selections.all::<Point>(&display_map);
13062 let mut selections = selections.iter().peekable();
13063 let mut contiguous_row_selections = Vec::new();
13064 let mut new_selections = Vec::new();
13065
13066 while let Some(selection) = selections.next() {
13067 // Find all the selections that span a contiguous row range
13068 let (start_row, end_row) = consume_contiguous_rows(
13069 &mut contiguous_row_selections,
13070 selection,
13071 &display_map,
13072 &mut selections,
13073 );
13074
13075 // Move the text spanned by the row range to be after the last line of the row range
13076 if end_row.0 <= buffer.max_point().row {
13077 let range_to_move =
13078 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13079 let insertion_point = display_map
13080 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13081 .0;
13082
13083 // Don't move lines across excerpt boundaries
13084 if buffer
13085 .excerpt_containing(range_to_move.start..insertion_point)
13086 .is_some()
13087 {
13088 let mut text = String::from("\n");
13089 text.extend(buffer.text_for_range(range_to_move.clone()));
13090 text.pop(); // Drop trailing newline
13091 edits.push((
13092 buffer.anchor_after(range_to_move.start)
13093 ..buffer.anchor_before(range_to_move.end),
13094 String::new(),
13095 ));
13096 let insertion_anchor = buffer.anchor_after(insertion_point);
13097 edits.push((insertion_anchor..insertion_anchor, text));
13098
13099 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13100
13101 // Move selections down
13102 new_selections.extend(contiguous_row_selections.drain(..).map(
13103 |mut selection| {
13104 selection.start.row += row_delta;
13105 selection.end.row += row_delta;
13106 selection
13107 },
13108 ));
13109
13110 // Move folds down
13111 unfold_ranges.push(range_to_move.clone());
13112 for fold in display_map.folds_in_range(
13113 buffer.anchor_before(range_to_move.start)
13114 ..buffer.anchor_after(range_to_move.end),
13115 ) {
13116 let mut start = fold.range.start.to_point(&buffer);
13117 let mut end = fold.range.end.to_point(&buffer);
13118 start.row += row_delta;
13119 end.row += row_delta;
13120 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13121 }
13122 }
13123 }
13124
13125 // If we didn't move line(s), preserve the existing selections
13126 new_selections.append(&mut contiguous_row_selections);
13127 }
13128
13129 self.transact(window, cx, |this, window, cx| {
13130 this.unfold_ranges(&unfold_ranges, true, true, cx);
13131 this.buffer.update(cx, |buffer, cx| {
13132 for (range, text) in edits {
13133 buffer.edit([(range, text)], None, cx);
13134 }
13135 });
13136 this.fold_creases(refold_creases, true, window, cx);
13137 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13138 });
13139 }
13140
13141 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13143 let text_layout_details = &self.text_layout_details(window, cx);
13144 self.transact(window, cx, |this, window, cx| {
13145 let edits = this.change_selections(Default::default(), window, cx, |s| {
13146 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13147 s.move_with(&mut |display_map, selection| {
13148 if !selection.is_empty() {
13149 return;
13150 }
13151
13152 let mut head = selection.head();
13153 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13154 if head.column() == display_map.line_len(head.row()) {
13155 transpose_offset = display_map
13156 .buffer_snapshot()
13157 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13158 }
13159
13160 if transpose_offset == MultiBufferOffset(0) {
13161 return;
13162 }
13163
13164 *head.column_mut() += 1;
13165 head = display_map.clip_point(head, Bias::Right);
13166 let goal = SelectionGoal::HorizontalPosition(
13167 display_map
13168 .x_for_display_point(head, text_layout_details)
13169 .into(),
13170 );
13171 selection.collapse_to(head, goal);
13172
13173 let transpose_start = display_map
13174 .buffer_snapshot()
13175 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13176 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13177 let transpose_end = display_map
13178 .buffer_snapshot()
13179 .clip_offset(transpose_offset + 1usize, Bias::Right);
13180 if let Some(ch) = display_map
13181 .buffer_snapshot()
13182 .chars_at(transpose_start)
13183 .next()
13184 {
13185 edits.push((transpose_start..transpose_offset, String::new()));
13186 edits.push((transpose_end..transpose_end, ch.to_string()));
13187 }
13188 }
13189 });
13190 edits
13191 });
13192 this.buffer
13193 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13194 let selections = this
13195 .selections
13196 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13197 this.change_selections(Default::default(), window, cx, |s| {
13198 s.select(selections);
13199 });
13200 });
13201 }
13202
13203 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13204 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13205 if self.mode.is_single_line() {
13206 cx.propagate();
13207 return;
13208 }
13209
13210 self.rewrap_impl(RewrapOptions::default(), cx)
13211 }
13212
13213 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13214 let buffer = self.buffer.read(cx).snapshot(cx);
13215 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13216
13217 #[derive(Clone, Debug, PartialEq)]
13218 enum CommentFormat {
13219 /// single line comment, with prefix for line
13220 Line(String),
13221 /// single line within a block comment, with prefix for line
13222 BlockLine(String),
13223 /// a single line of a block comment that includes the initial delimiter
13224 BlockCommentWithStart(BlockCommentConfig),
13225 /// a single line of a block comment that includes the ending delimiter
13226 BlockCommentWithEnd(BlockCommentConfig),
13227 }
13228
13229 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13230 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13231 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13232 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13233 .peekable();
13234
13235 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13236 row
13237 } else {
13238 return Vec::new();
13239 };
13240
13241 let language_settings = buffer.language_settings_at(selection.head(), cx);
13242 let language_scope = buffer.language_scope_at(selection.head());
13243
13244 let indent_and_prefix_for_row =
13245 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13246 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13247 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13248 &language_scope
13249 {
13250 let indent_end = Point::new(row, indent.len);
13251 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13252 let line_text_after_indent = buffer
13253 .text_for_range(indent_end..line_end)
13254 .collect::<String>();
13255
13256 let is_within_comment_override = buffer
13257 .language_scope_at(indent_end)
13258 .is_some_and(|scope| scope.override_name() == Some("comment"));
13259 let comment_delimiters = if is_within_comment_override {
13260 // we are within a comment syntax node, but we don't
13261 // yet know what kind of comment: block, doc or line
13262 match (
13263 language_scope.documentation_comment(),
13264 language_scope.block_comment(),
13265 ) {
13266 (Some(config), _) | (_, Some(config))
13267 if buffer.contains_str_at(indent_end, &config.start) =>
13268 {
13269 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13270 }
13271 (Some(config), _) | (_, Some(config))
13272 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13273 {
13274 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13275 }
13276 (Some(config), _) | (_, Some(config))
13277 if buffer.contains_str_at(indent_end, &config.prefix) =>
13278 {
13279 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13280 }
13281 (_, _) => language_scope
13282 .line_comment_prefixes()
13283 .iter()
13284 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13285 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13286 }
13287 } else {
13288 // we not in an overridden comment node, but we may
13289 // be within a non-overridden line comment node
13290 language_scope
13291 .line_comment_prefixes()
13292 .iter()
13293 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13294 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13295 };
13296
13297 let rewrap_prefix = language_scope
13298 .rewrap_prefixes()
13299 .iter()
13300 .find_map(|prefix_regex| {
13301 prefix_regex.find(&line_text_after_indent).map(|mat| {
13302 if mat.start() == 0 {
13303 Some(mat.as_str().to_string())
13304 } else {
13305 None
13306 }
13307 })
13308 })
13309 .flatten();
13310 (comment_delimiters, rewrap_prefix)
13311 } else {
13312 (None, None)
13313 };
13314 (indent, comment_prefix, rewrap_prefix)
13315 };
13316
13317 let mut ranges = Vec::new();
13318 let from_empty_selection = selection.is_empty();
13319
13320 let mut current_range_start = first_row;
13321 let mut prev_row = first_row;
13322 let (
13323 mut current_range_indent,
13324 mut current_range_comment_delimiters,
13325 mut current_range_rewrap_prefix,
13326 ) = indent_and_prefix_for_row(first_row);
13327
13328 for row in non_blank_rows_iter.skip(1) {
13329 let has_paragraph_break = row > prev_row + 1;
13330
13331 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13332 indent_and_prefix_for_row(row);
13333
13334 let has_indent_change = row_indent != current_range_indent;
13335 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13336
13337 let has_boundary_change = has_comment_change
13338 || row_rewrap_prefix.is_some()
13339 || (has_indent_change && current_range_comment_delimiters.is_some());
13340
13341 if has_paragraph_break || has_boundary_change {
13342 ranges.push((
13343 language_settings.clone(),
13344 Point::new(current_range_start, 0)
13345 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13346 current_range_indent,
13347 current_range_comment_delimiters.clone(),
13348 current_range_rewrap_prefix.clone(),
13349 from_empty_selection,
13350 ));
13351 current_range_start = row;
13352 current_range_indent = row_indent;
13353 current_range_comment_delimiters = row_comment_delimiters;
13354 current_range_rewrap_prefix = row_rewrap_prefix;
13355 }
13356 prev_row = row;
13357 }
13358
13359 ranges.push((
13360 language_settings.clone(),
13361 Point::new(current_range_start, 0)
13362 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13363 current_range_indent,
13364 current_range_comment_delimiters,
13365 current_range_rewrap_prefix,
13366 from_empty_selection,
13367 ));
13368
13369 ranges
13370 });
13371
13372 let mut edits = Vec::new();
13373 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13374
13375 for (
13376 language_settings,
13377 wrap_range,
13378 mut indent_size,
13379 comment_prefix,
13380 rewrap_prefix,
13381 from_empty_selection,
13382 ) in wrap_ranges
13383 {
13384 let mut start_row = wrap_range.start.row;
13385 let mut end_row = wrap_range.end.row;
13386
13387 // Skip selections that overlap with a range that has already been rewrapped.
13388 let selection_range = start_row..end_row;
13389 if rewrapped_row_ranges
13390 .iter()
13391 .any(|range| range.overlaps(&selection_range))
13392 {
13393 continue;
13394 }
13395
13396 let tab_size = language_settings.tab_size;
13397
13398 let (line_prefix, inside_comment) = match &comment_prefix {
13399 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13400 (Some(prefix.as_str()), true)
13401 }
13402 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13403 (Some(prefix.as_ref()), true)
13404 }
13405 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13406 start: _,
13407 end: _,
13408 prefix,
13409 tab_size,
13410 })) => {
13411 indent_size.len += tab_size;
13412 (Some(prefix.as_ref()), true)
13413 }
13414 None => (None, false),
13415 };
13416 let indent_prefix = indent_size.chars().collect::<String>();
13417 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13418
13419 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13420 RewrapBehavior::InComments => inside_comment,
13421 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13422 RewrapBehavior::Anywhere => true,
13423 };
13424
13425 let should_rewrap = options.override_language_settings
13426 || allow_rewrap_based_on_language
13427 || self.hard_wrap.is_some();
13428 if !should_rewrap {
13429 continue;
13430 }
13431
13432 if from_empty_selection {
13433 'expand_upwards: while start_row > 0 {
13434 let prev_row = start_row - 1;
13435 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13436 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13437 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13438 {
13439 start_row = prev_row;
13440 } else {
13441 break 'expand_upwards;
13442 }
13443 }
13444
13445 'expand_downwards: while end_row < buffer.max_point().row {
13446 let next_row = end_row + 1;
13447 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13448 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13449 && !buffer.is_line_blank(MultiBufferRow(next_row))
13450 {
13451 end_row = next_row;
13452 } else {
13453 break 'expand_downwards;
13454 }
13455 }
13456 }
13457
13458 let start = Point::new(start_row, 0);
13459 let start_offset = ToOffset::to_offset(&start, &buffer);
13460 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13461 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13462 let mut first_line_delimiter = None;
13463 let mut last_line_delimiter = None;
13464 let Some(lines_without_prefixes) = selection_text
13465 .lines()
13466 .enumerate()
13467 .map(|(ix, line)| {
13468 let line_trimmed = line.trim_start();
13469 if rewrap_prefix.is_some() && ix > 0 {
13470 Ok(line_trimmed)
13471 } else if let Some(
13472 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13473 start,
13474 prefix,
13475 end,
13476 tab_size,
13477 })
13478 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13479 start,
13480 prefix,
13481 end,
13482 tab_size,
13483 }),
13484 ) = &comment_prefix
13485 {
13486 let line_trimmed = line_trimmed
13487 .strip_prefix(start.as_ref())
13488 .map(|s| {
13489 let mut indent_size = indent_size;
13490 indent_size.len -= tab_size;
13491 let indent_prefix: String = indent_size.chars().collect();
13492 first_line_delimiter = Some((indent_prefix, start));
13493 s.trim_start()
13494 })
13495 .unwrap_or(line_trimmed);
13496 let line_trimmed = line_trimmed
13497 .strip_suffix(end.as_ref())
13498 .map(|s| {
13499 last_line_delimiter = Some(end);
13500 s.trim_end()
13501 })
13502 .unwrap_or(line_trimmed);
13503 let line_trimmed = line_trimmed
13504 .strip_prefix(prefix.as_ref())
13505 .unwrap_or(line_trimmed);
13506 Ok(line_trimmed)
13507 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13508 line_trimmed.strip_prefix(prefix).with_context(|| {
13509 format!("line did not start with prefix {prefix:?}: {line:?}")
13510 })
13511 } else {
13512 line_trimmed
13513 .strip_prefix(&line_prefix.trim_start())
13514 .with_context(|| {
13515 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13516 })
13517 }
13518 })
13519 .collect::<Result<Vec<_>, _>>()
13520 .log_err()
13521 else {
13522 continue;
13523 };
13524
13525 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13526 buffer
13527 .language_settings_at(Point::new(start_row, 0), cx)
13528 .preferred_line_length as usize
13529 });
13530
13531 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13532 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13533 } else {
13534 line_prefix.clone()
13535 };
13536
13537 let wrapped_text = {
13538 let mut wrapped_text = wrap_with_prefix(
13539 line_prefix,
13540 subsequent_lines_prefix,
13541 lines_without_prefixes.join("\n"),
13542 wrap_column,
13543 tab_size,
13544 options.preserve_existing_whitespace,
13545 );
13546
13547 if let Some((indent, delimiter)) = first_line_delimiter {
13548 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13549 }
13550 if let Some(last_line) = last_line_delimiter {
13551 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13552 }
13553
13554 wrapped_text
13555 };
13556
13557 // TODO: should always use char-based diff while still supporting cursor behavior that
13558 // matches vim.
13559 let mut diff_options = DiffOptions::default();
13560 if options.override_language_settings {
13561 diff_options.max_word_diff_len = 0;
13562 diff_options.max_word_diff_line_count = 0;
13563 } else {
13564 diff_options.max_word_diff_len = usize::MAX;
13565 diff_options.max_word_diff_line_count = usize::MAX;
13566 }
13567
13568 for (old_range, new_text) in
13569 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13570 {
13571 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13572 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13573 edits.push((edit_start..edit_end, new_text));
13574 }
13575
13576 rewrapped_row_ranges.push(start_row..=end_row);
13577 }
13578
13579 self.buffer
13580 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13581 }
13582
13583 pub fn cut_common(
13584 &mut self,
13585 cut_no_selection_line: bool,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) -> ClipboardItem {
13589 let mut text = String::new();
13590 let buffer = self.buffer.read(cx).snapshot(cx);
13591 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13592 let mut clipboard_selections = Vec::with_capacity(selections.len());
13593 {
13594 let max_point = buffer.max_point();
13595 let mut is_first = true;
13596 let mut prev_selection_was_entire_line = false;
13597 for selection in &mut selections {
13598 let is_entire_line =
13599 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13600 if is_entire_line {
13601 selection.start = Point::new(selection.start.row, 0);
13602 if !selection.is_empty() && selection.end.column == 0 {
13603 selection.end = cmp::min(max_point, selection.end);
13604 } else {
13605 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13606 }
13607 selection.goal = SelectionGoal::None;
13608 }
13609 if is_first {
13610 is_first = false;
13611 } else if !prev_selection_was_entire_line {
13612 text += "\n";
13613 }
13614 prev_selection_was_entire_line = is_entire_line;
13615 let mut len = 0;
13616 for chunk in buffer.text_for_range(selection.start..selection.end) {
13617 text.push_str(chunk);
13618 len += chunk.len();
13619 }
13620
13621 clipboard_selections.push(ClipboardSelection::for_buffer(
13622 len,
13623 is_entire_line,
13624 selection.range(),
13625 &buffer,
13626 self.project.as_ref(),
13627 cx,
13628 ));
13629 }
13630 }
13631
13632 self.transact(window, cx, |this, window, cx| {
13633 this.change_selections(Default::default(), window, cx, |s| {
13634 s.select(selections);
13635 });
13636 this.insert("", window, cx);
13637 });
13638 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13639 }
13640
13641 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13642 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13643 let item = self.cut_common(true, window, cx);
13644 cx.write_to_clipboard(item);
13645 }
13646
13647 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13649 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13650 s.move_with(&mut |snapshot, sel| {
13651 if sel.is_empty() {
13652 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13653 }
13654 if sel.is_empty() {
13655 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13656 }
13657 });
13658 });
13659 let item = self.cut_common(false, window, cx);
13660 cx.set_global(KillRing(item))
13661 }
13662
13663 pub fn kill_ring_yank(
13664 &mut self,
13665 _: &KillRingYank,
13666 window: &mut Window,
13667 cx: &mut Context<Self>,
13668 ) {
13669 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13670 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13671 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13672 (kill_ring.text().to_string(), kill_ring.metadata_json())
13673 } else {
13674 return;
13675 }
13676 } else {
13677 return;
13678 };
13679 self.do_paste(&text, metadata, false, window, cx);
13680 }
13681
13682 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13683 self.do_copy(true, cx);
13684 }
13685
13686 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13687 self.do_copy(false, cx);
13688 }
13689
13690 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13691 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13692 let buffer = self.buffer.read(cx).read(cx);
13693 let mut text = String::new();
13694 let mut clipboard_selections = Vec::with_capacity(selections.len());
13695
13696 let max_point = buffer.max_point();
13697 let mut is_first = true;
13698 for selection in &selections {
13699 let mut start = selection.start;
13700 let mut end = selection.end;
13701 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13702 let mut add_trailing_newline = false;
13703 if is_entire_line {
13704 start = Point::new(start.row, 0);
13705 let next_line_start = Point::new(end.row + 1, 0);
13706 if next_line_start <= max_point {
13707 end = next_line_start;
13708 } else {
13709 // We're on the last line without a trailing newline.
13710 // Copy to the end of the line and add a newline afterwards.
13711 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13712 add_trailing_newline = true;
13713 }
13714 }
13715
13716 let mut trimmed_selections = Vec::new();
13717 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13718 let row = MultiBufferRow(start.row);
13719 let first_indent = buffer.indent_size_for_line(row);
13720 if first_indent.len == 0 || start.column > first_indent.len {
13721 trimmed_selections.push(start..end);
13722 } else {
13723 trimmed_selections.push(
13724 Point::new(row.0, first_indent.len)
13725 ..Point::new(row.0, buffer.line_len(row)),
13726 );
13727 for row in start.row + 1..=end.row {
13728 let mut line_len = buffer.line_len(MultiBufferRow(row));
13729 if row == end.row {
13730 line_len = end.column;
13731 }
13732 if line_len == 0 {
13733 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13734 continue;
13735 }
13736 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13737 if row_indent_size.len >= first_indent.len {
13738 trimmed_selections
13739 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13740 } else {
13741 trimmed_selections.clear();
13742 trimmed_selections.push(start..end);
13743 break;
13744 }
13745 }
13746 }
13747 } else {
13748 trimmed_selections.push(start..end);
13749 }
13750
13751 let is_multiline_trim = trimmed_selections.len() > 1;
13752 let mut selection_len: usize = 0;
13753 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13754
13755 for trimmed_range in trimmed_selections {
13756 if is_first {
13757 is_first = false;
13758 } else if is_multiline_trim || !prev_selection_was_entire_line {
13759 text.push('\n');
13760 if is_multiline_trim {
13761 selection_len += 1;
13762 }
13763 }
13764 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13765 text.push_str(chunk);
13766 selection_len += chunk.len();
13767 }
13768 if add_trailing_newline {
13769 text.push('\n');
13770 selection_len += 1;
13771 }
13772 }
13773
13774 clipboard_selections.push(ClipboardSelection::for_buffer(
13775 selection_len,
13776 is_entire_line,
13777 start..end,
13778 &buffer,
13779 self.project.as_ref(),
13780 cx,
13781 ));
13782 }
13783
13784 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13785 text,
13786 clipboard_selections,
13787 ));
13788 }
13789
13790 pub fn do_paste(
13791 &mut self,
13792 text: &String,
13793 clipboard_selections: Option<Vec<ClipboardSelection>>,
13794 handle_entire_lines: bool,
13795 window: &mut Window,
13796 cx: &mut Context<Self>,
13797 ) {
13798 if self.read_only(cx) {
13799 return;
13800 }
13801
13802 let clipboard_text = Cow::Borrowed(text.as_str());
13803
13804 self.transact(window, cx, |this, window, cx| {
13805 let had_active_edit_prediction = this.has_active_edit_prediction();
13806 let display_map = this.display_snapshot(cx);
13807 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13808 let cursor_offset = this
13809 .selections
13810 .last::<MultiBufferOffset>(&display_map)
13811 .head();
13812
13813 if let Some(mut clipboard_selections) = clipboard_selections {
13814 let all_selections_were_entire_line =
13815 clipboard_selections.iter().all(|s| s.is_entire_line);
13816 let first_selection_indent_column =
13817 clipboard_selections.first().map(|s| s.first_line_indent);
13818 if clipboard_selections.len() != old_selections.len() {
13819 clipboard_selections.drain(..);
13820 }
13821 let mut auto_indent_on_paste = true;
13822
13823 this.buffer.update(cx, |buffer, cx| {
13824 let snapshot = buffer.read(cx);
13825 auto_indent_on_paste = snapshot
13826 .language_settings_at(cursor_offset, cx)
13827 .auto_indent_on_paste;
13828
13829 let mut start_offset = 0;
13830 let mut edits = Vec::new();
13831 let mut original_indent_columns = Vec::new();
13832 for (ix, selection) in old_selections.iter().enumerate() {
13833 let to_insert;
13834 let entire_line;
13835 let original_indent_column;
13836 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13837 let end_offset = start_offset + clipboard_selection.len;
13838 to_insert = &clipboard_text[start_offset..end_offset];
13839 entire_line = clipboard_selection.is_entire_line;
13840 start_offset = if entire_line {
13841 end_offset
13842 } else {
13843 end_offset + 1
13844 };
13845 original_indent_column = Some(clipboard_selection.first_line_indent);
13846 } else {
13847 to_insert = &*clipboard_text;
13848 entire_line = all_selections_were_entire_line;
13849 original_indent_column = first_selection_indent_column
13850 }
13851
13852 let (range, to_insert) =
13853 if selection.is_empty() && handle_entire_lines && entire_line {
13854 // If the corresponding selection was empty when this slice of the
13855 // clipboard text was written, then the entire line containing the
13856 // selection was copied. If this selection is also currently empty,
13857 // then paste the line before the current line of the buffer.
13858 let column = selection.start.to_point(&snapshot).column as usize;
13859 let line_start = selection.start - column;
13860 (line_start..line_start, Cow::Borrowed(to_insert))
13861 } else {
13862 let language = snapshot.language_at(selection.head());
13863 let range = selection.range();
13864 if let Some(language) = language
13865 && language.name() == "Markdown"
13866 {
13867 edit_for_markdown_paste(
13868 &snapshot,
13869 range,
13870 to_insert,
13871 url::Url::parse(to_insert).ok(),
13872 )
13873 } else {
13874 (range, Cow::Borrowed(to_insert))
13875 }
13876 };
13877
13878 edits.push((range, to_insert));
13879 original_indent_columns.push(original_indent_column);
13880 }
13881 drop(snapshot);
13882
13883 buffer.edit(
13884 edits,
13885 if auto_indent_on_paste {
13886 Some(AutoindentMode::Block {
13887 original_indent_columns,
13888 })
13889 } else {
13890 None
13891 },
13892 cx,
13893 );
13894 });
13895
13896 let selections = this
13897 .selections
13898 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13899 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13900 } else {
13901 let url = url::Url::parse(&clipboard_text).ok();
13902
13903 let auto_indent_mode = if !clipboard_text.is_empty() {
13904 Some(AutoindentMode::Block {
13905 original_indent_columns: Vec::new(),
13906 })
13907 } else {
13908 None
13909 };
13910
13911 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13912 let snapshot = buffer.snapshot(cx);
13913
13914 let anchors = old_selections
13915 .iter()
13916 .map(|s| {
13917 let anchor = snapshot.anchor_after(s.head());
13918 s.map(|_| anchor)
13919 })
13920 .collect::<Vec<_>>();
13921
13922 let mut edits = Vec::new();
13923
13924 // When pasting text without metadata (e.g. copied from an
13925 // external editor using multiple cursors) and the number of
13926 // lines matches the number of selections, distribute one
13927 // line per cursor instead of pasting the whole text at each.
13928 let lines: Vec<&str> = clipboard_text.split('\n').collect();
13929 let distribute_lines =
13930 old_selections.len() > 1 && lines.len() == old_selections.len();
13931
13932 for (ix, selection) in old_selections.iter().enumerate() {
13933 let language = snapshot.language_at(selection.head());
13934 let range = selection.range();
13935
13936 let text_for_cursor: &str = if distribute_lines {
13937 lines[ix]
13938 } else {
13939 &clipboard_text
13940 };
13941
13942 let (edit_range, edit_text) = if let Some(language) = language
13943 && language.name() == "Markdown"
13944 {
13945 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
13946 } else {
13947 (range, Cow::Borrowed(text_for_cursor))
13948 };
13949
13950 edits.push((edit_range, edit_text));
13951 }
13952
13953 drop(snapshot);
13954 buffer.edit(edits, auto_indent_mode, cx);
13955
13956 anchors
13957 });
13958
13959 this.change_selections(Default::default(), window, cx, |s| {
13960 s.select_anchors(selection_anchors);
13961 });
13962 }
13963
13964 // 🤔 | .. | show_in_menu |
13965 // | .. | true true
13966 // | had_edit_prediction | false true
13967
13968 let trigger_in_words =
13969 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13970
13971 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13972 });
13973 }
13974
13975 pub fn diff_clipboard_with_selection(
13976 &mut self,
13977 _: &DiffClipboardWithSelection,
13978 window: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) {
13981 let selections = self
13982 .selections
13983 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13984
13985 if selections.is_empty() {
13986 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13987 return;
13988 };
13989
13990 let clipboard_text = match cx.read_from_clipboard() {
13991 Some(item) => match item.entries().first() {
13992 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13993 _ => None,
13994 },
13995 None => None,
13996 };
13997
13998 let Some(clipboard_text) = clipboard_text else {
13999 log::warn!("Clipboard doesn't contain text.");
14000 return;
14001 };
14002
14003 window.dispatch_action(
14004 Box::new(DiffClipboardWithSelectionData {
14005 clipboard_text,
14006 editor: cx.entity(),
14007 }),
14008 cx,
14009 );
14010 }
14011
14012 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14013 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14014 if let Some(item) = cx.read_from_clipboard() {
14015 let entries = item.entries();
14016
14017 match entries.first() {
14018 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
14019 // of all the pasted entries.
14020 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
14021 .do_paste(
14022 clipboard_string.text(),
14023 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14024 true,
14025 window,
14026 cx,
14027 ),
14028 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14029 }
14030 }
14031 }
14032
14033 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14034 if self.read_only(cx) {
14035 return;
14036 }
14037
14038 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14039
14040 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14041 if let Some((selections, _)) =
14042 self.selection_history.transaction(transaction_id).cloned()
14043 {
14044 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14045 s.select_anchors(selections.to_vec());
14046 });
14047 } else {
14048 log::error!(
14049 "No entry in selection_history found for undo. \
14050 This may correspond to a bug where undo does not update the selection. \
14051 If this is occurring, please add details to \
14052 https://github.com/zed-industries/zed/issues/22692"
14053 );
14054 }
14055 self.request_autoscroll(Autoscroll::fit(), cx);
14056 self.unmark_text(window, cx);
14057 self.refresh_edit_prediction(true, false, window, cx);
14058 cx.emit(EditorEvent::Edited { transaction_id });
14059 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14060 }
14061 }
14062
14063 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14064 if self.read_only(cx) {
14065 return;
14066 }
14067
14068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14069
14070 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14071 if let Some((_, Some(selections))) =
14072 self.selection_history.transaction(transaction_id).cloned()
14073 {
14074 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14075 s.select_anchors(selections.to_vec());
14076 });
14077 } else {
14078 log::error!(
14079 "No entry in selection_history found for redo. \
14080 This may correspond to a bug where undo does not update the selection. \
14081 If this is occurring, please add details to \
14082 https://github.com/zed-industries/zed/issues/22692"
14083 );
14084 }
14085 self.request_autoscroll(Autoscroll::fit(), cx);
14086 self.unmark_text(window, cx);
14087 self.refresh_edit_prediction(true, false, window, cx);
14088 cx.emit(EditorEvent::Edited { transaction_id });
14089 }
14090 }
14091
14092 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14093 self.buffer
14094 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14095 }
14096
14097 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14098 self.buffer
14099 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14100 }
14101
14102 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14103 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14104 self.change_selections(Default::default(), window, cx, |s| {
14105 s.move_with(&mut |map, selection| {
14106 let cursor = if selection.is_empty() {
14107 movement::left(map, selection.start)
14108 } else {
14109 selection.start
14110 };
14111 selection.collapse_to(cursor, SelectionGoal::None);
14112 });
14113 })
14114 }
14115
14116 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14118 self.change_selections(Default::default(), window, cx, |s| {
14119 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14120 })
14121 }
14122
14123 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14124 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14125 self.change_selections(Default::default(), window, cx, |s| {
14126 s.move_with(&mut |map, selection| {
14127 let cursor = if selection.is_empty() {
14128 movement::right(map, selection.end)
14129 } else {
14130 selection.end
14131 };
14132 selection.collapse_to(cursor, SelectionGoal::None)
14133 });
14134 })
14135 }
14136
14137 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14138 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14139 self.change_selections(Default::default(), window, cx, |s| {
14140 s.move_heads_with(&mut |map, head, _| {
14141 (movement::right(map, head), SelectionGoal::None)
14142 });
14143 });
14144 }
14145
14146 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14147 if self.take_rename(true, window, cx).is_some() {
14148 return;
14149 }
14150
14151 if self.mode.is_single_line() {
14152 cx.propagate();
14153 return;
14154 }
14155
14156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14157
14158 let text_layout_details = &self.text_layout_details(window, cx);
14159 let selection_count = self.selections.count();
14160 let first_selection = self.selections.first_anchor();
14161
14162 self.change_selections(Default::default(), window, cx, |s| {
14163 s.move_with(&mut |map, selection| {
14164 if !selection.is_empty() {
14165 selection.goal = SelectionGoal::None;
14166 }
14167 let (cursor, goal) = movement::up(
14168 map,
14169 selection.start,
14170 selection.goal,
14171 false,
14172 text_layout_details,
14173 );
14174 selection.collapse_to(cursor, goal);
14175 });
14176 });
14177
14178 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14179 {
14180 cx.propagate();
14181 }
14182 }
14183
14184 pub fn move_up_by_lines(
14185 &mut self,
14186 action: &MoveUpByLines,
14187 window: &mut Window,
14188 cx: &mut Context<Self>,
14189 ) {
14190 if self.take_rename(true, window, cx).is_some() {
14191 return;
14192 }
14193
14194 if self.mode.is_single_line() {
14195 cx.propagate();
14196 return;
14197 }
14198
14199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14200
14201 let text_layout_details = &self.text_layout_details(window, cx);
14202
14203 self.change_selections(Default::default(), window, cx, |s| {
14204 s.move_with(&mut |map, selection| {
14205 if !selection.is_empty() {
14206 selection.goal = SelectionGoal::None;
14207 }
14208 let (cursor, goal) = movement::up_by_rows(
14209 map,
14210 selection.start,
14211 action.lines,
14212 selection.goal,
14213 false,
14214 text_layout_details,
14215 );
14216 selection.collapse_to(cursor, goal);
14217 });
14218 })
14219 }
14220
14221 pub fn move_down_by_lines(
14222 &mut self,
14223 action: &MoveDownByLines,
14224 window: &mut Window,
14225 cx: &mut Context<Self>,
14226 ) {
14227 if self.take_rename(true, window, cx).is_some() {
14228 return;
14229 }
14230
14231 if self.mode.is_single_line() {
14232 cx.propagate();
14233 return;
14234 }
14235
14236 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14237
14238 let text_layout_details = &self.text_layout_details(window, cx);
14239
14240 self.change_selections(Default::default(), window, cx, |s| {
14241 s.move_with(&mut |map, selection| {
14242 if !selection.is_empty() {
14243 selection.goal = SelectionGoal::None;
14244 }
14245 let (cursor, goal) = movement::down_by_rows(
14246 map,
14247 selection.start,
14248 action.lines,
14249 selection.goal,
14250 false,
14251 text_layout_details,
14252 );
14253 selection.collapse_to(cursor, goal);
14254 });
14255 })
14256 }
14257
14258 pub fn select_down_by_lines(
14259 &mut self,
14260 action: &SelectDownByLines,
14261 window: &mut Window,
14262 cx: &mut Context<Self>,
14263 ) {
14264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14265 let text_layout_details = &self.text_layout_details(window, cx);
14266 self.change_selections(Default::default(), window, cx, |s| {
14267 s.move_heads_with(&mut |map, head, goal| {
14268 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14269 })
14270 })
14271 }
14272
14273 pub fn select_up_by_lines(
14274 &mut self,
14275 action: &SelectUpByLines,
14276 window: &mut Window,
14277 cx: &mut Context<Self>,
14278 ) {
14279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14280 let text_layout_details = &self.text_layout_details(window, cx);
14281 self.change_selections(Default::default(), window, cx, |s| {
14282 s.move_heads_with(&mut |map, head, goal| {
14283 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14284 })
14285 })
14286 }
14287
14288 pub fn select_page_up(
14289 &mut self,
14290 _: &SelectPageUp,
14291 window: &mut Window,
14292 cx: &mut Context<Self>,
14293 ) {
14294 let Some(row_count) = self.visible_row_count() else {
14295 return;
14296 };
14297
14298 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14299
14300 let text_layout_details = &self.text_layout_details(window, cx);
14301
14302 self.change_selections(Default::default(), window, cx, |s| {
14303 s.move_heads_with(&mut |map, head, goal| {
14304 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14305 })
14306 })
14307 }
14308
14309 pub fn move_page_up(
14310 &mut self,
14311 action: &MovePageUp,
14312 window: &mut Window,
14313 cx: &mut Context<Self>,
14314 ) {
14315 if self.take_rename(true, window, cx).is_some() {
14316 return;
14317 }
14318
14319 if self
14320 .context_menu
14321 .borrow_mut()
14322 .as_mut()
14323 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14324 .unwrap_or(false)
14325 {
14326 return;
14327 }
14328
14329 if matches!(self.mode, EditorMode::SingleLine) {
14330 cx.propagate();
14331 return;
14332 }
14333
14334 let Some(row_count) = self.visible_row_count() else {
14335 return;
14336 };
14337
14338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14339
14340 let effects = if action.center_cursor {
14341 SelectionEffects::scroll(Autoscroll::center())
14342 } else {
14343 SelectionEffects::default()
14344 };
14345
14346 let text_layout_details = &self.text_layout_details(window, cx);
14347
14348 self.change_selections(effects, window, cx, |s| {
14349 s.move_with(&mut |map, selection| {
14350 if !selection.is_empty() {
14351 selection.goal = SelectionGoal::None;
14352 }
14353 let (cursor, goal) = movement::up_by_rows(
14354 map,
14355 selection.end,
14356 row_count,
14357 selection.goal,
14358 false,
14359 text_layout_details,
14360 );
14361 selection.collapse_to(cursor, goal);
14362 });
14363 });
14364 }
14365
14366 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14367 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14368 let text_layout_details = &self.text_layout_details(window, cx);
14369 self.change_selections(Default::default(), window, cx, |s| {
14370 s.move_heads_with(&mut |map, head, goal| {
14371 movement::up(map, head, goal, false, text_layout_details)
14372 })
14373 })
14374 }
14375
14376 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14377 self.take_rename(true, window, cx);
14378
14379 if self.mode.is_single_line() {
14380 cx.propagate();
14381 return;
14382 }
14383
14384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14385
14386 let text_layout_details = &self.text_layout_details(window, cx);
14387 let selection_count = self.selections.count();
14388 let first_selection = self.selections.first_anchor();
14389
14390 self.change_selections(Default::default(), window, cx, |s| {
14391 s.move_with(&mut |map, selection| {
14392 if !selection.is_empty() {
14393 selection.goal = SelectionGoal::None;
14394 }
14395 let (cursor, goal) = movement::down(
14396 map,
14397 selection.end,
14398 selection.goal,
14399 false,
14400 text_layout_details,
14401 );
14402 selection.collapse_to(cursor, goal);
14403 });
14404 });
14405
14406 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14407 {
14408 cx.propagate();
14409 }
14410 }
14411
14412 pub fn select_page_down(
14413 &mut self,
14414 _: &SelectPageDown,
14415 window: &mut Window,
14416 cx: &mut Context<Self>,
14417 ) {
14418 let Some(row_count) = self.visible_row_count() else {
14419 return;
14420 };
14421
14422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14423
14424 let text_layout_details = &self.text_layout_details(window, cx);
14425
14426 self.change_selections(Default::default(), window, cx, |s| {
14427 s.move_heads_with(&mut |map, head, goal| {
14428 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14429 })
14430 })
14431 }
14432
14433 pub fn move_page_down(
14434 &mut self,
14435 action: &MovePageDown,
14436 window: &mut Window,
14437 cx: &mut Context<Self>,
14438 ) {
14439 if self.take_rename(true, window, cx).is_some() {
14440 return;
14441 }
14442
14443 if self
14444 .context_menu
14445 .borrow_mut()
14446 .as_mut()
14447 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14448 .unwrap_or(false)
14449 {
14450 return;
14451 }
14452
14453 if matches!(self.mode, EditorMode::SingleLine) {
14454 cx.propagate();
14455 return;
14456 }
14457
14458 let Some(row_count) = self.visible_row_count() else {
14459 return;
14460 };
14461
14462 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14463
14464 let effects = if action.center_cursor {
14465 SelectionEffects::scroll(Autoscroll::center())
14466 } else {
14467 SelectionEffects::default()
14468 };
14469
14470 let text_layout_details = &self.text_layout_details(window, cx);
14471 self.change_selections(effects, window, cx, |s| {
14472 s.move_with(&mut |map, selection| {
14473 if !selection.is_empty() {
14474 selection.goal = SelectionGoal::None;
14475 }
14476 let (cursor, goal) = movement::down_by_rows(
14477 map,
14478 selection.end,
14479 row_count,
14480 selection.goal,
14481 false,
14482 text_layout_details,
14483 );
14484 selection.collapse_to(cursor, goal);
14485 });
14486 });
14487 }
14488
14489 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14490 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14491 let text_layout_details = &self.text_layout_details(window, cx);
14492 self.change_selections(Default::default(), window, cx, |s| {
14493 s.move_heads_with(&mut |map, head, goal| {
14494 movement::down(map, head, goal, false, text_layout_details)
14495 })
14496 });
14497 }
14498
14499 pub fn context_menu_first(
14500 &mut self,
14501 _: &ContextMenuFirst,
14502 window: &mut Window,
14503 cx: &mut Context<Self>,
14504 ) {
14505 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14506 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14507 }
14508 }
14509
14510 pub fn context_menu_prev(
14511 &mut self,
14512 _: &ContextMenuPrevious,
14513 window: &mut Window,
14514 cx: &mut Context<Self>,
14515 ) {
14516 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14517 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14518 }
14519 }
14520
14521 pub fn context_menu_next(
14522 &mut self,
14523 _: &ContextMenuNext,
14524 window: &mut Window,
14525 cx: &mut Context<Self>,
14526 ) {
14527 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14528 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14529 }
14530 }
14531
14532 pub fn context_menu_last(
14533 &mut self,
14534 _: &ContextMenuLast,
14535 window: &mut Window,
14536 cx: &mut Context<Self>,
14537 ) {
14538 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14539 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14540 }
14541 }
14542
14543 pub fn signature_help_prev(
14544 &mut self,
14545 _: &SignatureHelpPrevious,
14546 _: &mut Window,
14547 cx: &mut Context<Self>,
14548 ) {
14549 if let Some(popover) = self.signature_help_state.popover_mut() {
14550 if popover.current_signature == 0 {
14551 popover.current_signature = popover.signatures.len() - 1;
14552 } else {
14553 popover.current_signature -= 1;
14554 }
14555 cx.notify();
14556 }
14557 }
14558
14559 pub fn signature_help_next(
14560 &mut self,
14561 _: &SignatureHelpNext,
14562 _: &mut Window,
14563 cx: &mut Context<Self>,
14564 ) {
14565 if let Some(popover) = self.signature_help_state.popover_mut() {
14566 if popover.current_signature + 1 == popover.signatures.len() {
14567 popover.current_signature = 0;
14568 } else {
14569 popover.current_signature += 1;
14570 }
14571 cx.notify();
14572 }
14573 }
14574
14575 pub fn move_to_previous_word_start(
14576 &mut self,
14577 _: &MoveToPreviousWordStart,
14578 window: &mut Window,
14579 cx: &mut Context<Self>,
14580 ) {
14581 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14582 self.change_selections(Default::default(), window, cx, |s| {
14583 s.move_cursors_with(&mut |map, head, _| {
14584 (
14585 movement::previous_word_start(map, head),
14586 SelectionGoal::None,
14587 )
14588 });
14589 })
14590 }
14591
14592 pub fn move_to_previous_subword_start(
14593 &mut self,
14594 _: &MoveToPreviousSubwordStart,
14595 window: &mut Window,
14596 cx: &mut Context<Self>,
14597 ) {
14598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14599 self.change_selections(Default::default(), window, cx, |s| {
14600 s.move_cursors_with(&mut |map, head, _| {
14601 (
14602 movement::previous_subword_start(map, head),
14603 SelectionGoal::None,
14604 )
14605 });
14606 })
14607 }
14608
14609 pub fn select_to_previous_word_start(
14610 &mut self,
14611 _: &SelectToPreviousWordStart,
14612 window: &mut Window,
14613 cx: &mut Context<Self>,
14614 ) {
14615 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14616 self.change_selections(Default::default(), window, cx, |s| {
14617 s.move_heads_with(&mut |map, head, _| {
14618 (
14619 movement::previous_word_start(map, head),
14620 SelectionGoal::None,
14621 )
14622 });
14623 })
14624 }
14625
14626 pub fn select_to_previous_subword_start(
14627 &mut self,
14628 _: &SelectToPreviousSubwordStart,
14629 window: &mut Window,
14630 cx: &mut Context<Self>,
14631 ) {
14632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14633 self.change_selections(Default::default(), window, cx, |s| {
14634 s.move_heads_with(&mut |map, head, _| {
14635 (
14636 movement::previous_subword_start(map, head),
14637 SelectionGoal::None,
14638 )
14639 });
14640 })
14641 }
14642
14643 pub fn delete_to_previous_word_start(
14644 &mut self,
14645 action: &DeleteToPreviousWordStart,
14646 window: &mut Window,
14647 cx: &mut Context<Self>,
14648 ) {
14649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14650 self.transact(window, cx, |this, window, cx| {
14651 this.select_autoclose_pair(window, cx);
14652 this.change_selections(Default::default(), window, cx, |s| {
14653 s.move_with(&mut |map, selection| {
14654 if selection.is_empty() {
14655 let mut cursor = if action.ignore_newlines {
14656 movement::previous_word_start(map, selection.head())
14657 } else {
14658 movement::previous_word_start_or_newline(map, selection.head())
14659 };
14660 cursor = movement::adjust_greedy_deletion(
14661 map,
14662 selection.head(),
14663 cursor,
14664 action.ignore_brackets,
14665 );
14666 selection.set_head(cursor, SelectionGoal::None);
14667 }
14668 });
14669 });
14670 this.insert("", window, cx);
14671 });
14672 }
14673
14674 pub fn delete_to_previous_subword_start(
14675 &mut self,
14676 action: &DeleteToPreviousSubwordStart,
14677 window: &mut Window,
14678 cx: &mut Context<Self>,
14679 ) {
14680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14681 self.transact(window, cx, |this, window, cx| {
14682 this.select_autoclose_pair(window, cx);
14683 this.change_selections(Default::default(), window, cx, |s| {
14684 s.move_with(&mut |map, selection| {
14685 if selection.is_empty() {
14686 let mut cursor = if action.ignore_newlines {
14687 movement::previous_subword_start(map, selection.head())
14688 } else {
14689 movement::previous_subword_start_or_newline(map, selection.head())
14690 };
14691 cursor = movement::adjust_greedy_deletion(
14692 map,
14693 selection.head(),
14694 cursor,
14695 action.ignore_brackets,
14696 );
14697 selection.set_head(cursor, SelectionGoal::None);
14698 }
14699 });
14700 });
14701 this.insert("", window, cx);
14702 });
14703 }
14704
14705 pub fn move_to_next_word_end(
14706 &mut self,
14707 _: &MoveToNextWordEnd,
14708 window: &mut Window,
14709 cx: &mut Context<Self>,
14710 ) {
14711 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14712 self.change_selections(Default::default(), window, cx, |s| {
14713 s.move_cursors_with(&mut |map, head, _| {
14714 (movement::next_word_end(map, head), SelectionGoal::None)
14715 });
14716 })
14717 }
14718
14719 pub fn move_to_next_subword_end(
14720 &mut self,
14721 _: &MoveToNextSubwordEnd,
14722 window: &mut Window,
14723 cx: &mut Context<Self>,
14724 ) {
14725 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14726 self.change_selections(Default::default(), window, cx, |s| {
14727 s.move_cursors_with(&mut |map, head, _| {
14728 (movement::next_subword_end(map, head), SelectionGoal::None)
14729 });
14730 })
14731 }
14732
14733 pub fn select_to_next_word_end(
14734 &mut self,
14735 _: &SelectToNextWordEnd,
14736 window: &mut Window,
14737 cx: &mut Context<Self>,
14738 ) {
14739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14740 self.change_selections(Default::default(), window, cx, |s| {
14741 s.move_heads_with(&mut |map, head, _| {
14742 (movement::next_word_end(map, head), SelectionGoal::None)
14743 });
14744 })
14745 }
14746
14747 pub fn select_to_next_subword_end(
14748 &mut self,
14749 _: &SelectToNextSubwordEnd,
14750 window: &mut Window,
14751 cx: &mut Context<Self>,
14752 ) {
14753 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14754 self.change_selections(Default::default(), window, cx, |s| {
14755 s.move_heads_with(&mut |map, head, _| {
14756 (movement::next_subword_end(map, head), SelectionGoal::None)
14757 });
14758 })
14759 }
14760
14761 pub fn delete_to_next_word_end(
14762 &mut self,
14763 action: &DeleteToNextWordEnd,
14764 window: &mut Window,
14765 cx: &mut Context<Self>,
14766 ) {
14767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14768 self.transact(window, cx, |this, window, cx| {
14769 this.change_selections(Default::default(), window, cx, |s| {
14770 s.move_with(&mut |map, selection| {
14771 if selection.is_empty() {
14772 let mut cursor = if action.ignore_newlines {
14773 movement::next_word_end(map, selection.head())
14774 } else {
14775 movement::next_word_end_or_newline(map, selection.head())
14776 };
14777 cursor = movement::adjust_greedy_deletion(
14778 map,
14779 selection.head(),
14780 cursor,
14781 action.ignore_brackets,
14782 );
14783 selection.set_head(cursor, SelectionGoal::None);
14784 }
14785 });
14786 });
14787 this.insert("", window, cx);
14788 });
14789 }
14790
14791 pub fn delete_to_next_subword_end(
14792 &mut self,
14793 action: &DeleteToNextSubwordEnd,
14794 window: &mut Window,
14795 cx: &mut Context<Self>,
14796 ) {
14797 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14798 self.transact(window, cx, |this, window, cx| {
14799 this.change_selections(Default::default(), window, cx, |s| {
14800 s.move_with(&mut |map, selection| {
14801 if selection.is_empty() {
14802 let mut cursor = if action.ignore_newlines {
14803 movement::next_subword_end(map, selection.head())
14804 } else {
14805 movement::next_subword_end_or_newline(map, selection.head())
14806 };
14807 cursor = movement::adjust_greedy_deletion(
14808 map,
14809 selection.head(),
14810 cursor,
14811 action.ignore_brackets,
14812 );
14813 selection.set_head(cursor, SelectionGoal::None);
14814 }
14815 });
14816 });
14817 this.insert("", window, cx);
14818 });
14819 }
14820
14821 pub fn move_to_beginning_of_line(
14822 &mut self,
14823 action: &MoveToBeginningOfLine,
14824 window: &mut Window,
14825 cx: &mut Context<Self>,
14826 ) {
14827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14828 self.change_selections(Default::default(), window, cx, |s| {
14829 s.move_cursors_with(&mut |map, head, _| {
14830 (
14831 movement::indented_line_beginning(
14832 map,
14833 head,
14834 action.stop_at_soft_wraps,
14835 action.stop_at_indent,
14836 ),
14837 SelectionGoal::None,
14838 )
14839 });
14840 })
14841 }
14842
14843 pub fn select_to_beginning_of_line(
14844 &mut self,
14845 action: &SelectToBeginningOfLine,
14846 window: &mut Window,
14847 cx: &mut Context<Self>,
14848 ) {
14849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14850 self.change_selections(Default::default(), window, cx, |s| {
14851 s.move_heads_with(&mut |map, head, _| {
14852 (
14853 movement::indented_line_beginning(
14854 map,
14855 head,
14856 action.stop_at_soft_wraps,
14857 action.stop_at_indent,
14858 ),
14859 SelectionGoal::None,
14860 )
14861 });
14862 });
14863 }
14864
14865 pub fn delete_to_beginning_of_line(
14866 &mut self,
14867 action: &DeleteToBeginningOfLine,
14868 window: &mut Window,
14869 cx: &mut Context<Self>,
14870 ) {
14871 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14872 self.transact(window, cx, |this, window, cx| {
14873 this.change_selections(Default::default(), window, cx, |s| {
14874 s.move_with(&mut |_, selection| {
14875 selection.reversed = true;
14876 });
14877 });
14878
14879 this.select_to_beginning_of_line(
14880 &SelectToBeginningOfLine {
14881 stop_at_soft_wraps: false,
14882 stop_at_indent: action.stop_at_indent,
14883 },
14884 window,
14885 cx,
14886 );
14887 this.backspace(&Backspace, window, cx);
14888 });
14889 }
14890
14891 pub fn move_to_end_of_line(
14892 &mut self,
14893 action: &MoveToEndOfLine,
14894 window: &mut Window,
14895 cx: &mut Context<Self>,
14896 ) {
14897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14898 self.change_selections(Default::default(), window, cx, |s| {
14899 s.move_cursors_with(&mut |map, head, _| {
14900 (
14901 movement::line_end(map, head, action.stop_at_soft_wraps),
14902 SelectionGoal::None,
14903 )
14904 });
14905 })
14906 }
14907
14908 pub fn select_to_end_of_line(
14909 &mut self,
14910 action: &SelectToEndOfLine,
14911 window: &mut Window,
14912 cx: &mut Context<Self>,
14913 ) {
14914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14915 self.change_selections(Default::default(), window, cx, |s| {
14916 s.move_heads_with(&mut |map, head, _| {
14917 (
14918 movement::line_end(map, head, action.stop_at_soft_wraps),
14919 SelectionGoal::None,
14920 )
14921 });
14922 })
14923 }
14924
14925 pub fn delete_to_end_of_line(
14926 &mut self,
14927 _: &DeleteToEndOfLine,
14928 window: &mut Window,
14929 cx: &mut Context<Self>,
14930 ) {
14931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14932 self.transact(window, cx, |this, window, cx| {
14933 this.select_to_end_of_line(
14934 &SelectToEndOfLine {
14935 stop_at_soft_wraps: false,
14936 },
14937 window,
14938 cx,
14939 );
14940 this.delete(&Delete, window, cx);
14941 });
14942 }
14943
14944 pub fn cut_to_end_of_line(
14945 &mut self,
14946 action: &CutToEndOfLine,
14947 window: &mut Window,
14948 cx: &mut Context<Self>,
14949 ) {
14950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14951 self.transact(window, cx, |this, window, cx| {
14952 this.select_to_end_of_line(
14953 &SelectToEndOfLine {
14954 stop_at_soft_wraps: false,
14955 },
14956 window,
14957 cx,
14958 );
14959 if !action.stop_at_newlines {
14960 this.change_selections(Default::default(), window, cx, |s| {
14961 s.move_with(&mut |_, sel| {
14962 if sel.is_empty() {
14963 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14964 }
14965 });
14966 });
14967 }
14968 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14969 let item = this.cut_common(false, window, cx);
14970 cx.write_to_clipboard(item);
14971 });
14972 }
14973
14974 pub fn move_to_start_of_paragraph(
14975 &mut self,
14976 _: &MoveToStartOfParagraph,
14977 window: &mut Window,
14978 cx: &mut Context<Self>,
14979 ) {
14980 if matches!(self.mode, EditorMode::SingleLine) {
14981 cx.propagate();
14982 return;
14983 }
14984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14985 self.change_selections(Default::default(), window, cx, |s| {
14986 s.move_with(&mut |map, selection| {
14987 selection.collapse_to(
14988 movement::start_of_paragraph(map, selection.head(), 1),
14989 SelectionGoal::None,
14990 )
14991 });
14992 })
14993 }
14994
14995 pub fn move_to_end_of_paragraph(
14996 &mut self,
14997 _: &MoveToEndOfParagraph,
14998 window: &mut Window,
14999 cx: &mut Context<Self>,
15000 ) {
15001 if matches!(self.mode, EditorMode::SingleLine) {
15002 cx.propagate();
15003 return;
15004 }
15005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15006 self.change_selections(Default::default(), window, cx, |s| {
15007 s.move_with(&mut |map, selection| {
15008 selection.collapse_to(
15009 movement::end_of_paragraph(map, selection.head(), 1),
15010 SelectionGoal::None,
15011 )
15012 });
15013 })
15014 }
15015
15016 pub fn select_to_start_of_paragraph(
15017 &mut self,
15018 _: &SelectToStartOfParagraph,
15019 window: &mut Window,
15020 cx: &mut Context<Self>,
15021 ) {
15022 if matches!(self.mode, EditorMode::SingleLine) {
15023 cx.propagate();
15024 return;
15025 }
15026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15027 self.change_selections(Default::default(), window, cx, |s| {
15028 s.move_heads_with(&mut |map, head, _| {
15029 (
15030 movement::start_of_paragraph(map, head, 1),
15031 SelectionGoal::None,
15032 )
15033 });
15034 })
15035 }
15036
15037 pub fn select_to_end_of_paragraph(
15038 &mut self,
15039 _: &SelectToEndOfParagraph,
15040 window: &mut Window,
15041 cx: &mut Context<Self>,
15042 ) {
15043 if matches!(self.mode, EditorMode::SingleLine) {
15044 cx.propagate();
15045 return;
15046 }
15047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15048 self.change_selections(Default::default(), window, cx, |s| {
15049 s.move_heads_with(&mut |map, head, _| {
15050 (
15051 movement::end_of_paragraph(map, head, 1),
15052 SelectionGoal::None,
15053 )
15054 });
15055 })
15056 }
15057
15058 pub fn move_to_start_of_excerpt(
15059 &mut self,
15060 _: &MoveToStartOfExcerpt,
15061 window: &mut Window,
15062 cx: &mut Context<Self>,
15063 ) {
15064 if matches!(self.mode, EditorMode::SingleLine) {
15065 cx.propagate();
15066 return;
15067 }
15068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15069 self.change_selections(Default::default(), window, cx, |s| {
15070 s.move_with(&mut |map, selection| {
15071 selection.collapse_to(
15072 movement::start_of_excerpt(
15073 map,
15074 selection.head(),
15075 workspace::searchable::Direction::Prev,
15076 ),
15077 SelectionGoal::None,
15078 )
15079 });
15080 })
15081 }
15082
15083 pub fn move_to_start_of_next_excerpt(
15084 &mut self,
15085 _: &MoveToStartOfNextExcerpt,
15086 window: &mut Window,
15087 cx: &mut Context<Self>,
15088 ) {
15089 if matches!(self.mode, EditorMode::SingleLine) {
15090 cx.propagate();
15091 return;
15092 }
15093
15094 self.change_selections(Default::default(), window, cx, |s| {
15095 s.move_with(&mut |map, selection| {
15096 selection.collapse_to(
15097 movement::start_of_excerpt(
15098 map,
15099 selection.head(),
15100 workspace::searchable::Direction::Next,
15101 ),
15102 SelectionGoal::None,
15103 )
15104 });
15105 })
15106 }
15107
15108 pub fn move_to_end_of_excerpt(
15109 &mut self,
15110 _: &MoveToEndOfExcerpt,
15111 window: &mut Window,
15112 cx: &mut Context<Self>,
15113 ) {
15114 if matches!(self.mode, EditorMode::SingleLine) {
15115 cx.propagate();
15116 return;
15117 }
15118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15119 self.change_selections(Default::default(), window, cx, |s| {
15120 s.move_with(&mut |map, selection| {
15121 selection.collapse_to(
15122 movement::end_of_excerpt(
15123 map,
15124 selection.head(),
15125 workspace::searchable::Direction::Next,
15126 ),
15127 SelectionGoal::None,
15128 )
15129 });
15130 })
15131 }
15132
15133 pub fn move_to_end_of_previous_excerpt(
15134 &mut self,
15135 _: &MoveToEndOfPreviousExcerpt,
15136 window: &mut Window,
15137 cx: &mut Context<Self>,
15138 ) {
15139 if matches!(self.mode, EditorMode::SingleLine) {
15140 cx.propagate();
15141 return;
15142 }
15143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15144 self.change_selections(Default::default(), window, cx, |s| {
15145 s.move_with(&mut |map, selection| {
15146 selection.collapse_to(
15147 movement::end_of_excerpt(
15148 map,
15149 selection.head(),
15150 workspace::searchable::Direction::Prev,
15151 ),
15152 SelectionGoal::None,
15153 )
15154 });
15155 })
15156 }
15157
15158 pub fn select_to_start_of_excerpt(
15159 &mut self,
15160 _: &SelectToStartOfExcerpt,
15161 window: &mut Window,
15162 cx: &mut Context<Self>,
15163 ) {
15164 if matches!(self.mode, EditorMode::SingleLine) {
15165 cx.propagate();
15166 return;
15167 }
15168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15169 self.change_selections(Default::default(), window, cx, |s| {
15170 s.move_heads_with(&mut |map, head, _| {
15171 (
15172 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15173 SelectionGoal::None,
15174 )
15175 });
15176 })
15177 }
15178
15179 pub fn select_to_start_of_next_excerpt(
15180 &mut self,
15181 _: &SelectToStartOfNextExcerpt,
15182 window: &mut Window,
15183 cx: &mut Context<Self>,
15184 ) {
15185 if matches!(self.mode, EditorMode::SingleLine) {
15186 cx.propagate();
15187 return;
15188 }
15189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15190 self.change_selections(Default::default(), window, cx, |s| {
15191 s.move_heads_with(&mut |map, head, _| {
15192 (
15193 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15194 SelectionGoal::None,
15195 )
15196 });
15197 })
15198 }
15199
15200 pub fn select_to_end_of_excerpt(
15201 &mut self,
15202 _: &SelectToEndOfExcerpt,
15203 window: &mut Window,
15204 cx: &mut Context<Self>,
15205 ) {
15206 if matches!(self.mode, EditorMode::SingleLine) {
15207 cx.propagate();
15208 return;
15209 }
15210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15211 self.change_selections(Default::default(), window, cx, |s| {
15212 s.move_heads_with(&mut |map, head, _| {
15213 (
15214 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15215 SelectionGoal::None,
15216 )
15217 });
15218 })
15219 }
15220
15221 pub fn select_to_end_of_previous_excerpt(
15222 &mut self,
15223 _: &SelectToEndOfPreviousExcerpt,
15224 window: &mut Window,
15225 cx: &mut Context<Self>,
15226 ) {
15227 if matches!(self.mode, EditorMode::SingleLine) {
15228 cx.propagate();
15229 return;
15230 }
15231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15232 self.change_selections(Default::default(), window, cx, |s| {
15233 s.move_heads_with(&mut |map, head, _| {
15234 (
15235 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15236 SelectionGoal::None,
15237 )
15238 });
15239 })
15240 }
15241
15242 pub fn move_to_beginning(
15243 &mut self,
15244 _: &MoveToBeginning,
15245 window: &mut Window,
15246 cx: &mut Context<Self>,
15247 ) {
15248 if matches!(self.mode, EditorMode::SingleLine) {
15249 cx.propagate();
15250 return;
15251 }
15252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15253 self.change_selections(Default::default(), window, cx, |s| {
15254 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15255 });
15256 }
15257
15258 pub fn select_to_beginning(
15259 &mut self,
15260 _: &SelectToBeginning,
15261 window: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) {
15264 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15265 selection.set_head(Point::zero(), SelectionGoal::None);
15266 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15267 self.change_selections(Default::default(), window, cx, |s| {
15268 s.select(vec![selection]);
15269 });
15270 }
15271
15272 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15273 if matches!(self.mode, EditorMode::SingleLine) {
15274 cx.propagate();
15275 return;
15276 }
15277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15278 let cursor = self.buffer.read(cx).read(cx).len();
15279 self.change_selections(Default::default(), window, cx, |s| {
15280 s.select_ranges(vec![cursor..cursor])
15281 });
15282 }
15283
15284 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15285 self.nav_history = nav_history;
15286 }
15287
15288 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15289 self.nav_history.as_ref()
15290 }
15291
15292 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15293 self.push_to_nav_history(
15294 self.selections.newest_anchor().head(),
15295 None,
15296 false,
15297 true,
15298 cx,
15299 );
15300 }
15301
15302 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15303 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15304 let buffer = self.buffer.read(cx).read(cx);
15305 let cursor_position = cursor_anchor.to_point(&buffer);
15306 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15307 let scroll_top_row = scroll_anchor.top_row(&buffer);
15308 drop(buffer);
15309
15310 NavigationData {
15311 cursor_anchor,
15312 cursor_position,
15313 scroll_anchor,
15314 scroll_top_row,
15315 }
15316 }
15317
15318 fn navigation_entry(
15319 &self,
15320 cursor_anchor: Anchor,
15321 cx: &mut Context<Self>,
15322 ) -> Option<NavigationEntry> {
15323 let Some(history) = self.nav_history.clone() else {
15324 return None;
15325 };
15326 let data = self.navigation_data(cursor_anchor, cx);
15327 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15328 }
15329
15330 fn push_to_nav_history(
15331 &mut self,
15332 cursor_anchor: Anchor,
15333 new_position: Option<Point>,
15334 is_deactivate: bool,
15335 always: bool,
15336 cx: &mut Context<Self>,
15337 ) {
15338 let data = self.navigation_data(cursor_anchor, cx);
15339 if let Some(nav_history) = self.nav_history.as_mut() {
15340 if let Some(new_position) = new_position {
15341 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15342 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15343 return;
15344 }
15345 }
15346
15347 nav_history.push(Some(data), cx);
15348 cx.emit(EditorEvent::PushedToNavHistory {
15349 anchor: cursor_anchor,
15350 is_deactivate,
15351 })
15352 }
15353 }
15354
15355 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15357 let buffer = self.buffer.read(cx).snapshot(cx);
15358 let mut selection = self
15359 .selections
15360 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15361 selection.set_head(buffer.len(), SelectionGoal::None);
15362 self.change_selections(Default::default(), window, cx, |s| {
15363 s.select(vec![selection]);
15364 });
15365 }
15366
15367 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15368 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15369 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15370 s.select_ranges([Anchor::min()..Anchor::max()]);
15371 });
15372 }
15373
15374 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15376 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15377 let mut selections = self.selections.all::<Point>(&display_map);
15378 let max_point = display_map.buffer_snapshot().max_point();
15379 for selection in &mut selections {
15380 let rows = selection.spanned_rows(true, &display_map);
15381 selection.start = Point::new(rows.start.0, 0);
15382 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15383 selection.reversed = false;
15384 }
15385 self.change_selections(Default::default(), window, cx, |s| {
15386 s.select(selections);
15387 });
15388 }
15389
15390 pub fn split_selection_into_lines(
15391 &mut self,
15392 action: &SplitSelectionIntoLines,
15393 window: &mut Window,
15394 cx: &mut Context<Self>,
15395 ) {
15396 let selections = self
15397 .selections
15398 .all::<Point>(&self.display_snapshot(cx))
15399 .into_iter()
15400 .map(|selection| selection.start..selection.end)
15401 .collect::<Vec<_>>();
15402 self.unfold_ranges(&selections, true, false, cx);
15403
15404 let mut new_selection_ranges = Vec::new();
15405 {
15406 let buffer = self.buffer.read(cx).read(cx);
15407 for selection in selections {
15408 for row in selection.start.row..selection.end.row {
15409 let line_start = Point::new(row, 0);
15410 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15411
15412 if action.keep_selections {
15413 // Keep the selection range for each line
15414 let selection_start = if row == selection.start.row {
15415 selection.start
15416 } else {
15417 line_start
15418 };
15419 new_selection_ranges.push(selection_start..line_end);
15420 } else {
15421 // Collapse to cursor at end of line
15422 new_selection_ranges.push(line_end..line_end);
15423 }
15424 }
15425
15426 let is_multiline_selection = selection.start.row != selection.end.row;
15427 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15428 // so this action feels more ergonomic when paired with other selection operations
15429 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15430 if !should_skip_last {
15431 if action.keep_selections {
15432 if is_multiline_selection {
15433 let line_start = Point::new(selection.end.row, 0);
15434 new_selection_ranges.push(line_start..selection.end);
15435 } else {
15436 new_selection_ranges.push(selection.start..selection.end);
15437 }
15438 } else {
15439 new_selection_ranges.push(selection.end..selection.end);
15440 }
15441 }
15442 }
15443 }
15444 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15445 s.select_ranges(new_selection_ranges);
15446 });
15447 }
15448
15449 pub fn add_selection_above(
15450 &mut self,
15451 action: &AddSelectionAbove,
15452 window: &mut Window,
15453 cx: &mut Context<Self>,
15454 ) {
15455 self.add_selection(true, action.skip_soft_wrap, window, cx);
15456 }
15457
15458 pub fn add_selection_below(
15459 &mut self,
15460 action: &AddSelectionBelow,
15461 window: &mut Window,
15462 cx: &mut Context<Self>,
15463 ) {
15464 self.add_selection(false, action.skip_soft_wrap, window, cx);
15465 }
15466
15467 fn add_selection(
15468 &mut self,
15469 above: bool,
15470 skip_soft_wrap: bool,
15471 window: &mut Window,
15472 cx: &mut Context<Self>,
15473 ) {
15474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15475
15476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15477 let all_selections = self.selections.all::<Point>(&display_map);
15478 let text_layout_details = self.text_layout_details(window, cx);
15479
15480 let (mut columnar_selections, new_selections_to_columnarize) = {
15481 if let Some(state) = self.add_selections_state.as_ref() {
15482 let columnar_selection_ids: HashSet<_> = state
15483 .groups
15484 .iter()
15485 .flat_map(|group| group.stack.iter())
15486 .copied()
15487 .collect();
15488
15489 all_selections
15490 .into_iter()
15491 .partition(|s| columnar_selection_ids.contains(&s.id))
15492 } else {
15493 (Vec::new(), all_selections)
15494 }
15495 };
15496
15497 let mut state = self
15498 .add_selections_state
15499 .take()
15500 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15501
15502 for selection in new_selections_to_columnarize {
15503 let range = selection.display_range(&display_map).sorted();
15504 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15505 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15506 let positions = start_x.min(end_x)..start_x.max(end_x);
15507 let mut stack = Vec::new();
15508 for row in range.start.row().0..=range.end.row().0 {
15509 if let Some(selection) = self.selections.build_columnar_selection(
15510 &display_map,
15511 DisplayRow(row),
15512 &positions,
15513 selection.reversed,
15514 &text_layout_details,
15515 ) {
15516 stack.push(selection.id);
15517 columnar_selections.push(selection);
15518 }
15519 }
15520 if !stack.is_empty() {
15521 if above {
15522 stack.reverse();
15523 }
15524 state.groups.push(AddSelectionsGroup { above, stack });
15525 }
15526 }
15527
15528 let mut final_selections = Vec::new();
15529 let end_row = if above {
15530 DisplayRow(0)
15531 } else {
15532 display_map.max_point().row()
15533 };
15534
15535 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15536 // positions to place new selections, so we need to keep track of the
15537 // column range of the oldest selection in each group, because
15538 // intermediate selections may have been clamped to shorter lines.
15539 // selections may have been clamped to shorter lines.
15540 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15541 let mut map = HashMap::default();
15542 for group in state.groups.iter() {
15543 if let Some(oldest_id) = group.stack.first() {
15544 if let Some(oldest_selection) =
15545 columnar_selections.iter().find(|s| s.id == *oldest_id)
15546 {
15547 let start_col = oldest_selection.start.column;
15548 let end_col = oldest_selection.end.column;
15549 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15550 for id in &group.stack {
15551 map.insert(*id, goal_columns.clone());
15552 }
15553 }
15554 }
15555 }
15556 map
15557 } else {
15558 HashMap::default()
15559 };
15560
15561 let mut last_added_item_per_group = HashMap::default();
15562 for group in state.groups.iter_mut() {
15563 if let Some(last_id) = group.stack.last() {
15564 last_added_item_per_group.insert(*last_id, group);
15565 }
15566 }
15567
15568 for selection in columnar_selections {
15569 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15570 if above == group.above {
15571 let range = selection.display_range(&display_map).sorted();
15572 debug_assert_eq!(range.start.row(), range.end.row());
15573 let row = range.start.row();
15574 let positions =
15575 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15576 Pixels::from(start)..Pixels::from(end)
15577 } else {
15578 let start_x =
15579 display_map.x_for_display_point(range.start, &text_layout_details);
15580 let end_x =
15581 display_map.x_for_display_point(range.end, &text_layout_details);
15582 start_x.min(end_x)..start_x.max(end_x)
15583 };
15584
15585 let maybe_new_selection = if skip_soft_wrap {
15586 let goal_columns = goal_columns_by_selection_id
15587 .remove(&selection.id)
15588 .unwrap_or_else(|| {
15589 let start_col = selection.start.column;
15590 let end_col = selection.end.column;
15591 start_col.min(end_col)..start_col.max(end_col)
15592 });
15593 self.selections.find_next_columnar_selection_by_buffer_row(
15594 &display_map,
15595 row,
15596 end_row,
15597 above,
15598 &goal_columns,
15599 selection.reversed,
15600 &text_layout_details,
15601 )
15602 } else {
15603 self.selections.find_next_columnar_selection_by_display_row(
15604 &display_map,
15605 row,
15606 end_row,
15607 above,
15608 &positions,
15609 selection.reversed,
15610 &text_layout_details,
15611 )
15612 };
15613
15614 if let Some(new_selection) = maybe_new_selection {
15615 group.stack.push(new_selection.id);
15616 if above {
15617 final_selections.push(new_selection);
15618 final_selections.push(selection);
15619 } else {
15620 final_selections.push(selection);
15621 final_selections.push(new_selection);
15622 }
15623 } else {
15624 final_selections.push(selection);
15625 }
15626 } else {
15627 group.stack.pop();
15628 }
15629 } else {
15630 final_selections.push(selection);
15631 }
15632 }
15633
15634 self.change_selections(Default::default(), window, cx, |s| {
15635 s.select(final_selections);
15636 });
15637
15638 let final_selection_ids: HashSet<_> = self
15639 .selections
15640 .all::<Point>(&display_map)
15641 .iter()
15642 .map(|s| s.id)
15643 .collect();
15644 state.groups.retain_mut(|group| {
15645 // selections might get merged above so we remove invalid items from stacks
15646 group.stack.retain(|id| final_selection_ids.contains(id));
15647
15648 // single selection in stack can be treated as initial state
15649 group.stack.len() > 1
15650 });
15651
15652 if !state.groups.is_empty() {
15653 self.add_selections_state = Some(state);
15654 }
15655 }
15656
15657 pub fn insert_snippet_at_selections(
15658 &mut self,
15659 action: &InsertSnippet,
15660 window: &mut Window,
15661 cx: &mut Context<Self>,
15662 ) {
15663 self.try_insert_snippet_at_selections(action, window, cx)
15664 .log_err();
15665 }
15666
15667 fn try_insert_snippet_at_selections(
15668 &mut self,
15669 action: &InsertSnippet,
15670 window: &mut Window,
15671 cx: &mut Context<Self>,
15672 ) -> Result<()> {
15673 let insertion_ranges = self
15674 .selections
15675 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15676 .into_iter()
15677 .map(|selection| selection.range())
15678 .collect_vec();
15679
15680 let snippet = if let Some(snippet_body) = &action.snippet {
15681 if action.language.is_none() && action.name.is_none() {
15682 Snippet::parse(snippet_body)?
15683 } else {
15684 bail!("`snippet` is mutually exclusive with `language` and `name`")
15685 }
15686 } else if let Some(name) = &action.name {
15687 let project = self.project().context("no project")?;
15688 let snippet_store = project.read(cx).snippets().read(cx);
15689 let snippet = snippet_store
15690 .snippets_for(action.language.clone(), cx)
15691 .into_iter()
15692 .find(|snippet| snippet.name == *name)
15693 .context("snippet not found")?;
15694 Snippet::parse(&snippet.body)?
15695 } else {
15696 // todo(andrew): open modal to select snippet
15697 bail!("`name` or `snippet` is required")
15698 };
15699
15700 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15701 }
15702
15703 fn select_match_ranges(
15704 &mut self,
15705 range: Range<MultiBufferOffset>,
15706 reversed: bool,
15707 replace_newest: bool,
15708 auto_scroll: Option<Autoscroll>,
15709 window: &mut Window,
15710 cx: &mut Context<Editor>,
15711 ) {
15712 self.unfold_ranges(
15713 std::slice::from_ref(&range),
15714 false,
15715 auto_scroll.is_some(),
15716 cx,
15717 );
15718 let effects = if let Some(scroll) = auto_scroll {
15719 SelectionEffects::scroll(scroll)
15720 } else {
15721 SelectionEffects::no_scroll()
15722 };
15723 self.change_selections(effects, window, cx, |s| {
15724 if replace_newest {
15725 s.delete(s.newest_anchor().id);
15726 }
15727 if reversed {
15728 s.insert_range(range.end..range.start);
15729 } else {
15730 s.insert_range(range);
15731 }
15732 });
15733 }
15734
15735 pub fn select_next_match_internal(
15736 &mut self,
15737 display_map: &DisplaySnapshot,
15738 replace_newest: bool,
15739 autoscroll: Option<Autoscroll>,
15740 window: &mut Window,
15741 cx: &mut Context<Self>,
15742 ) -> Result<()> {
15743 let buffer = display_map.buffer_snapshot();
15744 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15745 if let Some(mut select_next_state) = self.select_next_state.take() {
15746 let query = &select_next_state.query;
15747 if !select_next_state.done {
15748 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15749 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15750 let mut next_selected_range = None;
15751
15752 let bytes_after_last_selection =
15753 buffer.bytes_in_range(last_selection.end..buffer.len());
15754 let bytes_before_first_selection =
15755 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15756 let query_matches = query
15757 .stream_find_iter(bytes_after_last_selection)
15758 .map(|result| (last_selection.end, result))
15759 .chain(
15760 query
15761 .stream_find_iter(bytes_before_first_selection)
15762 .map(|result| (MultiBufferOffset(0), result)),
15763 );
15764
15765 for (start_offset, query_match) in query_matches {
15766 let query_match = query_match.unwrap(); // can only fail due to I/O
15767 let offset_range =
15768 start_offset + query_match.start()..start_offset + query_match.end();
15769
15770 if !select_next_state.wordwise
15771 || (!buffer.is_inside_word(offset_range.start, None)
15772 && !buffer.is_inside_word(offset_range.end, None))
15773 {
15774 let idx = selections
15775 .partition_point(|selection| selection.end <= offset_range.start);
15776 let overlaps = selections
15777 .get(idx)
15778 .map_or(false, |selection| selection.start < offset_range.end);
15779
15780 if !overlaps {
15781 next_selected_range = Some(offset_range);
15782 break;
15783 }
15784 }
15785 }
15786
15787 if let Some(next_selected_range) = next_selected_range {
15788 self.select_match_ranges(
15789 next_selected_range,
15790 last_selection.reversed,
15791 replace_newest,
15792 autoscroll,
15793 window,
15794 cx,
15795 );
15796 } else {
15797 select_next_state.done = true;
15798 }
15799 }
15800
15801 self.select_next_state = Some(select_next_state);
15802 } else {
15803 let mut only_carets = true;
15804 let mut same_text_selected = true;
15805 let mut selected_text = None;
15806
15807 let mut selections_iter = selections.iter().peekable();
15808 while let Some(selection) = selections_iter.next() {
15809 if selection.start != selection.end {
15810 only_carets = false;
15811 }
15812
15813 if same_text_selected {
15814 if selected_text.is_none() {
15815 selected_text =
15816 Some(buffer.text_for_range(selection.range()).collect::<String>());
15817 }
15818
15819 if let Some(next_selection) = selections_iter.peek() {
15820 if next_selection.len() == selection.len() {
15821 let next_selected_text = buffer
15822 .text_for_range(next_selection.range())
15823 .collect::<String>();
15824 if Some(next_selected_text) != selected_text {
15825 same_text_selected = false;
15826 selected_text = None;
15827 }
15828 } else {
15829 same_text_selected = false;
15830 selected_text = None;
15831 }
15832 }
15833 }
15834 }
15835
15836 if only_carets {
15837 for selection in &mut selections {
15838 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15839 selection.start = word_range.start;
15840 selection.end = word_range.end;
15841 selection.goal = SelectionGoal::None;
15842 selection.reversed = false;
15843 self.select_match_ranges(
15844 selection.start..selection.end,
15845 selection.reversed,
15846 replace_newest,
15847 autoscroll,
15848 window,
15849 cx,
15850 );
15851 }
15852
15853 if selections.len() == 1 {
15854 let selection = selections
15855 .last()
15856 .expect("ensured that there's only one selection");
15857 let query = buffer
15858 .text_for_range(selection.start..selection.end)
15859 .collect::<String>();
15860 let is_empty = query.is_empty();
15861 let select_state = SelectNextState {
15862 query: self.build_query(&[query], cx)?,
15863 wordwise: true,
15864 done: is_empty,
15865 };
15866 self.select_next_state = Some(select_state);
15867 } else {
15868 self.select_next_state = None;
15869 }
15870 } else if let Some(selected_text) = selected_text {
15871 self.select_next_state = Some(SelectNextState {
15872 query: self.build_query(&[selected_text], cx)?,
15873 wordwise: false,
15874 done: false,
15875 });
15876 self.select_next_match_internal(
15877 display_map,
15878 replace_newest,
15879 autoscroll,
15880 window,
15881 cx,
15882 )?;
15883 }
15884 }
15885 Ok(())
15886 }
15887
15888 pub fn select_all_matches(
15889 &mut self,
15890 _action: &SelectAllMatches,
15891 window: &mut Window,
15892 cx: &mut Context<Self>,
15893 ) -> Result<()> {
15894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15895
15896 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15897
15898 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15899 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15900 else {
15901 return Ok(());
15902 };
15903
15904 let mut new_selections = Vec::new();
15905
15906 let reversed = self
15907 .selections
15908 .oldest::<MultiBufferOffset>(&display_map)
15909 .reversed;
15910 let buffer = display_map.buffer_snapshot();
15911 let query_matches = select_next_state
15912 .query
15913 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15914
15915 for query_match in query_matches.into_iter() {
15916 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15917 let offset_range = if reversed {
15918 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15919 } else {
15920 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15921 };
15922
15923 if !select_next_state.wordwise
15924 || (!buffer.is_inside_word(offset_range.start, None)
15925 && !buffer.is_inside_word(offset_range.end, None))
15926 {
15927 new_selections.push(offset_range.start..offset_range.end);
15928 }
15929 }
15930
15931 select_next_state.done = true;
15932
15933 if new_selections.is_empty() {
15934 log::error!("bug: new_selections is empty in select_all_matches");
15935 return Ok(());
15936 }
15937
15938 self.unfold_ranges(&new_selections, false, false, cx);
15939 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15940 selections.select_ranges(new_selections)
15941 });
15942
15943 Ok(())
15944 }
15945
15946 pub fn select_next(
15947 &mut self,
15948 action: &SelectNext,
15949 window: &mut Window,
15950 cx: &mut Context<Self>,
15951 ) -> Result<()> {
15952 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15953 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15954 self.select_next_match_internal(
15955 &display_map,
15956 action.replace_newest,
15957 Some(Autoscroll::newest()),
15958 window,
15959 cx,
15960 )
15961 }
15962
15963 pub fn select_previous(
15964 &mut self,
15965 action: &SelectPrevious,
15966 window: &mut Window,
15967 cx: &mut Context<Self>,
15968 ) -> Result<()> {
15969 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15970 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15971 let buffer = display_map.buffer_snapshot();
15972 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15973 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15974 let query = &select_prev_state.query;
15975 if !select_prev_state.done {
15976 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15977 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15978 let mut next_selected_range = None;
15979 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15980 let bytes_before_last_selection =
15981 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15982 let bytes_after_first_selection =
15983 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15984 let query_matches = query
15985 .stream_find_iter(bytes_before_last_selection)
15986 .map(|result| (last_selection.start, result))
15987 .chain(
15988 query
15989 .stream_find_iter(bytes_after_first_selection)
15990 .map(|result| (buffer.len(), result)),
15991 );
15992 for (end_offset, query_match) in query_matches {
15993 let query_match = query_match.unwrap(); // can only fail due to I/O
15994 let offset_range =
15995 end_offset - query_match.end()..end_offset - query_match.start();
15996
15997 if !select_prev_state.wordwise
15998 || (!buffer.is_inside_word(offset_range.start, None)
15999 && !buffer.is_inside_word(offset_range.end, None))
16000 {
16001 next_selected_range = Some(offset_range);
16002 break;
16003 }
16004 }
16005
16006 if let Some(next_selected_range) = next_selected_range {
16007 self.select_match_ranges(
16008 next_selected_range,
16009 last_selection.reversed,
16010 action.replace_newest,
16011 Some(Autoscroll::newest()),
16012 window,
16013 cx,
16014 );
16015 } else {
16016 select_prev_state.done = true;
16017 }
16018 }
16019
16020 self.select_prev_state = Some(select_prev_state);
16021 } else {
16022 let mut only_carets = true;
16023 let mut same_text_selected = true;
16024 let mut selected_text = None;
16025
16026 let mut selections_iter = selections.iter().peekable();
16027 while let Some(selection) = selections_iter.next() {
16028 if selection.start != selection.end {
16029 only_carets = false;
16030 }
16031
16032 if same_text_selected {
16033 if selected_text.is_none() {
16034 selected_text =
16035 Some(buffer.text_for_range(selection.range()).collect::<String>());
16036 }
16037
16038 if let Some(next_selection) = selections_iter.peek() {
16039 if next_selection.len() == selection.len() {
16040 let next_selected_text = buffer
16041 .text_for_range(next_selection.range())
16042 .collect::<String>();
16043 if Some(next_selected_text) != selected_text {
16044 same_text_selected = false;
16045 selected_text = None;
16046 }
16047 } else {
16048 same_text_selected = false;
16049 selected_text = None;
16050 }
16051 }
16052 }
16053 }
16054
16055 if only_carets {
16056 for selection in &mut selections {
16057 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16058 selection.start = word_range.start;
16059 selection.end = word_range.end;
16060 selection.goal = SelectionGoal::None;
16061 selection.reversed = false;
16062 self.select_match_ranges(
16063 selection.start..selection.end,
16064 selection.reversed,
16065 action.replace_newest,
16066 Some(Autoscroll::newest()),
16067 window,
16068 cx,
16069 );
16070 }
16071 if selections.len() == 1 {
16072 let selection = selections
16073 .last()
16074 .expect("ensured that there's only one selection");
16075 let query = buffer
16076 .text_for_range(selection.start..selection.end)
16077 .collect::<String>();
16078 let is_empty = query.is_empty();
16079 let select_state = SelectNextState {
16080 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16081 wordwise: true,
16082 done: is_empty,
16083 };
16084 self.select_prev_state = Some(select_state);
16085 } else {
16086 self.select_prev_state = None;
16087 }
16088 } else if let Some(selected_text) = selected_text {
16089 self.select_prev_state = Some(SelectNextState {
16090 query: self
16091 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16092 wordwise: false,
16093 done: false,
16094 });
16095 self.select_previous(action, window, cx)?;
16096 }
16097 }
16098 Ok(())
16099 }
16100
16101 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16102 /// setting the case sensitivity based on the global
16103 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16104 /// editor's settings.
16105 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16106 where
16107 I: IntoIterator<Item = P>,
16108 P: AsRef<[u8]>,
16109 {
16110 let case_sensitive = self
16111 .select_next_is_case_sensitive
16112 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16113
16114 let mut builder = AhoCorasickBuilder::new();
16115 builder.ascii_case_insensitive(!case_sensitive);
16116 builder.build(patterns)
16117 }
16118
16119 pub fn find_next_match(
16120 &mut self,
16121 _: &FindNextMatch,
16122 window: &mut Window,
16123 cx: &mut Context<Self>,
16124 ) -> Result<()> {
16125 let selections = self.selections.disjoint_anchors_arc();
16126 match selections.first() {
16127 Some(first) if selections.len() >= 2 => {
16128 self.change_selections(Default::default(), window, cx, |s| {
16129 s.select_ranges([first.range()]);
16130 });
16131 }
16132 _ => self.select_next(
16133 &SelectNext {
16134 replace_newest: true,
16135 },
16136 window,
16137 cx,
16138 )?,
16139 }
16140 Ok(())
16141 }
16142
16143 pub fn find_previous_match(
16144 &mut self,
16145 _: &FindPreviousMatch,
16146 window: &mut Window,
16147 cx: &mut Context<Self>,
16148 ) -> Result<()> {
16149 let selections = self.selections.disjoint_anchors_arc();
16150 match selections.last() {
16151 Some(last) if selections.len() >= 2 => {
16152 self.change_selections(Default::default(), window, cx, |s| {
16153 s.select_ranges([last.range()]);
16154 });
16155 }
16156 _ => self.select_previous(
16157 &SelectPrevious {
16158 replace_newest: true,
16159 },
16160 window,
16161 cx,
16162 )?,
16163 }
16164 Ok(())
16165 }
16166
16167 pub fn toggle_comments(
16168 &mut self,
16169 action: &ToggleComments,
16170 window: &mut Window,
16171 cx: &mut Context<Self>,
16172 ) {
16173 if self.read_only(cx) {
16174 return;
16175 }
16176 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16177 let text_layout_details = &self.text_layout_details(window, cx);
16178 self.transact(window, cx, |this, window, cx| {
16179 let mut selections = this
16180 .selections
16181 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16182 let mut edits = Vec::new();
16183 let mut selection_edit_ranges = Vec::new();
16184 let mut last_toggled_row = None;
16185 let snapshot = this.buffer.read(cx).read(cx);
16186 let empty_str: Arc<str> = Arc::default();
16187 let mut suffixes_inserted = Vec::new();
16188 let ignore_indent = action.ignore_indent;
16189
16190 fn comment_prefix_range(
16191 snapshot: &MultiBufferSnapshot,
16192 row: MultiBufferRow,
16193 comment_prefix: &str,
16194 comment_prefix_whitespace: &str,
16195 ignore_indent: bool,
16196 ) -> Range<Point> {
16197 let indent_size = if ignore_indent {
16198 0
16199 } else {
16200 snapshot.indent_size_for_line(row).len
16201 };
16202
16203 let start = Point::new(row.0, indent_size);
16204
16205 let mut line_bytes = snapshot
16206 .bytes_in_range(start..snapshot.max_point())
16207 .flatten()
16208 .copied();
16209
16210 // If this line currently begins with the line comment prefix, then record
16211 // the range containing the prefix.
16212 if line_bytes
16213 .by_ref()
16214 .take(comment_prefix.len())
16215 .eq(comment_prefix.bytes())
16216 {
16217 // Include any whitespace that matches the comment prefix.
16218 let matching_whitespace_len = line_bytes
16219 .zip(comment_prefix_whitespace.bytes())
16220 .take_while(|(a, b)| a == b)
16221 .count() as u32;
16222 let end = Point::new(
16223 start.row,
16224 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16225 );
16226 start..end
16227 } else {
16228 start..start
16229 }
16230 }
16231
16232 fn comment_suffix_range(
16233 snapshot: &MultiBufferSnapshot,
16234 row: MultiBufferRow,
16235 comment_suffix: &str,
16236 comment_suffix_has_leading_space: bool,
16237 ) -> Range<Point> {
16238 let end = Point::new(row.0, snapshot.line_len(row));
16239 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16240
16241 let mut line_end_bytes = snapshot
16242 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16243 .flatten()
16244 .copied();
16245
16246 let leading_space_len = if suffix_start_column > 0
16247 && line_end_bytes.next() == Some(b' ')
16248 && comment_suffix_has_leading_space
16249 {
16250 1
16251 } else {
16252 0
16253 };
16254
16255 // If this line currently begins with the line comment prefix, then record
16256 // the range containing the prefix.
16257 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16258 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16259 start..end
16260 } else {
16261 end..end
16262 }
16263 }
16264
16265 // TODO: Handle selections that cross excerpts
16266 for selection in &mut selections {
16267 let start_column = snapshot
16268 .indent_size_for_line(MultiBufferRow(selection.start.row))
16269 .len;
16270 let language = if let Some(language) =
16271 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16272 {
16273 language
16274 } else {
16275 continue;
16276 };
16277
16278 selection_edit_ranges.clear();
16279
16280 // If multiple selections contain a given row, avoid processing that
16281 // row more than once.
16282 let mut start_row = MultiBufferRow(selection.start.row);
16283 if last_toggled_row == Some(start_row) {
16284 start_row = start_row.next_row();
16285 }
16286 let end_row =
16287 if selection.end.row > selection.start.row && selection.end.column == 0 {
16288 MultiBufferRow(selection.end.row - 1)
16289 } else {
16290 MultiBufferRow(selection.end.row)
16291 };
16292 last_toggled_row = Some(end_row);
16293
16294 if start_row > end_row {
16295 continue;
16296 }
16297
16298 // If the language has line comments, toggle those.
16299 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16300
16301 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16302 if ignore_indent {
16303 full_comment_prefixes = full_comment_prefixes
16304 .into_iter()
16305 .map(|s| Arc::from(s.trim_end()))
16306 .collect();
16307 }
16308
16309 if !full_comment_prefixes.is_empty() {
16310 let first_prefix = full_comment_prefixes
16311 .first()
16312 .expect("prefixes is non-empty");
16313 let prefix_trimmed_lengths = full_comment_prefixes
16314 .iter()
16315 .map(|p| p.trim_end_matches(' ').len())
16316 .collect::<SmallVec<[usize; 4]>>();
16317
16318 let mut all_selection_lines_are_comments = true;
16319
16320 for row in start_row.0..=end_row.0 {
16321 let row = MultiBufferRow(row);
16322 if start_row < end_row && snapshot.is_line_blank(row) {
16323 continue;
16324 }
16325
16326 let prefix_range = full_comment_prefixes
16327 .iter()
16328 .zip(prefix_trimmed_lengths.iter().copied())
16329 .map(|(prefix, trimmed_prefix_len)| {
16330 comment_prefix_range(
16331 snapshot.deref(),
16332 row,
16333 &prefix[..trimmed_prefix_len],
16334 &prefix[trimmed_prefix_len..],
16335 ignore_indent,
16336 )
16337 })
16338 .max_by_key(|range| range.end.column - range.start.column)
16339 .expect("prefixes is non-empty");
16340
16341 if prefix_range.is_empty() {
16342 all_selection_lines_are_comments = false;
16343 }
16344
16345 selection_edit_ranges.push(prefix_range);
16346 }
16347
16348 if all_selection_lines_are_comments {
16349 edits.extend(
16350 selection_edit_ranges
16351 .iter()
16352 .cloned()
16353 .map(|range| (range, empty_str.clone())),
16354 );
16355 } else {
16356 let min_column = selection_edit_ranges
16357 .iter()
16358 .map(|range| range.start.column)
16359 .min()
16360 .unwrap_or(0);
16361 edits.extend(selection_edit_ranges.iter().map(|range| {
16362 let position = Point::new(range.start.row, min_column);
16363 (position..position, first_prefix.clone())
16364 }));
16365 }
16366 } else if let Some(BlockCommentConfig {
16367 start: full_comment_prefix,
16368 end: comment_suffix,
16369 ..
16370 }) = language.block_comment()
16371 {
16372 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16373 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16374 let prefix_range = comment_prefix_range(
16375 snapshot.deref(),
16376 start_row,
16377 comment_prefix,
16378 comment_prefix_whitespace,
16379 ignore_indent,
16380 );
16381 let suffix_range = comment_suffix_range(
16382 snapshot.deref(),
16383 end_row,
16384 comment_suffix.trim_start_matches(' '),
16385 comment_suffix.starts_with(' '),
16386 );
16387
16388 if prefix_range.is_empty() || suffix_range.is_empty() {
16389 edits.push((
16390 prefix_range.start..prefix_range.start,
16391 full_comment_prefix.clone(),
16392 ));
16393 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16394 suffixes_inserted.push((end_row, comment_suffix.len()));
16395 } else {
16396 edits.push((prefix_range, empty_str.clone()));
16397 edits.push((suffix_range, empty_str.clone()));
16398 }
16399 } else {
16400 continue;
16401 }
16402 }
16403
16404 drop(snapshot);
16405 this.buffer.update(cx, |buffer, cx| {
16406 buffer.edit(edits, None, cx);
16407 });
16408
16409 // Adjust selections so that they end before any comment suffixes that
16410 // were inserted.
16411 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16412 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16413 let snapshot = this.buffer.read(cx).read(cx);
16414 for selection in &mut selections {
16415 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16416 match row.cmp(&MultiBufferRow(selection.end.row)) {
16417 Ordering::Less => {
16418 suffixes_inserted.next();
16419 continue;
16420 }
16421 Ordering::Greater => break,
16422 Ordering::Equal => {
16423 if selection.end.column == snapshot.line_len(row) {
16424 if selection.is_empty() {
16425 selection.start.column -= suffix_len as u32;
16426 }
16427 selection.end.column -= suffix_len as u32;
16428 }
16429 break;
16430 }
16431 }
16432 }
16433 }
16434
16435 drop(snapshot);
16436 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16437
16438 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16439 let selections_on_single_row = selections.windows(2).all(|selections| {
16440 selections[0].start.row == selections[1].start.row
16441 && selections[0].end.row == selections[1].end.row
16442 && selections[0].start.row == selections[0].end.row
16443 });
16444 let selections_selecting = selections
16445 .iter()
16446 .any(|selection| selection.start != selection.end);
16447 let advance_downwards = action.advance_downwards
16448 && selections_on_single_row
16449 && !selections_selecting
16450 && !matches!(this.mode, EditorMode::SingleLine);
16451
16452 if advance_downwards {
16453 let snapshot = this.buffer.read(cx).snapshot(cx);
16454
16455 this.change_selections(Default::default(), window, cx, |s| {
16456 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16457 let mut point = display_point.to_point(display_snapshot);
16458 point.row += 1;
16459 point = snapshot.clip_point(point, Bias::Left);
16460 let display_point = point.to_display_point(display_snapshot);
16461 let goal = SelectionGoal::HorizontalPosition(
16462 display_snapshot
16463 .x_for_display_point(display_point, text_layout_details)
16464 .into(),
16465 );
16466 (display_point, goal)
16467 })
16468 });
16469 }
16470 });
16471 }
16472
16473 pub fn select_enclosing_symbol(
16474 &mut self,
16475 _: &SelectEnclosingSymbol,
16476 window: &mut Window,
16477 cx: &mut Context<Self>,
16478 ) {
16479 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16480
16481 let buffer = self.buffer.read(cx).snapshot(cx);
16482 let old_selections = self
16483 .selections
16484 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16485 .into_boxed_slice();
16486
16487 fn update_selection(
16488 selection: &Selection<MultiBufferOffset>,
16489 buffer_snap: &MultiBufferSnapshot,
16490 ) -> Option<Selection<MultiBufferOffset>> {
16491 let cursor = selection.head();
16492 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16493 for symbol in symbols.iter().rev() {
16494 let start = symbol.range.start.to_offset(buffer_snap);
16495 let end = symbol.range.end.to_offset(buffer_snap);
16496 let new_range = start..end;
16497 if start < selection.start || end > selection.end {
16498 return Some(Selection {
16499 id: selection.id,
16500 start: new_range.start,
16501 end: new_range.end,
16502 goal: SelectionGoal::None,
16503 reversed: selection.reversed,
16504 });
16505 }
16506 }
16507 None
16508 }
16509
16510 let mut selected_larger_symbol = false;
16511 let new_selections = old_selections
16512 .iter()
16513 .map(|selection| match update_selection(selection, &buffer) {
16514 Some(new_selection) => {
16515 if new_selection.range() != selection.range() {
16516 selected_larger_symbol = true;
16517 }
16518 new_selection
16519 }
16520 None => selection.clone(),
16521 })
16522 .collect::<Vec<_>>();
16523
16524 if selected_larger_symbol {
16525 self.change_selections(Default::default(), window, cx, |s| {
16526 s.select(new_selections);
16527 });
16528 }
16529 }
16530
16531 pub fn select_larger_syntax_node(
16532 &mut self,
16533 _: &SelectLargerSyntaxNode,
16534 window: &mut Window,
16535 cx: &mut Context<Self>,
16536 ) {
16537 let Some(visible_row_count) = self.visible_row_count() else {
16538 return;
16539 };
16540 let old_selections: Box<[_]> = self
16541 .selections
16542 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16543 .into();
16544 if old_selections.is_empty() {
16545 return;
16546 }
16547
16548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16549
16550 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16551 let buffer = self.buffer.read(cx).snapshot(cx);
16552
16553 let mut selected_larger_node = false;
16554 let mut new_selections = old_selections
16555 .iter()
16556 .map(|selection| {
16557 let old_range = selection.start..selection.end;
16558
16559 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16560 // manually select word at selection
16561 if ["string_content", "inline"].contains(&node.kind()) {
16562 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16563 // ignore if word is already selected
16564 if !word_range.is_empty() && old_range != word_range {
16565 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16566 // only select word if start and end point belongs to same word
16567 if word_range == last_word_range {
16568 selected_larger_node = true;
16569 return Selection {
16570 id: selection.id,
16571 start: word_range.start,
16572 end: word_range.end,
16573 goal: SelectionGoal::None,
16574 reversed: selection.reversed,
16575 };
16576 }
16577 }
16578 }
16579 }
16580
16581 let mut new_range = old_range.clone();
16582 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16583 new_range = range;
16584 if !node.is_named() {
16585 continue;
16586 }
16587 if !display_map.intersects_fold(new_range.start)
16588 && !display_map.intersects_fold(new_range.end)
16589 {
16590 break;
16591 }
16592 }
16593
16594 selected_larger_node |= new_range != old_range;
16595 Selection {
16596 id: selection.id,
16597 start: new_range.start,
16598 end: new_range.end,
16599 goal: SelectionGoal::None,
16600 reversed: selection.reversed,
16601 }
16602 })
16603 .collect::<Vec<_>>();
16604
16605 if !selected_larger_node {
16606 return; // don't put this call in the history
16607 }
16608
16609 // scroll based on transformation done to the last selection created by the user
16610 let (last_old, last_new) = old_selections
16611 .last()
16612 .zip(new_selections.last().cloned())
16613 .expect("old_selections isn't empty");
16614
16615 // revert selection
16616 let is_selection_reversed = {
16617 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16618 new_selections.last_mut().expect("checked above").reversed =
16619 should_newest_selection_be_reversed;
16620 should_newest_selection_be_reversed
16621 };
16622
16623 if selected_larger_node {
16624 self.select_syntax_node_history.disable_clearing = true;
16625 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16626 s.select(new_selections.clone());
16627 });
16628 self.select_syntax_node_history.disable_clearing = false;
16629 }
16630
16631 let start_row = last_new.start.to_display_point(&display_map).row().0;
16632 let end_row = last_new.end.to_display_point(&display_map).row().0;
16633 let selection_height = end_row - start_row + 1;
16634 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16635
16636 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16637 let scroll_behavior = if fits_on_the_screen {
16638 self.request_autoscroll(Autoscroll::fit(), cx);
16639 SelectSyntaxNodeScrollBehavior::FitSelection
16640 } else if is_selection_reversed {
16641 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16642 SelectSyntaxNodeScrollBehavior::CursorTop
16643 } else {
16644 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16645 SelectSyntaxNodeScrollBehavior::CursorBottom
16646 };
16647
16648 let old_selections: Box<[Selection<Anchor>]> = old_selections
16649 .iter()
16650 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16651 .collect();
16652 self.select_syntax_node_history.push((
16653 old_selections,
16654 scroll_behavior,
16655 is_selection_reversed,
16656 ));
16657 }
16658
16659 pub fn select_smaller_syntax_node(
16660 &mut self,
16661 _: &SelectSmallerSyntaxNode,
16662 window: &mut Window,
16663 cx: &mut Context<Self>,
16664 ) {
16665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16666
16667 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16668 self.select_syntax_node_history.pop()
16669 {
16670 if let Some(selection) = selections.last_mut() {
16671 selection.reversed = is_selection_reversed;
16672 }
16673
16674 let snapshot = self.buffer.read(cx).snapshot(cx);
16675 let selections: Vec<Selection<MultiBufferOffset>> = selections
16676 .iter()
16677 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16678 .collect();
16679
16680 self.select_syntax_node_history.disable_clearing = true;
16681 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16682 s.select(selections);
16683 });
16684 self.select_syntax_node_history.disable_clearing = false;
16685
16686 match scroll_behavior {
16687 SelectSyntaxNodeScrollBehavior::CursorTop => {
16688 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16689 }
16690 SelectSyntaxNodeScrollBehavior::FitSelection => {
16691 self.request_autoscroll(Autoscroll::fit(), cx);
16692 }
16693 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16694 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16695 }
16696 }
16697 }
16698 }
16699
16700 pub fn unwrap_syntax_node(
16701 &mut self,
16702 _: &UnwrapSyntaxNode,
16703 window: &mut Window,
16704 cx: &mut Context<Self>,
16705 ) {
16706 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16707
16708 let buffer = self.buffer.read(cx).snapshot(cx);
16709 let selections = self
16710 .selections
16711 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16712 .into_iter()
16713 // subtracting the offset requires sorting
16714 .sorted_by_key(|i| i.start);
16715
16716 let full_edits = selections
16717 .into_iter()
16718 .filter_map(|selection| {
16719 let child = if selection.is_empty()
16720 && let Some((_, ancestor_range)) =
16721 buffer.syntax_ancestor(selection.start..selection.end)
16722 {
16723 ancestor_range
16724 } else {
16725 selection.range()
16726 };
16727
16728 let mut parent = child.clone();
16729 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16730 parent = ancestor_range;
16731 if parent.start < child.start || parent.end > child.end {
16732 break;
16733 }
16734 }
16735
16736 if parent == child {
16737 return None;
16738 }
16739 let text = buffer.text_for_range(child).collect::<String>();
16740 Some((selection.id, parent, text))
16741 })
16742 .collect::<Vec<_>>();
16743 if full_edits.is_empty() {
16744 return;
16745 }
16746
16747 self.transact(window, cx, |this, window, cx| {
16748 this.buffer.update(cx, |buffer, cx| {
16749 buffer.edit(
16750 full_edits
16751 .iter()
16752 .map(|(_, p, t)| (p.clone(), t.clone()))
16753 .collect::<Vec<_>>(),
16754 None,
16755 cx,
16756 );
16757 });
16758 this.change_selections(Default::default(), window, cx, |s| {
16759 let mut offset = 0;
16760 let mut selections = vec![];
16761 for (id, parent, text) in full_edits {
16762 let start = parent.start - offset;
16763 offset += (parent.end - parent.start) - text.len();
16764 selections.push(Selection {
16765 id,
16766 start,
16767 end: start + text.len(),
16768 reversed: false,
16769 goal: Default::default(),
16770 });
16771 }
16772 s.select(selections);
16773 });
16774 });
16775 }
16776
16777 pub fn select_next_syntax_node(
16778 &mut self,
16779 _: &SelectNextSyntaxNode,
16780 window: &mut Window,
16781 cx: &mut Context<Self>,
16782 ) {
16783 let old_selections: Box<[_]> = self
16784 .selections
16785 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16786 .into();
16787 if old_selections.is_empty() {
16788 return;
16789 }
16790
16791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16792
16793 let buffer = self.buffer.read(cx).snapshot(cx);
16794 let mut selected_sibling = false;
16795
16796 let new_selections = old_selections
16797 .iter()
16798 .map(|selection| {
16799 let old_range = selection.start..selection.end;
16800
16801 let old_range =
16802 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16803 let excerpt = buffer.excerpt_containing(old_range.clone());
16804
16805 if let Some(mut excerpt) = excerpt
16806 && let Some(node) = excerpt
16807 .buffer()
16808 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16809 {
16810 let new_range = excerpt.map_range_from_buffer(
16811 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16812 );
16813 selected_sibling = true;
16814 Selection {
16815 id: selection.id,
16816 start: new_range.start,
16817 end: new_range.end,
16818 goal: SelectionGoal::None,
16819 reversed: selection.reversed,
16820 }
16821 } else {
16822 selection.clone()
16823 }
16824 })
16825 .collect::<Vec<_>>();
16826
16827 if selected_sibling {
16828 self.change_selections(
16829 SelectionEffects::scroll(Autoscroll::fit()),
16830 window,
16831 cx,
16832 |s| {
16833 s.select(new_selections);
16834 },
16835 );
16836 }
16837 }
16838
16839 pub fn select_prev_syntax_node(
16840 &mut self,
16841 _: &SelectPreviousSyntaxNode,
16842 window: &mut Window,
16843 cx: &mut Context<Self>,
16844 ) {
16845 let old_selections: Box<[_]> = self
16846 .selections
16847 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16848 .into();
16849 if old_selections.is_empty() {
16850 return;
16851 }
16852
16853 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16854
16855 let buffer = self.buffer.read(cx).snapshot(cx);
16856 let mut selected_sibling = false;
16857
16858 let new_selections = old_selections
16859 .iter()
16860 .map(|selection| {
16861 let old_range = selection.start..selection.end;
16862 let old_range =
16863 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16864 let excerpt = buffer.excerpt_containing(old_range.clone());
16865
16866 if let Some(mut excerpt) = excerpt
16867 && let Some(node) = excerpt
16868 .buffer()
16869 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16870 {
16871 let new_range = excerpt.map_range_from_buffer(
16872 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16873 );
16874 selected_sibling = true;
16875 Selection {
16876 id: selection.id,
16877 start: new_range.start,
16878 end: new_range.end,
16879 goal: SelectionGoal::None,
16880 reversed: selection.reversed,
16881 }
16882 } else {
16883 selection.clone()
16884 }
16885 })
16886 .collect::<Vec<_>>();
16887
16888 if selected_sibling {
16889 self.change_selections(
16890 SelectionEffects::scroll(Autoscroll::fit()),
16891 window,
16892 cx,
16893 |s| {
16894 s.select(new_selections);
16895 },
16896 );
16897 }
16898 }
16899
16900 pub fn move_to_start_of_larger_syntax_node(
16901 &mut self,
16902 _: &MoveToStartOfLargerSyntaxNode,
16903 window: &mut Window,
16904 cx: &mut Context<Self>,
16905 ) {
16906 self.move_cursors_to_syntax_nodes(window, cx, false);
16907 }
16908
16909 pub fn move_to_end_of_larger_syntax_node(
16910 &mut self,
16911 _: &MoveToEndOfLargerSyntaxNode,
16912 window: &mut Window,
16913 cx: &mut Context<Self>,
16914 ) {
16915 self.move_cursors_to_syntax_nodes(window, cx, true);
16916 }
16917
16918 fn find_syntax_node_boundary(
16919 &self,
16920 selection_pos: MultiBufferOffset,
16921 move_to_end: bool,
16922 display_map: &DisplaySnapshot,
16923 buffer: &MultiBufferSnapshot,
16924 ) -> MultiBufferOffset {
16925 let old_range = selection_pos..selection_pos;
16926 let mut new_pos = selection_pos;
16927 let mut search_range = old_range;
16928 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16929 search_range = range.clone();
16930 if !node.is_named()
16931 || display_map.intersects_fold(range.start)
16932 || display_map.intersects_fold(range.end)
16933 // If cursor is already at the end of the syntax node, continue searching
16934 || (move_to_end && range.end == selection_pos)
16935 // If cursor is already at the start of the syntax node, continue searching
16936 || (!move_to_end && range.start == selection_pos)
16937 {
16938 continue;
16939 }
16940
16941 // If we found a string_content node, find the largest parent that is still string_content
16942 // Enables us to skip to the end of strings without taking multiple steps inside the string
16943 let (_, final_range) = if node.kind() == "string_content" {
16944 let mut current_node = node;
16945 let mut current_range = range;
16946 while let Some((parent, parent_range)) =
16947 buffer.syntax_ancestor(current_range.clone())
16948 {
16949 if parent.kind() == "string_content" {
16950 current_node = parent;
16951 current_range = parent_range;
16952 } else {
16953 break;
16954 }
16955 }
16956
16957 (current_node, current_range)
16958 } else {
16959 (node, range)
16960 };
16961
16962 new_pos = if move_to_end {
16963 final_range.end
16964 } else {
16965 final_range.start
16966 };
16967
16968 break;
16969 }
16970
16971 new_pos
16972 }
16973
16974 fn move_cursors_to_syntax_nodes(
16975 &mut self,
16976 window: &mut Window,
16977 cx: &mut Context<Self>,
16978 move_to_end: bool,
16979 ) -> bool {
16980 let old_selections: Box<[_]> = self
16981 .selections
16982 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16983 .into();
16984 if old_selections.is_empty() {
16985 return false;
16986 }
16987
16988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16989
16990 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16991 let buffer = self.buffer.read(cx).snapshot(cx);
16992
16993 let mut any_cursor_moved = false;
16994 let new_selections = old_selections
16995 .iter()
16996 .map(|selection| {
16997 if !selection.is_empty() {
16998 return selection.clone();
16999 }
17000
17001 let selection_pos = selection.head();
17002 let new_pos = self.find_syntax_node_boundary(
17003 selection_pos,
17004 move_to_end,
17005 &display_map,
17006 &buffer,
17007 );
17008
17009 any_cursor_moved |= new_pos != selection_pos;
17010
17011 Selection {
17012 id: selection.id,
17013 start: new_pos,
17014 end: new_pos,
17015 goal: SelectionGoal::None,
17016 reversed: false,
17017 }
17018 })
17019 .collect::<Vec<_>>();
17020
17021 self.change_selections(Default::default(), window, cx, |s| {
17022 s.select(new_selections);
17023 });
17024 self.request_autoscroll(Autoscroll::newest(), cx);
17025
17026 any_cursor_moved
17027 }
17028
17029 pub fn select_to_start_of_larger_syntax_node(
17030 &mut self,
17031 _: &SelectToStartOfLargerSyntaxNode,
17032 window: &mut Window,
17033 cx: &mut Context<Self>,
17034 ) {
17035 self.select_to_syntax_nodes(window, cx, false);
17036 }
17037
17038 pub fn select_to_end_of_larger_syntax_node(
17039 &mut self,
17040 _: &SelectToEndOfLargerSyntaxNode,
17041 window: &mut Window,
17042 cx: &mut Context<Self>,
17043 ) {
17044 self.select_to_syntax_nodes(window, cx, true);
17045 }
17046
17047 fn select_to_syntax_nodes(
17048 &mut self,
17049 window: &mut Window,
17050 cx: &mut Context<Self>,
17051 move_to_end: bool,
17052 ) {
17053 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17054
17055 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17056 let buffer = self.buffer.read(cx).snapshot(cx);
17057 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17058
17059 let new_selections = old_selections
17060 .iter()
17061 .map(|selection| {
17062 let new_pos = self.find_syntax_node_boundary(
17063 selection.head(),
17064 move_to_end,
17065 &display_map,
17066 &buffer,
17067 );
17068
17069 let mut new_selection = selection.clone();
17070 new_selection.set_head(new_pos, SelectionGoal::None);
17071 new_selection
17072 })
17073 .collect::<Vec<_>>();
17074
17075 self.change_selections(Default::default(), window, cx, |s| {
17076 s.select(new_selections);
17077 });
17078 }
17079
17080 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
17081 if !EditorSettings::get_global(cx).gutter.runnables || !self.enable_runnables {
17082 self.clear_tasks();
17083 return Task::ready(());
17084 }
17085 let project = self.project().map(Entity::downgrade);
17086 let task_sources = self.lsp_task_sources(cx);
17087 let multi_buffer = self.buffer.downgrade();
17088 cx.spawn_in(window, async move |editor, cx| {
17089 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
17090 let Some(project) = project.and_then(|p| p.upgrade()) else {
17091 return;
17092 };
17093 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
17094 this.display_map.update(cx, |map, cx| map.snapshot(cx))
17095 }) else {
17096 return;
17097 };
17098
17099 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
17100 if hide_runnables {
17101 return;
17102 }
17103 let new_rows =
17104 cx.background_spawn({
17105 let snapshot = display_snapshot.clone();
17106 async move {
17107 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
17108 }
17109 })
17110 .await;
17111 let Ok(lsp_tasks) =
17112 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
17113 else {
17114 return;
17115 };
17116 let lsp_tasks = lsp_tasks.await;
17117
17118 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
17119 lsp_tasks
17120 .into_iter()
17121 .flat_map(|(kind, tasks)| {
17122 tasks.into_iter().filter_map(move |(location, task)| {
17123 Some((kind.clone(), location?, task))
17124 })
17125 })
17126 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
17127 let buffer = location.target.buffer;
17128 let buffer_snapshot = buffer.read(cx).snapshot();
17129 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
17130 |(excerpt_id, snapshot, _)| {
17131 if snapshot.remote_id() == buffer_snapshot.remote_id() {
17132 display_snapshot
17133 .buffer_snapshot()
17134 .anchor_in_excerpt(excerpt_id, location.target.range.start)
17135 } else {
17136 None
17137 }
17138 },
17139 );
17140 if let Some(offset) = offset {
17141 let task_buffer_range =
17142 location.target.range.to_point(&buffer_snapshot);
17143 let context_buffer_range =
17144 task_buffer_range.to_offset(&buffer_snapshot);
17145 let context_range = BufferOffset(context_buffer_range.start)
17146 ..BufferOffset(context_buffer_range.end);
17147
17148 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
17149 .or_insert_with(|| RunnableTasks {
17150 templates: Vec::new(),
17151 offset,
17152 column: task_buffer_range.start.column,
17153 extra_variables: HashMap::default(),
17154 context_range,
17155 })
17156 .templates
17157 .push((kind, task.original_task().clone()));
17158 }
17159
17160 acc
17161 })
17162 }) else {
17163 return;
17164 };
17165
17166 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
17167 buffer.language_settings(cx).tasks.prefer_lsp
17168 }) else {
17169 return;
17170 };
17171
17172 let rows = Self::runnable_rows(
17173 project,
17174 display_snapshot,
17175 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
17176 new_rows,
17177 cx.clone(),
17178 )
17179 .await;
17180 editor
17181 .update(cx, |editor, _| {
17182 editor.clear_tasks();
17183 for (key, mut value) in rows {
17184 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
17185 value.templates.extend(lsp_tasks.templates);
17186 }
17187
17188 editor.insert_tasks(key, value);
17189 }
17190 for (key, value) in lsp_tasks_by_rows {
17191 editor.insert_tasks(key, value);
17192 }
17193 })
17194 .ok();
17195 })
17196 }
17197 fn fetch_runnable_ranges(
17198 snapshot: &DisplaySnapshot,
17199 range: Range<Anchor>,
17200 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
17201 snapshot.buffer_snapshot().runnable_ranges(range).collect()
17202 }
17203
17204 fn runnable_rows(
17205 project: Entity<Project>,
17206 snapshot: DisplaySnapshot,
17207 prefer_lsp: bool,
17208 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
17209 cx: AsyncWindowContext,
17210 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
17211 cx.spawn(async move |cx| {
17212 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
17213 for (run_range, mut runnable) in runnable_ranges {
17214 let Some(tasks) = cx
17215 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
17216 .ok()
17217 else {
17218 continue;
17219 };
17220 let mut tasks = tasks.await;
17221
17222 if prefer_lsp {
17223 tasks.retain(|(task_kind, _)| {
17224 !matches!(task_kind, TaskSourceKind::Language { .. })
17225 });
17226 }
17227 if tasks.is_empty() {
17228 continue;
17229 }
17230
17231 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
17232 let Some(row) = snapshot
17233 .buffer_snapshot()
17234 .buffer_line_for_row(MultiBufferRow(point.row))
17235 .map(|(_, range)| range.start.row)
17236 else {
17237 continue;
17238 };
17239
17240 let context_range =
17241 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
17242 runnable_rows.push((
17243 (runnable.buffer_id, row),
17244 RunnableTasks {
17245 templates: tasks,
17246 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
17247 context_range,
17248 column: point.column,
17249 extra_variables: runnable.extra_captures,
17250 },
17251 ));
17252 }
17253 runnable_rows
17254 })
17255 }
17256
17257 fn templates_with_tags(
17258 project: &Entity<Project>,
17259 runnable: &mut Runnable,
17260 cx: &mut App,
17261 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
17262 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
17263 let (worktree_id, file) = project
17264 .buffer_for_id(runnable.buffer, cx)
17265 .and_then(|buffer| buffer.read(cx).file())
17266 .map(|file| (file.worktree_id(cx), file.clone()))
17267 .unzip();
17268
17269 (
17270 project.task_store().read(cx).task_inventory().cloned(),
17271 worktree_id,
17272 file,
17273 )
17274 });
17275
17276 let tags = mem::take(&mut runnable.tags);
17277 let language = runnable.language.clone();
17278 cx.spawn(async move |cx| {
17279 let mut templates_with_tags = Vec::new();
17280 if let Some(inventory) = inventory {
17281 for RunnableTag(tag) in tags {
17282 let new_tasks = inventory.update(cx, |inventory, cx| {
17283 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
17284 });
17285 templates_with_tags.extend(new_tasks.await.into_iter().filter(
17286 move |(_, template)| {
17287 template.tags.iter().any(|source_tag| source_tag == &tag)
17288 },
17289 ));
17290 }
17291 }
17292 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
17293
17294 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
17295 // Strongest source wins; if we have worktree tag binding, prefer that to
17296 // global and language bindings;
17297 // if we have a global binding, prefer that to language binding.
17298 let first_mismatch = templates_with_tags
17299 .iter()
17300 .position(|(tag_source, _)| tag_source != leading_tag_source);
17301 if let Some(index) = first_mismatch {
17302 templates_with_tags.truncate(index);
17303 }
17304 }
17305
17306 templates_with_tags
17307 })
17308 }
17309
17310 pub fn move_to_enclosing_bracket(
17311 &mut self,
17312 _: &MoveToEnclosingBracket,
17313 window: &mut Window,
17314 cx: &mut Context<Self>,
17315 ) {
17316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17317 self.change_selections(Default::default(), window, cx, |s| {
17318 s.move_offsets_with(&mut |snapshot, selection| {
17319 let Some(enclosing_bracket_ranges) =
17320 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17321 else {
17322 return;
17323 };
17324
17325 let mut best_length = usize::MAX;
17326 let mut best_inside = false;
17327 let mut best_in_bracket_range = false;
17328 let mut best_destination = None;
17329 for (open, close) in enclosing_bracket_ranges {
17330 let close = close.to_inclusive();
17331 let length = *close.end() - open.start;
17332 let inside = selection.start >= open.end && selection.end <= *close.start();
17333 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17334 || close.contains(&selection.head());
17335
17336 // If best is next to a bracket and current isn't, skip
17337 if !in_bracket_range && best_in_bracket_range {
17338 continue;
17339 }
17340
17341 // Prefer smaller lengths unless best is inside and current isn't
17342 if length > best_length && (best_inside || !inside) {
17343 continue;
17344 }
17345
17346 best_length = length;
17347 best_inside = inside;
17348 best_in_bracket_range = in_bracket_range;
17349 best_destination = Some(
17350 if close.contains(&selection.start) && close.contains(&selection.end) {
17351 if inside { open.end } else { open.start }
17352 } else if inside {
17353 *close.start()
17354 } else {
17355 *close.end()
17356 },
17357 );
17358 }
17359
17360 if let Some(destination) = best_destination {
17361 selection.collapse_to(destination, SelectionGoal::None);
17362 }
17363 })
17364 });
17365 }
17366
17367 pub fn undo_selection(
17368 &mut self,
17369 _: &UndoSelection,
17370 window: &mut Window,
17371 cx: &mut Context<Self>,
17372 ) {
17373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17374 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17375 self.selection_history.mode = SelectionHistoryMode::Undoing;
17376 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17377 this.end_selection(window, cx);
17378 this.change_selections(
17379 SelectionEffects::scroll(Autoscroll::newest()),
17380 window,
17381 cx,
17382 |s| s.select_anchors(entry.selections.to_vec()),
17383 );
17384 });
17385 self.selection_history.mode = SelectionHistoryMode::Normal;
17386
17387 self.select_next_state = entry.select_next_state;
17388 self.select_prev_state = entry.select_prev_state;
17389 self.add_selections_state = entry.add_selections_state;
17390 }
17391 }
17392
17393 pub fn redo_selection(
17394 &mut self,
17395 _: &RedoSelection,
17396 window: &mut Window,
17397 cx: &mut Context<Self>,
17398 ) {
17399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17400 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17401 self.selection_history.mode = SelectionHistoryMode::Redoing;
17402 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17403 this.end_selection(window, cx);
17404 this.change_selections(
17405 SelectionEffects::scroll(Autoscroll::newest()),
17406 window,
17407 cx,
17408 |s| s.select_anchors(entry.selections.to_vec()),
17409 );
17410 });
17411 self.selection_history.mode = SelectionHistoryMode::Normal;
17412
17413 self.select_next_state = entry.select_next_state;
17414 self.select_prev_state = entry.select_prev_state;
17415 self.add_selections_state = entry.add_selections_state;
17416 }
17417 }
17418
17419 pub fn expand_excerpts(
17420 &mut self,
17421 action: &ExpandExcerpts,
17422 _: &mut Window,
17423 cx: &mut Context<Self>,
17424 ) {
17425 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17426 }
17427
17428 pub fn expand_excerpts_down(
17429 &mut self,
17430 action: &ExpandExcerptsDown,
17431 _: &mut Window,
17432 cx: &mut Context<Self>,
17433 ) {
17434 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17435 }
17436
17437 pub fn expand_excerpts_up(
17438 &mut self,
17439 action: &ExpandExcerptsUp,
17440 _: &mut Window,
17441 cx: &mut Context<Self>,
17442 ) {
17443 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17444 }
17445
17446 pub fn expand_excerpts_for_direction(
17447 &mut self,
17448 lines: u32,
17449 direction: ExpandExcerptDirection,
17450 cx: &mut Context<Self>,
17451 ) {
17452 let selections = self.selections.disjoint_anchors_arc();
17453
17454 let lines = if lines == 0 {
17455 EditorSettings::get_global(cx).expand_excerpt_lines
17456 } else {
17457 lines
17458 };
17459
17460 let snapshot = self.buffer.read(cx).snapshot(cx);
17461 let excerpt_ids = selections
17462 .iter()
17463 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17464 .unique()
17465 .sorted()
17466 .collect::<Vec<_>>();
17467
17468 if self.delegate_expand_excerpts {
17469 cx.emit(EditorEvent::ExpandExcerptsRequested {
17470 excerpt_ids,
17471 lines,
17472 direction,
17473 });
17474 return;
17475 }
17476
17477 self.buffer.update(cx, |buffer, cx| {
17478 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17479 })
17480 }
17481
17482 pub fn expand_excerpt(
17483 &mut self,
17484 excerpt: ExcerptId,
17485 direction: ExpandExcerptDirection,
17486 window: &mut Window,
17487 cx: &mut Context<Self>,
17488 ) {
17489 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17490
17491 if self.delegate_expand_excerpts {
17492 cx.emit(EditorEvent::ExpandExcerptsRequested {
17493 excerpt_ids: vec![excerpt],
17494 lines: lines_to_expand,
17495 direction,
17496 });
17497 return;
17498 }
17499
17500 let current_scroll_position = self.scroll_position(cx);
17501 let mut scroll = None;
17502
17503 if direction == ExpandExcerptDirection::Down {
17504 let multi_buffer = self.buffer.read(cx);
17505 let snapshot = multi_buffer.snapshot(cx);
17506 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17507 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17508 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17509 {
17510 let buffer_snapshot = buffer.read(cx).snapshot();
17511 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17512 let last_row = buffer_snapshot.max_point().row;
17513 let lines_below = last_row.saturating_sub(excerpt_end_row);
17514 if lines_below >= lines_to_expand {
17515 scroll = Some(
17516 current_scroll_position
17517 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17518 );
17519 }
17520 }
17521 }
17522 if direction == ExpandExcerptDirection::Up
17523 && self
17524 .buffer
17525 .read(cx)
17526 .snapshot(cx)
17527 .excerpt_before(excerpt)
17528 .is_none()
17529 {
17530 scroll = Some(current_scroll_position);
17531 }
17532
17533 self.buffer.update(cx, |buffer, cx| {
17534 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17535 });
17536
17537 if let Some(new_scroll_position) = scroll {
17538 self.set_scroll_position(new_scroll_position, window, cx);
17539 }
17540 }
17541
17542 pub fn go_to_singleton_buffer_point(
17543 &mut self,
17544 point: Point,
17545 window: &mut Window,
17546 cx: &mut Context<Self>,
17547 ) {
17548 self.go_to_singleton_buffer_range(point..point, window, cx);
17549 }
17550
17551 pub fn go_to_singleton_buffer_range(
17552 &mut self,
17553 range: Range<Point>,
17554 window: &mut Window,
17555 cx: &mut Context<Self>,
17556 ) {
17557 let multibuffer = self.buffer().read(cx);
17558 let Some(buffer) = multibuffer.as_singleton() else {
17559 return;
17560 };
17561 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17562 return;
17563 };
17564 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17565 return;
17566 };
17567 self.change_selections(
17568 SelectionEffects::default().nav_history(true),
17569 window,
17570 cx,
17571 |s| s.select_anchor_ranges([start..end]),
17572 );
17573 }
17574
17575 pub fn go_to_diagnostic(
17576 &mut self,
17577 action: &GoToDiagnostic,
17578 window: &mut Window,
17579 cx: &mut Context<Self>,
17580 ) {
17581 if !self.diagnostics_enabled() {
17582 return;
17583 }
17584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17585 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17586 }
17587
17588 pub fn go_to_prev_diagnostic(
17589 &mut self,
17590 action: &GoToPreviousDiagnostic,
17591 window: &mut Window,
17592 cx: &mut Context<Self>,
17593 ) {
17594 if !self.diagnostics_enabled() {
17595 return;
17596 }
17597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17598 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17599 }
17600
17601 pub fn go_to_diagnostic_impl(
17602 &mut self,
17603 direction: Direction,
17604 severity: GoToDiagnosticSeverityFilter,
17605 window: &mut Window,
17606 cx: &mut Context<Self>,
17607 ) {
17608 let buffer = self.buffer.read(cx).snapshot(cx);
17609 let selection = self
17610 .selections
17611 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17612
17613 let mut active_group_id = None;
17614 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17615 && active_group.active_range.start.to_offset(&buffer) == selection.start
17616 {
17617 active_group_id = Some(active_group.group_id);
17618 }
17619
17620 fn filtered<'a>(
17621 severity: GoToDiagnosticSeverityFilter,
17622 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17623 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17624 diagnostics
17625 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17626 .filter(|entry| entry.range.start != entry.range.end)
17627 .filter(|entry| !entry.diagnostic.is_unnecessary)
17628 }
17629
17630 let before = filtered(
17631 severity,
17632 buffer
17633 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17634 .filter(|entry| entry.range.start <= selection.start),
17635 );
17636 let after = filtered(
17637 severity,
17638 buffer
17639 .diagnostics_in_range(selection.start..buffer.len())
17640 .filter(|entry| entry.range.start >= selection.start),
17641 );
17642
17643 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17644 if direction == Direction::Prev {
17645 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17646 {
17647 for diagnostic in prev_diagnostics.into_iter().rev() {
17648 if diagnostic.range.start != selection.start
17649 || active_group_id
17650 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17651 {
17652 found = Some(diagnostic);
17653 break 'outer;
17654 }
17655 }
17656 }
17657 } else {
17658 for diagnostic in after.chain(before) {
17659 if diagnostic.range.start != selection.start
17660 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17661 {
17662 found = Some(diagnostic);
17663 break;
17664 }
17665 }
17666 }
17667 let Some(next_diagnostic) = found else {
17668 return;
17669 };
17670
17671 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17672 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17673 return;
17674 };
17675 let snapshot = self.snapshot(window, cx);
17676 if snapshot.intersects_fold(next_diagnostic.range.start) {
17677 self.unfold_ranges(
17678 std::slice::from_ref(&next_diagnostic.range),
17679 true,
17680 false,
17681 cx,
17682 );
17683 }
17684 self.change_selections(Default::default(), window, cx, |s| {
17685 s.select_ranges(vec![
17686 next_diagnostic.range.start..next_diagnostic.range.start,
17687 ])
17688 });
17689 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17690 self.refresh_edit_prediction(false, true, window, cx);
17691 }
17692
17693 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17694 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17695 let snapshot = self.snapshot(window, cx);
17696 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17697 self.go_to_hunk_before_or_after_position(
17698 &snapshot,
17699 selection.head(),
17700 Direction::Next,
17701 window,
17702 cx,
17703 );
17704 }
17705
17706 pub fn go_to_hunk_before_or_after_position(
17707 &mut self,
17708 snapshot: &EditorSnapshot,
17709 position: Point,
17710 direction: Direction,
17711 window: &mut Window,
17712 cx: &mut Context<Editor>,
17713 ) {
17714 let row = if direction == Direction::Next {
17715 self.hunk_after_position(snapshot, position)
17716 .map(|hunk| hunk.row_range.start)
17717 } else {
17718 self.hunk_before_position(snapshot, position)
17719 };
17720
17721 if let Some(row) = row {
17722 let destination = Point::new(row.0, 0);
17723 let autoscroll = Autoscroll::center();
17724
17725 self.unfold_ranges(&[destination..destination], false, false, cx);
17726 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17727 s.select_ranges([destination..destination]);
17728 });
17729 }
17730 }
17731
17732 fn hunk_after_position(
17733 &mut self,
17734 snapshot: &EditorSnapshot,
17735 position: Point,
17736 ) -> Option<MultiBufferDiffHunk> {
17737 snapshot
17738 .buffer_snapshot()
17739 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17740 .find(|hunk| hunk.row_range.start.0 > position.row)
17741 .or_else(|| {
17742 snapshot
17743 .buffer_snapshot()
17744 .diff_hunks_in_range(Point::zero()..position)
17745 .find(|hunk| hunk.row_range.end.0 < position.row)
17746 })
17747 }
17748
17749 fn go_to_prev_hunk(
17750 &mut self,
17751 _: &GoToPreviousHunk,
17752 window: &mut Window,
17753 cx: &mut Context<Self>,
17754 ) {
17755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17756 let snapshot = self.snapshot(window, cx);
17757 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17758 self.go_to_hunk_before_or_after_position(
17759 &snapshot,
17760 selection.head(),
17761 Direction::Prev,
17762 window,
17763 cx,
17764 );
17765 }
17766
17767 fn hunk_before_position(
17768 &mut self,
17769 snapshot: &EditorSnapshot,
17770 position: Point,
17771 ) -> Option<MultiBufferRow> {
17772 snapshot
17773 .buffer_snapshot()
17774 .diff_hunk_before(position)
17775 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17776 }
17777
17778 fn go_to_next_change(
17779 &mut self,
17780 _: &GoToNextChange,
17781 window: &mut Window,
17782 cx: &mut Context<Self>,
17783 ) {
17784 if let Some(selections) = self
17785 .change_list
17786 .next_change(1, Direction::Next)
17787 .map(|s| s.to_vec())
17788 {
17789 self.change_selections(Default::default(), window, cx, |s| {
17790 let map = s.display_snapshot();
17791 s.select_display_ranges(selections.iter().map(|a| {
17792 let point = a.to_display_point(&map);
17793 point..point
17794 }))
17795 })
17796 }
17797 }
17798
17799 fn go_to_previous_change(
17800 &mut self,
17801 _: &GoToPreviousChange,
17802 window: &mut Window,
17803 cx: &mut Context<Self>,
17804 ) {
17805 if let Some(selections) = self
17806 .change_list
17807 .next_change(1, Direction::Prev)
17808 .map(|s| s.to_vec())
17809 {
17810 self.change_selections(Default::default(), window, cx, |s| {
17811 let map = s.display_snapshot();
17812 s.select_display_ranges(selections.iter().map(|a| {
17813 let point = a.to_display_point(&map);
17814 point..point
17815 }))
17816 })
17817 }
17818 }
17819
17820 pub fn go_to_next_document_highlight(
17821 &mut self,
17822 _: &GoToNextDocumentHighlight,
17823 window: &mut Window,
17824 cx: &mut Context<Self>,
17825 ) {
17826 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17827 }
17828
17829 pub fn go_to_prev_document_highlight(
17830 &mut self,
17831 _: &GoToPreviousDocumentHighlight,
17832 window: &mut Window,
17833 cx: &mut Context<Self>,
17834 ) {
17835 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17836 }
17837
17838 pub fn go_to_document_highlight_before_or_after_position(
17839 &mut self,
17840 direction: Direction,
17841 window: &mut Window,
17842 cx: &mut Context<Editor>,
17843 ) {
17844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17845 let snapshot = self.snapshot(window, cx);
17846 let buffer = &snapshot.buffer_snapshot();
17847 let position = self
17848 .selections
17849 .newest::<Point>(&snapshot.display_snapshot)
17850 .head();
17851 let anchor_position = buffer.anchor_after(position);
17852
17853 // Get all document highlights (both read and write)
17854 let mut all_highlights = Vec::new();
17855
17856 if let Some((_, read_highlights)) = self
17857 .background_highlights
17858 .get(&HighlightKey::DocumentHighlightRead)
17859 {
17860 all_highlights.extend(read_highlights.iter());
17861 }
17862
17863 if let Some((_, write_highlights)) = self
17864 .background_highlights
17865 .get(&HighlightKey::DocumentHighlightWrite)
17866 {
17867 all_highlights.extend(write_highlights.iter());
17868 }
17869
17870 if all_highlights.is_empty() {
17871 return;
17872 }
17873
17874 // Sort highlights by position
17875 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17876
17877 let target_highlight = match direction {
17878 Direction::Next => {
17879 // Find the first highlight after the current position
17880 all_highlights
17881 .iter()
17882 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17883 }
17884 Direction::Prev => {
17885 // Find the last highlight before the current position
17886 all_highlights
17887 .iter()
17888 .rev()
17889 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17890 }
17891 };
17892
17893 if let Some(highlight) = target_highlight {
17894 let destination = highlight.start.to_point(buffer);
17895 let autoscroll = Autoscroll::center();
17896
17897 self.unfold_ranges(&[destination..destination], false, false, cx);
17898 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17899 s.select_ranges([destination..destination]);
17900 });
17901 }
17902 }
17903
17904 fn go_to_line<T: 'static>(
17905 &mut self,
17906 position: Anchor,
17907 highlight_color: Option<Hsla>,
17908 window: &mut Window,
17909 cx: &mut Context<Self>,
17910 ) {
17911 let snapshot = self.snapshot(window, cx).display_snapshot;
17912 let position = position.to_point(&snapshot.buffer_snapshot());
17913 let start = snapshot
17914 .buffer_snapshot()
17915 .clip_point(Point::new(position.row, 0), Bias::Left);
17916 let end = start + Point::new(1, 0);
17917 let start = snapshot.buffer_snapshot().anchor_before(start);
17918 let end = snapshot.buffer_snapshot().anchor_before(end);
17919
17920 self.highlight_rows::<T>(
17921 start..end,
17922 highlight_color
17923 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17924 Default::default(),
17925 cx,
17926 );
17927
17928 if self.buffer.read(cx).is_singleton() {
17929 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17930 }
17931 }
17932
17933 pub fn go_to_definition(
17934 &mut self,
17935 _: &GoToDefinition,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) -> Task<Result<Navigated>> {
17939 let definition =
17940 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17941 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17942 cx.spawn_in(window, async move |editor, cx| {
17943 if definition.await? == Navigated::Yes {
17944 return Ok(Navigated::Yes);
17945 }
17946 match fallback_strategy {
17947 GoToDefinitionFallback::None => Ok(Navigated::No),
17948 GoToDefinitionFallback::FindAllReferences => {
17949 match editor.update_in(cx, |editor, window, cx| {
17950 editor.find_all_references(&FindAllReferences::default(), window, cx)
17951 })? {
17952 Some(references) => references.await,
17953 None => Ok(Navigated::No),
17954 }
17955 }
17956 }
17957 })
17958 }
17959
17960 pub fn go_to_declaration(
17961 &mut self,
17962 _: &GoToDeclaration,
17963 window: &mut Window,
17964 cx: &mut Context<Self>,
17965 ) -> Task<Result<Navigated>> {
17966 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17967 }
17968
17969 pub fn go_to_declaration_split(
17970 &mut self,
17971 _: &GoToDeclaration,
17972 window: &mut Window,
17973 cx: &mut Context<Self>,
17974 ) -> Task<Result<Navigated>> {
17975 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17976 }
17977
17978 pub fn go_to_implementation(
17979 &mut self,
17980 _: &GoToImplementation,
17981 window: &mut Window,
17982 cx: &mut Context<Self>,
17983 ) -> Task<Result<Navigated>> {
17984 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17985 }
17986
17987 pub fn go_to_implementation_split(
17988 &mut self,
17989 _: &GoToImplementationSplit,
17990 window: &mut Window,
17991 cx: &mut Context<Self>,
17992 ) -> Task<Result<Navigated>> {
17993 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17994 }
17995
17996 pub fn go_to_type_definition(
17997 &mut self,
17998 _: &GoToTypeDefinition,
17999 window: &mut Window,
18000 cx: &mut Context<Self>,
18001 ) -> Task<Result<Navigated>> {
18002 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18003 }
18004
18005 pub fn go_to_definition_split(
18006 &mut self,
18007 _: &GoToDefinitionSplit,
18008 window: &mut Window,
18009 cx: &mut Context<Self>,
18010 ) -> Task<Result<Navigated>> {
18011 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18012 }
18013
18014 pub fn go_to_type_definition_split(
18015 &mut self,
18016 _: &GoToTypeDefinitionSplit,
18017 window: &mut Window,
18018 cx: &mut Context<Self>,
18019 ) -> Task<Result<Navigated>> {
18020 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18021 }
18022
18023 fn go_to_definition_of_kind(
18024 &mut self,
18025 kind: GotoDefinitionKind,
18026 split: bool,
18027 window: &mut Window,
18028 cx: &mut Context<Self>,
18029 ) -> Task<Result<Navigated>> {
18030 let Some(provider) = self.semantics_provider.clone() else {
18031 return Task::ready(Ok(Navigated::No));
18032 };
18033 let head = self
18034 .selections
18035 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18036 .head();
18037 let buffer = self.buffer.read(cx);
18038 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18039 return Task::ready(Ok(Navigated::No));
18040 };
18041 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18042 return Task::ready(Ok(Navigated::No));
18043 };
18044
18045 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18046
18047 cx.spawn_in(window, async move |editor, cx| {
18048 let Some(definitions) = definitions.await? else {
18049 return Ok(Navigated::No);
18050 };
18051 let navigated = editor
18052 .update_in(cx, |editor, window, cx| {
18053 editor.navigate_to_hover_links(
18054 Some(kind),
18055 definitions
18056 .into_iter()
18057 .filter(|location| {
18058 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18059 })
18060 .map(HoverLink::Text)
18061 .collect::<Vec<_>>(),
18062 nav_entry,
18063 split,
18064 window,
18065 cx,
18066 )
18067 })?
18068 .await?;
18069 anyhow::Ok(navigated)
18070 })
18071 }
18072
18073 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18074 let selection = self.selections.newest_anchor();
18075 let head = selection.head();
18076 let tail = selection.tail();
18077
18078 let Some((buffer, start_position)) =
18079 self.buffer.read(cx).text_anchor_for_position(head, cx)
18080 else {
18081 return;
18082 };
18083
18084 let end_position = if head != tail {
18085 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18086 return;
18087 };
18088 Some(pos)
18089 } else {
18090 None
18091 };
18092
18093 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18094 let url = if let Some(end_pos) = end_position {
18095 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18096 } else {
18097 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18098 };
18099
18100 if let Some(url) = url {
18101 cx.update(|window, cx| {
18102 if parse_zed_link(&url, cx).is_some() {
18103 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18104 } else {
18105 cx.open_url(&url);
18106 }
18107 })?;
18108 }
18109
18110 anyhow::Ok(())
18111 });
18112
18113 url_finder.detach();
18114 }
18115
18116 pub fn open_selected_filename(
18117 &mut self,
18118 _: &OpenSelectedFilename,
18119 window: &mut Window,
18120 cx: &mut Context<Self>,
18121 ) {
18122 let Some(workspace) = self.workspace() else {
18123 return;
18124 };
18125
18126 let position = self.selections.newest_anchor().head();
18127
18128 let Some((buffer, buffer_position)) =
18129 self.buffer.read(cx).text_anchor_for_position(position, cx)
18130 else {
18131 return;
18132 };
18133
18134 let project = self.project.clone();
18135
18136 cx.spawn_in(window, async move |_, cx| {
18137 let result = find_file(&buffer, project, buffer_position, cx).await;
18138
18139 if let Some((_, path)) = result {
18140 workspace
18141 .update_in(cx, |workspace, window, cx| {
18142 workspace.open_resolved_path(path, window, cx)
18143 })?
18144 .await?;
18145 }
18146 anyhow::Ok(())
18147 })
18148 .detach();
18149 }
18150
18151 pub(crate) fn navigate_to_hover_links(
18152 &mut self,
18153 kind: Option<GotoDefinitionKind>,
18154 definitions: Vec<HoverLink>,
18155 origin: Option<NavigationEntry>,
18156 split: bool,
18157 window: &mut Window,
18158 cx: &mut Context<Editor>,
18159 ) -> Task<Result<Navigated>> {
18160 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18161 let mut first_url_or_file = None;
18162 let definitions: Vec<_> = definitions
18163 .into_iter()
18164 .filter_map(|def| match def {
18165 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18166 HoverLink::InlayHint(lsp_location, server_id) => {
18167 let computation =
18168 self.compute_target_location(lsp_location, server_id, window, cx);
18169 Some(cx.background_spawn(computation))
18170 }
18171 HoverLink::Url(url) => {
18172 first_url_or_file = Some(Either::Left(url));
18173 None
18174 }
18175 HoverLink::File(path) => {
18176 first_url_or_file = Some(Either::Right(path));
18177 None
18178 }
18179 })
18180 .collect();
18181
18182 let workspace = self.workspace();
18183
18184 cx.spawn_in(window, async move |editor, cx| {
18185 let locations: Vec<Location> = future::join_all(definitions)
18186 .await
18187 .into_iter()
18188 .filter_map(|location| location.transpose())
18189 .collect::<Result<_>>()
18190 .context("location tasks")?;
18191 let mut locations = cx.update(|_, cx| {
18192 locations
18193 .into_iter()
18194 .map(|location| {
18195 let buffer = location.buffer.read(cx);
18196 (location.buffer, location.range.to_point(buffer))
18197 })
18198 .into_group_map()
18199 })?;
18200 let mut num_locations = 0;
18201 for ranges in locations.values_mut() {
18202 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18203 ranges.dedup();
18204 num_locations += ranges.len();
18205 }
18206
18207 if num_locations > 1 {
18208 let tab_kind = match kind {
18209 Some(GotoDefinitionKind::Implementation) => "Implementations",
18210 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18211 Some(GotoDefinitionKind::Declaration) => "Declarations",
18212 Some(GotoDefinitionKind::Type) => "Types",
18213 };
18214 let title = editor
18215 .update_in(cx, |_, _, cx| {
18216 let target = locations
18217 .iter()
18218 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18219 .map(|(buffer, location)| {
18220 buffer
18221 .read(cx)
18222 .text_for_range(location.clone())
18223 .collect::<String>()
18224 })
18225 .filter(|text| !text.contains('\n'))
18226 .unique()
18227 .take(3)
18228 .join(", ");
18229 if target.is_empty() {
18230 tab_kind.to_owned()
18231 } else {
18232 format!("{tab_kind} for {target}")
18233 }
18234 })
18235 .context("buffer title")?;
18236
18237 let Some(workspace) = workspace else {
18238 return Ok(Navigated::No);
18239 };
18240
18241 let opened = workspace
18242 .update_in(cx, |workspace, window, cx| {
18243 let allow_preview = PreviewTabsSettings::get_global(cx)
18244 .enable_preview_multibuffer_from_code_navigation;
18245 if let Some((target_editor, target_pane)) =
18246 Self::open_locations_in_multibuffer(
18247 workspace,
18248 locations,
18249 title,
18250 split,
18251 allow_preview,
18252 MultibufferSelectionMode::First,
18253 window,
18254 cx,
18255 )
18256 {
18257 // We create our own nav history instead of using
18258 // `target_editor.nav_history` because `nav_history`
18259 // seems to be populated asynchronously when an item
18260 // is added to a pane
18261 let mut nav_history = target_pane
18262 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18263 target_editor.update(cx, |editor, cx| {
18264 let nav_data = editor
18265 .navigation_data(editor.selections.newest_anchor().head(), cx);
18266 let target =
18267 Some(nav_history.navigation_entry(Some(
18268 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18269 )));
18270 nav_history.push_tag(origin, target);
18271 })
18272 }
18273 })
18274 .is_ok();
18275
18276 anyhow::Ok(Navigated::from_bool(opened))
18277 } else if num_locations == 0 {
18278 // If there is one url or file, open it directly
18279 match first_url_or_file {
18280 Some(Either::Left(url)) => {
18281 cx.update(|window, cx| {
18282 if parse_zed_link(&url, cx).is_some() {
18283 window
18284 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18285 } else {
18286 cx.open_url(&url);
18287 }
18288 })?;
18289 Ok(Navigated::Yes)
18290 }
18291 Some(Either::Right(path)) => {
18292 // TODO(andrew): respect preview tab settings
18293 // `enable_keep_preview_on_code_navigation` and
18294 // `enable_preview_file_from_code_navigation`
18295 let Some(workspace) = workspace else {
18296 return Ok(Navigated::No);
18297 };
18298 workspace
18299 .update_in(cx, |workspace, window, cx| {
18300 workspace.open_resolved_path(path, window, cx)
18301 })?
18302 .await?;
18303 Ok(Navigated::Yes)
18304 }
18305 None => Ok(Navigated::No),
18306 }
18307 } else {
18308 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18309 let target_range = target_ranges.first().unwrap().clone();
18310
18311 editor.update_in(cx, |editor, window, cx| {
18312 let range = editor.range_for_match(&target_range);
18313 let range = collapse_multiline_range(range);
18314
18315 if !split
18316 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18317 {
18318 editor.go_to_singleton_buffer_range(range, window, cx);
18319
18320 let target =
18321 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18322 if let Some(mut nav_history) = editor.nav_history.clone() {
18323 nav_history.push_tag(origin, target);
18324 }
18325 } else {
18326 let Some(workspace) = workspace else {
18327 return Navigated::No;
18328 };
18329 let pane = workspace.read(cx).active_pane().clone();
18330 window.defer(cx, move |window, cx| {
18331 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18332 workspace.update(cx, |workspace, cx| {
18333 let pane = if split {
18334 workspace.adjacent_pane(window, cx)
18335 } else {
18336 workspace.active_pane().clone()
18337 };
18338
18339 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18340 let keep_old_preview = preview_tabs_settings
18341 .enable_keep_preview_on_code_navigation;
18342 let allow_new_preview = preview_tabs_settings
18343 .enable_preview_file_from_code_navigation;
18344
18345 let editor = workspace.open_project_item(
18346 pane.clone(),
18347 target_buffer.clone(),
18348 true,
18349 true,
18350 keep_old_preview,
18351 allow_new_preview,
18352 window,
18353 cx,
18354 );
18355 (editor, pane)
18356 });
18357 // We create our own nav history instead of using
18358 // `target_editor.nav_history` because `nav_history`
18359 // seems to be populated asynchronously when an item
18360 // is added to a pane
18361 let mut nav_history = target_pane
18362 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18363 target_editor.update(cx, |target_editor, cx| {
18364 // When selecting a definition in a different buffer, disable the nav history
18365 // to avoid creating a history entry at the previous cursor location.
18366 pane.update(cx, |pane, _| pane.disable_history());
18367 target_editor.go_to_singleton_buffer_range(range, window, cx);
18368
18369 let nav_data = target_editor.navigation_data(
18370 target_editor.selections.newest_anchor().head(),
18371 cx,
18372 );
18373 let target =
18374 Some(nav_history.navigation_entry(Some(
18375 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18376 )));
18377 nav_history.push_tag(origin, target);
18378 pane.update(cx, |pane, _| pane.enable_history());
18379 });
18380 });
18381 }
18382 Navigated::Yes
18383 })
18384 }
18385 })
18386 }
18387
18388 fn compute_target_location(
18389 &self,
18390 lsp_location: lsp::Location,
18391 server_id: LanguageServerId,
18392 window: &mut Window,
18393 cx: &mut Context<Self>,
18394 ) -> Task<anyhow::Result<Option<Location>>> {
18395 let Some(project) = self.project.clone() else {
18396 return Task::ready(Ok(None));
18397 };
18398
18399 cx.spawn_in(window, async move |editor, cx| {
18400 let location_task = editor.update(cx, |_, cx| {
18401 project.update(cx, |project, cx| {
18402 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18403 })
18404 })?;
18405 let location = Some({
18406 let target_buffer_handle = location_task.await.context("open local buffer")?;
18407 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18408 let target_start = target_buffer
18409 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18410 let target_end = target_buffer
18411 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18412 target_buffer.anchor_after(target_start)
18413 ..target_buffer.anchor_before(target_end)
18414 });
18415 Location {
18416 buffer: target_buffer_handle,
18417 range,
18418 }
18419 });
18420 Ok(location)
18421 })
18422 }
18423
18424 fn go_to_next_reference(
18425 &mut self,
18426 _: &GoToNextReference,
18427 window: &mut Window,
18428 cx: &mut Context<Self>,
18429 ) {
18430 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18431 if let Some(task) = task {
18432 task.detach();
18433 };
18434 }
18435
18436 fn go_to_prev_reference(
18437 &mut self,
18438 _: &GoToPreviousReference,
18439 window: &mut Window,
18440 cx: &mut Context<Self>,
18441 ) {
18442 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18443 if let Some(task) = task {
18444 task.detach();
18445 };
18446 }
18447
18448 pub fn go_to_reference_before_or_after_position(
18449 &mut self,
18450 direction: Direction,
18451 count: usize,
18452 window: &mut Window,
18453 cx: &mut Context<Self>,
18454 ) -> Option<Task<Result<()>>> {
18455 let selection = self.selections.newest_anchor();
18456 let head = selection.head();
18457
18458 let multi_buffer = self.buffer.read(cx);
18459
18460 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18461 let workspace = self.workspace()?;
18462 let project = workspace.read(cx).project().clone();
18463 let references =
18464 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18465 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18466 let Some(locations) = references.await? else {
18467 return Ok(());
18468 };
18469
18470 if locations.is_empty() {
18471 // totally normal - the cursor may be on something which is not
18472 // a symbol (e.g. a keyword)
18473 log::info!("no references found under cursor");
18474 return Ok(());
18475 }
18476
18477 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18478
18479 let (locations, current_location_index) =
18480 multi_buffer.update(cx, |multi_buffer, cx| {
18481 let mut locations = locations
18482 .into_iter()
18483 .filter_map(|loc| {
18484 let start = multi_buffer.buffer_anchor_to_anchor(
18485 &loc.buffer,
18486 loc.range.start,
18487 cx,
18488 )?;
18489 let end = multi_buffer.buffer_anchor_to_anchor(
18490 &loc.buffer,
18491 loc.range.end,
18492 cx,
18493 )?;
18494 Some(start..end)
18495 })
18496 .collect::<Vec<_>>();
18497
18498 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18499 // There is an O(n) implementation, but given this list will be
18500 // small (usually <100 items), the extra O(log(n)) factor isn't
18501 // worth the (surprisingly large amount of) extra complexity.
18502 locations
18503 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18504
18505 let head_offset = head.to_offset(&multi_buffer_snapshot);
18506
18507 let current_location_index = locations.iter().position(|loc| {
18508 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18509 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18510 });
18511
18512 (locations, current_location_index)
18513 });
18514
18515 let Some(current_location_index) = current_location_index else {
18516 // This indicates something has gone wrong, because we already
18517 // handle the "no references" case above
18518 log::error!(
18519 "failed to find current reference under cursor. Total references: {}",
18520 locations.len()
18521 );
18522 return Ok(());
18523 };
18524
18525 let destination_location_index = match direction {
18526 Direction::Next => (current_location_index + count) % locations.len(),
18527 Direction::Prev => {
18528 (current_location_index + locations.len() - count % locations.len())
18529 % locations.len()
18530 }
18531 };
18532
18533 // TODO(cameron): is this needed?
18534 // the thinking is to avoid "jumping to the current location" (avoid
18535 // polluting "jumplist" in vim terms)
18536 if current_location_index == destination_location_index {
18537 return Ok(());
18538 }
18539
18540 let Range { start, end } = locations[destination_location_index];
18541
18542 editor.update_in(cx, |editor, window, cx| {
18543 let effects = SelectionEffects::default();
18544
18545 editor.unfold_ranges(&[start..end], false, false, cx);
18546 editor.change_selections(effects, window, cx, |s| {
18547 s.select_ranges([start..start]);
18548 });
18549 })?;
18550
18551 Ok(())
18552 }))
18553 }
18554
18555 pub fn find_all_references(
18556 &mut self,
18557 action: &FindAllReferences,
18558 window: &mut Window,
18559 cx: &mut Context<Self>,
18560 ) -> Option<Task<Result<Navigated>>> {
18561 let always_open_multibuffer = action.always_open_multibuffer;
18562 let selection = self.selections.newest_anchor();
18563 let multi_buffer = self.buffer.read(cx);
18564 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18565 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18566 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18567 let head = selection_offset.head();
18568
18569 let head_anchor = multi_buffer_snapshot.anchor_at(
18570 head,
18571 if head < selection_offset.tail() {
18572 Bias::Right
18573 } else {
18574 Bias::Left
18575 },
18576 );
18577
18578 match self
18579 .find_all_references_task_sources
18580 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18581 {
18582 Ok(_) => {
18583 log::info!(
18584 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18585 );
18586 return None;
18587 }
18588 Err(i) => {
18589 self.find_all_references_task_sources.insert(i, head_anchor);
18590 }
18591 }
18592
18593 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18594 let workspace = self.workspace()?;
18595 let project = workspace.read(cx).project().clone();
18596 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18597 Some(cx.spawn_in(window, async move |editor, cx| {
18598 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18599 if let Ok(i) = editor
18600 .find_all_references_task_sources
18601 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18602 {
18603 editor.find_all_references_task_sources.remove(i);
18604 }
18605 });
18606
18607 let Some(locations) = references.await? else {
18608 return anyhow::Ok(Navigated::No);
18609 };
18610 let mut locations = cx.update(|_, cx| {
18611 locations
18612 .into_iter()
18613 .map(|location| {
18614 let buffer = location.buffer.read(cx);
18615 (location.buffer, location.range.to_point(buffer))
18616 })
18617 // if special-casing the single-match case, remove ranges
18618 // that intersect current selection
18619 .filter(|(location_buffer, location)| {
18620 if always_open_multibuffer || &buffer != location_buffer {
18621 return true;
18622 }
18623
18624 !location.contains_inclusive(&selection_point.range())
18625 })
18626 .into_group_map()
18627 })?;
18628 if locations.is_empty() {
18629 return anyhow::Ok(Navigated::No);
18630 }
18631 for ranges in locations.values_mut() {
18632 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18633 ranges.dedup();
18634 }
18635 let mut num_locations = 0;
18636 for ranges in locations.values_mut() {
18637 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18638 ranges.dedup();
18639 num_locations += ranges.len();
18640 }
18641
18642 if num_locations == 1 && !always_open_multibuffer {
18643 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18644 let target_range = target_ranges.first().unwrap().clone();
18645
18646 return editor.update_in(cx, |editor, window, cx| {
18647 let range = target_range.to_point(target_buffer.read(cx));
18648 let range = editor.range_for_match(&range);
18649 let range = range.start..range.start;
18650
18651 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18652 editor.go_to_singleton_buffer_range(range, window, cx);
18653 } else {
18654 let pane = workspace.read(cx).active_pane().clone();
18655 window.defer(cx, move |window, cx| {
18656 let target_editor: Entity<Self> =
18657 workspace.update(cx, |workspace, cx| {
18658 let pane = workspace.active_pane().clone();
18659
18660 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18661 let keep_old_preview = preview_tabs_settings
18662 .enable_keep_preview_on_code_navigation;
18663 let allow_new_preview = preview_tabs_settings
18664 .enable_preview_file_from_code_navigation;
18665
18666 workspace.open_project_item(
18667 pane,
18668 target_buffer.clone(),
18669 true,
18670 true,
18671 keep_old_preview,
18672 allow_new_preview,
18673 window,
18674 cx,
18675 )
18676 });
18677 target_editor.update(cx, |target_editor, cx| {
18678 // When selecting a definition in a different buffer, disable the nav history
18679 // to avoid creating a history entry at the previous cursor location.
18680 pane.update(cx, |pane, _| pane.disable_history());
18681 target_editor.go_to_singleton_buffer_range(range, window, cx);
18682 pane.update(cx, |pane, _| pane.enable_history());
18683 });
18684 });
18685 }
18686 Navigated::No
18687 });
18688 }
18689
18690 workspace.update_in(cx, |workspace, window, cx| {
18691 let target = locations
18692 .iter()
18693 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18694 .map(|(buffer, location)| {
18695 buffer
18696 .read(cx)
18697 .text_for_range(location.clone())
18698 .collect::<String>()
18699 })
18700 .filter(|text| !text.contains('\n'))
18701 .unique()
18702 .take(3)
18703 .join(", ");
18704 let title = if target.is_empty() {
18705 "References".to_owned()
18706 } else {
18707 format!("References to {target}")
18708 };
18709 let allow_preview = PreviewTabsSettings::get_global(cx)
18710 .enable_preview_multibuffer_from_code_navigation;
18711 Self::open_locations_in_multibuffer(
18712 workspace,
18713 locations,
18714 title,
18715 false,
18716 allow_preview,
18717 MultibufferSelectionMode::First,
18718 window,
18719 cx,
18720 );
18721 Navigated::Yes
18722 })
18723 }))
18724 }
18725
18726 /// Opens a multibuffer with the given project locations in it.
18727 pub fn open_locations_in_multibuffer(
18728 workspace: &mut Workspace,
18729 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18730 title: String,
18731 split: bool,
18732 allow_preview: bool,
18733 multibuffer_selection_mode: MultibufferSelectionMode,
18734 window: &mut Window,
18735 cx: &mut Context<Workspace>,
18736 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18737 if locations.is_empty() {
18738 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18739 return None;
18740 }
18741
18742 let capability = workspace.project().read(cx).capability();
18743 let mut ranges = <Vec<Range<Anchor>>>::new();
18744
18745 // a key to find existing multibuffer editors with the same set of locations
18746 // to prevent us from opening more and more multibuffer tabs for searches and the like
18747 let mut key = (title.clone(), vec![]);
18748 let excerpt_buffer = cx.new(|cx| {
18749 let key = &mut key.1;
18750 let mut multibuffer = MultiBuffer::new(capability);
18751 for (buffer, mut ranges_for_buffer) in locations {
18752 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18753 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18754 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18755 PathKey::for_buffer(&buffer, cx),
18756 buffer.clone(),
18757 ranges_for_buffer,
18758 multibuffer_context_lines(cx),
18759 cx,
18760 );
18761 ranges.extend(new_ranges)
18762 }
18763
18764 multibuffer.with_title(title)
18765 });
18766 let existing = workspace.active_pane().update(cx, |pane, cx| {
18767 pane.items()
18768 .filter_map(|item| item.downcast::<Editor>())
18769 .find(|editor| {
18770 editor
18771 .read(cx)
18772 .lookup_key
18773 .as_ref()
18774 .and_then(|it| {
18775 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18776 })
18777 .is_some_and(|it| *it == key)
18778 })
18779 });
18780 let was_existing = existing.is_some();
18781 let editor = existing.unwrap_or_else(|| {
18782 cx.new(|cx| {
18783 let mut editor = Editor::for_multibuffer(
18784 excerpt_buffer,
18785 Some(workspace.project().clone()),
18786 window,
18787 cx,
18788 );
18789 editor.lookup_key = Some(Box::new(key));
18790 editor
18791 })
18792 });
18793 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18794 MultibufferSelectionMode::First => {
18795 if let Some(first_range) = ranges.first() {
18796 editor.change_selections(
18797 SelectionEffects::no_scroll(),
18798 window,
18799 cx,
18800 |selections| {
18801 selections.clear_disjoint();
18802 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18803 },
18804 );
18805 }
18806 editor.highlight_background(
18807 HighlightKey::Editor,
18808 &ranges,
18809 |_, theme| theme.colors().editor_highlighted_line_background,
18810 cx,
18811 );
18812 }
18813 MultibufferSelectionMode::All => {
18814 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18815 selections.clear_disjoint();
18816 selections.select_anchor_ranges(ranges);
18817 });
18818 }
18819 });
18820
18821 let item = Box::new(editor.clone());
18822
18823 let pane = if split {
18824 workspace.adjacent_pane(window, cx)
18825 } else {
18826 workspace.active_pane().clone()
18827 };
18828 let activate_pane = split;
18829
18830 let mut destination_index = None;
18831 pane.update(cx, |pane, cx| {
18832 if allow_preview && !was_existing {
18833 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18834 }
18835 if was_existing && !allow_preview {
18836 pane.unpreview_item_if_preview(item.item_id());
18837 }
18838 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18839 });
18840
18841 Some((editor, pane))
18842 }
18843
18844 pub fn rename(
18845 &mut self,
18846 _: &Rename,
18847 window: &mut Window,
18848 cx: &mut Context<Self>,
18849 ) -> Option<Task<Result<()>>> {
18850 use language::ToOffset as _;
18851
18852 let provider = self.semantics_provider.clone()?;
18853 let selection = self.selections.newest_anchor().clone();
18854 let (cursor_buffer, cursor_buffer_position) = self
18855 .buffer
18856 .read(cx)
18857 .text_anchor_for_position(selection.head(), cx)?;
18858 let (tail_buffer, cursor_buffer_position_end) = self
18859 .buffer
18860 .read(cx)
18861 .text_anchor_for_position(selection.tail(), cx)?;
18862 if tail_buffer != cursor_buffer {
18863 return None;
18864 }
18865
18866 let snapshot = cursor_buffer.read(cx).snapshot();
18867 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18868 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18869 let prepare_rename = provider
18870 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18871 .unwrap_or_else(|| Task::ready(Ok(None)));
18872 drop(snapshot);
18873
18874 Some(cx.spawn_in(window, async move |this, cx| {
18875 let rename_range = if let Some(range) = prepare_rename.await? {
18876 Some(range)
18877 } else {
18878 this.update(cx, |this, cx| {
18879 let buffer = this.buffer.read(cx).snapshot(cx);
18880 let mut buffer_highlights = this
18881 .document_highlights_for_position(selection.head(), &buffer)
18882 .filter(|highlight| {
18883 highlight.start.excerpt_id == selection.head().excerpt_id
18884 && highlight.end.excerpt_id == selection.head().excerpt_id
18885 });
18886 buffer_highlights
18887 .next()
18888 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18889 })?
18890 };
18891 if let Some(rename_range) = rename_range {
18892 this.update_in(cx, |this, window, cx| {
18893 let snapshot = cursor_buffer.read(cx).snapshot();
18894 let rename_buffer_range = rename_range.to_offset(&snapshot);
18895 let cursor_offset_in_rename_range =
18896 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18897 let cursor_offset_in_rename_range_end =
18898 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18899
18900 this.take_rename(false, window, cx);
18901 let buffer = this.buffer.read(cx).read(cx);
18902 let cursor_offset = selection.head().to_offset(&buffer);
18903 let rename_start =
18904 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18905 let rename_end = rename_start + rename_buffer_range.len();
18906 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18907 let mut old_highlight_id = None;
18908 let old_name: Arc<str> = buffer
18909 .chunks(rename_start..rename_end, true)
18910 .map(|chunk| {
18911 if old_highlight_id.is_none() {
18912 old_highlight_id = chunk.syntax_highlight_id;
18913 }
18914 chunk.text
18915 })
18916 .collect::<String>()
18917 .into();
18918
18919 drop(buffer);
18920
18921 // Position the selection in the rename editor so that it matches the current selection.
18922 this.show_local_selections = false;
18923 let rename_editor = cx.new(|cx| {
18924 let mut editor = Editor::single_line(window, cx);
18925 editor.buffer.update(cx, |buffer, cx| {
18926 buffer.edit(
18927 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18928 None,
18929 cx,
18930 )
18931 });
18932 let cursor_offset_in_rename_range =
18933 MultiBufferOffset(cursor_offset_in_rename_range);
18934 let cursor_offset_in_rename_range_end =
18935 MultiBufferOffset(cursor_offset_in_rename_range_end);
18936 let rename_selection_range = match cursor_offset_in_rename_range
18937 .cmp(&cursor_offset_in_rename_range_end)
18938 {
18939 Ordering::Equal => {
18940 editor.select_all(&SelectAll, window, cx);
18941 return editor;
18942 }
18943 Ordering::Less => {
18944 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18945 }
18946 Ordering::Greater => {
18947 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18948 }
18949 };
18950 if rename_selection_range.end.0 > old_name.len() {
18951 editor.select_all(&SelectAll, window, cx);
18952 } else {
18953 editor.change_selections(Default::default(), window, cx, |s| {
18954 s.select_ranges([rename_selection_range]);
18955 });
18956 }
18957 editor
18958 });
18959 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18960 if e == &EditorEvent::Focused {
18961 cx.emit(EditorEvent::FocusedIn)
18962 }
18963 })
18964 .detach();
18965
18966 let write_highlights =
18967 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
18968 let read_highlights =
18969 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
18970 let ranges = write_highlights
18971 .iter()
18972 .flat_map(|(_, ranges)| ranges.iter())
18973 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18974 .cloned()
18975 .collect();
18976
18977 this.highlight_text(
18978 HighlightKey::Rename,
18979 ranges,
18980 HighlightStyle {
18981 fade_out: Some(0.6),
18982 ..Default::default()
18983 },
18984 cx,
18985 );
18986 let rename_focus_handle = rename_editor.focus_handle(cx);
18987 window.focus(&rename_focus_handle, cx);
18988 let block_id = this.insert_blocks(
18989 [BlockProperties {
18990 style: BlockStyle::Flex,
18991 placement: BlockPlacement::Below(range.start),
18992 height: Some(1),
18993 render: Arc::new({
18994 let rename_editor = rename_editor.clone();
18995 move |cx: &mut BlockContext| {
18996 let mut text_style = cx.editor_style.text.clone();
18997 if let Some(highlight_style) = old_highlight_id
18998 .and_then(|h| h.style(&cx.editor_style.syntax))
18999 {
19000 text_style = text_style.highlight(highlight_style);
19001 }
19002 div()
19003 .block_mouse_except_scroll()
19004 .pl(cx.anchor_x)
19005 .child(EditorElement::new(
19006 &rename_editor,
19007 EditorStyle {
19008 background: cx.theme().system().transparent,
19009 local_player: cx.editor_style.local_player,
19010 text: text_style,
19011 scrollbar_width: cx.editor_style.scrollbar_width,
19012 syntax: cx.editor_style.syntax.clone(),
19013 status: cx.editor_style.status.clone(),
19014 inlay_hints_style: HighlightStyle {
19015 font_weight: Some(FontWeight::BOLD),
19016 ..make_inlay_hints_style(cx.app)
19017 },
19018 edit_prediction_styles: make_suggestion_styles(
19019 cx.app,
19020 ),
19021 ..EditorStyle::default()
19022 },
19023 ))
19024 .into_any_element()
19025 }
19026 }),
19027 priority: 0,
19028 }],
19029 Some(Autoscroll::fit()),
19030 cx,
19031 )[0];
19032 this.pending_rename = Some(RenameState {
19033 range,
19034 old_name,
19035 editor: rename_editor,
19036 block_id,
19037 });
19038 })?;
19039 }
19040
19041 Ok(())
19042 }))
19043 }
19044
19045 pub fn confirm_rename(
19046 &mut self,
19047 _: &ConfirmRename,
19048 window: &mut Window,
19049 cx: &mut Context<Self>,
19050 ) -> Option<Task<Result<()>>> {
19051 let rename = self.take_rename(false, window, cx)?;
19052 let workspace = self.workspace()?.downgrade();
19053 let (buffer, start) = self
19054 .buffer
19055 .read(cx)
19056 .text_anchor_for_position(rename.range.start, cx)?;
19057 let (end_buffer, _) = self
19058 .buffer
19059 .read(cx)
19060 .text_anchor_for_position(rename.range.end, cx)?;
19061 if buffer != end_buffer {
19062 return None;
19063 }
19064
19065 let old_name = rename.old_name;
19066 let new_name = rename.editor.read(cx).text(cx);
19067
19068 let rename = self.semantics_provider.as_ref()?.perform_rename(
19069 &buffer,
19070 start,
19071 new_name.clone(),
19072 cx,
19073 )?;
19074
19075 Some(cx.spawn_in(window, async move |editor, cx| {
19076 let project_transaction = rename.await?;
19077 Self::open_project_transaction(
19078 &editor,
19079 workspace,
19080 project_transaction,
19081 format!("Rename: {} → {}", old_name, new_name),
19082 cx,
19083 )
19084 .await?;
19085
19086 editor.update(cx, |editor, cx| {
19087 editor.refresh_document_highlights(cx);
19088 })?;
19089 Ok(())
19090 }))
19091 }
19092
19093 fn take_rename(
19094 &mut self,
19095 moving_cursor: bool,
19096 window: &mut Window,
19097 cx: &mut Context<Self>,
19098 ) -> Option<RenameState> {
19099 let rename = self.pending_rename.take()?;
19100 if rename.editor.focus_handle(cx).is_focused(window) {
19101 window.focus(&self.focus_handle, cx);
19102 }
19103
19104 self.remove_blocks(
19105 [rename.block_id].into_iter().collect(),
19106 Some(Autoscroll::fit()),
19107 cx,
19108 );
19109 self.clear_highlights(HighlightKey::Rename, cx);
19110 self.show_local_selections = true;
19111
19112 if moving_cursor {
19113 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19114 editor
19115 .selections
19116 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19117 .head()
19118 });
19119
19120 // Update the selection to match the position of the selection inside
19121 // the rename editor.
19122 let snapshot = self.buffer.read(cx).read(cx);
19123 let rename_range = rename.range.to_offset(&snapshot);
19124 let cursor_in_editor = snapshot
19125 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19126 .min(rename_range.end);
19127 drop(snapshot);
19128
19129 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19130 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19131 });
19132 } else {
19133 self.refresh_document_highlights(cx);
19134 }
19135
19136 Some(rename)
19137 }
19138
19139 pub fn pending_rename(&self) -> Option<&RenameState> {
19140 self.pending_rename.as_ref()
19141 }
19142
19143 fn format(
19144 &mut self,
19145 _: &Format,
19146 window: &mut Window,
19147 cx: &mut Context<Self>,
19148 ) -> Option<Task<Result<()>>> {
19149 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19150
19151 let project = match &self.project {
19152 Some(project) => project.clone(),
19153 None => return None,
19154 };
19155
19156 Some(self.perform_format(
19157 project,
19158 FormatTrigger::Manual,
19159 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19160 window,
19161 cx,
19162 ))
19163 }
19164
19165 fn format_selections(
19166 &mut self,
19167 _: &FormatSelections,
19168 window: &mut Window,
19169 cx: &mut Context<Self>,
19170 ) -> Option<Task<Result<()>>> {
19171 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19172
19173 let project = match &self.project {
19174 Some(project) => project.clone(),
19175 None => return None,
19176 };
19177
19178 let ranges = self
19179 .selections
19180 .all_adjusted(&self.display_snapshot(cx))
19181 .into_iter()
19182 .map(|selection| selection.range())
19183 .collect_vec();
19184
19185 Some(self.perform_format(
19186 project,
19187 FormatTrigger::Manual,
19188 FormatTarget::Ranges(ranges),
19189 window,
19190 cx,
19191 ))
19192 }
19193
19194 fn perform_format(
19195 &mut self,
19196 project: Entity<Project>,
19197 trigger: FormatTrigger,
19198 target: FormatTarget,
19199 window: &mut Window,
19200 cx: &mut Context<Self>,
19201 ) -> Task<Result<()>> {
19202 let buffer = self.buffer.clone();
19203 let (buffers, target) = match target {
19204 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19205 FormatTarget::Ranges(selection_ranges) => {
19206 let multi_buffer = buffer.read(cx);
19207 let snapshot = multi_buffer.read(cx);
19208 let mut buffers = HashSet::default();
19209 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19210 BTreeMap::new();
19211 for selection_range in selection_ranges {
19212 for (buffer, buffer_range, _) in
19213 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19214 {
19215 let buffer_id = buffer.remote_id();
19216 let start = buffer.anchor_before(buffer_range.start);
19217 let end = buffer.anchor_after(buffer_range.end);
19218 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19219 buffer_id_to_ranges
19220 .entry(buffer_id)
19221 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19222 .or_insert_with(|| vec![start..end]);
19223 }
19224 }
19225 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19226 }
19227 };
19228
19229 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19230 let selections_prev = transaction_id_prev
19231 .and_then(|transaction_id_prev| {
19232 // default to selections as they were after the last edit, if we have them,
19233 // instead of how they are now.
19234 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19235 // will take you back to where you made the last edit, instead of staying where you scrolled
19236 self.selection_history
19237 .transaction(transaction_id_prev)
19238 .map(|t| t.0.clone())
19239 })
19240 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19241
19242 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19243 let format = project.update(cx, |project, cx| {
19244 project.format(buffers, target, true, trigger, cx)
19245 });
19246
19247 cx.spawn_in(window, async move |editor, cx| {
19248 let transaction = futures::select_biased! {
19249 transaction = format.log_err().fuse() => transaction,
19250 () = timeout => {
19251 log::warn!("timed out waiting for formatting");
19252 None
19253 }
19254 };
19255
19256 buffer.update(cx, |buffer, cx| {
19257 if let Some(transaction) = transaction
19258 && !buffer.is_singleton()
19259 {
19260 buffer.push_transaction(&transaction.0, cx);
19261 }
19262 cx.notify();
19263 });
19264
19265 if let Some(transaction_id_now) =
19266 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19267 {
19268 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19269 if has_new_transaction {
19270 editor
19271 .update(cx, |editor, _| {
19272 editor
19273 .selection_history
19274 .insert_transaction(transaction_id_now, selections_prev);
19275 })
19276 .ok();
19277 }
19278 }
19279
19280 Ok(())
19281 })
19282 }
19283
19284 fn organize_imports(
19285 &mut self,
19286 _: &OrganizeImports,
19287 window: &mut Window,
19288 cx: &mut Context<Self>,
19289 ) -> Option<Task<Result<()>>> {
19290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19291 let project = match &self.project {
19292 Some(project) => project.clone(),
19293 None => return None,
19294 };
19295 Some(self.perform_code_action_kind(
19296 project,
19297 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19298 window,
19299 cx,
19300 ))
19301 }
19302
19303 fn perform_code_action_kind(
19304 &mut self,
19305 project: Entity<Project>,
19306 kind: CodeActionKind,
19307 window: &mut Window,
19308 cx: &mut Context<Self>,
19309 ) -> Task<Result<()>> {
19310 let buffer = self.buffer.clone();
19311 let buffers = buffer.read(cx).all_buffers();
19312 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19313 let apply_action = project.update(cx, |project, cx| {
19314 project.apply_code_action_kind(buffers, kind, true, cx)
19315 });
19316 cx.spawn_in(window, async move |_, cx| {
19317 let transaction = futures::select_biased! {
19318 () = timeout => {
19319 log::warn!("timed out waiting for executing code action");
19320 None
19321 }
19322 transaction = apply_action.log_err().fuse() => transaction,
19323 };
19324 buffer.update(cx, |buffer, cx| {
19325 // check if we need this
19326 if let Some(transaction) = transaction
19327 && !buffer.is_singleton()
19328 {
19329 buffer.push_transaction(&transaction.0, cx);
19330 }
19331 cx.notify();
19332 });
19333 Ok(())
19334 })
19335 }
19336
19337 pub fn restart_language_server(
19338 &mut self,
19339 _: &RestartLanguageServer,
19340 _: &mut Window,
19341 cx: &mut Context<Self>,
19342 ) {
19343 if let Some(project) = self.project.clone() {
19344 self.buffer.update(cx, |multi_buffer, cx| {
19345 project.update(cx, |project, cx| {
19346 project.restart_language_servers_for_buffers(
19347 multi_buffer.all_buffers().into_iter().collect(),
19348 HashSet::default(),
19349 cx,
19350 );
19351 });
19352 })
19353 }
19354 }
19355
19356 pub fn stop_language_server(
19357 &mut self,
19358 _: &StopLanguageServer,
19359 _: &mut Window,
19360 cx: &mut Context<Self>,
19361 ) {
19362 if let Some(project) = self.project.clone() {
19363 self.buffer.update(cx, |multi_buffer, cx| {
19364 project.update(cx, |project, cx| {
19365 project.stop_language_servers_for_buffers(
19366 multi_buffer.all_buffers().into_iter().collect(),
19367 HashSet::default(),
19368 cx,
19369 );
19370 });
19371 });
19372 }
19373 }
19374
19375 fn cancel_language_server_work(
19376 workspace: &mut Workspace,
19377 _: &actions::CancelLanguageServerWork,
19378 _: &mut Window,
19379 cx: &mut Context<Workspace>,
19380 ) {
19381 let project = workspace.project();
19382 let buffers = workspace
19383 .active_item(cx)
19384 .and_then(|item| item.act_as::<Editor>(cx))
19385 .map_or(HashSet::default(), |editor| {
19386 editor.read(cx).buffer.read(cx).all_buffers()
19387 });
19388 project.update(cx, |project, cx| {
19389 project.cancel_language_server_work_for_buffers(buffers, cx);
19390 });
19391 }
19392
19393 fn show_character_palette(
19394 &mut self,
19395 _: &ShowCharacterPalette,
19396 window: &mut Window,
19397 _: &mut Context<Self>,
19398 ) {
19399 window.show_character_palette();
19400 }
19401
19402 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19403 if !self.diagnostics_enabled() {
19404 return;
19405 }
19406
19407 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19408 let buffer = self.buffer.read(cx).snapshot(cx);
19409 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19410 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19411 let is_valid = buffer
19412 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19413 .any(|entry| {
19414 entry.diagnostic.is_primary
19415 && !entry.range.is_empty()
19416 && entry.range.start == primary_range_start
19417 && entry.diagnostic.message == active_diagnostics.active_message
19418 });
19419
19420 if !is_valid {
19421 self.dismiss_diagnostics(cx);
19422 }
19423 }
19424 }
19425
19426 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19427 match &self.active_diagnostics {
19428 ActiveDiagnostic::Group(group) => Some(group),
19429 _ => None,
19430 }
19431 }
19432
19433 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19434 if !self.diagnostics_enabled() {
19435 return;
19436 }
19437 self.dismiss_diagnostics(cx);
19438 self.active_diagnostics = ActiveDiagnostic::All;
19439 }
19440
19441 fn activate_diagnostics(
19442 &mut self,
19443 buffer_id: BufferId,
19444 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19445 window: &mut Window,
19446 cx: &mut Context<Self>,
19447 ) {
19448 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19449 return;
19450 }
19451 self.dismiss_diagnostics(cx);
19452 let snapshot = self.snapshot(window, cx);
19453 let buffer = self.buffer.read(cx).snapshot(cx);
19454 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19455 return;
19456 };
19457
19458 let diagnostic_group = buffer
19459 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19460 .collect::<Vec<_>>();
19461
19462 let language_registry = self
19463 .project()
19464 .map(|project| project.read(cx).languages().clone());
19465
19466 let blocks = renderer.render_group(
19467 diagnostic_group,
19468 buffer_id,
19469 snapshot,
19470 cx.weak_entity(),
19471 language_registry,
19472 cx,
19473 );
19474
19475 let blocks = self.display_map.update(cx, |display_map, cx| {
19476 display_map.insert_blocks(blocks, cx).into_iter().collect()
19477 });
19478 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19479 active_range: buffer.anchor_before(diagnostic.range.start)
19480 ..buffer.anchor_after(diagnostic.range.end),
19481 active_message: diagnostic.diagnostic.message.clone(),
19482 group_id: diagnostic.diagnostic.group_id,
19483 blocks,
19484 });
19485 cx.notify();
19486 }
19487
19488 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19489 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19490 return;
19491 };
19492
19493 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19494 if let ActiveDiagnostic::Group(group) = prev {
19495 self.display_map.update(cx, |display_map, cx| {
19496 display_map.remove_blocks(group.blocks, cx);
19497 });
19498 cx.notify();
19499 }
19500 }
19501
19502 /// Disable inline diagnostics rendering for this editor.
19503 pub fn disable_inline_diagnostics(&mut self) {
19504 self.inline_diagnostics_enabled = false;
19505 self.inline_diagnostics_update = Task::ready(());
19506 self.inline_diagnostics.clear();
19507 }
19508
19509 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19510 self.diagnostics_enabled = false;
19511 self.dismiss_diagnostics(cx);
19512 self.inline_diagnostics_update = Task::ready(());
19513 self.inline_diagnostics.clear();
19514 }
19515
19516 pub fn disable_word_completions(&mut self) {
19517 self.word_completions_enabled = false;
19518 }
19519
19520 pub fn diagnostics_enabled(&self) -> bool {
19521 self.diagnostics_enabled && self.mode.is_full()
19522 }
19523
19524 pub fn inline_diagnostics_enabled(&self) -> bool {
19525 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19526 }
19527
19528 pub fn show_inline_diagnostics(&self) -> bool {
19529 self.show_inline_diagnostics
19530 }
19531
19532 pub fn toggle_inline_diagnostics(
19533 &mut self,
19534 _: &ToggleInlineDiagnostics,
19535 window: &mut Window,
19536 cx: &mut Context<Editor>,
19537 ) {
19538 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19539 self.refresh_inline_diagnostics(false, window, cx);
19540 }
19541
19542 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19543 self.diagnostics_max_severity = severity;
19544 self.display_map.update(cx, |display_map, _| {
19545 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19546 });
19547 }
19548
19549 pub fn toggle_diagnostics(
19550 &mut self,
19551 _: &ToggleDiagnostics,
19552 window: &mut Window,
19553 cx: &mut Context<Editor>,
19554 ) {
19555 if !self.diagnostics_enabled() {
19556 return;
19557 }
19558
19559 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19560 EditorSettings::get_global(cx)
19561 .diagnostics_max_severity
19562 .filter(|severity| severity != &DiagnosticSeverity::Off)
19563 .unwrap_or(DiagnosticSeverity::Hint)
19564 } else {
19565 DiagnosticSeverity::Off
19566 };
19567 self.set_max_diagnostics_severity(new_severity, cx);
19568 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19569 self.active_diagnostics = ActiveDiagnostic::None;
19570 self.inline_diagnostics_update = Task::ready(());
19571 self.inline_diagnostics.clear();
19572 } else {
19573 self.refresh_inline_diagnostics(false, window, cx);
19574 }
19575
19576 cx.notify();
19577 }
19578
19579 pub fn toggle_minimap(
19580 &mut self,
19581 _: &ToggleMinimap,
19582 window: &mut Window,
19583 cx: &mut Context<Editor>,
19584 ) {
19585 if self.supports_minimap(cx) {
19586 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19587 }
19588 }
19589
19590 fn refresh_inline_diagnostics(
19591 &mut self,
19592 debounce: bool,
19593 window: &mut Window,
19594 cx: &mut Context<Self>,
19595 ) {
19596 let max_severity = ProjectSettings::get_global(cx)
19597 .diagnostics
19598 .inline
19599 .max_severity
19600 .unwrap_or(self.diagnostics_max_severity);
19601
19602 if !self.inline_diagnostics_enabled()
19603 || !self.diagnostics_enabled()
19604 || !self.show_inline_diagnostics
19605 || max_severity == DiagnosticSeverity::Off
19606 {
19607 self.inline_diagnostics_update = Task::ready(());
19608 self.inline_diagnostics.clear();
19609 return;
19610 }
19611
19612 let debounce_ms = ProjectSettings::get_global(cx)
19613 .diagnostics
19614 .inline
19615 .update_debounce_ms;
19616 let debounce = if debounce && debounce_ms > 0 {
19617 Some(Duration::from_millis(debounce_ms))
19618 } else {
19619 None
19620 };
19621 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19622 if let Some(debounce) = debounce {
19623 cx.background_executor().timer(debounce).await;
19624 }
19625 let Some(snapshot) = editor.upgrade().map(|editor| {
19626 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19627 }) else {
19628 return;
19629 };
19630
19631 let new_inline_diagnostics = cx
19632 .background_spawn(async move {
19633 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19634 for diagnostic_entry in
19635 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19636 {
19637 let message = diagnostic_entry
19638 .diagnostic
19639 .message
19640 .split_once('\n')
19641 .map(|(line, _)| line)
19642 .map(SharedString::new)
19643 .unwrap_or_else(|| {
19644 SharedString::new(&*diagnostic_entry.diagnostic.message)
19645 });
19646 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19647 let (Ok(i) | Err(i)) = inline_diagnostics
19648 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19649 inline_diagnostics.insert(
19650 i,
19651 (
19652 start_anchor,
19653 InlineDiagnostic {
19654 message,
19655 group_id: diagnostic_entry.diagnostic.group_id,
19656 start: diagnostic_entry.range.start.to_point(&snapshot),
19657 is_primary: diagnostic_entry.diagnostic.is_primary,
19658 severity: diagnostic_entry.diagnostic.severity,
19659 },
19660 ),
19661 );
19662 }
19663 inline_diagnostics
19664 })
19665 .await;
19666
19667 editor
19668 .update(cx, |editor, cx| {
19669 editor.inline_diagnostics = new_inline_diagnostics;
19670 cx.notify();
19671 })
19672 .ok();
19673 });
19674 }
19675
19676 fn pull_diagnostics(
19677 &mut self,
19678 buffer_id: BufferId,
19679 _window: &Window,
19680 cx: &mut Context<Self>,
19681 ) -> Option<()> {
19682 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19683 // skip any LSP updates for it.
19684
19685 if self.active_diagnostics == ActiveDiagnostic::All
19686 || !self.mode().is_full()
19687 || !self.diagnostics_enabled()
19688 {
19689 return None;
19690 }
19691 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19692 .diagnostics
19693 .lsp_pull_diagnostics;
19694 if !pull_diagnostics_settings.enabled {
19695 return None;
19696 }
19697 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19698 let project = self.project()?.downgrade();
19699 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19700
19701 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19702 cx.background_executor().timer(debounce).await;
19703 if let Ok(task) = project.update(cx, |project, cx| {
19704 project.lsp_store().update(cx, |lsp_store, cx| {
19705 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19706 })
19707 }) {
19708 task.await.log_err();
19709 }
19710 project
19711 .update(cx, |project, cx| {
19712 project.lsp_store().update(cx, |lsp_store, cx| {
19713 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19714 })
19715 })
19716 .log_err();
19717 });
19718
19719 Some(())
19720 }
19721
19722 pub fn set_selections_from_remote(
19723 &mut self,
19724 selections: Vec<Selection<Anchor>>,
19725 pending_selection: Option<Selection<Anchor>>,
19726 window: &mut Window,
19727 cx: &mut Context<Self>,
19728 ) {
19729 let old_cursor_position = self.selections.newest_anchor().head();
19730 self.selections
19731 .change_with(&self.display_snapshot(cx), |s| {
19732 s.select_anchors(selections);
19733 if let Some(pending_selection) = pending_selection {
19734 s.set_pending(pending_selection, SelectMode::Character);
19735 } else {
19736 s.clear_pending();
19737 }
19738 });
19739 self.selections_did_change(
19740 false,
19741 &old_cursor_position,
19742 SelectionEffects::default(),
19743 window,
19744 cx,
19745 );
19746 }
19747
19748 pub fn transact(
19749 &mut self,
19750 window: &mut Window,
19751 cx: &mut Context<Self>,
19752 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19753 ) -> Option<TransactionId> {
19754 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19755 this.start_transaction_at(Instant::now(), window, cx);
19756 update(this, window, cx);
19757 this.end_transaction_at(Instant::now(), cx)
19758 })
19759 }
19760
19761 pub fn start_transaction_at(
19762 &mut self,
19763 now: Instant,
19764 window: &mut Window,
19765 cx: &mut Context<Self>,
19766 ) -> Option<TransactionId> {
19767 self.end_selection(window, cx);
19768 if let Some(tx_id) = self
19769 .buffer
19770 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19771 {
19772 self.selection_history
19773 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19774 cx.emit(EditorEvent::TransactionBegun {
19775 transaction_id: tx_id,
19776 });
19777 Some(tx_id)
19778 } else {
19779 None
19780 }
19781 }
19782
19783 pub fn end_transaction_at(
19784 &mut self,
19785 now: Instant,
19786 cx: &mut Context<Self>,
19787 ) -> Option<TransactionId> {
19788 if let Some(transaction_id) = self
19789 .buffer
19790 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19791 {
19792 if let Some((_, end_selections)) =
19793 self.selection_history.transaction_mut(transaction_id)
19794 {
19795 *end_selections = Some(self.selections.disjoint_anchors_arc());
19796 } else {
19797 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19798 }
19799
19800 cx.emit(EditorEvent::Edited { transaction_id });
19801 Some(transaction_id)
19802 } else {
19803 None
19804 }
19805 }
19806
19807 pub fn modify_transaction_selection_history(
19808 &mut self,
19809 transaction_id: TransactionId,
19810 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19811 ) -> bool {
19812 self.selection_history
19813 .transaction_mut(transaction_id)
19814 .map(modify)
19815 .is_some()
19816 }
19817
19818 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19819 if self.selection_mark_mode {
19820 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19821 s.move_with(&mut |_, sel| {
19822 sel.collapse_to(sel.head(), SelectionGoal::None);
19823 });
19824 })
19825 }
19826 self.selection_mark_mode = true;
19827 cx.notify();
19828 }
19829
19830 pub fn swap_selection_ends(
19831 &mut self,
19832 _: &actions::SwapSelectionEnds,
19833 window: &mut Window,
19834 cx: &mut Context<Self>,
19835 ) {
19836 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19837 s.move_with(&mut |_, sel| {
19838 if sel.start != sel.end {
19839 sel.reversed = !sel.reversed
19840 }
19841 });
19842 });
19843 self.request_autoscroll(Autoscroll::newest(), cx);
19844 cx.notify();
19845 }
19846
19847 pub fn toggle_focus(
19848 workspace: &mut Workspace,
19849 _: &actions::ToggleFocus,
19850 window: &mut Window,
19851 cx: &mut Context<Workspace>,
19852 ) {
19853 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19854 return;
19855 };
19856 workspace.activate_item(&item, true, true, window, cx);
19857 }
19858
19859 pub fn toggle_fold(
19860 &mut self,
19861 _: &actions::ToggleFold,
19862 window: &mut Window,
19863 cx: &mut Context<Self>,
19864 ) {
19865 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19866 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19867 let selection = self.selections.newest::<Point>(&display_map);
19868
19869 let range = if selection.is_empty() {
19870 let point = selection.head().to_display_point(&display_map);
19871 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19872 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19873 .to_point(&display_map);
19874 start..end
19875 } else {
19876 selection.range()
19877 };
19878 if display_map.folds_in_range(range).next().is_some() {
19879 self.unfold_lines(&Default::default(), window, cx)
19880 } else {
19881 self.fold(&Default::default(), window, cx)
19882 }
19883 } else {
19884 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19885 let buffer_ids: HashSet<_> = self
19886 .selections
19887 .disjoint_anchor_ranges()
19888 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19889 .collect();
19890
19891 let should_unfold = buffer_ids
19892 .iter()
19893 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19894
19895 for buffer_id in buffer_ids {
19896 if should_unfold {
19897 self.unfold_buffer(buffer_id, cx);
19898 } else {
19899 self.fold_buffer(buffer_id, cx);
19900 }
19901 }
19902 }
19903 }
19904
19905 pub fn toggle_fold_recursive(
19906 &mut self,
19907 _: &actions::ToggleFoldRecursive,
19908 window: &mut Window,
19909 cx: &mut Context<Self>,
19910 ) {
19911 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19912
19913 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19914 let range = if selection.is_empty() {
19915 let point = selection.head().to_display_point(&display_map);
19916 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19917 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19918 .to_point(&display_map);
19919 start..end
19920 } else {
19921 selection.range()
19922 };
19923 if display_map.folds_in_range(range).next().is_some() {
19924 self.unfold_recursive(&Default::default(), window, cx)
19925 } else {
19926 self.fold_recursive(&Default::default(), window, cx)
19927 }
19928 }
19929
19930 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19931 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19932 let mut to_fold = Vec::new();
19933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19934 let selections = self.selections.all_adjusted(&display_map);
19935
19936 for selection in selections {
19937 let range = selection.range().sorted();
19938 let buffer_start_row = range.start.row;
19939
19940 if range.start.row != range.end.row {
19941 let mut found = false;
19942 let mut row = range.start.row;
19943 while row <= range.end.row {
19944 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19945 {
19946 found = true;
19947 row = crease.range().end.row + 1;
19948 to_fold.push(crease);
19949 } else {
19950 row += 1
19951 }
19952 }
19953 if found {
19954 continue;
19955 }
19956 }
19957
19958 for row in (0..=range.start.row).rev() {
19959 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19960 && crease.range().end.row >= buffer_start_row
19961 {
19962 to_fold.push(crease);
19963 if row <= range.start.row {
19964 break;
19965 }
19966 }
19967 }
19968 }
19969
19970 self.fold_creases(to_fold, true, window, cx);
19971 } else {
19972 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19973 let buffer_ids = self
19974 .selections
19975 .disjoint_anchor_ranges()
19976 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19977 .collect::<HashSet<_>>();
19978 for buffer_id in buffer_ids {
19979 self.fold_buffer(buffer_id, cx);
19980 }
19981 }
19982 }
19983
19984 pub fn toggle_fold_all(
19985 &mut self,
19986 _: &actions::ToggleFoldAll,
19987 window: &mut Window,
19988 cx: &mut Context<Self>,
19989 ) {
19990 let has_folds = if self.buffer.read(cx).is_singleton() {
19991 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19992 let has_folds = display_map
19993 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19994 .next()
19995 .is_some();
19996 has_folds
19997 } else {
19998 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19999 let has_folds = buffer_ids
20000 .iter()
20001 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20002 has_folds
20003 };
20004
20005 if has_folds {
20006 self.unfold_all(&actions::UnfoldAll, window, cx);
20007 } else {
20008 self.fold_all(&actions::FoldAll, window, cx);
20009 }
20010 }
20011
20012 fn fold_at_level(
20013 &mut self,
20014 fold_at: &FoldAtLevel,
20015 window: &mut Window,
20016 cx: &mut Context<Self>,
20017 ) {
20018 if !self.buffer.read(cx).is_singleton() {
20019 return;
20020 }
20021
20022 let fold_at_level = fold_at.0;
20023 let snapshot = self.buffer.read(cx).snapshot(cx);
20024 let mut to_fold = Vec::new();
20025 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20026
20027 let row_ranges_to_keep: Vec<Range<u32>> = self
20028 .selections
20029 .all::<Point>(&self.display_snapshot(cx))
20030 .into_iter()
20031 .map(|sel| sel.start.row..sel.end.row)
20032 .collect();
20033
20034 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20035 while start_row < end_row {
20036 match self
20037 .snapshot(window, cx)
20038 .crease_for_buffer_row(MultiBufferRow(start_row))
20039 {
20040 Some(crease) => {
20041 let nested_start_row = crease.range().start.row + 1;
20042 let nested_end_row = crease.range().end.row;
20043
20044 if current_level < fold_at_level {
20045 stack.push((nested_start_row, nested_end_row, current_level + 1));
20046 } else if current_level == fold_at_level {
20047 // Fold iff there is no selection completely contained within the fold region
20048 if !row_ranges_to_keep.iter().any(|selection| {
20049 selection.end >= nested_start_row
20050 && selection.start <= nested_end_row
20051 }) {
20052 to_fold.push(crease);
20053 }
20054 }
20055
20056 start_row = nested_end_row + 1;
20057 }
20058 None => start_row += 1,
20059 }
20060 }
20061 }
20062
20063 self.fold_creases(to_fold, true, window, cx);
20064 }
20065
20066 pub fn fold_at_level_1(
20067 &mut self,
20068 _: &actions::FoldAtLevel1,
20069 window: &mut Window,
20070 cx: &mut Context<Self>,
20071 ) {
20072 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20073 }
20074
20075 pub fn fold_at_level_2(
20076 &mut self,
20077 _: &actions::FoldAtLevel2,
20078 window: &mut Window,
20079 cx: &mut Context<Self>,
20080 ) {
20081 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20082 }
20083
20084 pub fn fold_at_level_3(
20085 &mut self,
20086 _: &actions::FoldAtLevel3,
20087 window: &mut Window,
20088 cx: &mut Context<Self>,
20089 ) {
20090 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20091 }
20092
20093 pub fn fold_at_level_4(
20094 &mut self,
20095 _: &actions::FoldAtLevel4,
20096 window: &mut Window,
20097 cx: &mut Context<Self>,
20098 ) {
20099 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20100 }
20101
20102 pub fn fold_at_level_5(
20103 &mut self,
20104 _: &actions::FoldAtLevel5,
20105 window: &mut Window,
20106 cx: &mut Context<Self>,
20107 ) {
20108 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20109 }
20110
20111 pub fn fold_at_level_6(
20112 &mut self,
20113 _: &actions::FoldAtLevel6,
20114 window: &mut Window,
20115 cx: &mut Context<Self>,
20116 ) {
20117 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20118 }
20119
20120 pub fn fold_at_level_7(
20121 &mut self,
20122 _: &actions::FoldAtLevel7,
20123 window: &mut Window,
20124 cx: &mut Context<Self>,
20125 ) {
20126 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20127 }
20128
20129 pub fn fold_at_level_8(
20130 &mut self,
20131 _: &actions::FoldAtLevel8,
20132 window: &mut Window,
20133 cx: &mut Context<Self>,
20134 ) {
20135 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20136 }
20137
20138 pub fn fold_at_level_9(
20139 &mut self,
20140 _: &actions::FoldAtLevel9,
20141 window: &mut Window,
20142 cx: &mut Context<Self>,
20143 ) {
20144 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20145 }
20146
20147 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20148 if self.buffer.read(cx).is_singleton() {
20149 let mut fold_ranges = Vec::new();
20150 let snapshot = self.buffer.read(cx).snapshot(cx);
20151
20152 for row in 0..snapshot.max_row().0 {
20153 if let Some(foldable_range) = self
20154 .snapshot(window, cx)
20155 .crease_for_buffer_row(MultiBufferRow(row))
20156 {
20157 fold_ranges.push(foldable_range);
20158 }
20159 }
20160
20161 self.fold_creases(fold_ranges, true, window, cx);
20162 } else {
20163 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20164 editor
20165 .update_in(cx, |editor, _, cx| {
20166 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20167 editor.fold_buffer(buffer_id, cx);
20168 }
20169 })
20170 .ok();
20171 });
20172 }
20173 }
20174
20175 pub fn fold_function_bodies(
20176 &mut self,
20177 _: &actions::FoldFunctionBodies,
20178 window: &mut Window,
20179 cx: &mut Context<Self>,
20180 ) {
20181 let snapshot = self.buffer.read(cx).snapshot(cx);
20182
20183 let ranges = snapshot
20184 .text_object_ranges(
20185 MultiBufferOffset(0)..snapshot.len(),
20186 TreeSitterOptions::default(),
20187 )
20188 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20189 .collect::<Vec<_>>();
20190
20191 let creases = ranges
20192 .into_iter()
20193 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20194 .collect();
20195
20196 self.fold_creases(creases, true, window, cx);
20197 }
20198
20199 pub fn fold_recursive(
20200 &mut self,
20201 _: &actions::FoldRecursive,
20202 window: &mut Window,
20203 cx: &mut Context<Self>,
20204 ) {
20205 let mut to_fold = Vec::new();
20206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20207 let selections = self.selections.all_adjusted(&display_map);
20208
20209 for selection in selections {
20210 let range = selection.range().sorted();
20211 let buffer_start_row = range.start.row;
20212
20213 if range.start.row != range.end.row {
20214 let mut found = false;
20215 for row in range.start.row..=range.end.row {
20216 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20217 found = true;
20218 to_fold.push(crease);
20219 }
20220 }
20221 if found {
20222 continue;
20223 }
20224 }
20225
20226 for row in (0..=range.start.row).rev() {
20227 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20228 if crease.range().end.row >= buffer_start_row {
20229 to_fold.push(crease);
20230 } else {
20231 break;
20232 }
20233 }
20234 }
20235 }
20236
20237 self.fold_creases(to_fold, true, window, cx);
20238 }
20239
20240 pub fn fold_at(
20241 &mut self,
20242 buffer_row: MultiBufferRow,
20243 window: &mut Window,
20244 cx: &mut Context<Self>,
20245 ) {
20246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20247
20248 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20249 let autoscroll = self
20250 .selections
20251 .all::<Point>(&display_map)
20252 .iter()
20253 .any(|selection| crease.range().overlaps(&selection.range()));
20254
20255 self.fold_creases(vec![crease], autoscroll, window, cx);
20256 }
20257 }
20258
20259 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20260 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20262 let buffer = display_map.buffer_snapshot();
20263 let selections = self.selections.all::<Point>(&display_map);
20264 let ranges = selections
20265 .iter()
20266 .map(|s| {
20267 let range = s.display_range(&display_map).sorted();
20268 let mut start = range.start.to_point(&display_map);
20269 let mut end = range.end.to_point(&display_map);
20270 start.column = 0;
20271 end.column = buffer.line_len(MultiBufferRow(end.row));
20272 start..end
20273 })
20274 .collect::<Vec<_>>();
20275
20276 self.unfold_ranges(&ranges, true, true, cx);
20277 } else {
20278 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20279 let buffer_ids = self
20280 .selections
20281 .disjoint_anchor_ranges()
20282 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20283 .collect::<HashSet<_>>();
20284 for buffer_id in buffer_ids {
20285 self.unfold_buffer(buffer_id, cx);
20286 }
20287 }
20288 }
20289
20290 pub fn unfold_recursive(
20291 &mut self,
20292 _: &UnfoldRecursive,
20293 _window: &mut Window,
20294 cx: &mut Context<Self>,
20295 ) {
20296 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20297 let selections = self.selections.all::<Point>(&display_map);
20298 let ranges = selections
20299 .iter()
20300 .map(|s| {
20301 let mut range = s.display_range(&display_map).sorted();
20302 *range.start.column_mut() = 0;
20303 *range.end.column_mut() = display_map.line_len(range.end.row());
20304 let start = range.start.to_point(&display_map);
20305 let end = range.end.to_point(&display_map);
20306 start..end
20307 })
20308 .collect::<Vec<_>>();
20309
20310 self.unfold_ranges(&ranges, true, true, cx);
20311 }
20312
20313 pub fn unfold_at(
20314 &mut self,
20315 buffer_row: MultiBufferRow,
20316 _window: &mut Window,
20317 cx: &mut Context<Self>,
20318 ) {
20319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20320
20321 let intersection_range = Point::new(buffer_row.0, 0)
20322 ..Point::new(
20323 buffer_row.0,
20324 display_map.buffer_snapshot().line_len(buffer_row),
20325 );
20326
20327 let autoscroll = self
20328 .selections
20329 .all::<Point>(&display_map)
20330 .iter()
20331 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20332
20333 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20334 }
20335
20336 pub fn unfold_all(
20337 &mut self,
20338 _: &actions::UnfoldAll,
20339 _window: &mut Window,
20340 cx: &mut Context<Self>,
20341 ) {
20342 if self.buffer.read(cx).is_singleton() {
20343 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20344 self.unfold_ranges(
20345 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20346 true,
20347 true,
20348 cx,
20349 );
20350 } else {
20351 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20352 editor
20353 .update(cx, |editor, cx| {
20354 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20355 editor.unfold_buffer(buffer_id, cx);
20356 }
20357 })
20358 .ok();
20359 });
20360 }
20361 }
20362
20363 pub fn fold_selected_ranges(
20364 &mut self,
20365 _: &FoldSelectedRanges,
20366 window: &mut Window,
20367 cx: &mut Context<Self>,
20368 ) {
20369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20370 let selections = self.selections.all_adjusted(&display_map);
20371 let ranges = selections
20372 .into_iter()
20373 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20374 .collect::<Vec<_>>();
20375 self.fold_creases(ranges, true, window, cx);
20376 }
20377
20378 pub fn fold_ranges<T: ToOffset + Clone>(
20379 &mut self,
20380 ranges: Vec<Range<T>>,
20381 auto_scroll: bool,
20382 window: &mut Window,
20383 cx: &mut Context<Self>,
20384 ) {
20385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20386 let ranges = ranges
20387 .into_iter()
20388 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20389 .collect::<Vec<_>>();
20390 self.fold_creases(ranges, auto_scroll, window, cx);
20391 }
20392
20393 pub fn fold_creases<T: ToOffset + Clone>(
20394 &mut self,
20395 creases: Vec<Crease<T>>,
20396 auto_scroll: bool,
20397 _window: &mut Window,
20398 cx: &mut Context<Self>,
20399 ) {
20400 if creases.is_empty() {
20401 return;
20402 }
20403
20404 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20405
20406 if auto_scroll {
20407 self.request_autoscroll(Autoscroll::fit(), cx);
20408 }
20409
20410 cx.notify();
20411
20412 self.scrollbar_marker_state.dirty = true;
20413 self.folds_did_change(cx);
20414 }
20415
20416 /// Removes any folds whose ranges intersect any of the given ranges.
20417 pub fn unfold_ranges<T: ToOffset + Clone>(
20418 &mut self,
20419 ranges: &[Range<T>],
20420 inclusive: bool,
20421 auto_scroll: bool,
20422 cx: &mut Context<Self>,
20423 ) {
20424 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20425 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20426 });
20427 self.folds_did_change(cx);
20428 }
20429
20430 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20431 self.fold_buffers([buffer_id], cx);
20432 }
20433
20434 pub fn fold_buffers(
20435 &mut self,
20436 buffer_ids: impl IntoIterator<Item = BufferId>,
20437 cx: &mut Context<Self>,
20438 ) {
20439 if self.buffer().read(cx).is_singleton() {
20440 return;
20441 }
20442
20443 let ids_to_fold: Vec<BufferId> = buffer_ids
20444 .into_iter()
20445 .filter(|id| !self.is_buffer_folded(*id, cx))
20446 .collect();
20447
20448 if ids_to_fold.is_empty() {
20449 return;
20450 }
20451
20452 let mut all_folded_excerpt_ids = Vec::new();
20453 for buffer_id in &ids_to_fold {
20454 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20455 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _)| id));
20456 }
20457
20458 self.display_map.update(cx, |display_map, cx| {
20459 display_map.fold_buffers(ids_to_fold.clone(), cx)
20460 });
20461
20462 let snapshot = self.display_snapshot(cx);
20463 self.selections.change_with(&snapshot, |selections| {
20464 for buffer_id in ids_to_fold {
20465 selections.remove_selections_from_buffer(buffer_id);
20466 }
20467 });
20468
20469 cx.emit(EditorEvent::BufferFoldToggled {
20470 ids: all_folded_excerpt_ids,
20471 folded: true,
20472 });
20473 cx.notify();
20474 }
20475
20476 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20477 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20478 return;
20479 }
20480 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20481 self.display_map.update(cx, |display_map, cx| {
20482 display_map.unfold_buffers([buffer_id], cx);
20483 });
20484 cx.emit(EditorEvent::BufferFoldToggled {
20485 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20486 folded: false,
20487 });
20488 cx.notify();
20489 }
20490
20491 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20492 self.display_map.read(cx).is_buffer_folded(buffer)
20493 }
20494
20495 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20496 if self.buffer().read(cx).is_singleton() {
20497 return false;
20498 }
20499 !self.folded_buffers(cx).is_empty()
20500 }
20501
20502 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20503 self.display_map.read(cx).folded_buffers()
20504 }
20505
20506 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20507 self.display_map.update(cx, |display_map, cx| {
20508 display_map.disable_header_for_buffer(buffer_id, cx);
20509 });
20510 cx.notify();
20511 }
20512
20513 /// Removes any folds with the given ranges.
20514 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20515 &mut self,
20516 ranges: &[Range<T>],
20517 type_id: TypeId,
20518 auto_scroll: bool,
20519 cx: &mut Context<Self>,
20520 ) {
20521 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20522 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20523 });
20524 self.folds_did_change(cx);
20525 }
20526
20527 fn remove_folds_with<T: ToOffset + Clone>(
20528 &mut self,
20529 ranges: &[Range<T>],
20530 auto_scroll: bool,
20531 cx: &mut Context<Self>,
20532 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20533 ) {
20534 if ranges.is_empty() {
20535 return;
20536 }
20537
20538 let mut buffers_affected = HashSet::default();
20539 let multi_buffer = self.buffer().read(cx);
20540 for range in ranges {
20541 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20542 buffers_affected.insert(buffer.read(cx).remote_id());
20543 };
20544 }
20545
20546 self.display_map.update(cx, update);
20547
20548 if auto_scroll {
20549 self.request_autoscroll(Autoscroll::fit(), cx);
20550 }
20551
20552 cx.notify();
20553 self.scrollbar_marker_state.dirty = true;
20554 self.active_indent_guides_state.dirty = true;
20555 }
20556
20557 pub fn update_renderer_widths(
20558 &mut self,
20559 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20560 cx: &mut Context<Self>,
20561 ) -> bool {
20562 self.display_map
20563 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20564 }
20565
20566 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20567 self.display_map.read(cx).fold_placeholder.clone()
20568 }
20569
20570 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20571 self.buffer.update(cx, |buffer, cx| {
20572 buffer.set_all_diff_hunks_expanded(cx);
20573 });
20574 }
20575
20576 pub fn expand_all_diff_hunks(
20577 &mut self,
20578 _: &ExpandAllDiffHunks,
20579 _window: &mut Window,
20580 cx: &mut Context<Self>,
20581 ) {
20582 self.buffer.update(cx, |buffer, cx| {
20583 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20584 });
20585 }
20586
20587 pub fn collapse_all_diff_hunks(
20588 &mut self,
20589 _: &CollapseAllDiffHunks,
20590 _window: &mut Window,
20591 cx: &mut Context<Self>,
20592 ) {
20593 self.buffer.update(cx, |buffer, cx| {
20594 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20595 });
20596 }
20597
20598 pub fn toggle_selected_diff_hunks(
20599 &mut self,
20600 _: &ToggleSelectedDiffHunks,
20601 _window: &mut Window,
20602 cx: &mut Context<Self>,
20603 ) {
20604 let ranges: Vec<_> = self
20605 .selections
20606 .disjoint_anchors()
20607 .iter()
20608 .map(|s| s.range())
20609 .collect();
20610 self.toggle_diff_hunks_in_ranges(ranges, cx);
20611 }
20612
20613 pub fn diff_hunks_in_ranges<'a>(
20614 &'a self,
20615 ranges: &'a [Range<Anchor>],
20616 buffer: &'a MultiBufferSnapshot,
20617 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20618 ranges.iter().flat_map(move |range| {
20619 let end_excerpt_id = range.end.excerpt_id;
20620 let range = range.to_point(buffer);
20621 let mut peek_end = range.end;
20622 if range.end.row < buffer.max_row().0 {
20623 peek_end = Point::new(range.end.row + 1, 0);
20624 }
20625 buffer
20626 .diff_hunks_in_range(range.start..peek_end)
20627 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20628 })
20629 }
20630
20631 pub fn has_stageable_diff_hunks_in_ranges(
20632 &self,
20633 ranges: &[Range<Anchor>],
20634 snapshot: &MultiBufferSnapshot,
20635 ) -> bool {
20636 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20637 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20638 }
20639
20640 pub fn toggle_staged_selected_diff_hunks(
20641 &mut self,
20642 _: &::git::ToggleStaged,
20643 _: &mut Window,
20644 cx: &mut Context<Self>,
20645 ) {
20646 let snapshot = self.buffer.read(cx).snapshot(cx);
20647 let ranges: Vec<_> = self
20648 .selections
20649 .disjoint_anchors()
20650 .iter()
20651 .map(|s| s.range())
20652 .collect();
20653 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20654 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20655 }
20656
20657 pub fn set_render_diff_hunk_controls(
20658 &mut self,
20659 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20660 cx: &mut Context<Self>,
20661 ) {
20662 self.render_diff_hunk_controls = render_diff_hunk_controls;
20663 cx.notify();
20664 }
20665
20666 pub fn stage_and_next(
20667 &mut self,
20668 _: &::git::StageAndNext,
20669 window: &mut Window,
20670 cx: &mut Context<Self>,
20671 ) {
20672 self.do_stage_or_unstage_and_next(true, window, cx);
20673 }
20674
20675 pub fn unstage_and_next(
20676 &mut self,
20677 _: &::git::UnstageAndNext,
20678 window: &mut Window,
20679 cx: &mut Context<Self>,
20680 ) {
20681 self.do_stage_or_unstage_and_next(false, window, cx);
20682 }
20683
20684 pub fn stage_or_unstage_diff_hunks(
20685 &mut self,
20686 stage: bool,
20687 ranges: Vec<Range<Anchor>>,
20688 cx: &mut Context<Self>,
20689 ) {
20690 if self.delegate_stage_and_restore {
20691 let snapshot = self.buffer.read(cx).snapshot(cx);
20692 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20693 if !hunks.is_empty() {
20694 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20695 }
20696 return;
20697 }
20698 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20699 cx.spawn(async move |this, cx| {
20700 task.await?;
20701 this.update(cx, |this, cx| {
20702 let snapshot = this.buffer.read(cx).snapshot(cx);
20703 let chunk_by = this
20704 .diff_hunks_in_ranges(&ranges, &snapshot)
20705 .chunk_by(|hunk| hunk.buffer_id);
20706 for (buffer_id, hunks) in &chunk_by {
20707 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20708 }
20709 })
20710 })
20711 .detach_and_log_err(cx);
20712 }
20713
20714 fn save_buffers_for_ranges_if_needed(
20715 &mut self,
20716 ranges: &[Range<Anchor>],
20717 cx: &mut Context<Editor>,
20718 ) -> Task<Result<()>> {
20719 let multibuffer = self.buffer.read(cx);
20720 let snapshot = multibuffer.read(cx);
20721 let buffer_ids: HashSet<_> = ranges
20722 .iter()
20723 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20724 .collect();
20725 drop(snapshot);
20726
20727 let mut buffers = HashSet::default();
20728 for buffer_id in buffer_ids {
20729 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20730 let buffer = buffer_entity.read(cx);
20731 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20732 {
20733 buffers.insert(buffer_entity);
20734 }
20735 }
20736 }
20737
20738 if let Some(project) = &self.project {
20739 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20740 } else {
20741 Task::ready(Ok(()))
20742 }
20743 }
20744
20745 fn do_stage_or_unstage_and_next(
20746 &mut self,
20747 stage: bool,
20748 window: &mut Window,
20749 cx: &mut Context<Self>,
20750 ) {
20751 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20752
20753 if ranges.iter().any(|range| range.start != range.end) {
20754 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20755 return;
20756 }
20757
20758 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20759 let snapshot = self.snapshot(window, cx);
20760 let position = self
20761 .selections
20762 .newest::<Point>(&snapshot.display_snapshot)
20763 .head();
20764 let mut row = snapshot
20765 .buffer_snapshot()
20766 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20767 .find(|hunk| hunk.row_range.start.0 > position.row)
20768 .map(|hunk| hunk.row_range.start);
20769
20770 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20771 // Outside of the project diff editor, wrap around to the beginning.
20772 if !all_diff_hunks_expanded {
20773 row = row.or_else(|| {
20774 snapshot
20775 .buffer_snapshot()
20776 .diff_hunks_in_range(Point::zero()..position)
20777 .find(|hunk| hunk.row_range.end.0 < position.row)
20778 .map(|hunk| hunk.row_range.start)
20779 });
20780 }
20781
20782 if let Some(row) = row {
20783 let destination = Point::new(row.0, 0);
20784 let autoscroll = Autoscroll::center();
20785
20786 self.unfold_ranges(&[destination..destination], false, false, cx);
20787 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20788 s.select_ranges([destination..destination]);
20789 });
20790 }
20791 }
20792
20793 pub(crate) fn do_stage_or_unstage(
20794 &self,
20795 stage: bool,
20796 buffer_id: BufferId,
20797 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20798 cx: &mut App,
20799 ) -> Option<()> {
20800 let project = self.project()?;
20801 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20802 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20803 let buffer_snapshot = buffer.read(cx).snapshot();
20804 let file_exists = buffer_snapshot
20805 .file()
20806 .is_some_and(|file| file.disk_state().exists());
20807 diff.update(cx, |diff, cx| {
20808 diff.stage_or_unstage_hunks(
20809 stage,
20810 &hunks
20811 .map(|hunk| buffer_diff::DiffHunk {
20812 buffer_range: hunk.buffer_range,
20813 // We don't need to pass in word diffs here because they're only used for rendering and
20814 // this function changes internal state
20815 base_word_diffs: Vec::default(),
20816 buffer_word_diffs: Vec::default(),
20817 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20818 ..hunk.diff_base_byte_range.end.0,
20819 secondary_status: hunk.status.secondary,
20820 range: Point::zero()..Point::zero(), // unused
20821 })
20822 .collect::<Vec<_>>(),
20823 &buffer_snapshot,
20824 file_exists,
20825 cx,
20826 )
20827 });
20828 None
20829 }
20830
20831 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20832 let ranges: Vec<_> = self
20833 .selections
20834 .disjoint_anchors()
20835 .iter()
20836 .map(|s| s.range())
20837 .collect();
20838 self.buffer
20839 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20840 }
20841
20842 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20843 self.buffer.update(cx, |buffer, cx| {
20844 let ranges = vec![Anchor::min()..Anchor::max()];
20845 if !buffer.all_diff_hunks_expanded()
20846 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20847 {
20848 buffer.collapse_diff_hunks(ranges, cx);
20849 true
20850 } else {
20851 false
20852 }
20853 })
20854 }
20855
20856 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20857 if self.buffer.read(cx).all_diff_hunks_expanded() {
20858 return true;
20859 }
20860 let ranges = vec![Anchor::min()..Anchor::max()];
20861 self.buffer
20862 .read(cx)
20863 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20864 }
20865
20866 fn toggle_diff_hunks_in_ranges(
20867 &mut self,
20868 ranges: Vec<Range<Anchor>>,
20869 cx: &mut Context<Editor>,
20870 ) {
20871 self.buffer.update(cx, |buffer, cx| {
20872 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20873 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20874 })
20875 }
20876
20877 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20878 self.buffer.update(cx, |buffer, cx| {
20879 buffer.toggle_single_diff_hunk(range, cx);
20880 })
20881 }
20882
20883 pub(crate) fn apply_all_diff_hunks(
20884 &mut self,
20885 _: &ApplyAllDiffHunks,
20886 window: &mut Window,
20887 cx: &mut Context<Self>,
20888 ) {
20889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20890
20891 let buffers = self.buffer.read(cx).all_buffers();
20892 for branch_buffer in buffers {
20893 branch_buffer.update(cx, |branch_buffer, cx| {
20894 branch_buffer.merge_into_base(Vec::new(), cx);
20895 });
20896 }
20897
20898 if let Some(project) = self.project.clone() {
20899 self.save(
20900 SaveOptions {
20901 format: true,
20902 autosave: false,
20903 },
20904 project,
20905 window,
20906 cx,
20907 )
20908 .detach_and_log_err(cx);
20909 }
20910 }
20911
20912 pub(crate) fn apply_selected_diff_hunks(
20913 &mut self,
20914 _: &ApplyDiffHunk,
20915 window: &mut Window,
20916 cx: &mut Context<Self>,
20917 ) {
20918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20919 let snapshot = self.snapshot(window, cx);
20920 let hunks = snapshot.hunks_for_ranges(
20921 self.selections
20922 .all(&snapshot.display_snapshot)
20923 .into_iter()
20924 .map(|selection| selection.range()),
20925 );
20926 let mut ranges_by_buffer = HashMap::default();
20927 self.transact(window, cx, |editor, _window, cx| {
20928 for hunk in hunks {
20929 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20930 ranges_by_buffer
20931 .entry(buffer.clone())
20932 .or_insert_with(Vec::new)
20933 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20934 }
20935 }
20936
20937 for (buffer, ranges) in ranges_by_buffer {
20938 buffer.update(cx, |buffer, cx| {
20939 buffer.merge_into_base(ranges, cx);
20940 });
20941 }
20942 });
20943
20944 if let Some(project) = self.project.clone() {
20945 self.save(
20946 SaveOptions {
20947 format: true,
20948 autosave: false,
20949 },
20950 project,
20951 window,
20952 cx,
20953 )
20954 .detach_and_log_err(cx);
20955 }
20956 }
20957
20958 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20959 if hovered != self.gutter_hovered {
20960 self.gutter_hovered = hovered;
20961 cx.notify();
20962 }
20963 }
20964
20965 pub fn insert_blocks(
20966 &mut self,
20967 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20968 autoscroll: Option<Autoscroll>,
20969 cx: &mut Context<Self>,
20970 ) -> Vec<CustomBlockId> {
20971 let blocks = self
20972 .display_map
20973 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20974 if let Some(autoscroll) = autoscroll {
20975 self.request_autoscroll(autoscroll, cx);
20976 }
20977 cx.notify();
20978 blocks
20979 }
20980
20981 pub fn resize_blocks(
20982 &mut self,
20983 heights: HashMap<CustomBlockId, u32>,
20984 autoscroll: Option<Autoscroll>,
20985 cx: &mut Context<Self>,
20986 ) {
20987 self.display_map
20988 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20989 if let Some(autoscroll) = autoscroll {
20990 self.request_autoscroll(autoscroll, cx);
20991 }
20992 cx.notify();
20993 }
20994
20995 pub fn replace_blocks(
20996 &mut self,
20997 renderers: HashMap<CustomBlockId, RenderBlock>,
20998 autoscroll: Option<Autoscroll>,
20999 cx: &mut Context<Self>,
21000 ) {
21001 self.display_map
21002 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21003 if let Some(autoscroll) = autoscroll {
21004 self.request_autoscroll(autoscroll, cx);
21005 }
21006 cx.notify();
21007 }
21008
21009 pub fn remove_blocks(
21010 &mut self,
21011 block_ids: HashSet<CustomBlockId>,
21012 autoscroll: Option<Autoscroll>,
21013 cx: &mut Context<Self>,
21014 ) {
21015 self.display_map.update(cx, |display_map, cx| {
21016 display_map.remove_blocks(block_ids, cx)
21017 });
21018 if let Some(autoscroll) = autoscroll {
21019 self.request_autoscroll(autoscroll, cx);
21020 }
21021 cx.notify();
21022 }
21023
21024 pub fn row_for_block(
21025 &self,
21026 block_id: CustomBlockId,
21027 cx: &mut Context<Self>,
21028 ) -> Option<DisplayRow> {
21029 self.display_map
21030 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21031 }
21032
21033 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21034 self.focused_block = Some(focused_block);
21035 }
21036
21037 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21038 self.focused_block.take()
21039 }
21040
21041 pub fn insert_creases(
21042 &mut self,
21043 creases: impl IntoIterator<Item = Crease<Anchor>>,
21044 cx: &mut Context<Self>,
21045 ) -> Vec<CreaseId> {
21046 self.display_map
21047 .update(cx, |map, cx| map.insert_creases(creases, cx))
21048 }
21049
21050 pub fn remove_creases(
21051 &mut self,
21052 ids: impl IntoIterator<Item = CreaseId>,
21053 cx: &mut Context<Self>,
21054 ) -> Vec<(CreaseId, Range<Anchor>)> {
21055 self.display_map
21056 .update(cx, |map, cx| map.remove_creases(ids, cx))
21057 }
21058
21059 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21060 self.display_map
21061 .update(cx, |map, cx| map.snapshot(cx))
21062 .longest_row()
21063 }
21064
21065 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21066 self.display_map
21067 .update(cx, |map, cx| map.snapshot(cx))
21068 .max_point()
21069 }
21070
21071 pub fn text(&self, cx: &App) -> String {
21072 self.buffer.read(cx).read(cx).text()
21073 }
21074
21075 pub fn is_empty(&self, cx: &App) -> bool {
21076 self.buffer.read(cx).read(cx).is_empty()
21077 }
21078
21079 pub fn text_option(&self, cx: &App) -> Option<String> {
21080 let text = self.text(cx);
21081 let text = text.trim();
21082
21083 if text.is_empty() {
21084 return None;
21085 }
21086
21087 Some(text.to_string())
21088 }
21089
21090 pub fn set_text(
21091 &mut self,
21092 text: impl Into<Arc<str>>,
21093 window: &mut Window,
21094 cx: &mut Context<Self>,
21095 ) {
21096 self.transact(window, cx, |this, _, cx| {
21097 this.buffer
21098 .read(cx)
21099 .as_singleton()
21100 .expect("you can only call set_text on editors for singleton buffers")
21101 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21102 });
21103 }
21104
21105 pub fn display_text(&self, cx: &mut App) -> String {
21106 self.display_map
21107 .update(cx, |map, cx| map.snapshot(cx))
21108 .text()
21109 }
21110
21111 fn create_minimap(
21112 &self,
21113 minimap_settings: MinimapSettings,
21114 window: &mut Window,
21115 cx: &mut Context<Self>,
21116 ) -> Option<Entity<Self>> {
21117 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21118 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21119 }
21120
21121 fn initialize_new_minimap(
21122 &self,
21123 minimap_settings: MinimapSettings,
21124 window: &mut Window,
21125 cx: &mut Context<Self>,
21126 ) -> Entity<Self> {
21127 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21128 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21129
21130 let mut minimap = Editor::new_internal(
21131 EditorMode::Minimap {
21132 parent: cx.weak_entity(),
21133 },
21134 self.buffer.clone(),
21135 None,
21136 Some(self.display_map.clone()),
21137 window,
21138 cx,
21139 );
21140 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21141 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21142 minimap.scroll_manager.clone_state(
21143 &self.scroll_manager,
21144 &my_snapshot,
21145 &minimap_snapshot,
21146 cx,
21147 );
21148 minimap.set_text_style_refinement(TextStyleRefinement {
21149 font_size: Some(MINIMAP_FONT_SIZE),
21150 font_weight: Some(MINIMAP_FONT_WEIGHT),
21151 font_family: Some(MINIMAP_FONT_FAMILY),
21152 ..Default::default()
21153 });
21154 minimap.update_minimap_configuration(minimap_settings, cx);
21155 cx.new(|_| minimap)
21156 }
21157
21158 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21159 let current_line_highlight = minimap_settings
21160 .current_line_highlight
21161 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21162 self.set_current_line_highlight(Some(current_line_highlight));
21163 }
21164
21165 pub fn minimap(&self) -> Option<&Entity<Self>> {
21166 self.minimap
21167 .as_ref()
21168 .filter(|_| self.minimap_visibility.visible())
21169 }
21170
21171 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21172 let mut wrap_guides = smallvec![];
21173
21174 if self.show_wrap_guides == Some(false) {
21175 return wrap_guides;
21176 }
21177
21178 let settings = self.buffer.read(cx).language_settings(cx);
21179 if settings.show_wrap_guides {
21180 match self.soft_wrap_mode(cx) {
21181 SoftWrap::Column(soft_wrap) => {
21182 wrap_guides.push((soft_wrap as usize, true));
21183 }
21184 SoftWrap::Bounded(soft_wrap) => {
21185 wrap_guides.push((soft_wrap as usize, true));
21186 }
21187 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21188 }
21189 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21190 }
21191
21192 wrap_guides
21193 }
21194
21195 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21196 let settings = self.buffer.read(cx).language_settings(cx);
21197 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21198 match mode {
21199 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21200 SoftWrap::None
21201 }
21202 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21203 language_settings::SoftWrap::PreferredLineLength => {
21204 SoftWrap::Column(settings.preferred_line_length)
21205 }
21206 language_settings::SoftWrap::Bounded => {
21207 SoftWrap::Bounded(settings.preferred_line_length)
21208 }
21209 }
21210 }
21211
21212 pub fn set_soft_wrap_mode(
21213 &mut self,
21214 mode: language_settings::SoftWrap,
21215 cx: &mut Context<Self>,
21216 ) {
21217 self.soft_wrap_mode_override = Some(mode);
21218 cx.notify();
21219 }
21220
21221 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21222 self.hard_wrap = hard_wrap;
21223 cx.notify();
21224 }
21225
21226 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21227 self.text_style_refinement = Some(style);
21228 }
21229
21230 /// called by the Element so we know what style we were most recently rendered with.
21231 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21232 // We intentionally do not inform the display map about the minimap style
21233 // so that wrapping is not recalculated and stays consistent for the editor
21234 // and its linked minimap.
21235 if !self.mode.is_minimap() {
21236 let font = style.text.font();
21237 let font_size = style.text.font_size.to_pixels(window.rem_size());
21238 let display_map = self
21239 .placeholder_display_map
21240 .as_ref()
21241 .filter(|_| self.is_empty(cx))
21242 .unwrap_or(&self.display_map);
21243
21244 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21245 }
21246 self.style = Some(style);
21247 }
21248
21249 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21250 if self.style.is_none() {
21251 self.style = Some(self.create_style(cx));
21252 }
21253 self.style.as_ref().unwrap()
21254 }
21255
21256 // Called by the element. This method is not designed to be called outside of the editor
21257 // element's layout code because it does not notify when rewrapping is computed synchronously.
21258 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21259 if self.is_empty(cx) {
21260 self.placeholder_display_map
21261 .as_ref()
21262 .map_or(false, |display_map| {
21263 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21264 })
21265 } else {
21266 self.display_map
21267 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21268 }
21269 }
21270
21271 pub fn set_soft_wrap(&mut self) {
21272 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21273 }
21274
21275 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21276 if self.soft_wrap_mode_override.is_some() {
21277 self.soft_wrap_mode_override.take();
21278 } else {
21279 let soft_wrap = match self.soft_wrap_mode(cx) {
21280 SoftWrap::GitDiff => return,
21281 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21282 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21283 language_settings::SoftWrap::None
21284 }
21285 };
21286 self.soft_wrap_mode_override = Some(soft_wrap);
21287 }
21288 cx.notify();
21289 }
21290
21291 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21292 let Some(workspace) = self.workspace() else {
21293 return;
21294 };
21295 let fs = workspace.read(cx).app_state().fs.clone();
21296 let current_show = TabBarSettings::get_global(cx).show;
21297 update_settings_file(fs, cx, move |setting, _| {
21298 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21299 });
21300 }
21301
21302 pub fn toggle_indent_guides(
21303 &mut self,
21304 _: &ToggleIndentGuides,
21305 _: &mut Window,
21306 cx: &mut Context<Self>,
21307 ) {
21308 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21309 self.buffer
21310 .read(cx)
21311 .language_settings(cx)
21312 .indent_guides
21313 .enabled
21314 });
21315 self.show_indent_guides = Some(!currently_enabled);
21316 cx.notify();
21317 }
21318
21319 fn should_show_indent_guides(&self) -> Option<bool> {
21320 self.show_indent_guides
21321 }
21322
21323 pub fn disable_indent_guides_for_buffer(
21324 &mut self,
21325 buffer_id: BufferId,
21326 cx: &mut Context<Self>,
21327 ) {
21328 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21329 cx.notify();
21330 }
21331
21332 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21333 self.buffers_with_disabled_indent_guides
21334 .contains(&buffer_id)
21335 }
21336
21337 pub fn toggle_line_numbers(
21338 &mut self,
21339 _: &ToggleLineNumbers,
21340 _: &mut Window,
21341 cx: &mut Context<Self>,
21342 ) {
21343 let mut editor_settings = EditorSettings::get_global(cx).clone();
21344 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21345 EditorSettings::override_global(editor_settings, cx);
21346 }
21347
21348 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21349 if let Some(show_line_numbers) = self.show_line_numbers {
21350 return show_line_numbers;
21351 }
21352 EditorSettings::get_global(cx).gutter.line_numbers
21353 }
21354
21355 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21356 match (
21357 self.use_relative_line_numbers,
21358 EditorSettings::get_global(cx).relative_line_numbers,
21359 ) {
21360 (None, setting) => setting,
21361 (Some(false), _) => RelativeLineNumbers::Disabled,
21362 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21363 (Some(true), _) => RelativeLineNumbers::Enabled,
21364 }
21365 }
21366
21367 pub fn toggle_relative_line_numbers(
21368 &mut self,
21369 _: &ToggleRelativeLineNumbers,
21370 _: &mut Window,
21371 cx: &mut Context<Self>,
21372 ) {
21373 let is_relative = self.relative_line_numbers(cx);
21374 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21375 }
21376
21377 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21378 self.use_relative_line_numbers = is_relative;
21379 cx.notify();
21380 }
21381
21382 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21383 self.show_gutter = show_gutter;
21384 cx.notify();
21385 }
21386
21387 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21388 self.show_scrollbars = ScrollbarAxes {
21389 horizontal: show,
21390 vertical: show,
21391 };
21392 cx.notify();
21393 }
21394
21395 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21396 self.show_scrollbars.vertical = show;
21397 cx.notify();
21398 }
21399
21400 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21401 self.show_scrollbars.horizontal = show;
21402 cx.notify();
21403 }
21404
21405 pub fn set_minimap_visibility(
21406 &mut self,
21407 minimap_visibility: MinimapVisibility,
21408 window: &mut Window,
21409 cx: &mut Context<Self>,
21410 ) {
21411 if self.minimap_visibility != minimap_visibility {
21412 if minimap_visibility.visible() && self.minimap.is_none() {
21413 let minimap_settings = EditorSettings::get_global(cx).minimap;
21414 self.minimap =
21415 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21416 }
21417 self.minimap_visibility = minimap_visibility;
21418 cx.notify();
21419 }
21420 }
21421
21422 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21423 self.set_show_scrollbars(false, cx);
21424 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21425 }
21426
21427 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21428 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21429 }
21430
21431 /// Normally the text in full mode and auto height editors is padded on the
21432 /// left side by roughly half a character width for improved hit testing.
21433 ///
21434 /// Use this method to disable this for cases where this is not wanted (e.g.
21435 /// if you want to align the editor text with some other text above or below)
21436 /// or if you want to add this padding to single-line editors.
21437 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21438 self.offset_content = offset_content;
21439 cx.notify();
21440 }
21441
21442 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21443 self.show_line_numbers = Some(show_line_numbers);
21444 cx.notify();
21445 }
21446
21447 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21448 self.disable_expand_excerpt_buttons = true;
21449 cx.notify();
21450 }
21451
21452 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21453 self.number_deleted_lines = number;
21454 cx.notify();
21455 }
21456
21457 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21458 self.delegate_expand_excerpts = delegate;
21459 }
21460
21461 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21462 self.delegate_stage_and_restore = delegate;
21463 }
21464
21465 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21466 self.delegate_open_excerpts = delegate;
21467 }
21468
21469 pub fn set_on_local_selections_changed(
21470 &mut self,
21471 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21472 ) {
21473 self.on_local_selections_changed = callback;
21474 }
21475
21476 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21477 self.suppress_selection_callback = suppress;
21478 }
21479
21480 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21481 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21482 cx.notify();
21483 }
21484
21485 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21486 self.show_code_actions = Some(show_code_actions);
21487 cx.notify();
21488 }
21489
21490 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21491 self.show_runnables = Some(show_runnables);
21492 cx.notify();
21493 }
21494
21495 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21496 self.show_breakpoints = Some(show_breakpoints);
21497 cx.notify();
21498 }
21499
21500 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21501 self.show_diff_review_button = show;
21502 cx.notify();
21503 }
21504
21505 pub fn show_diff_review_button(&self) -> bool {
21506 self.show_diff_review_button
21507 }
21508
21509 pub fn render_diff_review_button(
21510 &self,
21511 display_row: DisplayRow,
21512 width: Pixels,
21513 cx: &mut Context<Self>,
21514 ) -> impl IntoElement {
21515 let text_color = cx.theme().colors().text;
21516 let icon_color = cx.theme().colors().icon_accent;
21517
21518 h_flex()
21519 .id("diff_review_button")
21520 .cursor_pointer()
21521 .w(width - px(1.))
21522 .h(relative(0.9))
21523 .justify_center()
21524 .rounded_sm()
21525 .border_1()
21526 .border_color(text_color.opacity(0.1))
21527 .bg(text_color.opacity(0.15))
21528 .hover(|s| {
21529 s.bg(icon_color.opacity(0.4))
21530 .border_color(icon_color.opacity(0.5))
21531 })
21532 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21533 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21534 .on_mouse_down(
21535 gpui::MouseButton::Left,
21536 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21537 editor.start_diff_review_drag(display_row, window, cx);
21538 }),
21539 )
21540 }
21541
21542 pub fn start_diff_review_drag(
21543 &mut self,
21544 display_row: DisplayRow,
21545 window: &mut Window,
21546 cx: &mut Context<Self>,
21547 ) {
21548 let snapshot = self.snapshot(window, cx);
21549 let point = snapshot
21550 .display_snapshot
21551 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21552 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21553 self.diff_review_drag_state = Some(DiffReviewDragState {
21554 start_anchor: anchor,
21555 current_anchor: anchor,
21556 });
21557 cx.notify();
21558 }
21559
21560 pub fn update_diff_review_drag(
21561 &mut self,
21562 display_row: DisplayRow,
21563 window: &mut Window,
21564 cx: &mut Context<Self>,
21565 ) {
21566 if self.diff_review_drag_state.is_none() {
21567 return;
21568 }
21569 let snapshot = self.snapshot(window, cx);
21570 let point = snapshot
21571 .display_snapshot
21572 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21573 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21574 if let Some(drag_state) = &mut self.diff_review_drag_state {
21575 drag_state.current_anchor = anchor;
21576 cx.notify();
21577 }
21578 }
21579
21580 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21581 if let Some(drag_state) = self.diff_review_drag_state.take() {
21582 let snapshot = self.snapshot(window, cx);
21583 let range = drag_state.row_range(&snapshot.display_snapshot);
21584 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21585 }
21586 cx.notify();
21587 }
21588
21589 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21590 self.diff_review_drag_state = None;
21591 cx.notify();
21592 }
21593
21594 /// Calculates the appropriate block height for the diff review overlay.
21595 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21596 /// and 2 lines per comment when expanded.
21597 fn calculate_overlay_height(
21598 &self,
21599 hunk_key: &DiffHunkKey,
21600 comments_expanded: bool,
21601 snapshot: &MultiBufferSnapshot,
21602 ) -> u32 {
21603 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21604 let base_height: u32 = 2; // Input row with avatar and buttons
21605
21606 if comment_count == 0 {
21607 base_height
21608 } else if comments_expanded {
21609 // Header (1 line) + 2 lines per comment
21610 base_height + 1 + (comment_count as u32 * 2)
21611 } else {
21612 // Just header when collapsed
21613 base_height + 1
21614 }
21615 }
21616
21617 pub fn show_diff_review_overlay(
21618 &mut self,
21619 display_range: Range<DisplayRow>,
21620 window: &mut Window,
21621 cx: &mut Context<Self>,
21622 ) {
21623 let Range { start, end } = display_range.sorted();
21624
21625 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21626 let editor_snapshot = self.snapshot(window, cx);
21627
21628 // Convert display rows to multibuffer points
21629 let start_point = editor_snapshot
21630 .display_snapshot
21631 .display_point_to_point(start.as_display_point(), Bias::Left);
21632 let end_point = editor_snapshot
21633 .display_snapshot
21634 .display_point_to_point(end.as_display_point(), Bias::Left);
21635 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21636
21637 // Create anchor range for the selected lines (start of first line to end of last line)
21638 let line_end = Point::new(
21639 end_point.row,
21640 buffer_snapshot.line_len(end_multi_buffer_row),
21641 );
21642 let anchor_range =
21643 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21644
21645 // Compute the hunk key for this display row
21646 let file_path = buffer_snapshot
21647 .file_at(start_point)
21648 .map(|file: &Arc<dyn language::File>| file.path().clone())
21649 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21650 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21651 let new_hunk_key = DiffHunkKey {
21652 file_path,
21653 hunk_start_anchor,
21654 };
21655
21656 // Check if we already have an overlay for this hunk
21657 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21658 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21659 }) {
21660 // Just focus the existing overlay's prompt editor
21661 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21662 window.focus(&focus_handle, cx);
21663 return;
21664 }
21665
21666 // Dismiss overlays that have no comments for their hunks
21667 self.dismiss_overlays_without_comments(cx);
21668
21669 // Get the current user's avatar URI from the project's user_store
21670 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21671 let user_store = project.read(cx).user_store();
21672 user_store
21673 .read(cx)
21674 .current_user()
21675 .map(|user| user.avatar_uri.clone())
21676 });
21677
21678 // Create anchor at the end of the last row so the block appears immediately below it
21679 // Use multibuffer coordinates for anchor creation
21680 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21681 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21682
21683 // Use the hunk key we already computed
21684 let hunk_key = new_hunk_key;
21685
21686 // Create the prompt editor for the review input
21687 let prompt_editor = cx.new(|cx| {
21688 let mut editor = Editor::single_line(window, cx);
21689 editor.set_placeholder_text("Add a review comment...", window, cx);
21690 editor
21691 });
21692
21693 // Register the Newline action on the prompt editor to submit the review
21694 let parent_editor = cx.entity().downgrade();
21695 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21696 prompt_editor.register_action({
21697 let parent_editor = parent_editor.clone();
21698 move |_: &crate::actions::Newline, window, cx| {
21699 if let Some(editor) = parent_editor.upgrade() {
21700 editor.update(cx, |editor, cx| {
21701 editor.submit_diff_review_comment(window, cx);
21702 });
21703 }
21704 }
21705 })
21706 });
21707
21708 // Calculate initial height based on existing comments for this hunk
21709 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21710
21711 // Create the overlay block
21712 let prompt_editor_for_render = prompt_editor.clone();
21713 let hunk_key_for_render = hunk_key.clone();
21714 let editor_handle = cx.entity().downgrade();
21715 let block = BlockProperties {
21716 style: BlockStyle::Sticky,
21717 placement: BlockPlacement::Below(anchor),
21718 height: Some(initial_height),
21719 render: Arc::new(move |cx| {
21720 Self::render_diff_review_overlay(
21721 &prompt_editor_for_render,
21722 &hunk_key_for_render,
21723 &editor_handle,
21724 cx,
21725 )
21726 }),
21727 priority: 0,
21728 };
21729
21730 let block_ids = self.insert_blocks([block], None, cx);
21731 let Some(block_id) = block_ids.into_iter().next() else {
21732 log::error!("Failed to insert diff review overlay block");
21733 return;
21734 };
21735
21736 self.diff_review_overlays.push(DiffReviewOverlay {
21737 anchor_range,
21738 block_id,
21739 prompt_editor: prompt_editor.clone(),
21740 hunk_key,
21741 comments_expanded: true,
21742 inline_edit_editors: HashMap::default(),
21743 inline_edit_subscriptions: HashMap::default(),
21744 user_avatar_uri,
21745 _subscription: subscription,
21746 });
21747
21748 // Focus the prompt editor
21749 let focus_handle = prompt_editor.focus_handle(cx);
21750 window.focus(&focus_handle, cx);
21751
21752 cx.notify();
21753 }
21754
21755 /// Dismisses all diff review overlays.
21756 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21757 if self.diff_review_overlays.is_empty() {
21758 return;
21759 }
21760 let block_ids: HashSet<_> = self
21761 .diff_review_overlays
21762 .drain(..)
21763 .map(|overlay| overlay.block_id)
21764 .collect();
21765 self.remove_blocks(block_ids, None, cx);
21766 cx.notify();
21767 }
21768
21769 /// Dismisses overlays that have no comments stored for their hunks.
21770 /// Keeps overlays that have at least one comment.
21771 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21772 let snapshot = self.buffer.read(cx).snapshot(cx);
21773
21774 // First, compute which overlays have comments (to avoid borrow issues with retain)
21775 let overlays_with_comments: Vec<bool> = self
21776 .diff_review_overlays
21777 .iter()
21778 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21779 .collect();
21780
21781 // Now collect block IDs to remove and retain overlays
21782 let mut block_ids_to_remove = HashSet::default();
21783 let mut index = 0;
21784 self.diff_review_overlays.retain(|overlay| {
21785 let has_comments = overlays_with_comments[index];
21786 index += 1;
21787 if !has_comments {
21788 block_ids_to_remove.insert(overlay.block_id);
21789 }
21790 has_comments
21791 });
21792
21793 if !block_ids_to_remove.is_empty() {
21794 self.remove_blocks(block_ids_to_remove, None, cx);
21795 cx.notify();
21796 }
21797 }
21798
21799 /// Refreshes the diff review overlay block to update its height and render function.
21800 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21801 fn refresh_diff_review_overlay_height(
21802 &mut self,
21803 hunk_key: &DiffHunkKey,
21804 _window: &mut Window,
21805 cx: &mut Context<Self>,
21806 ) {
21807 // Extract all needed data from overlay first to avoid borrow conflicts
21808 let snapshot = self.buffer.read(cx).snapshot(cx);
21809 let (comments_expanded, block_id, prompt_editor) = {
21810 let Some(overlay) = self
21811 .diff_review_overlays
21812 .iter()
21813 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21814 else {
21815 return;
21816 };
21817
21818 (
21819 overlay.comments_expanded,
21820 overlay.block_id,
21821 overlay.prompt_editor.clone(),
21822 )
21823 };
21824
21825 // Calculate new height
21826 let snapshot = self.buffer.read(cx).snapshot(cx);
21827 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21828
21829 // Update the block height using resize_blocks (avoids flicker)
21830 let mut heights = HashMap::default();
21831 heights.insert(block_id, new_height);
21832 self.resize_blocks(heights, None, cx);
21833
21834 // Update the render function using replace_blocks (avoids flicker)
21835 let hunk_key_for_render = hunk_key.clone();
21836 let editor_handle = cx.entity().downgrade();
21837 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21838 Arc::new(move |cx| {
21839 Self::render_diff_review_overlay(
21840 &prompt_editor,
21841 &hunk_key_for_render,
21842 &editor_handle,
21843 cx,
21844 )
21845 });
21846
21847 let mut renderers = HashMap::default();
21848 renderers.insert(block_id, render);
21849 self.replace_blocks(renderers, None, cx);
21850 }
21851
21852 /// Action handler for SubmitDiffReviewComment.
21853 pub fn submit_diff_review_comment_action(
21854 &mut self,
21855 _: &SubmitDiffReviewComment,
21856 window: &mut Window,
21857 cx: &mut Context<Self>,
21858 ) {
21859 self.submit_diff_review_comment(window, cx);
21860 }
21861
21862 /// Stores the diff review comment locally.
21863 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21864 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21865 // Find the overlay that currently has focus
21866 let overlay_index = self
21867 .diff_review_overlays
21868 .iter()
21869 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21870 let Some(overlay_index) = overlay_index else {
21871 return;
21872 };
21873 let overlay = &self.diff_review_overlays[overlay_index];
21874
21875 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21876 if comment_text.is_empty() {
21877 return;
21878 }
21879
21880 let anchor_range = overlay.anchor_range.clone();
21881 let hunk_key = overlay.hunk_key.clone();
21882
21883 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21884
21885 // Clear the prompt editor but keep the overlay open
21886 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21887 overlay.prompt_editor.update(cx, |editor, cx| {
21888 editor.clear(window, cx);
21889 });
21890 }
21891
21892 // Refresh the overlay to update the block height for the new comment
21893 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21894
21895 cx.notify();
21896 }
21897
21898 /// Returns the prompt editor for the diff review overlay, if one is active.
21899 /// This is primarily used for testing.
21900 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21901 self.diff_review_overlays
21902 .first()
21903 .map(|overlay| &overlay.prompt_editor)
21904 }
21905
21906 /// Returns the line range for the first diff review overlay, if one is active.
21907 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21908 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21909 let overlay = self.diff_review_overlays.first()?;
21910 let snapshot = self.buffer.read(cx).snapshot(cx);
21911 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21912 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21913 let start_row = snapshot
21914 .point_to_buffer_point(start_point)
21915 .map(|(_, p, _)| p.row)
21916 .unwrap_or(start_point.row);
21917 let end_row = snapshot
21918 .point_to_buffer_point(end_point)
21919 .map(|(_, p, _)| p.row)
21920 .unwrap_or(end_point.row);
21921 Some((start_row, end_row))
21922 }
21923
21924 /// Sets whether the comments section is expanded in the diff review overlay.
21925 /// This is primarily used for testing.
21926 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21927 for overlay in &mut self.diff_review_overlays {
21928 overlay.comments_expanded = expanded;
21929 }
21930 cx.notify();
21931 }
21932
21933 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21934 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21935 a.file_path == b.file_path
21936 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21937 }
21938
21939 /// Returns comments for a specific hunk, ordered by creation time.
21940 pub fn comments_for_hunk<'a>(
21941 &'a self,
21942 key: &DiffHunkKey,
21943 snapshot: &MultiBufferSnapshot,
21944 ) -> &'a [StoredReviewComment] {
21945 let key_point = key.hunk_start_anchor.to_point(snapshot);
21946 self.stored_review_comments
21947 .iter()
21948 .find(|(k, _)| {
21949 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21950 })
21951 .map(|(_, comments)| comments.as_slice())
21952 .unwrap_or(&[])
21953 }
21954
21955 /// Returns the total count of stored review comments across all hunks.
21956 pub fn total_review_comment_count(&self) -> usize {
21957 self.stored_review_comments
21958 .iter()
21959 .map(|(_, v)| v.len())
21960 .sum()
21961 }
21962
21963 /// Returns the count of comments for a specific hunk.
21964 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21965 let key_point = key.hunk_start_anchor.to_point(snapshot);
21966 self.stored_review_comments
21967 .iter()
21968 .find(|(k, _)| {
21969 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21970 })
21971 .map(|(_, v)| v.len())
21972 .unwrap_or(0)
21973 }
21974
21975 /// Adds a new review comment to a specific hunk.
21976 pub fn add_review_comment(
21977 &mut self,
21978 hunk_key: DiffHunkKey,
21979 comment: String,
21980 anchor_range: Range<Anchor>,
21981 cx: &mut Context<Self>,
21982 ) -> usize {
21983 let id = self.next_review_comment_id;
21984 self.next_review_comment_id += 1;
21985
21986 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21987
21988 let snapshot = self.buffer.read(cx).snapshot(cx);
21989 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21990
21991 // Find existing entry for this hunk or add a new one
21992 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21993 k.file_path == hunk_key.file_path
21994 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21995 }) {
21996 comments.push(stored_comment);
21997 } else {
21998 self.stored_review_comments
21999 .push((hunk_key, vec![stored_comment]));
22000 }
22001
22002 cx.emit(EditorEvent::ReviewCommentsChanged {
22003 total_count: self.total_review_comment_count(),
22004 });
22005 cx.notify();
22006 id
22007 }
22008
22009 /// Removes a review comment by ID from any hunk.
22010 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22011 for (_, comments) in self.stored_review_comments.iter_mut() {
22012 if let Some(index) = comments.iter().position(|c| c.id == id) {
22013 comments.remove(index);
22014 cx.emit(EditorEvent::ReviewCommentsChanged {
22015 total_count: self.total_review_comment_count(),
22016 });
22017 cx.notify();
22018 return true;
22019 }
22020 }
22021 false
22022 }
22023
22024 /// Updates a review comment's text by ID.
22025 pub fn update_review_comment(
22026 &mut self,
22027 id: usize,
22028 new_comment: String,
22029 cx: &mut Context<Self>,
22030 ) -> bool {
22031 for (_, comments) in self.stored_review_comments.iter_mut() {
22032 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22033 comment.comment = new_comment;
22034 comment.is_editing = false;
22035 cx.emit(EditorEvent::ReviewCommentsChanged {
22036 total_count: self.total_review_comment_count(),
22037 });
22038 cx.notify();
22039 return true;
22040 }
22041 }
22042 false
22043 }
22044
22045 /// Sets a comment's editing state.
22046 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22047 for (_, comments) in self.stored_review_comments.iter_mut() {
22048 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22049 comment.is_editing = is_editing;
22050 cx.notify();
22051 return;
22052 }
22053 }
22054 }
22055
22056 /// Takes all stored comments from all hunks, clearing the storage.
22057 /// Returns a Vec of (hunk_key, comments) pairs.
22058 pub fn take_all_review_comments(
22059 &mut self,
22060 cx: &mut Context<Self>,
22061 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22062 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22063 self.dismiss_all_diff_review_overlays(cx);
22064 let comments = std::mem::take(&mut self.stored_review_comments);
22065 // Reset the ID counter since all comments have been taken
22066 self.next_review_comment_id = 0;
22067 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22068 cx.notify();
22069 comments
22070 }
22071
22072 /// Removes review comments whose anchors are no longer valid or whose
22073 /// associated diff hunks no longer exist.
22074 ///
22075 /// This should be called when the buffer changes to prevent orphaned comments
22076 /// from accumulating.
22077 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22078 let snapshot = self.buffer.read(cx).snapshot(cx);
22079 let original_count = self.total_review_comment_count();
22080
22081 // Remove comments with invalid hunk anchors
22082 self.stored_review_comments
22083 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22084
22085 // Also clean up individual comments with invalid anchor ranges
22086 for (_, comments) in &mut self.stored_review_comments {
22087 comments.retain(|comment| {
22088 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22089 });
22090 }
22091
22092 // Remove empty hunk entries
22093 self.stored_review_comments
22094 .retain(|(_, comments)| !comments.is_empty());
22095
22096 let new_count = self.total_review_comment_count();
22097 if new_count != original_count {
22098 cx.emit(EditorEvent::ReviewCommentsChanged {
22099 total_count: new_count,
22100 });
22101 cx.notify();
22102 }
22103 }
22104
22105 /// Toggles the expanded state of the comments section in the overlay.
22106 pub fn toggle_review_comments_expanded(
22107 &mut self,
22108 _: &ToggleReviewCommentsExpanded,
22109 window: &mut Window,
22110 cx: &mut Context<Self>,
22111 ) {
22112 // Find the overlay that currently has focus, or use the first one
22113 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22114 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22115 overlay.comments_expanded = !overlay.comments_expanded;
22116 Some(overlay.hunk_key.clone())
22117 } else {
22118 None
22119 }
22120 });
22121
22122 // If no focused overlay found, toggle the first one
22123 let hunk_key = overlay_info.or_else(|| {
22124 self.diff_review_overlays.first_mut().map(|overlay| {
22125 overlay.comments_expanded = !overlay.comments_expanded;
22126 overlay.hunk_key.clone()
22127 })
22128 });
22129
22130 if let Some(hunk_key) = hunk_key {
22131 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22132 cx.notify();
22133 }
22134 }
22135
22136 /// Handles the EditReviewComment action - sets a comment into editing mode.
22137 pub fn edit_review_comment(
22138 &mut self,
22139 action: &EditReviewComment,
22140 window: &mut Window,
22141 cx: &mut Context<Self>,
22142 ) {
22143 let comment_id = action.id;
22144
22145 // Set the comment to editing mode
22146 self.set_comment_editing(comment_id, true, cx);
22147
22148 // Find the overlay that contains this comment and create an inline editor if needed
22149 // First, find which hunk this comment belongs to
22150 let hunk_key = self
22151 .stored_review_comments
22152 .iter()
22153 .find_map(|(key, comments)| {
22154 if comments.iter().any(|c| c.id == comment_id) {
22155 Some(key.clone())
22156 } else {
22157 None
22158 }
22159 });
22160
22161 let snapshot = self.buffer.read(cx).snapshot(cx);
22162 if let Some(hunk_key) = hunk_key {
22163 if let Some(overlay) = self
22164 .diff_review_overlays
22165 .iter_mut()
22166 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22167 {
22168 if let std::collections::hash_map::Entry::Vacant(entry) =
22169 overlay.inline_edit_editors.entry(comment_id)
22170 {
22171 // Find the comment text
22172 let comment_text = self
22173 .stored_review_comments
22174 .iter()
22175 .flat_map(|(_, comments)| comments)
22176 .find(|c| c.id == comment_id)
22177 .map(|c| c.comment.clone())
22178 .unwrap_or_default();
22179
22180 // Create inline editor
22181 let parent_editor = cx.entity().downgrade();
22182 let inline_editor = cx.new(|cx| {
22183 let mut editor = Editor::single_line(window, cx);
22184 editor.set_text(&*comment_text, window, cx);
22185 // Select all text for easy replacement
22186 editor.select_all(&crate::actions::SelectAll, window, cx);
22187 editor
22188 });
22189
22190 // Register the Newline action to confirm the edit
22191 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22192 inline_editor.register_action({
22193 let parent_editor = parent_editor.clone();
22194 move |_: &crate::actions::Newline, window, cx| {
22195 if let Some(editor) = parent_editor.upgrade() {
22196 editor.update(cx, |editor, cx| {
22197 editor.confirm_edit_review_comment(comment_id, window, cx);
22198 });
22199 }
22200 }
22201 })
22202 });
22203
22204 // Store the subscription to keep the action handler alive
22205 overlay
22206 .inline_edit_subscriptions
22207 .insert(comment_id, subscription);
22208
22209 // Focus the inline editor
22210 let focus_handle = inline_editor.focus_handle(cx);
22211 window.focus(&focus_handle, cx);
22212
22213 entry.insert(inline_editor);
22214 }
22215 }
22216 }
22217
22218 cx.notify();
22219 }
22220
22221 /// Confirms an inline edit of a review comment.
22222 pub fn confirm_edit_review_comment(
22223 &mut self,
22224 comment_id: usize,
22225 _window: &mut Window,
22226 cx: &mut Context<Self>,
22227 ) {
22228 // Get the new text from the inline editor
22229 // Find the overlay containing this comment's inline editor
22230 let snapshot = self.buffer.read(cx).snapshot(cx);
22231 let hunk_key = self
22232 .stored_review_comments
22233 .iter()
22234 .find_map(|(key, comments)| {
22235 if comments.iter().any(|c| c.id == comment_id) {
22236 Some(key.clone())
22237 } else {
22238 None
22239 }
22240 });
22241
22242 let new_text = hunk_key
22243 .as_ref()
22244 .and_then(|hunk_key| {
22245 self.diff_review_overlays
22246 .iter()
22247 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22248 })
22249 .as_ref()
22250 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22251 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22252
22253 if let Some(new_text) = new_text {
22254 if !new_text.is_empty() {
22255 self.update_review_comment(comment_id, new_text, cx);
22256 }
22257 }
22258
22259 // Remove the inline editor and its subscription
22260 if let Some(hunk_key) = hunk_key {
22261 if let Some(overlay) = self
22262 .diff_review_overlays
22263 .iter_mut()
22264 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22265 {
22266 overlay.inline_edit_editors.remove(&comment_id);
22267 overlay.inline_edit_subscriptions.remove(&comment_id);
22268 }
22269 }
22270
22271 // Clear editing state
22272 self.set_comment_editing(comment_id, false, cx);
22273 }
22274
22275 /// Cancels an inline edit of a review comment.
22276 pub fn cancel_edit_review_comment(
22277 &mut self,
22278 comment_id: usize,
22279 _window: &mut Window,
22280 cx: &mut Context<Self>,
22281 ) {
22282 // Find which hunk this comment belongs to
22283 let hunk_key = self
22284 .stored_review_comments
22285 .iter()
22286 .find_map(|(key, comments)| {
22287 if comments.iter().any(|c| c.id == comment_id) {
22288 Some(key.clone())
22289 } else {
22290 None
22291 }
22292 });
22293
22294 // Remove the inline editor and its subscription
22295 if let Some(hunk_key) = hunk_key {
22296 let snapshot = self.buffer.read(cx).snapshot(cx);
22297 if let Some(overlay) = self
22298 .diff_review_overlays
22299 .iter_mut()
22300 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22301 {
22302 overlay.inline_edit_editors.remove(&comment_id);
22303 overlay.inline_edit_subscriptions.remove(&comment_id);
22304 }
22305 }
22306
22307 // Clear editing state
22308 self.set_comment_editing(comment_id, false, cx);
22309 }
22310
22311 /// Action handler for ConfirmEditReviewComment.
22312 pub fn confirm_edit_review_comment_action(
22313 &mut self,
22314 action: &ConfirmEditReviewComment,
22315 window: &mut Window,
22316 cx: &mut Context<Self>,
22317 ) {
22318 self.confirm_edit_review_comment(action.id, window, cx);
22319 }
22320
22321 /// Action handler for CancelEditReviewComment.
22322 pub fn cancel_edit_review_comment_action(
22323 &mut self,
22324 action: &CancelEditReviewComment,
22325 window: &mut Window,
22326 cx: &mut Context<Self>,
22327 ) {
22328 self.cancel_edit_review_comment(action.id, window, cx);
22329 }
22330
22331 /// Handles the DeleteReviewComment action - removes a comment.
22332 pub fn delete_review_comment(
22333 &mut self,
22334 action: &DeleteReviewComment,
22335 window: &mut Window,
22336 cx: &mut Context<Self>,
22337 ) {
22338 // Get the hunk key before removing the comment
22339 // Find the hunk key from the comment itself
22340 let comment_id = action.id;
22341 let hunk_key = self
22342 .stored_review_comments
22343 .iter()
22344 .find_map(|(key, comments)| {
22345 if comments.iter().any(|c| c.id == comment_id) {
22346 Some(key.clone())
22347 } else {
22348 None
22349 }
22350 });
22351
22352 // Also get it from the overlay for refresh purposes
22353 let overlay_hunk_key = self
22354 .diff_review_overlays
22355 .first()
22356 .map(|o| o.hunk_key.clone());
22357
22358 self.remove_review_comment(action.id, cx);
22359
22360 // Refresh the overlay height after removing a comment
22361 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22362 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22363 }
22364 }
22365
22366 fn render_diff_review_overlay(
22367 prompt_editor: &Entity<Editor>,
22368 hunk_key: &DiffHunkKey,
22369 editor_handle: &WeakEntity<Editor>,
22370 cx: &mut BlockContext,
22371 ) -> AnyElement {
22372 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22373 if ranges.is_empty() {
22374 return None;
22375 }
22376 let formatted: Vec<String> = ranges
22377 .iter()
22378 .map(|(start, end)| {
22379 let start_line = start + 1;
22380 let end_line = end + 1;
22381 if start_line == end_line {
22382 format!("Line {start_line}")
22383 } else {
22384 format!("Lines {start_line}-{end_line}")
22385 }
22386 })
22387 .collect();
22388 // Don't show label for single line in single excerpt
22389 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22390 return None;
22391 }
22392 Some(formatted.join(" ⋯ "))
22393 }
22394
22395 let theme = cx.theme();
22396 let colors = theme.colors();
22397
22398 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22399 editor_handle
22400 .upgrade()
22401 .map(|editor| {
22402 let editor = editor.read(cx);
22403 let snapshot = editor.buffer().read(cx).snapshot(cx);
22404 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22405 let (expanded, editors, avatar_uri, line_ranges) = editor
22406 .diff_review_overlays
22407 .iter()
22408 .find(|overlay| {
22409 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22410 })
22411 .map(|o| {
22412 let start_point = o.anchor_range.start.to_point(&snapshot);
22413 let end_point = o.anchor_range.end.to_point(&snapshot);
22414 // Get line ranges per excerpt to detect discontinuities
22415 let buffer_ranges =
22416 snapshot.range_to_buffer_ranges(start_point..end_point);
22417 let ranges: Vec<(u32, u32)> = buffer_ranges
22418 .iter()
22419 .map(|(buffer, range, _)| {
22420 let start = buffer.offset_to_point(range.start.0).row;
22421 let end = buffer.offset_to_point(range.end.0).row;
22422 (start, end)
22423 })
22424 .collect();
22425 (
22426 o.comments_expanded,
22427 o.inline_edit_editors.clone(),
22428 o.user_avatar_uri.clone(),
22429 if ranges.is_empty() {
22430 None
22431 } else {
22432 Some(ranges)
22433 },
22434 )
22435 })
22436 .unwrap_or((true, HashMap::default(), None, None));
22437 (comments, expanded, editors, avatar_uri, line_ranges)
22438 })
22439 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22440
22441 let comment_count = comments.len();
22442 let avatar_size = px(20.);
22443 let action_icon_size = IconSize::XSmall;
22444
22445 v_flex()
22446 .w_full()
22447 .bg(colors.editor_background)
22448 .border_b_1()
22449 .border_color(colors.border)
22450 .px_2()
22451 .pb_2()
22452 .gap_2()
22453 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22454 .when_some(line_ranges, |el, ranges| {
22455 let label = format_line_ranges(&ranges);
22456 if let Some(label) = label {
22457 el.child(
22458 h_flex()
22459 .w_full()
22460 .px_2()
22461 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22462 )
22463 } else {
22464 el
22465 }
22466 })
22467 // Top row: editable input with user's avatar
22468 .child(
22469 h_flex()
22470 .w_full()
22471 .items_center()
22472 .gap_2()
22473 .px_2()
22474 .py_1p5()
22475 .rounded_md()
22476 .bg(colors.surface_background)
22477 .child(
22478 div()
22479 .size(avatar_size)
22480 .flex_shrink_0()
22481 .rounded_full()
22482 .overflow_hidden()
22483 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22484 Avatar::new(avatar_uri.clone())
22485 .size(avatar_size)
22486 .into_any_element()
22487 } else {
22488 Icon::new(IconName::Person)
22489 .size(IconSize::Small)
22490 .color(ui::Color::Muted)
22491 .into_any_element()
22492 }),
22493 )
22494 .child(
22495 div()
22496 .flex_1()
22497 .border_1()
22498 .border_color(colors.border)
22499 .rounded_md()
22500 .bg(colors.editor_background)
22501 .px_2()
22502 .py_1()
22503 .child(prompt_editor.clone()),
22504 )
22505 .child(
22506 h_flex()
22507 .flex_shrink_0()
22508 .gap_1()
22509 .child(
22510 IconButton::new("diff-review-close", IconName::Close)
22511 .icon_color(ui::Color::Muted)
22512 .icon_size(action_icon_size)
22513 .tooltip(Tooltip::text("Close"))
22514 .on_click(|_, window, cx| {
22515 window
22516 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22517 }),
22518 )
22519 .child(
22520 IconButton::new("diff-review-add", IconName::Return)
22521 .icon_color(ui::Color::Muted)
22522 .icon_size(action_icon_size)
22523 .tooltip(Tooltip::text("Add comment"))
22524 .on_click(|_, window, cx| {
22525 window.dispatch_action(
22526 Box::new(crate::actions::SubmitDiffReviewComment),
22527 cx,
22528 );
22529 }),
22530 ),
22531 ),
22532 )
22533 // Expandable comments section (only shown when there are comments)
22534 .when(comment_count > 0, |el| {
22535 el.child(Self::render_comments_section(
22536 comments,
22537 comments_expanded,
22538 inline_editors,
22539 user_avatar_uri,
22540 avatar_size,
22541 action_icon_size,
22542 colors,
22543 ))
22544 })
22545 .into_any_element()
22546 }
22547
22548 fn render_comments_section(
22549 comments: Vec<StoredReviewComment>,
22550 expanded: bool,
22551 inline_editors: HashMap<usize, Entity<Editor>>,
22552 user_avatar_uri: Option<SharedUri>,
22553 avatar_size: Pixels,
22554 action_icon_size: IconSize,
22555 colors: &theme::ThemeColors,
22556 ) -> impl IntoElement {
22557 let comment_count = comments.len();
22558
22559 v_flex()
22560 .w_full()
22561 .gap_1()
22562 // Header with expand/collapse toggle
22563 .child(
22564 h_flex()
22565 .id("review-comments-header")
22566 .w_full()
22567 .items_center()
22568 .gap_1()
22569 .px_2()
22570 .py_1()
22571 .cursor_pointer()
22572 .rounded_md()
22573 .hover(|style| style.bg(colors.ghost_element_hover))
22574 .on_click(|_, window: &mut Window, cx| {
22575 window.dispatch_action(
22576 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22577 cx,
22578 );
22579 })
22580 .child(
22581 Icon::new(if expanded {
22582 IconName::ChevronDown
22583 } else {
22584 IconName::ChevronRight
22585 })
22586 .size(IconSize::Small)
22587 .color(ui::Color::Muted),
22588 )
22589 .child(
22590 Label::new(format!(
22591 "{} Comment{}",
22592 comment_count,
22593 if comment_count == 1 { "" } else { "s" }
22594 ))
22595 .size(LabelSize::Small)
22596 .color(Color::Muted),
22597 ),
22598 )
22599 // Comments list (when expanded)
22600 .when(expanded, |el| {
22601 el.children(comments.into_iter().map(|comment| {
22602 let inline_editor = inline_editors.get(&comment.id).cloned();
22603 Self::render_comment_row(
22604 comment,
22605 inline_editor,
22606 user_avatar_uri.clone(),
22607 avatar_size,
22608 action_icon_size,
22609 colors,
22610 )
22611 }))
22612 })
22613 }
22614
22615 fn render_comment_row(
22616 comment: StoredReviewComment,
22617 inline_editor: Option<Entity<Editor>>,
22618 user_avatar_uri: Option<SharedUri>,
22619 avatar_size: Pixels,
22620 action_icon_size: IconSize,
22621 colors: &theme::ThemeColors,
22622 ) -> impl IntoElement {
22623 let comment_id = comment.id;
22624 let is_editing = inline_editor.is_some();
22625
22626 h_flex()
22627 .w_full()
22628 .items_center()
22629 .gap_2()
22630 .px_2()
22631 .py_1p5()
22632 .rounded_md()
22633 .bg(colors.surface_background)
22634 .child(
22635 div()
22636 .size(avatar_size)
22637 .flex_shrink_0()
22638 .rounded_full()
22639 .overflow_hidden()
22640 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22641 Avatar::new(avatar_uri.clone())
22642 .size(avatar_size)
22643 .into_any_element()
22644 } else {
22645 Icon::new(IconName::Person)
22646 .size(IconSize::Small)
22647 .color(ui::Color::Muted)
22648 .into_any_element()
22649 }),
22650 )
22651 .child(if let Some(editor) = inline_editor {
22652 // Inline edit mode: show an editable text field
22653 div()
22654 .flex_1()
22655 .border_1()
22656 .border_color(colors.border)
22657 .rounded_md()
22658 .bg(colors.editor_background)
22659 .px_2()
22660 .py_1()
22661 .child(editor)
22662 .into_any_element()
22663 } else {
22664 // Display mode: show the comment text
22665 div()
22666 .flex_1()
22667 .text_sm()
22668 .text_color(colors.text)
22669 .child(comment.comment)
22670 .into_any_element()
22671 })
22672 .child(if is_editing {
22673 // Editing mode: show close and confirm buttons
22674 h_flex()
22675 .gap_1()
22676 .child(
22677 IconButton::new(
22678 format!("diff-review-cancel-edit-{comment_id}"),
22679 IconName::Close,
22680 )
22681 .icon_color(ui::Color::Muted)
22682 .icon_size(action_icon_size)
22683 .tooltip(Tooltip::text("Cancel"))
22684 .on_click(move |_, window, cx| {
22685 window.dispatch_action(
22686 Box::new(crate::actions::CancelEditReviewComment {
22687 id: comment_id,
22688 }),
22689 cx,
22690 );
22691 }),
22692 )
22693 .child(
22694 IconButton::new(
22695 format!("diff-review-confirm-edit-{comment_id}"),
22696 IconName::Return,
22697 )
22698 .icon_color(ui::Color::Muted)
22699 .icon_size(action_icon_size)
22700 .tooltip(Tooltip::text("Confirm"))
22701 .on_click(move |_, window, cx| {
22702 window.dispatch_action(
22703 Box::new(crate::actions::ConfirmEditReviewComment {
22704 id: comment_id,
22705 }),
22706 cx,
22707 );
22708 }),
22709 )
22710 .into_any_element()
22711 } else {
22712 // Display mode: no action buttons for now (edit/delete not yet implemented)
22713 gpui::Empty.into_any_element()
22714 })
22715 }
22716
22717 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22718 if self.display_map.read(cx).masked != masked {
22719 self.display_map.update(cx, |map, _| map.masked = masked);
22720 }
22721 cx.notify()
22722 }
22723
22724 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22725 self.show_wrap_guides = Some(show_wrap_guides);
22726 cx.notify();
22727 }
22728
22729 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22730 self.show_indent_guides = Some(show_indent_guides);
22731 cx.notify();
22732 }
22733
22734 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22735 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22736 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22737 && let Some(dir) = file.abs_path(cx).parent()
22738 {
22739 return Some(dir.to_owned());
22740 }
22741 }
22742
22743 None
22744 }
22745
22746 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22747 self.active_excerpt(cx)?
22748 .1
22749 .read(cx)
22750 .file()
22751 .and_then(|f| f.as_local())
22752 }
22753
22754 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22755 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22756 let buffer = buffer.read(cx);
22757 if let Some(project_path) = buffer.project_path(cx) {
22758 let project = self.project()?.read(cx);
22759 project.absolute_path(&project_path, cx)
22760 } else {
22761 buffer
22762 .file()
22763 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22764 }
22765 })
22766 }
22767
22768 pub fn reveal_in_finder(
22769 &mut self,
22770 _: &RevealInFileManager,
22771 _window: &mut Window,
22772 cx: &mut Context<Self>,
22773 ) {
22774 if let Some(path) = self.target_file_abs_path(cx) {
22775 if let Some(project) = self.project() {
22776 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22777 } else {
22778 cx.reveal_path(&path);
22779 }
22780 }
22781 }
22782
22783 pub fn copy_path(
22784 &mut self,
22785 _: &zed_actions::workspace::CopyPath,
22786 _window: &mut Window,
22787 cx: &mut Context<Self>,
22788 ) {
22789 if let Some(path) = self.target_file_abs_path(cx)
22790 && let Some(path) = path.to_str()
22791 {
22792 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22793 } else {
22794 cx.propagate();
22795 }
22796 }
22797
22798 pub fn copy_relative_path(
22799 &mut self,
22800 _: &zed_actions::workspace::CopyRelativePath,
22801 _window: &mut Window,
22802 cx: &mut Context<Self>,
22803 ) {
22804 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22805 let project = self.project()?.read(cx);
22806 let path = buffer.read(cx).file()?.path();
22807 let path = path.display(project.path_style(cx));
22808 Some(path)
22809 }) {
22810 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22811 } else {
22812 cx.propagate();
22813 }
22814 }
22815
22816 /// Returns the project path for the editor's buffer, if any buffer is
22817 /// opened in the editor.
22818 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22819 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22820 buffer.read(cx).project_path(cx)
22821 } else {
22822 None
22823 }
22824 }
22825
22826 // Returns true if the editor handled a go-to-line request
22827 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22828 maybe!({
22829 let breakpoint_store = self.breakpoint_store.as_ref()?;
22830
22831 let (active_stack_frame, debug_line_pane_id) = {
22832 let store = breakpoint_store.read(cx);
22833 let active_stack_frame = store.active_position().cloned();
22834 let debug_line_pane_id = store.active_debug_line_pane_id();
22835 (active_stack_frame, debug_line_pane_id)
22836 };
22837
22838 let Some(active_stack_frame) = active_stack_frame else {
22839 self.clear_row_highlights::<ActiveDebugLine>();
22840 return None;
22841 };
22842
22843 if let Some(debug_line_pane_id) = debug_line_pane_id {
22844 if let Some(workspace) = self
22845 .workspace
22846 .as_ref()
22847 .and_then(|(workspace, _)| workspace.upgrade())
22848 {
22849 let editor_pane_id = workspace
22850 .read(cx)
22851 .pane_for_item_id(cx.entity_id())
22852 .map(|pane| pane.entity_id());
22853
22854 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22855 self.clear_row_highlights::<ActiveDebugLine>();
22856 return None;
22857 }
22858 }
22859 }
22860
22861 let position = active_stack_frame.position;
22862 let buffer_id = position.buffer_id?;
22863 let snapshot = self
22864 .project
22865 .as_ref()?
22866 .read(cx)
22867 .buffer_for_id(buffer_id, cx)?
22868 .read(cx)
22869 .snapshot();
22870
22871 let mut handled = false;
22872 for (id, ExcerptRange { context, .. }) in
22873 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22874 {
22875 if context.start.cmp(&position, &snapshot).is_ge()
22876 || context.end.cmp(&position, &snapshot).is_lt()
22877 {
22878 continue;
22879 }
22880 let snapshot = self.buffer.read(cx).snapshot(cx);
22881 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22882
22883 handled = true;
22884 self.clear_row_highlights::<ActiveDebugLine>();
22885
22886 self.go_to_line::<ActiveDebugLine>(
22887 multibuffer_anchor,
22888 Some(cx.theme().colors().editor_debugger_active_line_background),
22889 window,
22890 cx,
22891 );
22892
22893 cx.notify();
22894 }
22895
22896 handled.then_some(())
22897 })
22898 .is_some()
22899 }
22900
22901 pub fn copy_file_name_without_extension(
22902 &mut self,
22903 _: &CopyFileNameWithoutExtension,
22904 _: &mut Window,
22905 cx: &mut Context<Self>,
22906 ) {
22907 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22908 let file = buffer.read(cx).file()?;
22909 file.path().file_stem()
22910 }) {
22911 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22912 }
22913 }
22914
22915 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22916 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22917 let file = buffer.read(cx).file()?;
22918 Some(file.file_name(cx))
22919 }) {
22920 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22921 }
22922 }
22923
22924 pub fn toggle_git_blame(
22925 &mut self,
22926 _: &::git::Blame,
22927 window: &mut Window,
22928 cx: &mut Context<Self>,
22929 ) {
22930 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22931
22932 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22933 self.start_git_blame(true, window, cx);
22934 }
22935
22936 cx.notify();
22937 }
22938
22939 pub fn toggle_git_blame_inline(
22940 &mut self,
22941 _: &ToggleGitBlameInline,
22942 window: &mut Window,
22943 cx: &mut Context<Self>,
22944 ) {
22945 self.toggle_git_blame_inline_internal(true, window, cx);
22946 cx.notify();
22947 }
22948
22949 pub fn open_git_blame_commit(
22950 &mut self,
22951 _: &OpenGitBlameCommit,
22952 window: &mut Window,
22953 cx: &mut Context<Self>,
22954 ) {
22955 self.open_git_blame_commit_internal(window, cx);
22956 }
22957
22958 fn open_git_blame_commit_internal(
22959 &mut self,
22960 window: &mut Window,
22961 cx: &mut Context<Self>,
22962 ) -> Option<()> {
22963 let blame = self.blame.as_ref()?;
22964 let snapshot = self.snapshot(window, cx);
22965 let cursor = self
22966 .selections
22967 .newest::<Point>(&snapshot.display_snapshot)
22968 .head();
22969 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22970 let (_, blame_entry) = blame
22971 .update(cx, |blame, cx| {
22972 blame
22973 .blame_for_rows(
22974 &[RowInfo {
22975 buffer_id: Some(buffer.remote_id()),
22976 buffer_row: Some(point.row),
22977 ..Default::default()
22978 }],
22979 cx,
22980 )
22981 .next()
22982 })
22983 .flatten()?;
22984 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22985 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22986 let workspace = self.workspace()?.downgrade();
22987 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22988 None
22989 }
22990
22991 pub fn git_blame_inline_enabled(&self) -> bool {
22992 self.git_blame_inline_enabled
22993 }
22994
22995 pub fn toggle_selection_menu(
22996 &mut self,
22997 _: &ToggleSelectionMenu,
22998 _: &mut Window,
22999 cx: &mut Context<Self>,
23000 ) {
23001 self.show_selection_menu = self
23002 .show_selection_menu
23003 .map(|show_selections_menu| !show_selections_menu)
23004 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23005
23006 cx.notify();
23007 }
23008
23009 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23010 self.show_selection_menu
23011 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23012 }
23013
23014 fn start_git_blame(
23015 &mut self,
23016 user_triggered: bool,
23017 window: &mut Window,
23018 cx: &mut Context<Self>,
23019 ) {
23020 if let Some(project) = self.project() {
23021 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23022 && buffer.read(cx).file().is_none()
23023 {
23024 return;
23025 }
23026
23027 let focused = self.focus_handle(cx).contains_focused(window, cx);
23028
23029 let project = project.clone();
23030 let blame = cx
23031 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23032 self.blame_subscription =
23033 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23034 self.blame = Some(blame);
23035 }
23036 }
23037
23038 fn toggle_git_blame_inline_internal(
23039 &mut self,
23040 user_triggered: bool,
23041 window: &mut Window,
23042 cx: &mut Context<Self>,
23043 ) {
23044 if self.git_blame_inline_enabled {
23045 self.git_blame_inline_enabled = false;
23046 self.show_git_blame_inline = false;
23047 self.show_git_blame_inline_delay_task.take();
23048 } else {
23049 self.git_blame_inline_enabled = true;
23050 self.start_git_blame_inline(user_triggered, window, cx);
23051 }
23052
23053 cx.notify();
23054 }
23055
23056 fn start_git_blame_inline(
23057 &mut self,
23058 user_triggered: bool,
23059 window: &mut Window,
23060 cx: &mut Context<Self>,
23061 ) {
23062 self.start_git_blame(user_triggered, window, cx);
23063
23064 if ProjectSettings::get_global(cx)
23065 .git
23066 .inline_blame_delay()
23067 .is_some()
23068 {
23069 self.start_inline_blame_timer(window, cx);
23070 } else {
23071 self.show_git_blame_inline = true
23072 }
23073 }
23074
23075 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23076 self.blame.as_ref()
23077 }
23078
23079 pub fn show_git_blame_gutter(&self) -> bool {
23080 self.show_git_blame_gutter
23081 }
23082
23083 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23084 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23085 }
23086
23087 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23088 self.show_git_blame_inline
23089 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23090 && !self.newest_selection_head_on_empty_line(cx)
23091 && self.has_blame_entries(cx)
23092 }
23093
23094 fn has_blame_entries(&self, cx: &App) -> bool {
23095 self.blame()
23096 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23097 }
23098
23099 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23100 let cursor_anchor = self.selections.newest_anchor().head();
23101
23102 let snapshot = self.buffer.read(cx).snapshot(cx);
23103 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23104
23105 snapshot.line_len(buffer_row) == 0
23106 }
23107
23108 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23109 let buffer_and_selection = maybe!({
23110 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23111 let selection_range = selection.range();
23112
23113 let multi_buffer = self.buffer().read(cx);
23114 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23115 let buffer_ranges = multi_buffer_snapshot
23116 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23117
23118 let (buffer, range, _) = if selection.reversed {
23119 buffer_ranges.first()
23120 } else {
23121 buffer_ranges.last()
23122 }?;
23123
23124 let buffer_range = range.to_point(buffer);
23125
23126 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23127 return Some((
23128 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23129 buffer_range.start.row..buffer_range.end.row,
23130 ));
23131 };
23132
23133 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23134 let start =
23135 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23136 let end =
23137 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23138
23139 Some((
23140 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23141 start.row..end.row,
23142 ))
23143 });
23144
23145 let Some((buffer, selection)) = buffer_and_selection else {
23146 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23147 };
23148
23149 let Some(project) = self.project() else {
23150 return Task::ready(Err(anyhow!("editor does not have project")));
23151 };
23152
23153 project.update(cx, |project, cx| {
23154 project.get_permalink_to_line(&buffer, selection, cx)
23155 })
23156 }
23157
23158 pub fn copy_permalink_to_line(
23159 &mut self,
23160 _: &CopyPermalinkToLine,
23161 window: &mut Window,
23162 cx: &mut Context<Self>,
23163 ) {
23164 let permalink_task = self.get_permalink_to_line(cx);
23165 let workspace = self.workspace();
23166
23167 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23168 Ok(permalink) => {
23169 cx.update(|_, cx| {
23170 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23171 })
23172 .ok();
23173 }
23174 Err(err) => {
23175 let message = format!("Failed to copy permalink: {err}");
23176
23177 anyhow::Result::<()>::Err(err).log_err();
23178
23179 if let Some(workspace) = workspace {
23180 workspace
23181 .update_in(cx, |workspace, _, cx| {
23182 struct CopyPermalinkToLine;
23183
23184 workspace.show_toast(
23185 Toast::new(
23186 NotificationId::unique::<CopyPermalinkToLine>(),
23187 message,
23188 ),
23189 cx,
23190 )
23191 })
23192 .ok();
23193 }
23194 }
23195 })
23196 .detach();
23197 }
23198
23199 pub fn copy_file_location(
23200 &mut self,
23201 _: &CopyFileLocation,
23202 _: &mut Window,
23203 cx: &mut Context<Self>,
23204 ) {
23205 let selection = self
23206 .selections
23207 .newest::<Point>(&self.display_snapshot(cx))
23208 .start
23209 .row
23210 + 1;
23211 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23212 let project = self.project()?.read(cx);
23213 let file = buffer.read(cx).file()?;
23214 let path = file.path().display(project.path_style(cx));
23215
23216 Some(format!("{path}:{selection}"))
23217 }) {
23218 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23219 }
23220 }
23221
23222 pub fn open_permalink_to_line(
23223 &mut self,
23224 _: &OpenPermalinkToLine,
23225 window: &mut Window,
23226 cx: &mut Context<Self>,
23227 ) {
23228 let permalink_task = self.get_permalink_to_line(cx);
23229 let workspace = self.workspace();
23230
23231 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23232 Ok(permalink) => {
23233 cx.update(|_, cx| {
23234 cx.open_url(permalink.as_ref());
23235 })
23236 .ok();
23237 }
23238 Err(err) => {
23239 let message = format!("Failed to open permalink: {err}");
23240
23241 anyhow::Result::<()>::Err(err).log_err();
23242
23243 if let Some(workspace) = workspace {
23244 workspace.update(cx, |workspace, cx| {
23245 struct OpenPermalinkToLine;
23246
23247 workspace.show_toast(
23248 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23249 cx,
23250 )
23251 });
23252 }
23253 }
23254 })
23255 .detach();
23256 }
23257
23258 pub fn insert_uuid_v4(
23259 &mut self,
23260 _: &InsertUuidV4,
23261 window: &mut Window,
23262 cx: &mut Context<Self>,
23263 ) {
23264 self.insert_uuid(UuidVersion::V4, window, cx);
23265 }
23266
23267 pub fn insert_uuid_v7(
23268 &mut self,
23269 _: &InsertUuidV7,
23270 window: &mut Window,
23271 cx: &mut Context<Self>,
23272 ) {
23273 self.insert_uuid(UuidVersion::V7, window, cx);
23274 }
23275
23276 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23277 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23278 self.transact(window, cx, |this, window, cx| {
23279 let edits = this
23280 .selections
23281 .all::<Point>(&this.display_snapshot(cx))
23282 .into_iter()
23283 .map(|selection| {
23284 let uuid = match version {
23285 UuidVersion::V4 => uuid::Uuid::new_v4(),
23286 UuidVersion::V7 => uuid::Uuid::now_v7(),
23287 };
23288
23289 (selection.range(), uuid.to_string())
23290 });
23291 this.edit(edits, cx);
23292 this.refresh_edit_prediction(true, false, window, cx);
23293 });
23294 }
23295
23296 pub fn open_selections_in_multibuffer(
23297 &mut self,
23298 _: &OpenSelectionsInMultibuffer,
23299 window: &mut Window,
23300 cx: &mut Context<Self>,
23301 ) {
23302 let multibuffer = self.buffer.read(cx);
23303
23304 let Some(buffer) = multibuffer.as_singleton() else {
23305 return;
23306 };
23307
23308 let Some(workspace) = self.workspace() else {
23309 return;
23310 };
23311
23312 let title = multibuffer.title(cx).to_string();
23313
23314 let locations = self
23315 .selections
23316 .all_anchors(&self.display_snapshot(cx))
23317 .iter()
23318 .map(|selection| {
23319 (
23320 buffer.clone(),
23321 (selection.start.text_anchor..selection.end.text_anchor)
23322 .to_point(buffer.read(cx)),
23323 )
23324 })
23325 .into_group_map();
23326
23327 cx.spawn_in(window, async move |_, cx| {
23328 workspace.update_in(cx, |workspace, window, cx| {
23329 Self::open_locations_in_multibuffer(
23330 workspace,
23331 locations,
23332 format!("Selections for '{title}'"),
23333 false,
23334 false,
23335 MultibufferSelectionMode::All,
23336 window,
23337 cx,
23338 );
23339 })
23340 })
23341 .detach();
23342 }
23343
23344 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23345 /// last highlight added will be used.
23346 ///
23347 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23348 pub fn highlight_rows<T: 'static>(
23349 &mut self,
23350 range: Range<Anchor>,
23351 color: Hsla,
23352 options: RowHighlightOptions,
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 ix = row_highlights.binary_search_by(|highlight| {
23358 Ordering::Equal
23359 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23360 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23361 });
23362
23363 if let Err(mut ix) = ix {
23364 let index = post_inc(&mut self.highlight_order);
23365
23366 // If this range intersects with the preceding highlight, then merge it with
23367 // the preceding highlight. Otherwise insert a new highlight.
23368 let mut merged = false;
23369 if ix > 0 {
23370 let prev_highlight = &mut row_highlights[ix - 1];
23371 if prev_highlight
23372 .range
23373 .end
23374 .cmp(&range.start, &snapshot)
23375 .is_ge()
23376 {
23377 ix -= 1;
23378 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23379 prev_highlight.range.end = range.end;
23380 }
23381 merged = true;
23382 prev_highlight.index = index;
23383 prev_highlight.color = color;
23384 prev_highlight.options = options;
23385 }
23386 }
23387
23388 if !merged {
23389 row_highlights.insert(
23390 ix,
23391 RowHighlight {
23392 range,
23393 index,
23394 color,
23395 options,
23396 type_id: TypeId::of::<T>(),
23397 },
23398 );
23399 }
23400
23401 // If any of the following highlights intersect with this one, merge them.
23402 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23403 let highlight = &row_highlights[ix];
23404 if next_highlight
23405 .range
23406 .start
23407 .cmp(&highlight.range.end, &snapshot)
23408 .is_le()
23409 {
23410 if next_highlight
23411 .range
23412 .end
23413 .cmp(&highlight.range.end, &snapshot)
23414 .is_gt()
23415 {
23416 row_highlights[ix].range.end = next_highlight.range.end;
23417 }
23418 row_highlights.remove(ix + 1);
23419 } else {
23420 break;
23421 }
23422 }
23423 }
23424 }
23425
23426 /// Remove any highlighted row ranges of the given type that intersect the
23427 /// given ranges.
23428 pub fn remove_highlighted_rows<T: 'static>(
23429 &mut self,
23430 ranges_to_remove: Vec<Range<Anchor>>,
23431 cx: &mut Context<Self>,
23432 ) {
23433 let snapshot = self.buffer().read(cx).snapshot(cx);
23434 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23435 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23436 row_highlights.retain(|highlight| {
23437 while let Some(range_to_remove) = ranges_to_remove.peek() {
23438 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23439 Ordering::Less | Ordering::Equal => {
23440 ranges_to_remove.next();
23441 }
23442 Ordering::Greater => {
23443 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23444 Ordering::Less | Ordering::Equal => {
23445 return false;
23446 }
23447 Ordering::Greater => break,
23448 }
23449 }
23450 }
23451 }
23452
23453 true
23454 })
23455 }
23456
23457 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23458 pub fn clear_row_highlights<T: 'static>(&mut self) {
23459 self.highlighted_rows.remove(&TypeId::of::<T>());
23460 }
23461
23462 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23463 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23464 self.highlighted_rows
23465 .get(&TypeId::of::<T>())
23466 .map_or(&[] as &[_], |vec| vec.as_slice())
23467 .iter()
23468 .map(|highlight| (highlight.range.clone(), highlight.color))
23469 }
23470
23471 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23472 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23473 /// Allows to ignore certain kinds of highlights.
23474 pub fn highlighted_display_rows(
23475 &self,
23476 window: &mut Window,
23477 cx: &mut App,
23478 ) -> BTreeMap<DisplayRow, LineHighlight> {
23479 let snapshot = self.snapshot(window, cx);
23480 let mut used_highlight_orders = HashMap::default();
23481 self.highlighted_rows
23482 .iter()
23483 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23484 .fold(
23485 BTreeMap::<DisplayRow, LineHighlight>::new(),
23486 |mut unique_rows, highlight| {
23487 let start = highlight.range.start.to_display_point(&snapshot);
23488 let end = highlight.range.end.to_display_point(&snapshot);
23489 let start_row = start.row().0;
23490 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23491 {
23492 end.row().0.saturating_sub(1)
23493 } else {
23494 end.row().0
23495 };
23496 for row in start_row..=end_row {
23497 let used_index =
23498 used_highlight_orders.entry(row).or_insert(highlight.index);
23499 if highlight.index >= *used_index {
23500 *used_index = highlight.index;
23501 unique_rows.insert(
23502 DisplayRow(row),
23503 LineHighlight {
23504 include_gutter: highlight.options.include_gutter,
23505 border: None,
23506 background: highlight.color.into(),
23507 type_id: Some(highlight.type_id),
23508 },
23509 );
23510 }
23511 }
23512 unique_rows
23513 },
23514 )
23515 }
23516
23517 pub fn highlighted_display_row_for_autoscroll(
23518 &self,
23519 snapshot: &DisplaySnapshot,
23520 ) -> Option<DisplayRow> {
23521 self.highlighted_rows
23522 .values()
23523 .flat_map(|highlighted_rows| highlighted_rows.iter())
23524 .filter_map(|highlight| {
23525 if highlight.options.autoscroll {
23526 Some(highlight.range.start.to_display_point(snapshot).row())
23527 } else {
23528 None
23529 }
23530 })
23531 .min()
23532 }
23533
23534 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23535 self.highlight_background(
23536 HighlightKey::SearchWithinRange,
23537 ranges,
23538 |_, colors| colors.colors().editor_document_highlight_read_background,
23539 cx,
23540 )
23541 }
23542
23543 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23544 self.breadcrumb_header = Some(new_header);
23545 }
23546
23547 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23548 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23549 }
23550
23551 pub fn highlight_background(
23552 &mut self,
23553 key: HighlightKey,
23554 ranges: &[Range<Anchor>],
23555 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23556 cx: &mut Context<Self>,
23557 ) {
23558 self.background_highlights
23559 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23560 self.scrollbar_marker_state.dirty = true;
23561 cx.notify();
23562 }
23563
23564 pub fn clear_background_highlights(
23565 &mut self,
23566 key: HighlightKey,
23567 cx: &mut Context<Self>,
23568 ) -> Option<BackgroundHighlight> {
23569 let text_highlights = self.background_highlights.remove(&key)?;
23570 if !text_highlights.1.is_empty() {
23571 self.scrollbar_marker_state.dirty = true;
23572 cx.notify();
23573 }
23574 Some(text_highlights)
23575 }
23576
23577 pub fn highlight_gutter<T: 'static>(
23578 &mut self,
23579 ranges: impl Into<Vec<Range<Anchor>>>,
23580 color_fetcher: fn(&App) -> Hsla,
23581 cx: &mut Context<Self>,
23582 ) {
23583 self.gutter_highlights
23584 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23585 cx.notify();
23586 }
23587
23588 pub fn clear_gutter_highlights<T: 'static>(
23589 &mut self,
23590 cx: &mut Context<Self>,
23591 ) -> Option<GutterHighlight> {
23592 cx.notify();
23593 self.gutter_highlights.remove(&TypeId::of::<T>())
23594 }
23595
23596 pub fn insert_gutter_highlight<T: 'static>(
23597 &mut self,
23598 range: Range<Anchor>,
23599 color_fetcher: fn(&App) -> Hsla,
23600 cx: &mut Context<Self>,
23601 ) {
23602 let snapshot = self.buffer().read(cx).snapshot(cx);
23603 let mut highlights = self
23604 .gutter_highlights
23605 .remove(&TypeId::of::<T>())
23606 .map(|(_, highlights)| highlights)
23607 .unwrap_or_default();
23608 let ix = highlights.binary_search_by(|highlight| {
23609 Ordering::Equal
23610 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23611 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23612 });
23613 if let Err(ix) = ix {
23614 highlights.insert(ix, range);
23615 }
23616 self.gutter_highlights
23617 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23618 }
23619
23620 pub fn remove_gutter_highlights<T: 'static>(
23621 &mut self,
23622 ranges_to_remove: Vec<Range<Anchor>>,
23623 cx: &mut Context<Self>,
23624 ) {
23625 let snapshot = self.buffer().read(cx).snapshot(cx);
23626 let Some((color_fetcher, mut gutter_highlights)) =
23627 self.gutter_highlights.remove(&TypeId::of::<T>())
23628 else {
23629 return;
23630 };
23631 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23632 gutter_highlights.retain(|highlight| {
23633 while let Some(range_to_remove) = ranges_to_remove.peek() {
23634 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23635 Ordering::Less | Ordering::Equal => {
23636 ranges_to_remove.next();
23637 }
23638 Ordering::Greater => {
23639 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23640 Ordering::Less | Ordering::Equal => {
23641 return false;
23642 }
23643 Ordering::Greater => break,
23644 }
23645 }
23646 }
23647 }
23648
23649 true
23650 });
23651 self.gutter_highlights
23652 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23653 }
23654
23655 #[cfg(any(test, feature = "test-support"))]
23656 pub fn all_text_highlights(
23657 &self,
23658 window: &mut Window,
23659 cx: &mut Context<Self>,
23660 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23661 let snapshot = self.snapshot(window, cx);
23662 self.display_map.update(cx, |display_map, _| {
23663 display_map
23664 .all_text_highlights()
23665 .map(|(_, highlight)| {
23666 let (style, ranges) = highlight.as_ref();
23667 (
23668 *style,
23669 ranges
23670 .iter()
23671 .map(|range| range.clone().to_display_points(&snapshot))
23672 .collect(),
23673 )
23674 })
23675 .collect()
23676 })
23677 }
23678
23679 #[cfg(any(test, feature = "test-support"))]
23680 pub fn all_text_background_highlights(
23681 &self,
23682 window: &mut Window,
23683 cx: &mut Context<Self>,
23684 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23685 let snapshot = self.snapshot(window, cx);
23686 let buffer = &snapshot.buffer_snapshot();
23687 let start = buffer.anchor_before(MultiBufferOffset(0));
23688 let end = buffer.anchor_after(buffer.len());
23689 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23690 }
23691
23692 #[cfg(any(test, feature = "test-support"))]
23693 pub fn sorted_background_highlights_in_range(
23694 &self,
23695 search_range: Range<Anchor>,
23696 display_snapshot: &DisplaySnapshot,
23697 theme: &Theme,
23698 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23699 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23700 res.sort_by(|a, b| {
23701 a.0.start
23702 .cmp(&b.0.start)
23703 .then_with(|| a.0.end.cmp(&b.0.end))
23704 .then_with(|| a.1.cmp(&b.1))
23705 });
23706 res
23707 }
23708
23709 #[cfg(any(test, feature = "test-support"))]
23710 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23711 let snapshot = self.buffer().read(cx).snapshot(cx);
23712
23713 let highlights = self
23714 .background_highlights
23715 .get(&HighlightKey::BufferSearchHighlights);
23716
23717 if let Some((_color, ranges)) = highlights {
23718 ranges
23719 .iter()
23720 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23721 .collect_vec()
23722 } else {
23723 vec![]
23724 }
23725 }
23726
23727 fn document_highlights_for_position<'a>(
23728 &'a self,
23729 position: Anchor,
23730 buffer: &'a MultiBufferSnapshot,
23731 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23732 let read_highlights = self
23733 .background_highlights
23734 .get(&HighlightKey::DocumentHighlightRead)
23735 .map(|h| &h.1);
23736 let write_highlights = self
23737 .background_highlights
23738 .get(&HighlightKey::DocumentHighlightWrite)
23739 .map(|h| &h.1);
23740 let left_position = position.bias_left(buffer);
23741 let right_position = position.bias_right(buffer);
23742 read_highlights
23743 .into_iter()
23744 .chain(write_highlights)
23745 .flat_map(move |ranges| {
23746 let start_ix = match ranges.binary_search_by(|probe| {
23747 let cmp = probe.end.cmp(&left_position, buffer);
23748 if cmp.is_ge() {
23749 Ordering::Greater
23750 } else {
23751 Ordering::Less
23752 }
23753 }) {
23754 Ok(i) | Err(i) => i,
23755 };
23756
23757 ranges[start_ix..]
23758 .iter()
23759 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23760 })
23761 }
23762
23763 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23764 self.background_highlights
23765 .get(&key)
23766 .is_some_and(|(_, highlights)| !highlights.is_empty())
23767 }
23768
23769 /// Returns all background highlights for a given range.
23770 ///
23771 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23772 pub fn background_highlights_in_range(
23773 &self,
23774 search_range: Range<Anchor>,
23775 display_snapshot: &DisplaySnapshot,
23776 theme: &Theme,
23777 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23778 let mut results = Vec::new();
23779 for (color_fetcher, ranges) in self.background_highlights.values() {
23780 let start_ix = match ranges.binary_search_by(|probe| {
23781 let cmp = probe
23782 .end
23783 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23784 if cmp.is_gt() {
23785 Ordering::Greater
23786 } else {
23787 Ordering::Less
23788 }
23789 }) {
23790 Ok(i) | Err(i) => i,
23791 };
23792 for (index, range) in ranges[start_ix..].iter().enumerate() {
23793 if range
23794 .start
23795 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23796 .is_ge()
23797 {
23798 break;
23799 }
23800
23801 let color = color_fetcher(&(start_ix + index), theme);
23802 let start = range.start.to_display_point(display_snapshot);
23803 let end = range.end.to_display_point(display_snapshot);
23804 results.push((start..end, color))
23805 }
23806 }
23807 results
23808 }
23809
23810 pub fn gutter_highlights_in_range(
23811 &self,
23812 search_range: Range<Anchor>,
23813 display_snapshot: &DisplaySnapshot,
23814 cx: &App,
23815 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23816 let mut results = Vec::new();
23817 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23818 let color = color_fetcher(cx);
23819 let start_ix = match ranges.binary_search_by(|probe| {
23820 let cmp = probe
23821 .end
23822 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23823 if cmp.is_gt() {
23824 Ordering::Greater
23825 } else {
23826 Ordering::Less
23827 }
23828 }) {
23829 Ok(i) | Err(i) => i,
23830 };
23831 for range in &ranges[start_ix..] {
23832 if range
23833 .start
23834 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23835 .is_ge()
23836 {
23837 break;
23838 }
23839
23840 let start = range.start.to_display_point(display_snapshot);
23841 let end = range.end.to_display_point(display_snapshot);
23842 results.push((start..end, color))
23843 }
23844 }
23845 results
23846 }
23847
23848 /// Get the text ranges corresponding to the redaction query
23849 pub fn redacted_ranges(
23850 &self,
23851 search_range: Range<Anchor>,
23852 display_snapshot: &DisplaySnapshot,
23853 cx: &App,
23854 ) -> Vec<Range<DisplayPoint>> {
23855 display_snapshot
23856 .buffer_snapshot()
23857 .redacted_ranges(search_range, |file| {
23858 if let Some(file) = file {
23859 file.is_private()
23860 && EditorSettings::get(
23861 Some(SettingsLocation {
23862 worktree_id: file.worktree_id(cx),
23863 path: file.path().as_ref(),
23864 }),
23865 cx,
23866 )
23867 .redact_private_values
23868 } else {
23869 false
23870 }
23871 })
23872 .map(|range| {
23873 range.start.to_display_point(display_snapshot)
23874 ..range.end.to_display_point(display_snapshot)
23875 })
23876 .collect()
23877 }
23878
23879 pub fn highlight_text_key(
23880 &mut self,
23881 key: HighlightKey,
23882 ranges: Vec<Range<Anchor>>,
23883 style: HighlightStyle,
23884 merge: bool,
23885 cx: &mut Context<Self>,
23886 ) {
23887 self.display_map.update(cx, |map, cx| {
23888 map.highlight_text(key, ranges, style, merge, cx);
23889 });
23890 cx.notify();
23891 }
23892
23893 pub fn highlight_text(
23894 &mut self,
23895 key: HighlightKey,
23896 ranges: Vec<Range<Anchor>>,
23897 style: HighlightStyle,
23898 cx: &mut Context<Self>,
23899 ) {
23900 self.display_map.update(cx, |map, cx| {
23901 map.highlight_text(key, ranges, style, false, cx)
23902 });
23903 cx.notify();
23904 }
23905
23906 pub fn text_highlights<'a>(
23907 &'a self,
23908 key: HighlightKey,
23909 cx: &'a App,
23910 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23911 self.display_map.read(cx).text_highlights(key)
23912 }
23913
23914 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23915 let cleared = self
23916 .display_map
23917 .update(cx, |map, _| map.clear_highlights(key));
23918 if cleared {
23919 cx.notify();
23920 }
23921 }
23922
23923 pub fn clear_highlights_with(
23924 &mut self,
23925 f: &mut dyn FnMut(&HighlightKey) -> bool,
23926 cx: &mut Context<Self>,
23927 ) {
23928 let cleared = self
23929 .display_map
23930 .update(cx, |map, _| map.clear_highlights_with(f));
23931 if cleared {
23932 cx.notify();
23933 }
23934 }
23935
23936 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23937 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23938 && self.focus_handle.is_focused(window)
23939 }
23940
23941 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23942 self.show_cursor_when_unfocused = is_enabled;
23943 cx.notify();
23944 }
23945
23946 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23947 cx.notify();
23948 }
23949
23950 fn on_debug_session_event(
23951 &mut self,
23952 _session: Entity<Session>,
23953 event: &SessionEvent,
23954 cx: &mut Context<Self>,
23955 ) {
23956 if let SessionEvent::InvalidateInlineValue = event {
23957 self.refresh_inline_values(cx);
23958 }
23959 }
23960
23961 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23962 let Some(semantics) = self.semantics_provider.clone() else {
23963 return;
23964 };
23965
23966 if !self.inline_value_cache.enabled {
23967 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23968 self.splice_inlays(&inlays, Vec::new(), cx);
23969 return;
23970 }
23971
23972 let current_execution_position = self
23973 .highlighted_rows
23974 .get(&TypeId::of::<ActiveDebugLine>())
23975 .and_then(|lines| lines.last().map(|line| line.range.end));
23976
23977 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23978 let inline_values = editor
23979 .update(cx, |editor, cx| {
23980 let Some(current_execution_position) = current_execution_position else {
23981 return Some(Task::ready(Ok(Vec::new())));
23982 };
23983
23984 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23985 let snapshot = buffer.snapshot(cx);
23986
23987 let excerpt = snapshot.excerpt_containing(
23988 current_execution_position..current_execution_position,
23989 )?;
23990
23991 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23992 })?;
23993
23994 let range =
23995 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23996
23997 semantics.inline_values(buffer, range, cx)
23998 })
23999 .ok()
24000 .flatten()?
24001 .await
24002 .context("refreshing debugger inlays")
24003 .log_err()?;
24004
24005 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24006
24007 for (buffer_id, inline_value) in inline_values
24008 .into_iter()
24009 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
24010 {
24011 buffer_inline_values
24012 .entry(buffer_id)
24013 .or_default()
24014 .push(inline_value);
24015 }
24016
24017 editor
24018 .update(cx, |editor, cx| {
24019 let snapshot = editor.buffer.read(cx).snapshot(cx);
24020 let mut new_inlays = Vec::default();
24021
24022 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
24023 let buffer_id = buffer_snapshot.remote_id();
24024 buffer_inline_values
24025 .get(&buffer_id)
24026 .into_iter()
24027 .flatten()
24028 .for_each(|hint| {
24029 let inlay = Inlay::debugger(
24030 post_inc(&mut editor.next_inlay_id),
24031 Anchor::in_buffer(excerpt_id, hint.position),
24032 hint.text(),
24033 );
24034 if !inlay.text().chars().contains(&'\n') {
24035 new_inlays.push(inlay);
24036 }
24037 });
24038 }
24039
24040 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24041 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24042
24043 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24044 })
24045 .ok()?;
24046 Some(())
24047 });
24048 }
24049
24050 fn on_buffer_event(
24051 &mut self,
24052 multibuffer: &Entity<MultiBuffer>,
24053 event: &multi_buffer::Event,
24054 window: &mut Window,
24055 cx: &mut Context<Self>,
24056 ) {
24057 match event {
24058 multi_buffer::Event::Edited { edited_buffer } => {
24059 self.scrollbar_marker_state.dirty = true;
24060 self.active_indent_guides_state.dirty = true;
24061 self.refresh_active_diagnostics(cx);
24062 self.refresh_code_actions(window, cx);
24063 self.refresh_single_line_folds(window, cx);
24064 let snapshot = self.snapshot(window, cx);
24065 self.refresh_matching_bracket_highlights(&snapshot, cx);
24066 self.refresh_outline_symbols_at_cursor(cx);
24067 self.refresh_sticky_headers(&snapshot, cx);
24068 if self.has_active_edit_prediction() {
24069 self.update_visible_edit_prediction(window, cx);
24070 }
24071
24072 // Clean up orphaned review comments after edits
24073 self.cleanup_orphaned_review_comments(cx);
24074
24075 if let Some(buffer) = edited_buffer {
24076 if buffer.read(cx).file().is_none() {
24077 cx.emit(EditorEvent::TitleChanged);
24078 }
24079
24080 if self.project.is_some() {
24081 let buffer_id = buffer.read(cx).remote_id();
24082 self.register_buffer(buffer_id, cx);
24083 self.update_lsp_data(Some(buffer_id), window, cx);
24084 self.refresh_inlay_hints(
24085 InlayHintRefreshReason::BufferEdited(buffer_id),
24086 cx,
24087 );
24088 }
24089 }
24090
24091 cx.emit(EditorEvent::BufferEdited);
24092 cx.emit(SearchEvent::MatchesInvalidated);
24093
24094 let Some(project) = &self.project else { return };
24095 let (telemetry, is_via_ssh) = {
24096 let project = project.read(cx);
24097 let telemetry = project.client().telemetry().clone();
24098 let is_via_ssh = project.is_via_remote_server();
24099 (telemetry, is_via_ssh)
24100 };
24101 telemetry.log_edit_event("editor", is_via_ssh);
24102 }
24103 multi_buffer::Event::ExcerptsAdded {
24104 buffer,
24105 predecessor,
24106 excerpts,
24107 } => {
24108 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24109 let buffer_id = buffer.read(cx).remote_id();
24110 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24111 && let Some(project) = &self.project
24112 {
24113 update_uncommitted_diff_for_buffer(
24114 cx.entity(),
24115 project,
24116 [buffer.clone()],
24117 self.buffer.clone(),
24118 cx,
24119 )
24120 .detach();
24121 }
24122 self.semantic_token_state
24123 .invalidate_buffer(&buffer.read(cx).remote_id());
24124 self.update_lsp_data(Some(buffer_id), window, cx);
24125 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24126 self.colorize_brackets(false, cx);
24127 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24128 cx.emit(EditorEvent::ExcerptsAdded {
24129 buffer: buffer.clone(),
24130 predecessor: *predecessor,
24131 excerpts: excerpts.clone(),
24132 });
24133 }
24134 multi_buffer::Event::ExcerptsRemoved {
24135 ids,
24136 removed_buffer_ids,
24137 } => {
24138 if let Some(inlay_hints) = &mut self.inlay_hints {
24139 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24140 }
24141 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24142 for buffer_id in removed_buffer_ids {
24143 self.registered_buffers.remove(buffer_id);
24144 self.tasks
24145 .retain(|(task_buffer_id, _), _| task_buffer_id != buffer_id);
24146 self.semantic_token_state.invalidate_buffer(buffer_id);
24147 self.display_map.update(cx, |display_map, cx| {
24148 display_map.invalidate_semantic_highlights(*buffer_id);
24149 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24150 });
24151 }
24152
24153 self.display_map.update(cx, |display_map, cx| {
24154 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24155 });
24156
24157 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24158 cx.emit(EditorEvent::ExcerptsRemoved {
24159 ids: ids.clone(),
24160 removed_buffer_ids: removed_buffer_ids.clone(),
24161 });
24162 }
24163 multi_buffer::Event::ExcerptsEdited {
24164 excerpt_ids,
24165 buffer_ids,
24166 } => {
24167 self.display_map.update(cx, |map, cx| {
24168 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24169 });
24170 cx.emit(EditorEvent::ExcerptsEdited {
24171 ids: excerpt_ids.clone(),
24172 });
24173 }
24174 multi_buffer::Event::ExcerptsExpanded { ids } => {
24175 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24176 self.refresh_document_highlights(cx);
24177 let snapshot = multibuffer.read(cx).snapshot(cx);
24178 for id in ids {
24179 self.bracket_fetched_tree_sitter_chunks.remove(id);
24180 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24181 self.semantic_token_state
24182 .invalidate_buffer(&buffer.remote_id());
24183 }
24184 }
24185 self.colorize_brackets(false, cx);
24186 self.update_lsp_data(None, window, cx);
24187 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24188 }
24189 multi_buffer::Event::Reparsed(buffer_id) => {
24190 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24191 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24192 self.colorize_brackets(true, cx);
24193 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24194
24195 cx.emit(EditorEvent::Reparsed(*buffer_id));
24196 }
24197 multi_buffer::Event::DiffHunksToggled => {
24198 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24199 }
24200 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24201 if !is_fresh_language {
24202 self.registered_buffers.remove(&buffer_id);
24203 }
24204 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24205 cx.emit(EditorEvent::Reparsed(*buffer_id));
24206 cx.notify();
24207 }
24208 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24209 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24210 multi_buffer::Event::FileHandleChanged
24211 | multi_buffer::Event::Reloaded
24212 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24213 multi_buffer::Event::DiagnosticsUpdated => {
24214 self.update_diagnostics_state(window, cx);
24215 }
24216 _ => {}
24217 };
24218 }
24219
24220 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24221 if !self.diagnostics_enabled() {
24222 return;
24223 }
24224 self.refresh_active_diagnostics(cx);
24225 self.refresh_inline_diagnostics(true, window, cx);
24226 self.scrollbar_marker_state.dirty = true;
24227 cx.notify();
24228 }
24229
24230 pub fn start_temporary_diff_override(&mut self) {
24231 self.load_diff_task.take();
24232 self.temporary_diff_override = true;
24233 }
24234
24235 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24236 self.temporary_diff_override = false;
24237 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24238 self.buffer.update(cx, |buffer, cx| {
24239 buffer.set_all_diff_hunks_collapsed(cx);
24240 });
24241
24242 if let Some(project) = self.project.clone() {
24243 self.load_diff_task = Some(
24244 update_uncommitted_diff_for_buffer(
24245 cx.entity(),
24246 &project,
24247 self.buffer.read(cx).all_buffers(),
24248 self.buffer.clone(),
24249 cx,
24250 )
24251 .shared(),
24252 );
24253 }
24254 }
24255
24256 fn on_display_map_changed(
24257 &mut self,
24258 _: Entity<DisplayMap>,
24259 _: &mut Window,
24260 cx: &mut Context<Self>,
24261 ) {
24262 cx.notify();
24263 }
24264
24265 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24266 if !self.mode.is_full() {
24267 return None;
24268 }
24269
24270 let theme_settings = theme::ThemeSettings::get_global(cx);
24271 let theme = cx.theme();
24272 let accent_colors = theme.accents().clone();
24273
24274 let accent_overrides = theme_settings
24275 .theme_overrides
24276 .get(theme.name.as_ref())
24277 .map(|theme_style| &theme_style.accents)
24278 .into_iter()
24279 .flatten()
24280 .chain(
24281 theme_settings
24282 .experimental_theme_overrides
24283 .as_ref()
24284 .map(|overrides| &overrides.accents)
24285 .into_iter()
24286 .flatten(),
24287 )
24288 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24289 .collect();
24290
24291 Some(AccentData {
24292 colors: accent_colors,
24293 overrides: accent_overrides,
24294 })
24295 }
24296
24297 fn fetch_applicable_language_settings(
24298 &self,
24299 cx: &App,
24300 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24301 if !self.mode.is_full() {
24302 return HashMap::default();
24303 }
24304
24305 self.buffer().read(cx).all_buffers().into_iter().fold(
24306 HashMap::default(),
24307 |mut acc, buffer| {
24308 let buffer = buffer.read(cx);
24309 let language = buffer.language().map(|language| language.name());
24310 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24311 let file = buffer.file();
24312 v.insert(language_settings(language, file, cx).into_owned());
24313 }
24314 acc
24315 },
24316 )
24317 }
24318
24319 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24320 let new_language_settings = self.fetch_applicable_language_settings(cx);
24321 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24322 self.applicable_language_settings = new_language_settings;
24323
24324 let new_accents = self.fetch_accent_data(cx);
24325 let accents_changed = new_accents != self.accent_data;
24326 self.accent_data = new_accents;
24327
24328 if self.diagnostics_enabled() {
24329 let new_severity = EditorSettings::get_global(cx)
24330 .diagnostics_max_severity
24331 .unwrap_or(DiagnosticSeverity::Hint);
24332 self.set_max_diagnostics_severity(new_severity, cx);
24333 }
24334 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24335 self.update_edit_prediction_settings(cx);
24336 self.refresh_edit_prediction(true, false, window, cx);
24337 self.refresh_inline_values(cx);
24338
24339 let old_cursor_shape = self.cursor_shape;
24340 let old_show_breadcrumbs = self.show_breadcrumbs;
24341
24342 {
24343 let editor_settings = EditorSettings::get_global(cx);
24344 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24345 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24346 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24347 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24348 }
24349
24350 if old_cursor_shape != self.cursor_shape {
24351 cx.emit(EditorEvent::CursorShapeChanged);
24352 }
24353
24354 if old_show_breadcrumbs != self.show_breadcrumbs {
24355 cx.emit(EditorEvent::BreadcrumbsChanged);
24356 }
24357
24358 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24359 let project_settings = ProjectSettings::get_global(cx);
24360 (
24361 project_settings.session.restore_unsaved_buffers,
24362 project_settings.diagnostics.inline.enabled,
24363 project_settings.git.inline_blame.enabled,
24364 )
24365 };
24366 self.buffer_serialization = self
24367 .should_serialize_buffer()
24368 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24369
24370 if self.mode.is_full() {
24371 if self.show_inline_diagnostics != show_inline_diagnostics {
24372 self.show_inline_diagnostics = show_inline_diagnostics;
24373 self.refresh_inline_diagnostics(false, window, cx);
24374 }
24375
24376 if self.git_blame_inline_enabled != inline_blame_enabled {
24377 self.toggle_git_blame_inline_internal(false, window, cx);
24378 }
24379
24380 let minimap_settings = EditorSettings::get_global(cx).minimap;
24381 if self.minimap_visibility != MinimapVisibility::Disabled {
24382 if self.minimap_visibility.settings_visibility()
24383 != minimap_settings.minimap_enabled()
24384 {
24385 self.set_minimap_visibility(
24386 MinimapVisibility::for_mode(self.mode(), cx),
24387 window,
24388 cx,
24389 );
24390 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24391 minimap_entity.update(cx, |minimap_editor, cx| {
24392 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24393 })
24394 }
24395 }
24396
24397 if language_settings_changed || accents_changed {
24398 self.colorize_brackets(true, cx);
24399 }
24400
24401 if language_settings_changed {
24402 self.clear_disabled_lsp_folding_ranges(window, cx);
24403 self.refresh_document_symbols(None, cx);
24404 }
24405
24406 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24407 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24408 }) {
24409 if !inlay_splice.is_empty() {
24410 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24411 }
24412 self.refresh_document_colors(None, window, cx);
24413 }
24414
24415 self.refresh_inlay_hints(
24416 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24417 self.selections.newest_anchor().head(),
24418 &self.buffer.read(cx).snapshot(cx),
24419 cx,
24420 )),
24421 cx,
24422 );
24423
24424 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24425 .global_lsp_settings
24426 .semantic_token_rules
24427 .clone();
24428 let semantic_token_rules_changed = self
24429 .semantic_token_state
24430 .update_rules(new_semantic_token_rules);
24431 if language_settings_changed || semantic_token_rules_changed {
24432 self.invalidate_semantic_tokens(None);
24433 self.refresh_semantic_tokens(None, None, cx);
24434 }
24435 }
24436
24437 cx.notify();
24438 }
24439
24440 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24441 if !self.mode.is_full() {
24442 return;
24443 }
24444
24445 let new_accents = self.fetch_accent_data(cx);
24446 if new_accents != self.accent_data {
24447 self.accent_data = new_accents;
24448 self.colorize_brackets(true, cx);
24449 }
24450
24451 self.invalidate_semantic_tokens(None);
24452 self.refresh_semantic_tokens(None, None, cx);
24453 }
24454
24455 pub fn set_searchable(&mut self, searchable: bool) {
24456 self.searchable = searchable;
24457 }
24458
24459 pub fn searchable(&self) -> bool {
24460 self.searchable
24461 }
24462
24463 pub fn open_excerpts_in_split(
24464 &mut self,
24465 _: &OpenExcerptsSplit,
24466 window: &mut Window,
24467 cx: &mut Context<Self>,
24468 ) {
24469 self.open_excerpts_common(None, true, window, cx)
24470 }
24471
24472 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24473 self.open_excerpts_common(None, false, window, cx)
24474 }
24475
24476 pub(crate) fn open_excerpts_common(
24477 &mut self,
24478 jump_data: Option<JumpData>,
24479 split: bool,
24480 window: &mut Window,
24481 cx: &mut Context<Self>,
24482 ) {
24483 if self.buffer.read(cx).is_singleton() {
24484 cx.propagate();
24485 return;
24486 }
24487
24488 let mut new_selections_by_buffer = HashMap::default();
24489 match &jump_data {
24490 Some(JumpData::MultiBufferPoint {
24491 excerpt_id,
24492 position,
24493 anchor,
24494 line_offset_from_top,
24495 }) => {
24496 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24497 if let Some(buffer) = multi_buffer_snapshot
24498 .buffer_id_for_excerpt(*excerpt_id)
24499 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24500 {
24501 let buffer_snapshot = buffer.read(cx).snapshot();
24502 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24503 language::ToPoint::to_point(anchor, &buffer_snapshot)
24504 } else {
24505 buffer_snapshot.clip_point(*position, Bias::Left)
24506 };
24507 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24508 new_selections_by_buffer.insert(
24509 buffer,
24510 (
24511 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24512 Some(*line_offset_from_top),
24513 ),
24514 );
24515 }
24516 }
24517 Some(JumpData::MultiBufferRow {
24518 row,
24519 line_offset_from_top,
24520 }) => {
24521 let point = MultiBufferPoint::new(row.0, 0);
24522 if let Some((buffer, buffer_point, _)) =
24523 self.buffer.read(cx).point_to_buffer_point(point, cx)
24524 {
24525 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24526 new_selections_by_buffer
24527 .entry(buffer)
24528 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24529 .0
24530 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24531 }
24532 }
24533 None => {
24534 let selections = self
24535 .selections
24536 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24537 let multi_buffer = self.buffer.read(cx);
24538 for selection in selections {
24539 for (snapshot, range, _, anchor) in multi_buffer
24540 .snapshot(cx)
24541 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24542 {
24543 if let Some(anchor) = anchor {
24544 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24545 else {
24546 continue;
24547 };
24548 let offset = text::ToOffset::to_offset(
24549 &anchor.text_anchor,
24550 &buffer_handle.read(cx).snapshot(),
24551 );
24552 let range = BufferOffset(offset)..BufferOffset(offset);
24553 new_selections_by_buffer
24554 .entry(buffer_handle)
24555 .or_insert((Vec::new(), None))
24556 .0
24557 .push(range)
24558 } else {
24559 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24560 else {
24561 continue;
24562 };
24563 new_selections_by_buffer
24564 .entry(buffer_handle)
24565 .or_insert((Vec::new(), None))
24566 .0
24567 .push(range)
24568 }
24569 }
24570 }
24571 }
24572 }
24573
24574 if self.delegate_open_excerpts {
24575 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24576 .into_iter()
24577 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24578 .collect();
24579 if !selections_by_buffer.is_empty() {
24580 cx.emit(EditorEvent::OpenExcerptsRequested {
24581 selections_by_buffer,
24582 split,
24583 });
24584 }
24585 return;
24586 }
24587
24588 let Some(workspace) = self.workspace() else {
24589 cx.propagate();
24590 return;
24591 };
24592
24593 new_selections_by_buffer
24594 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24595
24596 if new_selections_by_buffer.is_empty() {
24597 return;
24598 }
24599
24600 Self::open_buffers_in_workspace(
24601 workspace.downgrade(),
24602 new_selections_by_buffer,
24603 split,
24604 window,
24605 cx,
24606 );
24607 }
24608
24609 pub(crate) fn open_buffers_in_workspace(
24610 workspace: WeakEntity<Workspace>,
24611 new_selections_by_buffer: HashMap<
24612 Entity<language::Buffer>,
24613 (Vec<Range<BufferOffset>>, Option<u32>),
24614 >,
24615 split: bool,
24616 window: &mut Window,
24617 cx: &mut App,
24618 ) {
24619 // We defer the pane interaction because we ourselves are a workspace item
24620 // and activating a new item causes the pane to call a method on us reentrantly,
24621 // which panics if we're on the stack.
24622 window.defer(cx, move |window, cx| {
24623 workspace
24624 .update(cx, |workspace, cx| {
24625 let pane = if split {
24626 workspace.adjacent_pane(window, cx)
24627 } else {
24628 workspace.active_pane().clone()
24629 };
24630
24631 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24632 let buffer_read = buffer.read(cx);
24633 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24634 (true, project::File::from_dyn(Some(file)).is_some())
24635 } else {
24636 (false, false)
24637 };
24638
24639 // If project file is none workspace.open_project_item will fail to open the excerpt
24640 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24641 // so we check if there's a tab match in that case first
24642 let editor = (!has_file || !is_project_file)
24643 .then(|| {
24644 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24645 // so `workspace.open_project_item` will never find them, always opening a new editor.
24646 // Instead, we try to activate the existing editor in the pane first.
24647 let (editor, pane_item_index, pane_item_id) =
24648 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24649 let editor = item.downcast::<Editor>()?;
24650 let singleton_buffer =
24651 editor.read(cx).buffer().read(cx).as_singleton()?;
24652 if singleton_buffer == buffer {
24653 Some((editor, i, item.item_id()))
24654 } else {
24655 None
24656 }
24657 })?;
24658 pane.update(cx, |pane, cx| {
24659 pane.activate_item(pane_item_index, true, true, window, cx);
24660 if !PreviewTabsSettings::get_global(cx)
24661 .enable_preview_from_multibuffer
24662 {
24663 pane.unpreview_item_if_preview(pane_item_id);
24664 }
24665 });
24666 Some(editor)
24667 })
24668 .flatten()
24669 .unwrap_or_else(|| {
24670 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24671 .enable_keep_preview_on_code_navigation;
24672 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24673 .enable_preview_from_multibuffer;
24674 workspace.open_project_item::<Self>(
24675 pane.clone(),
24676 buffer,
24677 true,
24678 true,
24679 keep_old_preview,
24680 allow_new_preview,
24681 window,
24682 cx,
24683 )
24684 });
24685
24686 editor.update(cx, |editor, cx| {
24687 if has_file && !is_project_file {
24688 editor.set_read_only(true);
24689 }
24690 let autoscroll = match scroll_offset {
24691 Some(scroll_offset) => {
24692 Autoscroll::top_relative(scroll_offset as usize)
24693 }
24694 None => Autoscroll::newest(),
24695 };
24696 let nav_history = editor.nav_history.take();
24697 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24698 let Some((excerpt_id, _, buffer_snapshot)) =
24699 multibuffer_snapshot.as_singleton()
24700 else {
24701 return;
24702 };
24703 editor.change_selections(
24704 SelectionEffects::scroll(autoscroll),
24705 window,
24706 cx,
24707 |s| {
24708 s.select_ranges(ranges.into_iter().map(|range| {
24709 let range = buffer_snapshot.anchor_before(range.start)
24710 ..buffer_snapshot.anchor_after(range.end);
24711 multibuffer_snapshot
24712 .anchor_range_in_excerpt(excerpt_id, range)
24713 .unwrap()
24714 }));
24715 },
24716 );
24717 editor.nav_history = nav_history;
24718 });
24719 }
24720 })
24721 .ok();
24722 });
24723 }
24724
24725 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24726 let snapshot = self.buffer.read(cx).read(cx);
24727 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24728 Some(
24729 ranges
24730 .iter()
24731 .map(move |range| {
24732 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24733 })
24734 .collect(),
24735 )
24736 }
24737
24738 fn selection_replacement_ranges(
24739 &self,
24740 range: Range<MultiBufferOffsetUtf16>,
24741 cx: &mut App,
24742 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24743 let selections = self
24744 .selections
24745 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24746 let newest_selection = selections
24747 .iter()
24748 .max_by_key(|selection| selection.id)
24749 .unwrap();
24750 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24751 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24752 let snapshot = self.buffer.read(cx).read(cx);
24753 selections
24754 .into_iter()
24755 .map(|mut selection| {
24756 selection.start.0.0 =
24757 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24758 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24759 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24760 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24761 })
24762 .collect()
24763 }
24764
24765 fn report_editor_event(
24766 &self,
24767 reported_event: ReportEditorEvent,
24768 file_extension: Option<String>,
24769 cx: &App,
24770 ) {
24771 if cfg!(any(test, feature = "test-support")) {
24772 return;
24773 }
24774
24775 let Some(project) = &self.project else { return };
24776
24777 // If None, we are in a file without an extension
24778 let file = self
24779 .buffer
24780 .read(cx)
24781 .as_singleton()
24782 .and_then(|b| b.read(cx).file());
24783 let file_extension = file_extension.or(file
24784 .as_ref()
24785 .and_then(|file| Path::new(file.file_name(cx)).extension())
24786 .and_then(|e| e.to_str())
24787 .map(|a| a.to_string()));
24788
24789 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24790 .map(|vim_mode| vim_mode.0)
24791 .unwrap_or(false);
24792
24793 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24794 let copilot_enabled = edit_predictions_provider
24795 == language::language_settings::EditPredictionProvider::Copilot;
24796 let copilot_enabled_for_language = self
24797 .buffer
24798 .read(cx)
24799 .language_settings(cx)
24800 .show_edit_predictions;
24801
24802 let project = project.read(cx);
24803 let event_type = reported_event.event_type();
24804
24805 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24806 telemetry::event!(
24807 event_type,
24808 type = if auto_saved {"autosave"} else {"manual"},
24809 file_extension,
24810 vim_mode,
24811 copilot_enabled,
24812 copilot_enabled_for_language,
24813 edit_predictions_provider,
24814 is_via_ssh = project.is_via_remote_server(),
24815 );
24816 } else {
24817 telemetry::event!(
24818 event_type,
24819 file_extension,
24820 vim_mode,
24821 copilot_enabled,
24822 copilot_enabled_for_language,
24823 edit_predictions_provider,
24824 is_via_ssh = project.is_via_remote_server(),
24825 );
24826 };
24827 }
24828
24829 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24830 /// with each line being an array of {text, highlight} objects.
24831 fn copy_highlight_json(
24832 &mut self,
24833 _: &CopyHighlightJson,
24834 window: &mut Window,
24835 cx: &mut Context<Self>,
24836 ) {
24837 #[derive(Serialize)]
24838 struct Chunk<'a> {
24839 text: String,
24840 highlight: Option<&'a str>,
24841 }
24842
24843 let snapshot = self.buffer.read(cx).snapshot(cx);
24844 let range = self
24845 .selected_text_range(false, window, cx)
24846 .and_then(|selection| {
24847 if selection.range.is_empty() {
24848 None
24849 } else {
24850 Some(
24851 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24852 selection.range.start,
24853 )))
24854 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24855 selection.range.end,
24856 ))),
24857 )
24858 }
24859 })
24860 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24861
24862 let chunks = snapshot.chunks(range, true);
24863 let mut lines = Vec::new();
24864 let mut line: VecDeque<Chunk> = VecDeque::new();
24865
24866 let Some(style) = self.style.as_ref() else {
24867 return;
24868 };
24869
24870 for chunk in chunks {
24871 let highlight = chunk
24872 .syntax_highlight_id
24873 .and_then(|id| id.name(&style.syntax));
24874 let mut chunk_lines = chunk.text.split('\n').peekable();
24875 while let Some(text) = chunk_lines.next() {
24876 let mut merged_with_last_token = false;
24877 if let Some(last_token) = line.back_mut()
24878 && last_token.highlight == highlight
24879 {
24880 last_token.text.push_str(text);
24881 merged_with_last_token = true;
24882 }
24883
24884 if !merged_with_last_token {
24885 line.push_back(Chunk {
24886 text: text.into(),
24887 highlight,
24888 });
24889 }
24890
24891 if chunk_lines.peek().is_some() {
24892 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24893 line.pop_front();
24894 }
24895 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24896 line.pop_back();
24897 }
24898
24899 lines.push(mem::take(&mut line));
24900 }
24901 }
24902 }
24903
24904 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24905 return;
24906 };
24907 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24908 }
24909
24910 pub fn open_context_menu(
24911 &mut self,
24912 _: &OpenContextMenu,
24913 window: &mut Window,
24914 cx: &mut Context<Self>,
24915 ) {
24916 self.request_autoscroll(Autoscroll::newest(), cx);
24917 let position = self
24918 .selections
24919 .newest_display(&self.display_snapshot(cx))
24920 .start;
24921 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24922 }
24923
24924 pub fn replay_insert_event(
24925 &mut self,
24926 text: &str,
24927 relative_utf16_range: Option<Range<isize>>,
24928 window: &mut Window,
24929 cx: &mut Context<Self>,
24930 ) {
24931 if !self.input_enabled {
24932 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24933 return;
24934 }
24935 if let Some(relative_utf16_range) = relative_utf16_range {
24936 let selections = self
24937 .selections
24938 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24939 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24940 let new_ranges = selections.into_iter().map(|range| {
24941 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24942 range
24943 .head()
24944 .0
24945 .0
24946 .saturating_add_signed(relative_utf16_range.start),
24947 ));
24948 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24949 range
24950 .head()
24951 .0
24952 .0
24953 .saturating_add_signed(relative_utf16_range.end),
24954 ));
24955 start..end
24956 });
24957 s.select_ranges(new_ranges);
24958 });
24959 }
24960
24961 self.handle_input(text, window, cx);
24962 }
24963
24964 pub fn is_focused(&self, window: &Window) -> bool {
24965 self.focus_handle.is_focused(window)
24966 }
24967
24968 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24969 cx.emit(EditorEvent::Focused);
24970
24971 if let Some(descendant) = self
24972 .last_focused_descendant
24973 .take()
24974 .and_then(|descendant| descendant.upgrade())
24975 {
24976 window.focus(&descendant, cx);
24977 } else {
24978 if let Some(blame) = self.blame.as_ref() {
24979 blame.update(cx, GitBlame::focus)
24980 }
24981
24982 self.blink_manager.update(cx, BlinkManager::enable);
24983 self.show_cursor_names(window, cx);
24984 self.buffer.update(cx, |buffer, cx| {
24985 buffer.finalize_last_transaction(cx);
24986 if self.leader_id.is_none() {
24987 buffer.set_active_selections(
24988 &self.selections.disjoint_anchors_arc(),
24989 self.selections.line_mode(),
24990 self.cursor_shape,
24991 cx,
24992 );
24993 }
24994 });
24995
24996 if let Some(position_map) = self.last_position_map.clone() {
24997 EditorElement::mouse_moved(
24998 self,
24999 &MouseMoveEvent {
25000 position: window.mouse_position(),
25001 pressed_button: None,
25002 modifiers: window.modifiers(),
25003 },
25004 &position_map,
25005 None,
25006 window,
25007 cx,
25008 );
25009 }
25010 }
25011 }
25012
25013 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25014 cx.emit(EditorEvent::FocusedIn)
25015 }
25016
25017 fn handle_focus_out(
25018 &mut self,
25019 event: FocusOutEvent,
25020 _window: &mut Window,
25021 cx: &mut Context<Self>,
25022 ) {
25023 if event.blurred != self.focus_handle {
25024 self.last_focused_descendant = Some(event.blurred);
25025 }
25026 self.selection_drag_state = SelectionDragState::None;
25027 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25028 }
25029
25030 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25031 self.blink_manager.update(cx, BlinkManager::disable);
25032 self.buffer
25033 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25034
25035 if let Some(blame) = self.blame.as_ref() {
25036 blame.update(cx, GitBlame::blur)
25037 }
25038 if !self.hover_state.focused(window, cx) {
25039 hide_hover(self, cx);
25040 }
25041 if !self
25042 .context_menu
25043 .borrow()
25044 .as_ref()
25045 .is_some_and(|context_menu| context_menu.focused(window, cx))
25046 {
25047 self.hide_context_menu(window, cx);
25048 }
25049 self.take_active_edit_prediction(cx);
25050 cx.emit(EditorEvent::Blurred);
25051 cx.notify();
25052 }
25053
25054 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25055 let mut pending: String = window
25056 .pending_input_keystrokes()
25057 .into_iter()
25058 .flatten()
25059 .filter_map(|keystroke| keystroke.key_char.clone())
25060 .collect();
25061
25062 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25063 pending = "".to_string();
25064 }
25065
25066 let existing_pending = self
25067 .text_highlights(HighlightKey::PendingInput, cx)
25068 .map(|(_, ranges)| ranges.to_vec());
25069 if existing_pending.is_none() && pending.is_empty() {
25070 return;
25071 }
25072 let transaction =
25073 self.transact(window, cx, |this, window, cx| {
25074 let selections = this
25075 .selections
25076 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25077 let edits = selections
25078 .iter()
25079 .map(|selection| (selection.end..selection.end, pending.clone()));
25080 this.edit(edits, cx);
25081 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25082 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25083 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25084 }));
25085 });
25086 if let Some(existing_ranges) = existing_pending {
25087 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25088 this.edit(edits, cx);
25089 }
25090 });
25091
25092 let snapshot = self.snapshot(window, cx);
25093 let ranges = self
25094 .selections
25095 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25096 .into_iter()
25097 .map(|selection| {
25098 snapshot.buffer_snapshot().anchor_after(selection.end)
25099 ..snapshot
25100 .buffer_snapshot()
25101 .anchor_before(selection.end + pending.len())
25102 })
25103 .collect();
25104
25105 if pending.is_empty() {
25106 self.clear_highlights(HighlightKey::PendingInput, cx);
25107 } else {
25108 self.highlight_text(
25109 HighlightKey::PendingInput,
25110 ranges,
25111 HighlightStyle {
25112 underline: Some(UnderlineStyle {
25113 thickness: px(1.),
25114 color: None,
25115 wavy: false,
25116 }),
25117 ..Default::default()
25118 },
25119 cx,
25120 );
25121 }
25122
25123 self.ime_transaction = self.ime_transaction.or(transaction);
25124 if let Some(transaction) = self.ime_transaction {
25125 self.buffer.update(cx, |buffer, cx| {
25126 buffer.group_until_transaction(transaction, cx);
25127 });
25128 }
25129
25130 if self
25131 .text_highlights(HighlightKey::PendingInput, cx)
25132 .is_none()
25133 {
25134 self.ime_transaction.take();
25135 }
25136 }
25137
25138 pub fn register_action_renderer(
25139 &mut self,
25140 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25141 ) -> Subscription {
25142 let id = self.next_editor_action_id.post_inc();
25143 self.editor_actions
25144 .borrow_mut()
25145 .insert(id, Box::new(listener));
25146
25147 let editor_actions = self.editor_actions.clone();
25148 Subscription::new(move || {
25149 editor_actions.borrow_mut().remove(&id);
25150 })
25151 }
25152
25153 pub fn register_action<A: Action>(
25154 &mut self,
25155 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25156 ) -> Subscription {
25157 let id = self.next_editor_action_id.post_inc();
25158 let listener = Arc::new(listener);
25159 self.editor_actions.borrow_mut().insert(
25160 id,
25161 Box::new(move |_, window, _| {
25162 let listener = listener.clone();
25163 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25164 let action = action.downcast_ref().unwrap();
25165 if phase == DispatchPhase::Bubble {
25166 listener(action, window, cx)
25167 }
25168 })
25169 }),
25170 );
25171
25172 let editor_actions = self.editor_actions.clone();
25173 Subscription::new(move || {
25174 editor_actions.borrow_mut().remove(&id);
25175 })
25176 }
25177
25178 pub fn file_header_size(&self) -> u32 {
25179 FILE_HEADER_HEIGHT
25180 }
25181
25182 pub fn restore(
25183 &mut self,
25184 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25185 window: &mut Window,
25186 cx: &mut Context<Self>,
25187 ) {
25188 self.buffer().update(cx, |multi_buffer, cx| {
25189 for (buffer_id, changes) in revert_changes {
25190 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25191 buffer.update(cx, |buffer, cx| {
25192 buffer.edit(
25193 changes
25194 .into_iter()
25195 .map(|(range, text)| (range, text.to_string())),
25196 None,
25197 cx,
25198 );
25199 });
25200 }
25201 }
25202 });
25203 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25204 selections.refresh()
25205 });
25206 }
25207
25208 pub fn to_pixel_point(
25209 &mut self,
25210 source: Anchor,
25211 editor_snapshot: &EditorSnapshot,
25212 window: &mut Window,
25213 cx: &mut App,
25214 ) -> Option<gpui::Point<Pixels>> {
25215 let source_point = source.to_display_point(editor_snapshot);
25216 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25217 }
25218
25219 pub fn display_to_pixel_point(
25220 &mut self,
25221 source: DisplayPoint,
25222 editor_snapshot: &EditorSnapshot,
25223 window: &mut Window,
25224 cx: &mut App,
25225 ) -> Option<gpui::Point<Pixels>> {
25226 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25227 let text_layout_details = self.text_layout_details(window, cx);
25228 let scroll_top = text_layout_details
25229 .scroll_anchor
25230 .scroll_position(editor_snapshot)
25231 .y;
25232
25233 if source.row().as_f64() < scroll_top.floor() {
25234 return None;
25235 }
25236 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25237 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25238 Some(gpui::Point::new(source_x, source_y))
25239 }
25240
25241 pub fn has_visible_completions_menu(&self) -> bool {
25242 !self.edit_prediction_preview_is_active()
25243 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25244 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25245 })
25246 }
25247
25248 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25249 if self.mode.is_minimap() {
25250 return;
25251 }
25252 self.addons
25253 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25254 }
25255
25256 pub fn unregister_addon<T: Addon>(&mut self) {
25257 self.addons.remove(&std::any::TypeId::of::<T>());
25258 }
25259
25260 pub fn addon<T: Addon>(&self) -> Option<&T> {
25261 let type_id = std::any::TypeId::of::<T>();
25262 self.addons
25263 .get(&type_id)
25264 .and_then(|item| item.to_any().downcast_ref::<T>())
25265 }
25266
25267 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25268 let type_id = std::any::TypeId::of::<T>();
25269 self.addons
25270 .get_mut(&type_id)
25271 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25272 }
25273
25274 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25275 let text_layout_details = self.text_layout_details(window, cx);
25276 let style = &text_layout_details.editor_style;
25277 let font_id = window.text_system().resolve_font(&style.text.font());
25278 let font_size = style.text.font_size.to_pixels(window.rem_size());
25279 let line_height = style.text.line_height_in_pixels(window.rem_size());
25280 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25281 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25282
25283 CharacterDimensions {
25284 em_width,
25285 em_advance,
25286 line_height,
25287 }
25288 }
25289
25290 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25291 self.load_diff_task.clone()
25292 }
25293
25294 fn read_metadata_from_db(
25295 &mut self,
25296 item_id: u64,
25297 workspace_id: WorkspaceId,
25298 window: &mut Window,
25299 cx: &mut Context<Editor>,
25300 ) {
25301 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25302 && !self.mode.is_minimap()
25303 && WorkspaceSettings::get(None, cx).restore_on_startup
25304 != RestoreOnStartupBehavior::EmptyTab
25305 {
25306 let buffer_snapshot = OnceCell::new();
25307
25308 // Get file path for path-based fold lookup
25309 let file_path: Option<Arc<Path>> =
25310 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25311 project::File::from_dyn(buffer.read(cx).file())
25312 .map(|file| Arc::from(file.abs_path(cx)))
25313 });
25314
25315 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25316 let (folds, needs_migration) = if let Some(ref path) = file_path {
25317 if let Some(folds) = DB.get_file_folds(workspace_id, path).log_err()
25318 && !folds.is_empty()
25319 {
25320 (Some(folds), false)
25321 } else if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
25322 && !folds.is_empty()
25323 {
25324 // Found old editor_folds data, will migrate to file_folds
25325 (Some(folds), true)
25326 } else {
25327 (None, false)
25328 }
25329 } else {
25330 // No file path, try editor_folds as fallback
25331 let folds = DB.get_editor_folds(item_id, workspace_id).log_err();
25332 (folds.filter(|f| !f.is_empty()), false)
25333 };
25334
25335 if let Some(folds) = folds {
25336 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25337 let snapshot_len = snapshot.len().0;
25338
25339 // Helper: search for fingerprint in buffer, return offset if found
25340 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25341 // Ensure we start at a character boundary (defensive)
25342 let search_start = snapshot
25343 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25344 .0;
25345 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25346
25347 let mut byte_offset = search_start;
25348 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25349 if byte_offset > search_end {
25350 break;
25351 }
25352 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25353 return Some(byte_offset);
25354 }
25355 byte_offset += ch.len_utf8();
25356 }
25357 None
25358 };
25359
25360 // Track search position to handle duplicate fingerprints correctly.
25361 // Folds are stored in document order, so we advance after each match.
25362 let mut search_start = 0usize;
25363
25364 // Collect db_folds for migration (only folds with valid fingerprints)
25365 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25366
25367 let valid_folds: Vec<_> = folds
25368 .into_iter()
25369 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25370 // Skip folds without fingerprints (old data before migration)
25371 let sfp = start_fp?;
25372 let efp = end_fp?;
25373 let efp_len = efp.len();
25374
25375 // Fast path: check if fingerprints match at stored offsets
25376 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25377 let start_matches = stored_start < snapshot_len
25378 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25379 let efp_check_pos = stored_end.saturating_sub(efp_len);
25380 let end_matches = efp_check_pos >= stored_start
25381 && stored_end <= snapshot_len
25382 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25383
25384 let (new_start, new_end) = if start_matches && end_matches {
25385 // Offsets unchanged, use stored values
25386 (stored_start, stored_end)
25387 } else if sfp == efp {
25388 // Short fold: identical fingerprints can only match once per search
25389 // Use stored fold length to compute new_end
25390 let new_start = find_fingerprint(&sfp, search_start)?;
25391 let fold_len = stored_end - stored_start;
25392 let new_end = new_start + fold_len;
25393 (new_start, new_end)
25394 } else {
25395 // Slow path: search for fingerprints in buffer
25396 let new_start = find_fingerprint(&sfp, search_start)?;
25397 // Search for end_fp after start, then add efp_len to get actual fold end
25398 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25399 let new_end = efp_pos + efp_len;
25400 (new_start, new_end)
25401 };
25402
25403 // Advance search position for next fold
25404 search_start = new_end;
25405
25406 // Validate fold makes sense (end must be after start)
25407 if new_end <= new_start {
25408 return None;
25409 }
25410
25411 // Collect for migration if needed
25412 if needs_migration {
25413 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25414 }
25415
25416 Some(
25417 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25418 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25419 )
25420 })
25421 .collect();
25422
25423 if !valid_folds.is_empty() {
25424 self.fold_ranges(valid_folds, false, window, cx);
25425
25426 // Migrate from editor_folds to file_folds if we loaded from old table
25427 if needs_migration {
25428 if let Some(ref path) = file_path {
25429 let path = path.clone();
25430 cx.spawn(async move |_, _| {
25431 DB.save_file_folds(workspace_id, path, db_folds_for_migration)
25432 .await
25433 .log_err();
25434 })
25435 .detach();
25436 }
25437 }
25438 }
25439 }
25440
25441 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25442 && !selections.is_empty()
25443 {
25444 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25445 // skip adding the initial selection to selection history
25446 self.selection_history.mode = SelectionHistoryMode::Skipping;
25447 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25448 s.select_ranges(selections.into_iter().map(|(start, end)| {
25449 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25450 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25451 }));
25452 });
25453 self.selection_history.mode = SelectionHistoryMode::Normal;
25454 };
25455 }
25456
25457 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25458 }
25459
25460 /// Load folds from the file_folds database table by file path.
25461 /// Used when manually opening a file that was previously closed.
25462 fn load_folds_from_db(
25463 &mut self,
25464 workspace_id: WorkspaceId,
25465 file_path: PathBuf,
25466 window: &mut Window,
25467 cx: &mut Context<Editor>,
25468 ) {
25469 if self.mode.is_minimap()
25470 || WorkspaceSettings::get(None, cx).restore_on_startup
25471 == RestoreOnStartupBehavior::EmptyTab
25472 {
25473 return;
25474 }
25475
25476 let Some(folds) = DB.get_file_folds(workspace_id, &file_path).log_err() else {
25477 return;
25478 };
25479 if folds.is_empty() {
25480 return;
25481 }
25482
25483 let snapshot = self.buffer.read(cx).snapshot(cx);
25484 let snapshot_len = snapshot.len().0;
25485
25486 // Helper: search for fingerprint in buffer, return offset if found
25487 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25488 let search_start = snapshot
25489 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25490 .0;
25491 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25492
25493 let mut byte_offset = search_start;
25494 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25495 if byte_offset > search_end {
25496 break;
25497 }
25498 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25499 return Some(byte_offset);
25500 }
25501 byte_offset += ch.len_utf8();
25502 }
25503 None
25504 };
25505
25506 let mut search_start = 0usize;
25507
25508 let valid_folds: Vec<_> = folds
25509 .into_iter()
25510 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25511 let sfp = start_fp?;
25512 let efp = end_fp?;
25513 let efp_len = efp.len();
25514
25515 let start_matches = stored_start < snapshot_len
25516 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25517 let efp_check_pos = stored_end.saturating_sub(efp_len);
25518 let end_matches = efp_check_pos >= stored_start
25519 && stored_end <= snapshot_len
25520 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25521
25522 let (new_start, new_end) = if start_matches && end_matches {
25523 (stored_start, stored_end)
25524 } else if sfp == efp {
25525 let new_start = find_fingerprint(&sfp, search_start)?;
25526 let fold_len = stored_end - stored_start;
25527 let new_end = new_start + fold_len;
25528 (new_start, new_end)
25529 } else {
25530 let new_start = find_fingerprint(&sfp, search_start)?;
25531 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25532 let new_end = efp_pos + efp_len;
25533 (new_start, new_end)
25534 };
25535
25536 search_start = new_end;
25537
25538 if new_end <= new_start {
25539 return None;
25540 }
25541
25542 Some(
25543 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25544 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25545 )
25546 })
25547 .collect();
25548
25549 if !valid_folds.is_empty() {
25550 self.fold_ranges(valid_folds, false, window, cx);
25551 }
25552 }
25553
25554 fn update_lsp_data(
25555 &mut self,
25556 for_buffer: Option<BufferId>,
25557 window: &mut Window,
25558 cx: &mut Context<'_, Self>,
25559 ) {
25560 if !self.enable_lsp_data {
25561 return;
25562 }
25563
25564 if let Some(buffer_id) = for_buffer {
25565 self.pull_diagnostics(buffer_id, window, cx);
25566 }
25567 self.refresh_semantic_tokens(for_buffer, None, cx);
25568 self.refresh_document_colors(for_buffer, window, cx);
25569 self.refresh_folding_ranges(for_buffer, window, cx);
25570 self.refresh_document_symbols(for_buffer, cx);
25571 }
25572
25573 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25574 if !self.mode().is_full() {
25575 return;
25576 }
25577 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25578 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25579 }
25580 }
25581
25582 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25583 if !self.mode().is_full() {
25584 return;
25585 }
25586
25587 if !self.registered_buffers.contains_key(&buffer_id)
25588 && let Some(project) = self.project.as_ref()
25589 {
25590 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25591 project.update(cx, |project, cx| {
25592 self.registered_buffers.insert(
25593 buffer_id,
25594 project.register_buffer_with_language_servers(&buffer, cx),
25595 );
25596 });
25597 } else {
25598 self.registered_buffers.remove(&buffer_id);
25599 }
25600 }
25601 }
25602
25603 fn create_style(&self, cx: &App) -> EditorStyle {
25604 let settings = ThemeSettings::get_global(cx);
25605
25606 let mut text_style = match self.mode {
25607 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25608 color: cx.theme().colors().editor_foreground,
25609 font_family: settings.ui_font.family.clone(),
25610 font_features: settings.ui_font.features.clone(),
25611 font_fallbacks: settings.ui_font.fallbacks.clone(),
25612 font_size: rems(0.875).into(),
25613 font_weight: settings.ui_font.weight,
25614 line_height: relative(settings.buffer_line_height.value()),
25615 ..Default::default()
25616 },
25617 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25618 color: cx.theme().colors().editor_foreground,
25619 font_family: settings.buffer_font.family.clone(),
25620 font_features: settings.buffer_font.features.clone(),
25621 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25622 font_size: settings.buffer_font_size(cx).into(),
25623 font_weight: settings.buffer_font.weight,
25624 line_height: relative(settings.buffer_line_height.value()),
25625 ..Default::default()
25626 },
25627 };
25628 if let Some(text_style_refinement) = &self.text_style_refinement {
25629 text_style.refine(text_style_refinement)
25630 }
25631
25632 let background = match self.mode {
25633 EditorMode::SingleLine => cx.theme().system().transparent,
25634 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25635 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25636 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25637 };
25638
25639 EditorStyle {
25640 background,
25641 border: cx.theme().colors().border,
25642 local_player: cx.theme().players().local(),
25643 text: text_style,
25644 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25645 syntax: cx.theme().syntax().clone(),
25646 status: cx.theme().status().clone(),
25647 inlay_hints_style: make_inlay_hints_style(cx),
25648 edit_prediction_styles: make_suggestion_styles(cx),
25649 unnecessary_code_fade: settings.unnecessary_code_fade,
25650 show_underlines: self.diagnostics_enabled(),
25651 }
25652 }
25653
25654 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
25655 let multibuffer = self.buffer().read(cx);
25656 let is_singleton = multibuffer.is_singleton();
25657 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25658 let buffer = multibuffer.buffer(*buffer_id)?;
25659
25660 let buffer = buffer.read(cx);
25661 let settings = ThemeSettings::get_global(cx);
25662 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25663 let mut breadcrumbs = if is_singleton {
25664 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25665 buffer
25666 .snapshot()
25667 .resolve_file_path(
25668 self.project
25669 .as_ref()
25670 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25671 .unwrap_or_default(),
25672 cx,
25673 )
25674 .unwrap_or_else(|| {
25675 if multibuffer.is_singleton() {
25676 multibuffer.title(cx).to_string()
25677 } else {
25678 "untitled".to_string()
25679 }
25680 })
25681 });
25682 vec![BreadcrumbText {
25683 text,
25684 highlights: None,
25685 font: Some(settings.buffer_font.clone()),
25686 }]
25687 } else {
25688 vec![]
25689 };
25690
25691 breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
25692 text: symbol.text.clone(),
25693 highlights: Some(symbol.highlight_ranges.clone()),
25694 font: Some(settings.buffer_font.clone()),
25695 }));
25696 Some(breadcrumbs)
25697 }
25698
25699 fn disable_lsp_data(&mut self) {
25700 self.enable_lsp_data = false;
25701 }
25702
25703 fn disable_runnables(&mut self) {
25704 self.enable_runnables = false;
25705 }
25706}
25707
25708fn edit_for_markdown_paste<'a>(
25709 buffer: &MultiBufferSnapshot,
25710 range: Range<MultiBufferOffset>,
25711 to_insert: &'a str,
25712 url: Option<url::Url>,
25713) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25714 if url.is_none() {
25715 return (range, Cow::Borrowed(to_insert));
25716 };
25717
25718 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25719
25720 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25721 Cow::Borrowed(to_insert)
25722 } else {
25723 Cow::Owned(format!("[{old_text}]({to_insert})"))
25724 };
25725 (range, new_text)
25726}
25727
25728fn process_completion_for_edit(
25729 completion: &Completion,
25730 intent: CompletionIntent,
25731 buffer: &Entity<Buffer>,
25732 cursor_position: &text::Anchor,
25733 cx: &mut Context<Editor>,
25734) -> CompletionEdit {
25735 let buffer = buffer.read(cx);
25736 let buffer_snapshot = buffer.snapshot();
25737 let (snippet, new_text) = if completion.is_snippet() {
25738 let mut snippet_source = completion.new_text.clone();
25739 // Workaround for typescript language server issues so that methods don't expand within
25740 // strings and functions with type expressions. The previous point is used because the query
25741 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25742 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25743 let previous_point = if previous_point.column > 0 {
25744 cursor_position.to_previous_offset(&buffer_snapshot)
25745 } else {
25746 cursor_position.to_offset(&buffer_snapshot)
25747 };
25748 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25749 && scope.prefers_label_for_snippet_in_completion()
25750 && let Some(label) = completion.label()
25751 && matches!(
25752 completion.kind(),
25753 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25754 )
25755 {
25756 snippet_source = label;
25757 }
25758 match Snippet::parse(&snippet_source).log_err() {
25759 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25760 None => (None, completion.new_text.clone()),
25761 }
25762 } else {
25763 (None, completion.new_text.clone())
25764 };
25765
25766 let mut range_to_replace = {
25767 let replace_range = &completion.replace_range;
25768 if let CompletionSource::Lsp {
25769 insert_range: Some(insert_range),
25770 ..
25771 } = &completion.source
25772 {
25773 debug_assert_eq!(
25774 insert_range.start, replace_range.start,
25775 "insert_range and replace_range should start at the same position"
25776 );
25777 debug_assert!(
25778 insert_range
25779 .start
25780 .cmp(cursor_position, &buffer_snapshot)
25781 .is_le(),
25782 "insert_range should start before or at cursor position"
25783 );
25784 debug_assert!(
25785 replace_range
25786 .start
25787 .cmp(cursor_position, &buffer_snapshot)
25788 .is_le(),
25789 "replace_range should start before or at cursor position"
25790 );
25791
25792 let should_replace = match intent {
25793 CompletionIntent::CompleteWithInsert => false,
25794 CompletionIntent::CompleteWithReplace => true,
25795 CompletionIntent::Complete | CompletionIntent::Compose => {
25796 let insert_mode =
25797 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25798 .completions
25799 .lsp_insert_mode;
25800 match insert_mode {
25801 LspInsertMode::Insert => false,
25802 LspInsertMode::Replace => true,
25803 LspInsertMode::ReplaceSubsequence => {
25804 let mut text_to_replace = buffer.chars_for_range(
25805 buffer.anchor_before(replace_range.start)
25806 ..buffer.anchor_after(replace_range.end),
25807 );
25808 let mut current_needle = text_to_replace.next();
25809 for haystack_ch in completion.label.text.chars() {
25810 if let Some(needle_ch) = current_needle
25811 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25812 {
25813 current_needle = text_to_replace.next();
25814 }
25815 }
25816 current_needle.is_none()
25817 }
25818 LspInsertMode::ReplaceSuffix => {
25819 if replace_range
25820 .end
25821 .cmp(cursor_position, &buffer_snapshot)
25822 .is_gt()
25823 {
25824 let range_after_cursor = *cursor_position..replace_range.end;
25825 let text_after_cursor = buffer
25826 .text_for_range(
25827 buffer.anchor_before(range_after_cursor.start)
25828 ..buffer.anchor_after(range_after_cursor.end),
25829 )
25830 .collect::<String>()
25831 .to_ascii_lowercase();
25832 completion
25833 .label
25834 .text
25835 .to_ascii_lowercase()
25836 .ends_with(&text_after_cursor)
25837 } else {
25838 true
25839 }
25840 }
25841 }
25842 }
25843 };
25844
25845 if should_replace {
25846 replace_range.clone()
25847 } else {
25848 insert_range.clone()
25849 }
25850 } else {
25851 replace_range.clone()
25852 }
25853 };
25854
25855 if range_to_replace
25856 .end
25857 .cmp(cursor_position, &buffer_snapshot)
25858 .is_lt()
25859 {
25860 range_to_replace.end = *cursor_position;
25861 }
25862
25863 let replace_range = range_to_replace.to_offset(buffer);
25864 CompletionEdit {
25865 new_text,
25866 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25867 snippet,
25868 }
25869}
25870
25871struct CompletionEdit {
25872 new_text: String,
25873 replace_range: Range<BufferOffset>,
25874 snippet: Option<Snippet>,
25875}
25876
25877fn comment_delimiter_for_newline(
25878 start_point: &Point,
25879 buffer: &MultiBufferSnapshot,
25880 language: &LanguageScope,
25881) -> Option<Arc<str>> {
25882 let delimiters = language.line_comment_prefixes();
25883 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25884 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25885
25886 let num_of_whitespaces = snapshot
25887 .chars_for_range(range.clone())
25888 .take_while(|c| c.is_whitespace())
25889 .count();
25890 let comment_candidate = snapshot
25891 .chars_for_range(range.clone())
25892 .skip(num_of_whitespaces)
25893 .take(max_len_of_delimiter + 2)
25894 .collect::<String>();
25895 let (delimiter, trimmed_len, is_repl) = delimiters
25896 .iter()
25897 .filter_map(|delimiter| {
25898 let prefix = delimiter.trim_end();
25899 if comment_candidate.starts_with(prefix) {
25900 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
25901 {
25902 stripped_comment.starts_with(" %%")
25903 } else {
25904 false
25905 };
25906 Some((delimiter, prefix.len(), is_repl))
25907 } else {
25908 None
25909 }
25910 })
25911 .max_by_key(|(_, len, _)| *len)?;
25912
25913 if let Some(BlockCommentConfig {
25914 start: block_start, ..
25915 }) = language.block_comment()
25916 {
25917 let block_start_trimmed = block_start.trim_end();
25918 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25919 let line_content = snapshot
25920 .chars_for_range(range.clone())
25921 .skip(num_of_whitespaces)
25922 .take(block_start_trimmed.len())
25923 .collect::<String>();
25924
25925 if line_content.starts_with(block_start_trimmed) {
25926 return None;
25927 }
25928 }
25929 }
25930
25931 let cursor_is_placed_after_comment_marker =
25932 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25933 if cursor_is_placed_after_comment_marker {
25934 if !is_repl {
25935 return Some(delimiter.clone());
25936 }
25937
25938 let line_content_after_cursor: String = snapshot
25939 .chars_for_range(range)
25940 .skip(start_point.column as usize)
25941 .collect();
25942
25943 if line_content_after_cursor.trim().is_empty() {
25944 return None;
25945 } else {
25946 return Some(delimiter.clone());
25947 }
25948 } else {
25949 None
25950 }
25951}
25952
25953fn documentation_delimiter_for_newline(
25954 start_point: &Point,
25955 buffer: &MultiBufferSnapshot,
25956 language: &LanguageScope,
25957 newline_config: &mut NewlineConfig,
25958) -> Option<Arc<str>> {
25959 let BlockCommentConfig {
25960 start: start_tag,
25961 end: end_tag,
25962 prefix: delimiter,
25963 tab_size: len,
25964 } = language.documentation_comment()?;
25965 let is_within_block_comment = buffer
25966 .language_scope_at(*start_point)
25967 .is_some_and(|scope| scope.override_name() == Some("comment"));
25968 if !is_within_block_comment {
25969 return None;
25970 }
25971
25972 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25973
25974 let num_of_whitespaces = snapshot
25975 .chars_for_range(range.clone())
25976 .take_while(|c| c.is_whitespace())
25977 .count();
25978
25979 // 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.
25980 let column = start_point.column;
25981 let cursor_is_after_start_tag = {
25982 let start_tag_len = start_tag.len();
25983 let start_tag_line = snapshot
25984 .chars_for_range(range.clone())
25985 .skip(num_of_whitespaces)
25986 .take(start_tag_len)
25987 .collect::<String>();
25988 if start_tag_line.starts_with(start_tag.as_ref()) {
25989 num_of_whitespaces + start_tag_len <= column as usize
25990 } else {
25991 false
25992 }
25993 };
25994
25995 let cursor_is_after_delimiter = {
25996 let delimiter_trim = delimiter.trim_end();
25997 let delimiter_line = snapshot
25998 .chars_for_range(range.clone())
25999 .skip(num_of_whitespaces)
26000 .take(delimiter_trim.len())
26001 .collect::<String>();
26002 if delimiter_line.starts_with(delimiter_trim) {
26003 num_of_whitespaces + delimiter_trim.len() <= column as usize
26004 } else {
26005 false
26006 }
26007 };
26008
26009 let mut needs_extra_line = false;
26010 let mut extra_line_additional_indent = IndentSize::spaces(0);
26011
26012 let cursor_is_before_end_tag_if_exists = {
26013 let mut char_position = 0u32;
26014 let mut end_tag_offset = None;
26015
26016 'outer: for chunk in snapshot.text_for_range(range) {
26017 if let Some(byte_pos) = chunk.find(&**end_tag) {
26018 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26019 end_tag_offset = Some(char_position + chars_before_match);
26020 break 'outer;
26021 }
26022 char_position += chunk.chars().count() as u32;
26023 }
26024
26025 if let Some(end_tag_offset) = end_tag_offset {
26026 let cursor_is_before_end_tag = column <= end_tag_offset;
26027 if cursor_is_after_start_tag {
26028 if cursor_is_before_end_tag {
26029 needs_extra_line = true;
26030 }
26031 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26032 if cursor_is_at_start_of_end_tag {
26033 extra_line_additional_indent.len = *len;
26034 }
26035 }
26036 cursor_is_before_end_tag
26037 } else {
26038 true
26039 }
26040 };
26041
26042 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26043 && cursor_is_before_end_tag_if_exists
26044 {
26045 let additional_indent = if cursor_is_after_start_tag {
26046 IndentSize::spaces(*len)
26047 } else {
26048 IndentSize::spaces(0)
26049 };
26050
26051 *newline_config = NewlineConfig::Newline {
26052 additional_indent,
26053 extra_line_additional_indent: if needs_extra_line {
26054 Some(extra_line_additional_indent)
26055 } else {
26056 None
26057 },
26058 prevent_auto_indent: true,
26059 };
26060 Some(delimiter.clone())
26061 } else {
26062 None
26063 }
26064}
26065
26066const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26067
26068fn list_delimiter_for_newline(
26069 start_point: &Point,
26070 buffer: &MultiBufferSnapshot,
26071 language: &LanguageScope,
26072 newline_config: &mut NewlineConfig,
26073) -> Option<Arc<str>> {
26074 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26075
26076 let num_of_whitespaces = snapshot
26077 .chars_for_range(range.clone())
26078 .take_while(|c| c.is_whitespace())
26079 .count();
26080
26081 let task_list_entries: Vec<_> = language
26082 .task_list()
26083 .into_iter()
26084 .flat_map(|config| {
26085 config
26086 .prefixes
26087 .iter()
26088 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26089 })
26090 .collect();
26091 let unordered_list_entries: Vec<_> = language
26092 .unordered_list()
26093 .iter()
26094 .map(|marker| (marker.as_ref(), marker.as_ref()))
26095 .collect();
26096
26097 let all_entries: Vec<_> = task_list_entries
26098 .into_iter()
26099 .chain(unordered_list_entries)
26100 .collect();
26101
26102 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26103 let candidate: String = snapshot
26104 .chars_for_range(range.clone())
26105 .skip(num_of_whitespaces)
26106 .take(max_prefix_len)
26107 .collect();
26108
26109 if let Some((prefix, continuation)) = all_entries
26110 .iter()
26111 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26112 .max_by_key(|(prefix, _)| prefix.len())
26113 {
26114 let end_of_prefix = num_of_whitespaces + prefix.len();
26115 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26116 let has_content_after_marker = snapshot
26117 .chars_for_range(range)
26118 .skip(end_of_prefix)
26119 .any(|c| !c.is_whitespace());
26120
26121 if has_content_after_marker && cursor_is_after_prefix {
26122 return Some((*continuation).into());
26123 }
26124
26125 if start_point.column as usize == end_of_prefix {
26126 if num_of_whitespaces == 0 {
26127 *newline_config = NewlineConfig::ClearCurrentLine;
26128 } else {
26129 *newline_config = NewlineConfig::UnindentCurrentLine {
26130 continuation: (*continuation).into(),
26131 };
26132 }
26133 }
26134
26135 return None;
26136 }
26137 }
26138
26139 let candidate: String = snapshot
26140 .chars_for_range(range.clone())
26141 .skip(num_of_whitespaces)
26142 .take(ORDERED_LIST_MAX_MARKER_LEN)
26143 .collect();
26144
26145 for ordered_config in language.ordered_list() {
26146 let regex = match Regex::new(&ordered_config.pattern) {
26147 Ok(r) => r,
26148 Err(_) => continue,
26149 };
26150
26151 if let Some(captures) = regex.captures(&candidate) {
26152 let full_match = captures.get(0)?;
26153 let marker_len = full_match.len();
26154 let end_of_prefix = num_of_whitespaces + marker_len;
26155 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26156
26157 let has_content_after_marker = snapshot
26158 .chars_for_range(range)
26159 .skip(end_of_prefix)
26160 .any(|c| !c.is_whitespace());
26161
26162 if has_content_after_marker && cursor_is_after_prefix {
26163 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26164 let continuation = ordered_config
26165 .format
26166 .replace("{1}", &(number + 1).to_string());
26167 return Some(continuation.into());
26168 }
26169
26170 if start_point.column as usize == end_of_prefix {
26171 let continuation = ordered_config.format.replace("{1}", "1");
26172 if num_of_whitespaces == 0 {
26173 *newline_config = NewlineConfig::ClearCurrentLine;
26174 } else {
26175 *newline_config = NewlineConfig::UnindentCurrentLine {
26176 continuation: continuation.into(),
26177 };
26178 }
26179 }
26180
26181 return None;
26182 }
26183 }
26184
26185 None
26186}
26187
26188fn is_list_prefix_row(
26189 row: MultiBufferRow,
26190 buffer: &MultiBufferSnapshot,
26191 language: &LanguageScope,
26192) -> bool {
26193 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26194 return false;
26195 };
26196
26197 let num_of_whitespaces = snapshot
26198 .chars_for_range(range.clone())
26199 .take_while(|c| c.is_whitespace())
26200 .count();
26201
26202 let task_list_prefixes: Vec<_> = language
26203 .task_list()
26204 .into_iter()
26205 .flat_map(|config| {
26206 config
26207 .prefixes
26208 .iter()
26209 .map(|p| p.as_ref())
26210 .collect::<Vec<_>>()
26211 })
26212 .collect();
26213 let unordered_list_markers: Vec<_> = language
26214 .unordered_list()
26215 .iter()
26216 .map(|marker| marker.as_ref())
26217 .collect();
26218 let all_prefixes: Vec<_> = task_list_prefixes
26219 .into_iter()
26220 .chain(unordered_list_markers)
26221 .collect();
26222 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26223 let candidate: String = snapshot
26224 .chars_for_range(range.clone())
26225 .skip(num_of_whitespaces)
26226 .take(max_prefix_len)
26227 .collect();
26228 if all_prefixes
26229 .iter()
26230 .any(|prefix| candidate.starts_with(*prefix))
26231 {
26232 return true;
26233 }
26234 }
26235
26236 let ordered_list_candidate: String = snapshot
26237 .chars_for_range(range)
26238 .skip(num_of_whitespaces)
26239 .take(ORDERED_LIST_MAX_MARKER_LEN)
26240 .collect();
26241 for ordered_config in language.ordered_list() {
26242 let regex = match Regex::new(&ordered_config.pattern) {
26243 Ok(r) => r,
26244 Err(_) => continue,
26245 };
26246 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26247 return captures.get(0).is_some();
26248 }
26249 }
26250
26251 false
26252}
26253
26254#[derive(Debug)]
26255enum NewlineConfig {
26256 /// Insert newline with optional additional indent and optional extra blank line
26257 Newline {
26258 additional_indent: IndentSize,
26259 extra_line_additional_indent: Option<IndentSize>,
26260 prevent_auto_indent: bool,
26261 },
26262 /// Clear the current line
26263 ClearCurrentLine,
26264 /// Unindent the current line and add continuation
26265 UnindentCurrentLine { continuation: Arc<str> },
26266}
26267
26268impl NewlineConfig {
26269 fn has_extra_line(&self) -> bool {
26270 matches!(
26271 self,
26272 Self::Newline {
26273 extra_line_additional_indent: Some(_),
26274 ..
26275 }
26276 )
26277 }
26278
26279 fn insert_extra_newline_brackets(
26280 buffer: &MultiBufferSnapshot,
26281 range: Range<MultiBufferOffset>,
26282 language: &language::LanguageScope,
26283 ) -> bool {
26284 let leading_whitespace_len = buffer
26285 .reversed_chars_at(range.start)
26286 .take_while(|c| c.is_whitespace() && *c != '\n')
26287 .map(|c| c.len_utf8())
26288 .sum::<usize>();
26289 let trailing_whitespace_len = buffer
26290 .chars_at(range.end)
26291 .take_while(|c| c.is_whitespace() && *c != '\n')
26292 .map(|c| c.len_utf8())
26293 .sum::<usize>();
26294 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26295
26296 language.brackets().any(|(pair, enabled)| {
26297 let pair_start = pair.start.trim_end();
26298 let pair_end = pair.end.trim_start();
26299
26300 enabled
26301 && pair.newline
26302 && buffer.contains_str_at(range.end, pair_end)
26303 && buffer.contains_str_at(
26304 range.start.saturating_sub_usize(pair_start.len()),
26305 pair_start,
26306 )
26307 })
26308 }
26309
26310 fn insert_extra_newline_tree_sitter(
26311 buffer: &MultiBufferSnapshot,
26312 range: Range<MultiBufferOffset>,
26313 ) -> bool {
26314 let (buffer, range) = match buffer
26315 .range_to_buffer_ranges(range.start..=range.end)
26316 .as_slice()
26317 {
26318 [(buffer, range, _)] => (*buffer, range.clone()),
26319 _ => return false,
26320 };
26321 let pair = {
26322 let mut result: Option<BracketMatch<usize>> = None;
26323
26324 for pair in buffer
26325 .all_bracket_ranges(range.start.0..range.end.0)
26326 .filter(move |pair| {
26327 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26328 })
26329 {
26330 let len = pair.close_range.end - pair.open_range.start;
26331
26332 if let Some(existing) = &result {
26333 let existing_len = existing.close_range.end - existing.open_range.start;
26334 if len > existing_len {
26335 continue;
26336 }
26337 }
26338
26339 result = Some(pair);
26340 }
26341
26342 result
26343 };
26344 let Some(pair) = pair else {
26345 return false;
26346 };
26347 pair.newline_only
26348 && buffer
26349 .chars_for_range(pair.open_range.end..range.start.0)
26350 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26351 .all(|c| c.is_whitespace() && c != '\n')
26352 }
26353}
26354
26355fn update_uncommitted_diff_for_buffer(
26356 editor: Entity<Editor>,
26357 project: &Entity<Project>,
26358 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26359 buffer: Entity<MultiBuffer>,
26360 cx: &mut App,
26361) -> Task<()> {
26362 let mut tasks = Vec::new();
26363 project.update(cx, |project, cx| {
26364 for buffer in buffers {
26365 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26366 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26367 }
26368 }
26369 });
26370 cx.spawn(async move |cx| {
26371 let diffs = future::join_all(tasks).await;
26372 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26373 return;
26374 }
26375
26376 buffer.update(cx, |buffer, cx| {
26377 for diff in diffs.into_iter().flatten() {
26378 buffer.add_diff(diff, cx);
26379 }
26380 });
26381 })
26382}
26383
26384fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26385 let tab_size = tab_size.get() as usize;
26386 let mut width = offset;
26387
26388 for ch in text.chars() {
26389 width += if ch == '\t' {
26390 tab_size - (width % tab_size)
26391 } else {
26392 1
26393 };
26394 }
26395
26396 width - offset
26397}
26398
26399#[cfg(test)]
26400mod tests {
26401 use super::*;
26402
26403 #[test]
26404 fn test_string_size_with_expanded_tabs() {
26405 let nz = |val| NonZeroU32::new(val).unwrap();
26406 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26407 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26408 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26409 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26410 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26411 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26412 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26413 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26414 }
26415}
26416
26417/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26418struct WordBreakingTokenizer<'a> {
26419 input: &'a str,
26420}
26421
26422impl<'a> WordBreakingTokenizer<'a> {
26423 fn new(input: &'a str) -> Self {
26424 Self { input }
26425 }
26426}
26427
26428fn is_char_ideographic(ch: char) -> bool {
26429 use unicode_script::Script::*;
26430 use unicode_script::UnicodeScript;
26431 matches!(ch.script(), Han | Tangut | Yi)
26432}
26433
26434fn is_grapheme_ideographic(text: &str) -> bool {
26435 text.chars().any(is_char_ideographic)
26436}
26437
26438fn is_grapheme_whitespace(text: &str) -> bool {
26439 text.chars().any(|x| x.is_whitespace())
26440}
26441
26442fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26443 text.chars()
26444 .next()
26445 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26446}
26447
26448#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26449enum WordBreakToken<'a> {
26450 Word { token: &'a str, grapheme_len: usize },
26451 InlineWhitespace { token: &'a str, grapheme_len: usize },
26452 Newline,
26453}
26454
26455impl<'a> Iterator for WordBreakingTokenizer<'a> {
26456 /// Yields a span, the count of graphemes in the token, and whether it was
26457 /// whitespace. Note that it also breaks at word boundaries.
26458 type Item = WordBreakToken<'a>;
26459
26460 fn next(&mut self) -> Option<Self::Item> {
26461 use unicode_segmentation::UnicodeSegmentation;
26462 if self.input.is_empty() {
26463 return None;
26464 }
26465
26466 let mut iter = self.input.graphemes(true).peekable();
26467 let mut offset = 0;
26468 let mut grapheme_len = 0;
26469 if let Some(first_grapheme) = iter.next() {
26470 let is_newline = first_grapheme == "\n";
26471 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26472 offset += first_grapheme.len();
26473 grapheme_len += 1;
26474 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26475 if let Some(grapheme) = iter.peek().copied()
26476 && should_stay_with_preceding_ideograph(grapheme)
26477 {
26478 offset += grapheme.len();
26479 grapheme_len += 1;
26480 }
26481 } else {
26482 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26483 let mut next_word_bound = words.peek().copied();
26484 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26485 next_word_bound = words.next();
26486 }
26487 while let Some(grapheme) = iter.peek().copied() {
26488 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26489 break;
26490 };
26491 if is_grapheme_whitespace(grapheme) != is_whitespace
26492 || (grapheme == "\n") != is_newline
26493 {
26494 break;
26495 };
26496 offset += grapheme.len();
26497 grapheme_len += 1;
26498 iter.next();
26499 }
26500 }
26501 let token = &self.input[..offset];
26502 self.input = &self.input[offset..];
26503 if token == "\n" {
26504 Some(WordBreakToken::Newline)
26505 } else if is_whitespace {
26506 Some(WordBreakToken::InlineWhitespace {
26507 token,
26508 grapheme_len,
26509 })
26510 } else {
26511 Some(WordBreakToken::Word {
26512 token,
26513 grapheme_len,
26514 })
26515 }
26516 } else {
26517 None
26518 }
26519 }
26520}
26521
26522#[test]
26523fn test_word_breaking_tokenizer() {
26524 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26525 ("", &[]),
26526 (" ", &[whitespace(" ", 2)]),
26527 ("Ʒ", &[word("Ʒ", 1)]),
26528 ("Ǽ", &[word("Ǽ", 1)]),
26529 ("⋑", &[word("⋑", 1)]),
26530 ("⋑⋑", &[word("⋑⋑", 2)]),
26531 (
26532 "原理,进而",
26533 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26534 ),
26535 (
26536 "hello world",
26537 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26538 ),
26539 (
26540 "hello, world",
26541 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26542 ),
26543 (
26544 " hello world",
26545 &[
26546 whitespace(" ", 2),
26547 word("hello", 5),
26548 whitespace(" ", 1),
26549 word("world", 5),
26550 ],
26551 ),
26552 (
26553 "这是什么 \n 钢笔",
26554 &[
26555 word("这", 1),
26556 word("是", 1),
26557 word("什", 1),
26558 word("么", 1),
26559 whitespace(" ", 1),
26560 newline(),
26561 whitespace(" ", 1),
26562 word("钢", 1),
26563 word("笔", 1),
26564 ],
26565 ),
26566 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26567 ];
26568
26569 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26570 WordBreakToken::Word {
26571 token,
26572 grapheme_len,
26573 }
26574 }
26575
26576 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26577 WordBreakToken::InlineWhitespace {
26578 token,
26579 grapheme_len,
26580 }
26581 }
26582
26583 fn newline() -> WordBreakToken<'static> {
26584 WordBreakToken::Newline
26585 }
26586
26587 for (input, result) in tests {
26588 assert_eq!(
26589 WordBreakingTokenizer::new(input)
26590 .collect::<Vec<_>>()
26591 .as_slice(),
26592 *result,
26593 );
26594 }
26595}
26596
26597fn wrap_with_prefix(
26598 first_line_prefix: String,
26599 subsequent_lines_prefix: String,
26600 unwrapped_text: String,
26601 wrap_column: usize,
26602 tab_size: NonZeroU32,
26603 preserve_existing_whitespace: bool,
26604) -> String {
26605 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26606 let subsequent_lines_prefix_len =
26607 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26608 let mut wrapped_text = String::new();
26609 let mut current_line = first_line_prefix;
26610 let mut is_first_line = true;
26611
26612 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26613 let mut current_line_len = first_line_prefix_len;
26614 let mut in_whitespace = false;
26615 for token in tokenizer {
26616 let have_preceding_whitespace = in_whitespace;
26617 match token {
26618 WordBreakToken::Word {
26619 token,
26620 grapheme_len,
26621 } => {
26622 in_whitespace = false;
26623 let current_prefix_len = if is_first_line {
26624 first_line_prefix_len
26625 } else {
26626 subsequent_lines_prefix_len
26627 };
26628 if current_line_len + grapheme_len > wrap_column
26629 && current_line_len != current_prefix_len
26630 {
26631 wrapped_text.push_str(current_line.trim_end());
26632 wrapped_text.push('\n');
26633 is_first_line = false;
26634 current_line = subsequent_lines_prefix.clone();
26635 current_line_len = subsequent_lines_prefix_len;
26636 }
26637 current_line.push_str(token);
26638 current_line_len += grapheme_len;
26639 }
26640 WordBreakToken::InlineWhitespace {
26641 mut token,
26642 mut grapheme_len,
26643 } => {
26644 in_whitespace = true;
26645 if have_preceding_whitespace && !preserve_existing_whitespace {
26646 continue;
26647 }
26648 if !preserve_existing_whitespace {
26649 // Keep a single whitespace grapheme as-is
26650 if let Some(first) =
26651 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26652 {
26653 token = first;
26654 } else {
26655 token = " ";
26656 }
26657 grapheme_len = 1;
26658 }
26659 let current_prefix_len = if is_first_line {
26660 first_line_prefix_len
26661 } else {
26662 subsequent_lines_prefix_len
26663 };
26664 if current_line_len + grapheme_len > wrap_column {
26665 wrapped_text.push_str(current_line.trim_end());
26666 wrapped_text.push('\n');
26667 is_first_line = false;
26668 current_line = subsequent_lines_prefix.clone();
26669 current_line_len = subsequent_lines_prefix_len;
26670 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26671 current_line.push_str(token);
26672 current_line_len += grapheme_len;
26673 }
26674 }
26675 WordBreakToken::Newline => {
26676 in_whitespace = true;
26677 let current_prefix_len = if is_first_line {
26678 first_line_prefix_len
26679 } else {
26680 subsequent_lines_prefix_len
26681 };
26682 if preserve_existing_whitespace {
26683 wrapped_text.push_str(current_line.trim_end());
26684 wrapped_text.push('\n');
26685 is_first_line = false;
26686 current_line = subsequent_lines_prefix.clone();
26687 current_line_len = subsequent_lines_prefix_len;
26688 } else if have_preceding_whitespace {
26689 continue;
26690 } else if current_line_len + 1 > wrap_column
26691 && current_line_len != current_prefix_len
26692 {
26693 wrapped_text.push_str(current_line.trim_end());
26694 wrapped_text.push('\n');
26695 is_first_line = false;
26696 current_line = subsequent_lines_prefix.clone();
26697 current_line_len = subsequent_lines_prefix_len;
26698 } else if current_line_len != current_prefix_len {
26699 current_line.push(' ');
26700 current_line_len += 1;
26701 }
26702 }
26703 }
26704 }
26705
26706 if !current_line.is_empty() {
26707 wrapped_text.push_str(¤t_line);
26708 }
26709 wrapped_text
26710}
26711
26712#[test]
26713fn test_wrap_with_prefix() {
26714 assert_eq!(
26715 wrap_with_prefix(
26716 "# ".to_string(),
26717 "# ".to_string(),
26718 "abcdefg".to_string(),
26719 4,
26720 NonZeroU32::new(4).unwrap(),
26721 false,
26722 ),
26723 "# abcdefg"
26724 );
26725 assert_eq!(
26726 wrap_with_prefix(
26727 "".to_string(),
26728 "".to_string(),
26729 "\thello world".to_string(),
26730 8,
26731 NonZeroU32::new(4).unwrap(),
26732 false,
26733 ),
26734 "hello\nworld"
26735 );
26736 assert_eq!(
26737 wrap_with_prefix(
26738 "// ".to_string(),
26739 "// ".to_string(),
26740 "xx \nyy zz aa bb cc".to_string(),
26741 12,
26742 NonZeroU32::new(4).unwrap(),
26743 false,
26744 ),
26745 "// xx yy zz\n// aa bb cc"
26746 );
26747 assert_eq!(
26748 wrap_with_prefix(
26749 String::new(),
26750 String::new(),
26751 "这是什么 \n 钢笔".to_string(),
26752 3,
26753 NonZeroU32::new(4).unwrap(),
26754 false,
26755 ),
26756 "这是什\n么 钢\n笔"
26757 );
26758 assert_eq!(
26759 wrap_with_prefix(
26760 String::new(),
26761 String::new(),
26762 format!("foo{}bar", '\u{2009}'), // thin space
26763 80,
26764 NonZeroU32::new(4).unwrap(),
26765 false,
26766 ),
26767 format!("foo{}bar", '\u{2009}')
26768 );
26769}
26770
26771pub trait CollaborationHub {
26772 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26773 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26774 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26775}
26776
26777impl CollaborationHub for Entity<Project> {
26778 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26779 self.read(cx).collaborators()
26780 }
26781
26782 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26783 self.read(cx).user_store().read(cx).participant_indices()
26784 }
26785
26786 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26787 let this = self.read(cx);
26788 let user_ids = this.collaborators().values().map(|c| c.user_id);
26789 this.user_store().read(cx).participant_names(user_ids, cx)
26790 }
26791}
26792
26793pub trait SemanticsProvider {
26794 fn hover(
26795 &self,
26796 buffer: &Entity<Buffer>,
26797 position: text::Anchor,
26798 cx: &mut App,
26799 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26800
26801 fn inline_values(
26802 &self,
26803 buffer_handle: Entity<Buffer>,
26804 range: Range<text::Anchor>,
26805 cx: &mut App,
26806 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26807
26808 fn applicable_inlay_chunks(
26809 &self,
26810 buffer: &Entity<Buffer>,
26811 ranges: &[Range<text::Anchor>],
26812 cx: &mut App,
26813 ) -> Vec<Range<BufferRow>>;
26814
26815 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26816
26817 fn inlay_hints(
26818 &self,
26819 invalidate: InvalidationStrategy,
26820 buffer: Entity<Buffer>,
26821 ranges: Vec<Range<text::Anchor>>,
26822 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26823 cx: &mut App,
26824 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26825
26826 fn semantic_tokens(
26827 &self,
26828 buffer: Entity<Buffer>,
26829 refresh: Option<RefreshForServer>,
26830 cx: &mut App,
26831 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
26832
26833 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26834
26835 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26836
26837 fn document_highlights(
26838 &self,
26839 buffer: &Entity<Buffer>,
26840 position: text::Anchor,
26841 cx: &mut App,
26842 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26843
26844 fn definitions(
26845 &self,
26846 buffer: &Entity<Buffer>,
26847 position: text::Anchor,
26848 kind: GotoDefinitionKind,
26849 cx: &mut App,
26850 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26851
26852 fn range_for_rename(
26853 &self,
26854 buffer: &Entity<Buffer>,
26855 position: text::Anchor,
26856 cx: &mut App,
26857 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26858
26859 fn perform_rename(
26860 &self,
26861 buffer: &Entity<Buffer>,
26862 position: text::Anchor,
26863 new_name: String,
26864 cx: &mut App,
26865 ) -> Option<Task<Result<ProjectTransaction>>>;
26866}
26867
26868pub trait CompletionProvider {
26869 fn completions(
26870 &self,
26871 excerpt_id: ExcerptId,
26872 buffer: &Entity<Buffer>,
26873 buffer_position: text::Anchor,
26874 trigger: CompletionContext,
26875 window: &mut Window,
26876 cx: &mut Context<Editor>,
26877 ) -> Task<Result<Vec<CompletionResponse>>>;
26878
26879 fn resolve_completions(
26880 &self,
26881 _buffer: Entity<Buffer>,
26882 _completion_indices: Vec<usize>,
26883 _completions: Rc<RefCell<Box<[Completion]>>>,
26884 _cx: &mut Context<Editor>,
26885 ) -> Task<Result<bool>> {
26886 Task::ready(Ok(false))
26887 }
26888
26889 fn apply_additional_edits_for_completion(
26890 &self,
26891 _buffer: Entity<Buffer>,
26892 _completions: Rc<RefCell<Box<[Completion]>>>,
26893 _completion_index: usize,
26894 _push_to_history: bool,
26895 _cx: &mut Context<Editor>,
26896 ) -> Task<Result<Option<language::Transaction>>> {
26897 Task::ready(Ok(None))
26898 }
26899
26900 fn is_completion_trigger(
26901 &self,
26902 buffer: &Entity<Buffer>,
26903 position: language::Anchor,
26904 text: &str,
26905 trigger_in_words: bool,
26906 cx: &mut Context<Editor>,
26907 ) -> bool;
26908
26909 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26910
26911 fn sort_completions(&self) -> bool {
26912 true
26913 }
26914
26915 fn filter_completions(&self) -> bool {
26916 true
26917 }
26918
26919 fn show_snippets(&self) -> bool {
26920 false
26921 }
26922}
26923
26924pub trait CodeActionProvider {
26925 fn id(&self) -> Arc<str>;
26926
26927 fn code_actions(
26928 &self,
26929 buffer: &Entity<Buffer>,
26930 range: Range<text::Anchor>,
26931 window: &mut Window,
26932 cx: &mut App,
26933 ) -> Task<Result<Vec<CodeAction>>>;
26934
26935 fn apply_code_action(
26936 &self,
26937 buffer_handle: Entity<Buffer>,
26938 action: CodeAction,
26939 excerpt_id: ExcerptId,
26940 push_to_history: bool,
26941 window: &mut Window,
26942 cx: &mut App,
26943 ) -> Task<Result<ProjectTransaction>>;
26944}
26945
26946impl CodeActionProvider for Entity<Project> {
26947 fn id(&self) -> Arc<str> {
26948 "project".into()
26949 }
26950
26951 fn code_actions(
26952 &self,
26953 buffer: &Entity<Buffer>,
26954 range: Range<text::Anchor>,
26955 _window: &mut Window,
26956 cx: &mut App,
26957 ) -> Task<Result<Vec<CodeAction>>> {
26958 self.update(cx, |project, cx| {
26959 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26960 let code_actions = project.code_actions(buffer, range, None, cx);
26961 cx.background_spawn(async move {
26962 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26963 Ok(code_lens_actions
26964 .context("code lens fetch")?
26965 .into_iter()
26966 .flatten()
26967 .chain(
26968 code_actions
26969 .context("code action fetch")?
26970 .into_iter()
26971 .flatten(),
26972 )
26973 .collect())
26974 })
26975 })
26976 }
26977
26978 fn apply_code_action(
26979 &self,
26980 buffer_handle: Entity<Buffer>,
26981 action: CodeAction,
26982 _excerpt_id: ExcerptId,
26983 push_to_history: bool,
26984 _window: &mut Window,
26985 cx: &mut App,
26986 ) -> Task<Result<ProjectTransaction>> {
26987 self.update(cx, |project, cx| {
26988 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26989 })
26990 }
26991}
26992
26993fn snippet_completions(
26994 project: &Project,
26995 buffer: &Entity<Buffer>,
26996 buffer_anchor: text::Anchor,
26997 classifier: CharClassifier,
26998 cx: &mut App,
26999) -> Task<Result<CompletionResponse>> {
27000 let languages = buffer.read(cx).languages_at(buffer_anchor);
27001 let snippet_store = project.snippets().read(cx);
27002
27003 let scopes: Vec<_> = languages
27004 .iter()
27005 .filter_map(|language| {
27006 let language_name = language.lsp_id();
27007 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27008
27009 if snippets.is_empty() {
27010 None
27011 } else {
27012 Some((language.default_scope(), snippets))
27013 }
27014 })
27015 .collect();
27016
27017 if scopes.is_empty() {
27018 return Task::ready(Ok(CompletionResponse {
27019 completions: vec![],
27020 display_options: CompletionDisplayOptions::default(),
27021 is_incomplete: false,
27022 }));
27023 }
27024
27025 let snapshot = buffer.read(cx).text_snapshot();
27026 let executor = cx.background_executor().clone();
27027
27028 cx.background_spawn(async move {
27029 let is_word_char = |c| classifier.is_word(c);
27030
27031 let mut is_incomplete = false;
27032 let mut completions: Vec<Completion> = Vec::new();
27033
27034 const MAX_PREFIX_LEN: usize = 128;
27035 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27036 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27037 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27038
27039 let max_buffer_window: String = snapshot
27040 .text_for_range(window_start..buffer_offset)
27041 .collect();
27042
27043 if max_buffer_window.is_empty() {
27044 return Ok(CompletionResponse {
27045 completions: vec![],
27046 display_options: CompletionDisplayOptions::default(),
27047 is_incomplete: true,
27048 });
27049 }
27050
27051 for (_scope, snippets) in scopes.into_iter() {
27052 // Sort snippets by word count to match longer snippet prefixes first.
27053 let mut sorted_snippet_candidates = snippets
27054 .iter()
27055 .enumerate()
27056 .flat_map(|(snippet_ix, snippet)| {
27057 snippet
27058 .prefix
27059 .iter()
27060 .enumerate()
27061 .map(move |(prefix_ix, prefix)| {
27062 let word_count =
27063 snippet_candidate_suffixes(prefix, &is_word_char).count();
27064 ((snippet_ix, prefix_ix), prefix, word_count)
27065 })
27066 })
27067 .collect_vec();
27068 sorted_snippet_candidates
27069 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27070
27071 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27072
27073 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27074 .take(
27075 sorted_snippet_candidates
27076 .first()
27077 .map(|(_, _, word_count)| *word_count)
27078 .unwrap_or_default(),
27079 )
27080 .collect_vec();
27081
27082 const MAX_RESULTS: usize = 100;
27083 // Each match also remembers how many characters from the buffer it consumed
27084 let mut matches: Vec<(StringMatch, usize)> = vec![];
27085
27086 let mut snippet_list_cutoff_index = 0;
27087 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27088 let word_count = buffer_index + 1;
27089 // Increase `snippet_list_cutoff_index` until we have all of the
27090 // snippets with sufficiently many words.
27091 while sorted_snippet_candidates
27092 .get(snippet_list_cutoff_index)
27093 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27094 *snippet_word_count >= word_count
27095 })
27096 {
27097 snippet_list_cutoff_index += 1;
27098 }
27099
27100 // Take only the candidates with at least `word_count` many words
27101 let snippet_candidates_at_word_len =
27102 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27103
27104 let candidates = snippet_candidates_at_word_len
27105 .iter()
27106 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27107 .enumerate() // index in `sorted_snippet_candidates`
27108 // First char must match
27109 .filter(|(_ix, prefix)| {
27110 itertools::equal(
27111 prefix
27112 .chars()
27113 .next()
27114 .into_iter()
27115 .flat_map(|c| c.to_lowercase()),
27116 buffer_window
27117 .chars()
27118 .next()
27119 .into_iter()
27120 .flat_map(|c| c.to_lowercase()),
27121 )
27122 })
27123 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27124 .collect::<Vec<StringMatchCandidate>>();
27125
27126 matches.extend(
27127 fuzzy::match_strings(
27128 &candidates,
27129 &buffer_window,
27130 buffer_window.chars().any(|c| c.is_uppercase()),
27131 true,
27132 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27133 &Default::default(),
27134 executor.clone(),
27135 )
27136 .await
27137 .into_iter()
27138 .map(|string_match| (string_match, buffer_window.len())),
27139 );
27140
27141 if matches.len() >= MAX_RESULTS {
27142 break;
27143 }
27144 }
27145
27146 let to_lsp = |point: &text::Anchor| {
27147 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27148 point_to_lsp(end)
27149 };
27150 let lsp_end = to_lsp(&buffer_anchor);
27151
27152 if matches.len() >= MAX_RESULTS {
27153 is_incomplete = true;
27154 }
27155
27156 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27157 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27158 sorted_snippet_candidates[string_match.candidate_id];
27159 let snippet = &snippets[snippet_index];
27160 let start = buffer_offset - buffer_window_len;
27161 let start = snapshot.anchor_before(start);
27162 let range = start..buffer_anchor;
27163 let lsp_start = to_lsp(&start);
27164 let lsp_range = lsp::Range {
27165 start: lsp_start,
27166 end: lsp_end,
27167 };
27168 Completion {
27169 replace_range: range,
27170 new_text: snippet.body.clone(),
27171 source: CompletionSource::Lsp {
27172 insert_range: None,
27173 server_id: LanguageServerId(usize::MAX),
27174 resolved: true,
27175 lsp_completion: Box::new(lsp::CompletionItem {
27176 label: snippet.prefix.first().unwrap().clone(),
27177 kind: Some(CompletionItemKind::SNIPPET),
27178 label_details: snippet.description.as_ref().map(|description| {
27179 lsp::CompletionItemLabelDetails {
27180 detail: Some(description.clone()),
27181 description: None,
27182 }
27183 }),
27184 insert_text_format: Some(InsertTextFormat::SNIPPET),
27185 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27186 lsp::InsertReplaceEdit {
27187 new_text: snippet.body.clone(),
27188 insert: lsp_range,
27189 replace: lsp_range,
27190 },
27191 )),
27192 filter_text: Some(snippet.body.clone()),
27193 sort_text: Some(char::MAX.to_string()),
27194 ..lsp::CompletionItem::default()
27195 }),
27196 lsp_defaults: None,
27197 },
27198 label: CodeLabel {
27199 text: matching_prefix.clone(),
27200 runs: Vec::new(),
27201 filter_range: 0..matching_prefix.len(),
27202 },
27203 icon_path: None,
27204 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27205 single_line: snippet.name.clone().into(),
27206 plain_text: snippet
27207 .description
27208 .clone()
27209 .map(|description| description.into()),
27210 }),
27211 insert_text_mode: None,
27212 confirm: None,
27213 match_start: Some(start),
27214 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27215 }
27216 }));
27217 }
27218
27219 Ok(CompletionResponse {
27220 completions,
27221 display_options: CompletionDisplayOptions::default(),
27222 is_incomplete,
27223 })
27224 })
27225}
27226
27227impl CompletionProvider for Entity<Project> {
27228 fn completions(
27229 &self,
27230 _excerpt_id: ExcerptId,
27231 buffer: &Entity<Buffer>,
27232 buffer_position: text::Anchor,
27233 options: CompletionContext,
27234 _window: &mut Window,
27235 cx: &mut Context<Editor>,
27236 ) -> Task<Result<Vec<CompletionResponse>>> {
27237 self.update(cx, |project, cx| {
27238 let task = project.completions(buffer, buffer_position, options, cx);
27239 cx.background_spawn(task)
27240 })
27241 }
27242
27243 fn resolve_completions(
27244 &self,
27245 buffer: Entity<Buffer>,
27246 completion_indices: Vec<usize>,
27247 completions: Rc<RefCell<Box<[Completion]>>>,
27248 cx: &mut Context<Editor>,
27249 ) -> Task<Result<bool>> {
27250 self.update(cx, |project, cx| {
27251 project.lsp_store().update(cx, |lsp_store, cx| {
27252 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27253 })
27254 })
27255 }
27256
27257 fn apply_additional_edits_for_completion(
27258 &self,
27259 buffer: Entity<Buffer>,
27260 completions: Rc<RefCell<Box<[Completion]>>>,
27261 completion_index: usize,
27262 push_to_history: bool,
27263 cx: &mut Context<Editor>,
27264 ) -> Task<Result<Option<language::Transaction>>> {
27265 self.update(cx, |project, cx| {
27266 project.lsp_store().update(cx, |lsp_store, cx| {
27267 lsp_store.apply_additional_edits_for_completion(
27268 buffer,
27269 completions,
27270 completion_index,
27271 push_to_history,
27272 cx,
27273 )
27274 })
27275 })
27276 }
27277
27278 fn is_completion_trigger(
27279 &self,
27280 buffer: &Entity<Buffer>,
27281 position: language::Anchor,
27282 text: &str,
27283 trigger_in_words: bool,
27284 cx: &mut Context<Editor>,
27285 ) -> bool {
27286 let mut chars = text.chars();
27287 let char = if let Some(char) = chars.next() {
27288 char
27289 } else {
27290 return false;
27291 };
27292 if chars.next().is_some() {
27293 return false;
27294 }
27295
27296 let buffer = buffer.read(cx);
27297 let snapshot = buffer.snapshot();
27298 let classifier = snapshot
27299 .char_classifier_at(position)
27300 .scope_context(Some(CharScopeContext::Completion));
27301 if trigger_in_words && classifier.is_word(char) {
27302 return true;
27303 }
27304
27305 buffer.completion_triggers().contains(text)
27306 }
27307
27308 fn show_snippets(&self) -> bool {
27309 true
27310 }
27311}
27312
27313impl SemanticsProvider for WeakEntity<Project> {
27314 fn hover(
27315 &self,
27316 buffer: &Entity<Buffer>,
27317 position: text::Anchor,
27318 cx: &mut App,
27319 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27320 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27321 .ok()
27322 }
27323
27324 fn document_highlights(
27325 &self,
27326 buffer: &Entity<Buffer>,
27327 position: text::Anchor,
27328 cx: &mut App,
27329 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27330 self.update(cx, |project, cx| {
27331 project.document_highlights(buffer, position, cx)
27332 })
27333 .ok()
27334 }
27335
27336 fn definitions(
27337 &self,
27338 buffer: &Entity<Buffer>,
27339 position: text::Anchor,
27340 kind: GotoDefinitionKind,
27341 cx: &mut App,
27342 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27343 self.update(cx, |project, cx| match kind {
27344 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27345 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27346 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27347 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27348 })
27349 .ok()
27350 }
27351
27352 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27353 self.update(cx, |project, cx| {
27354 if project
27355 .active_debug_session(cx)
27356 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27357 {
27358 return true;
27359 }
27360
27361 buffer.update(cx, |buffer, cx| {
27362 project.any_language_server_supports_inlay_hints(buffer, cx)
27363 })
27364 })
27365 .unwrap_or(false)
27366 }
27367
27368 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27369 self.update(cx, |project, cx| {
27370 buffer.update(cx, |buffer, cx| {
27371 project.any_language_server_supports_semantic_tokens(buffer, cx)
27372 })
27373 })
27374 .unwrap_or(false)
27375 }
27376
27377 fn inline_values(
27378 &self,
27379 buffer_handle: Entity<Buffer>,
27380 range: Range<text::Anchor>,
27381 cx: &mut App,
27382 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27383 self.update(cx, |project, cx| {
27384 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27385
27386 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27387 })
27388 .ok()
27389 .flatten()
27390 }
27391
27392 fn applicable_inlay_chunks(
27393 &self,
27394 buffer: &Entity<Buffer>,
27395 ranges: &[Range<text::Anchor>],
27396 cx: &mut App,
27397 ) -> Vec<Range<BufferRow>> {
27398 self.update(cx, |project, cx| {
27399 project.lsp_store().update(cx, |lsp_store, cx| {
27400 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27401 })
27402 })
27403 .unwrap_or_default()
27404 }
27405
27406 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27407 self.update(cx, |project, cx| {
27408 project.lsp_store().update(cx, |lsp_store, _| {
27409 lsp_store.invalidate_inlay_hints(for_buffers)
27410 })
27411 })
27412 .ok();
27413 }
27414
27415 fn inlay_hints(
27416 &self,
27417 invalidate: InvalidationStrategy,
27418 buffer: Entity<Buffer>,
27419 ranges: Vec<Range<text::Anchor>>,
27420 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27421 cx: &mut App,
27422 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27423 self.update(cx, |project, cx| {
27424 project.lsp_store().update(cx, |lsp_store, cx| {
27425 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27426 })
27427 })
27428 .ok()
27429 }
27430
27431 fn semantic_tokens(
27432 &self,
27433 buffer: Entity<Buffer>,
27434 refresh: Option<RefreshForServer>,
27435 cx: &mut App,
27436 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27437 self.update(cx, |this, cx| {
27438 this.lsp_store().update(cx, |lsp_store, cx| {
27439 lsp_store.semantic_tokens(buffer, refresh, cx)
27440 })
27441 })
27442 .ok()
27443 }
27444
27445 fn range_for_rename(
27446 &self,
27447 buffer: &Entity<Buffer>,
27448 position: text::Anchor,
27449 cx: &mut App,
27450 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27451 self.update(cx, |project, cx| {
27452 let buffer = buffer.clone();
27453 let task = project.prepare_rename(buffer.clone(), position, cx);
27454 cx.spawn(async move |_, cx| {
27455 Ok(match task.await? {
27456 PrepareRenameResponse::Success(range) => Some(range),
27457 PrepareRenameResponse::InvalidPosition => None,
27458 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27459 // Fallback on using TreeSitter info to determine identifier range
27460 buffer.read_with(cx, |buffer, _| {
27461 let snapshot = buffer.snapshot();
27462 let (range, kind) = snapshot.surrounding_word(position, None);
27463 if kind != Some(CharKind::Word) {
27464 return None;
27465 }
27466 Some(
27467 snapshot.anchor_before(range.start)
27468 ..snapshot.anchor_after(range.end),
27469 )
27470 })
27471 }
27472 })
27473 })
27474 })
27475 .ok()
27476 }
27477
27478 fn perform_rename(
27479 &self,
27480 buffer: &Entity<Buffer>,
27481 position: text::Anchor,
27482 new_name: String,
27483 cx: &mut App,
27484 ) -> Option<Task<Result<ProjectTransaction>>> {
27485 self.update(cx, |project, cx| {
27486 project.perform_rename(buffer.clone(), position, new_name, cx)
27487 })
27488 .ok()
27489 }
27490}
27491
27492fn consume_contiguous_rows(
27493 contiguous_row_selections: &mut Vec<Selection<Point>>,
27494 selection: &Selection<Point>,
27495 display_map: &DisplaySnapshot,
27496 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27497) -> (MultiBufferRow, MultiBufferRow) {
27498 contiguous_row_selections.push(selection.clone());
27499 let start_row = starting_row(selection, display_map);
27500 let mut end_row = ending_row(selection, display_map);
27501
27502 while let Some(next_selection) = selections.peek() {
27503 if next_selection.start.row <= end_row.0 {
27504 end_row = ending_row(next_selection, display_map);
27505 contiguous_row_selections.push(selections.next().unwrap().clone());
27506 } else {
27507 break;
27508 }
27509 }
27510 (start_row, end_row)
27511}
27512
27513fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27514 if selection.start.column > 0 {
27515 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27516 } else {
27517 MultiBufferRow(selection.start.row)
27518 }
27519}
27520
27521fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27522 if next_selection.end.column > 0 || next_selection.is_empty() {
27523 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27524 } else {
27525 MultiBufferRow(next_selection.end.row)
27526 }
27527}
27528
27529impl EditorSnapshot {
27530 pub fn remote_selections_in_range<'a>(
27531 &'a self,
27532 range: &'a Range<Anchor>,
27533 collaboration_hub: &dyn CollaborationHub,
27534 cx: &'a App,
27535 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27536 let participant_names = collaboration_hub.user_names(cx);
27537 let participant_indices = collaboration_hub.user_participant_indices(cx);
27538 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27539 let collaborators_by_replica_id = collaborators_by_peer_id
27540 .values()
27541 .map(|collaborator| (collaborator.replica_id, collaborator))
27542 .collect::<HashMap<_, _>>();
27543 self.buffer_snapshot()
27544 .selections_in_range(range, false)
27545 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27546 if replica_id == ReplicaId::AGENT {
27547 Some(RemoteSelection {
27548 replica_id,
27549 selection,
27550 cursor_shape,
27551 line_mode,
27552 collaborator_id: CollaboratorId::Agent,
27553 user_name: Some("Agent".into()),
27554 color: cx.theme().players().agent(),
27555 })
27556 } else {
27557 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27558 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27559 let user_name = participant_names.get(&collaborator.user_id).cloned();
27560 Some(RemoteSelection {
27561 replica_id,
27562 selection,
27563 cursor_shape,
27564 line_mode,
27565 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27566 user_name,
27567 color: if let Some(index) = participant_index {
27568 cx.theme().players().color_for_participant(index.0)
27569 } else {
27570 cx.theme().players().absent()
27571 },
27572 })
27573 }
27574 })
27575 }
27576
27577 pub fn hunks_for_ranges(
27578 &self,
27579 ranges: impl IntoIterator<Item = Range<Point>>,
27580 ) -> Vec<MultiBufferDiffHunk> {
27581 let mut hunks = Vec::new();
27582 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27583 HashMap::default();
27584 for query_range in ranges {
27585 let query_rows =
27586 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27587 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27588 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27589 ) {
27590 // Include deleted hunks that are adjacent to the query range, because
27591 // otherwise they would be missed.
27592 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27593 if hunk.status().is_deleted() {
27594 intersects_range |= hunk.row_range.start == query_rows.end;
27595 intersects_range |= hunk.row_range.end == query_rows.start;
27596 }
27597 if intersects_range {
27598 if !processed_buffer_rows
27599 .entry(hunk.buffer_id)
27600 .or_default()
27601 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27602 {
27603 continue;
27604 }
27605 hunks.push(hunk);
27606 }
27607 }
27608 }
27609
27610 hunks
27611 }
27612
27613 fn display_diff_hunks_for_rows<'a>(
27614 &'a self,
27615 display_rows: Range<DisplayRow>,
27616 folded_buffers: &'a HashSet<BufferId>,
27617 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27618 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27619 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27620
27621 self.buffer_snapshot()
27622 .diff_hunks_in_range(buffer_start..buffer_end)
27623 .filter_map(|hunk| {
27624 if folded_buffers.contains(&hunk.buffer_id)
27625 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27626 {
27627 return None;
27628 }
27629
27630 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27631 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27632 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27633 let line_len = self.buffer_snapshot().line_len(last_row);
27634 Point::new(last_row.0, line_len)
27635 } else {
27636 Point::new(hunk.row_range.end.0, 0)
27637 };
27638
27639 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27640 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27641
27642 let display_hunk = if hunk_display_start.column() != 0 {
27643 DisplayDiffHunk::Folded {
27644 display_row: hunk_display_start.row(),
27645 }
27646 } else {
27647 let mut end_row = hunk_display_end.row();
27648 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27649 end_row.0 += 1;
27650 }
27651 let is_created_file = hunk.is_created_file();
27652
27653 DisplayDiffHunk::Unfolded {
27654 status: hunk.status(),
27655 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27656 ..hunk.diff_base_byte_range.end.0,
27657 word_diffs: hunk.word_diffs,
27658 display_row_range: hunk_display_start.row()..end_row,
27659 multi_buffer_range: Anchor::range_in_buffer(
27660 hunk.excerpt_id,
27661 hunk.buffer_range,
27662 ),
27663 is_created_file,
27664 }
27665 };
27666
27667 Some(display_hunk)
27668 })
27669 }
27670
27671 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27672 self.display_snapshot
27673 .buffer_snapshot()
27674 .language_at(position)
27675 }
27676
27677 pub fn is_focused(&self) -> bool {
27678 self.is_focused
27679 }
27680
27681 pub fn placeholder_text(&self) -> Option<String> {
27682 self.placeholder_display_snapshot
27683 .as_ref()
27684 .map(|display_map| display_map.text())
27685 }
27686
27687 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27688 self.scroll_anchor.scroll_position(&self.display_snapshot)
27689 }
27690
27691 pub fn gutter_dimensions(
27692 &self,
27693 font_id: FontId,
27694 font_size: Pixels,
27695 style: &EditorStyle,
27696 window: &mut Window,
27697 cx: &App,
27698 ) -> GutterDimensions {
27699 if self.show_gutter
27700 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27701 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27702 {
27703 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27704 matches!(
27705 ProjectSettings::get_global(cx).git.git_gutter,
27706 GitGutterSetting::TrackedFiles
27707 )
27708 });
27709 let gutter_settings = EditorSettings::get_global(cx).gutter;
27710 let show_line_numbers = self
27711 .show_line_numbers
27712 .unwrap_or(gutter_settings.line_numbers);
27713 let line_gutter_width = if show_line_numbers {
27714 // Avoid flicker-like gutter resizes when the line number gains another digit by
27715 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27716 let min_width_for_number_on_gutter =
27717 ch_advance * gutter_settings.min_line_number_digits as f32;
27718 self.max_line_number_width(style, window)
27719 .max(min_width_for_number_on_gutter)
27720 } else {
27721 0.0.into()
27722 };
27723
27724 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27725 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27726
27727 let git_blame_entries_width =
27728 self.git_blame_gutter_max_author_length
27729 .map(|max_author_length| {
27730 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27731 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27732
27733 /// The number of characters to dedicate to gaps and margins.
27734 const SPACING_WIDTH: usize = 4;
27735
27736 let max_char_count = max_author_length.min(renderer.max_author_length())
27737 + ::git::SHORT_SHA_LENGTH
27738 + MAX_RELATIVE_TIMESTAMP.len()
27739 + SPACING_WIDTH;
27740
27741 ch_advance * max_char_count
27742 });
27743
27744 let is_singleton = self.buffer_snapshot().is_singleton();
27745
27746 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27747 left_padding += if !is_singleton {
27748 ch_width * 4.0
27749 } else if show_runnables || show_breakpoints {
27750 ch_width * 3.0
27751 } else if show_git_gutter && show_line_numbers {
27752 ch_width * 2.0
27753 } else if show_git_gutter || show_line_numbers {
27754 ch_width
27755 } else {
27756 px(0.)
27757 };
27758
27759 let shows_folds = is_singleton && gutter_settings.folds;
27760
27761 let right_padding = if shows_folds && show_line_numbers {
27762 ch_width * 4.0
27763 } else if shows_folds || (!is_singleton && show_line_numbers) {
27764 ch_width * 3.0
27765 } else if show_line_numbers {
27766 ch_width
27767 } else {
27768 px(0.)
27769 };
27770
27771 GutterDimensions {
27772 left_padding,
27773 right_padding,
27774 width: line_gutter_width + left_padding + right_padding,
27775 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27776 git_blame_entries_width,
27777 }
27778 } else if self.offset_content {
27779 GutterDimensions::default_with_margin(font_id, font_size, cx)
27780 } else {
27781 GutterDimensions::default()
27782 }
27783 }
27784
27785 pub fn render_crease_toggle(
27786 &self,
27787 buffer_row: MultiBufferRow,
27788 row_contains_cursor: bool,
27789 editor: Entity<Editor>,
27790 window: &mut Window,
27791 cx: &mut App,
27792 ) -> Option<AnyElement> {
27793 let folded = self.is_line_folded(buffer_row);
27794 let mut is_foldable = false;
27795
27796 if let Some(crease) = self
27797 .crease_snapshot
27798 .query_row(buffer_row, self.buffer_snapshot())
27799 {
27800 is_foldable = true;
27801 match crease {
27802 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27803 if let Some(render_toggle) = render_toggle {
27804 let toggle_callback =
27805 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27806 if folded {
27807 editor.update(cx, |editor, cx| {
27808 editor.fold_at(buffer_row, window, cx)
27809 });
27810 } else {
27811 editor.update(cx, |editor, cx| {
27812 editor.unfold_at(buffer_row, window, cx)
27813 });
27814 }
27815 });
27816 return Some((render_toggle)(
27817 buffer_row,
27818 folded,
27819 toggle_callback,
27820 window,
27821 cx,
27822 ));
27823 }
27824 }
27825 }
27826 }
27827
27828 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27829
27830 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27831 Some(
27832 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27833 .toggle_state(folded)
27834 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27835 if folded {
27836 this.unfold_at(buffer_row, window, cx);
27837 } else {
27838 this.fold_at(buffer_row, window, cx);
27839 }
27840 }))
27841 .into_any_element(),
27842 )
27843 } else {
27844 None
27845 }
27846 }
27847
27848 pub fn render_crease_trailer(
27849 &self,
27850 buffer_row: MultiBufferRow,
27851 window: &mut Window,
27852 cx: &mut App,
27853 ) -> Option<AnyElement> {
27854 let folded = self.is_line_folded(buffer_row);
27855 if let Crease::Inline { render_trailer, .. } = self
27856 .crease_snapshot
27857 .query_row(buffer_row, self.buffer_snapshot())?
27858 {
27859 let render_trailer = render_trailer.as_ref()?;
27860 Some(render_trailer(buffer_row, folded, window, cx))
27861 } else {
27862 None
27863 }
27864 }
27865
27866 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27867 let digit_count = self.widest_line_number().ilog10() + 1;
27868 column_pixels(style, digit_count as usize, window)
27869 }
27870
27871 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27872 ///
27873 /// This is positive if `base` is before `line`.
27874 fn relative_line_delta(
27875 &self,
27876 current_selection_head: DisplayRow,
27877 first_visible_row: DisplayRow,
27878 consider_wrapped_lines: bool,
27879 ) -> i64 {
27880 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27881 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27882
27883 if consider_wrapped_lines {
27884 let wrap_snapshot = self.wrap_snapshot();
27885 let base_wrap_row = wrap_snapshot
27886 .make_wrap_point(current_selection_head, Bias::Left)
27887 .row();
27888 let wrap_row = wrap_snapshot
27889 .make_wrap_point(first_visible_row, Bias::Left)
27890 .row();
27891
27892 wrap_row.0 as i64 - base_wrap_row.0 as i64
27893 } else {
27894 let fold_snapshot = self.fold_snapshot();
27895 let base_fold_row = fold_snapshot
27896 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27897 .row();
27898 let fold_row = fold_snapshot
27899 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27900 .row();
27901
27902 fold_row as i64 - base_fold_row as i64
27903 }
27904 }
27905
27906 /// Returns the unsigned relative line number to display for each row in `rows`.
27907 ///
27908 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27909 pub fn calculate_relative_line_numbers(
27910 &self,
27911 rows: &Range<DisplayRow>,
27912 current_selection_head: DisplayRow,
27913 count_wrapped_lines: bool,
27914 ) -> HashMap<DisplayRow, u32> {
27915 let initial_offset =
27916 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27917
27918 self.row_infos(rows.start)
27919 .take(rows.len())
27920 .enumerate()
27921 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27922 .filter(|(_row, row_info)| {
27923 row_info.buffer_row.is_some()
27924 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27925 })
27926 .enumerate()
27927 .filter_map(|(i, (row, row_info))| {
27928 // We want to ensure here that the current line has absolute
27929 // numbering, even if we are in a soft-wrapped line. With the
27930 // exception that if we are in a deleted line, we should number this
27931 // relative with 0, as otherwise it would have no line number at all
27932 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27933
27934 (relative_line_number != 0
27935 || row_info
27936 .diff_status
27937 .is_some_and(|status| status.is_deleted()))
27938 .then_some((row, relative_line_number))
27939 })
27940 .collect()
27941 }
27942}
27943
27944pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27945 let font_size = style.text.font_size.to_pixels(window.rem_size());
27946 let layout = window.text_system().shape_line(
27947 SharedString::from(" ".repeat(column)),
27948 font_size,
27949 &[TextRun {
27950 len: column,
27951 font: style.text.font(),
27952 color: Hsla::default(),
27953 ..Default::default()
27954 }],
27955 None,
27956 );
27957
27958 layout.width
27959}
27960
27961impl Deref for EditorSnapshot {
27962 type Target = DisplaySnapshot;
27963
27964 fn deref(&self) -> &Self::Target {
27965 &self.display_snapshot
27966 }
27967}
27968
27969#[derive(Clone, Debug, PartialEq, Eq)]
27970pub enum EditorEvent {
27971 /// Emitted when the stored review comments change (added, removed, or updated).
27972 ReviewCommentsChanged {
27973 /// The new total count of review comments.
27974 total_count: usize,
27975 },
27976 InputIgnored {
27977 text: Arc<str>,
27978 },
27979 InputHandled {
27980 utf16_range_to_replace: Option<Range<isize>>,
27981 text: Arc<str>,
27982 },
27983 ExcerptsAdded {
27984 buffer: Entity<Buffer>,
27985 predecessor: ExcerptId,
27986 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27987 },
27988 ExcerptsRemoved {
27989 ids: Vec<ExcerptId>,
27990 removed_buffer_ids: Vec<BufferId>,
27991 },
27992 BufferFoldToggled {
27993 ids: Vec<ExcerptId>,
27994 folded: bool,
27995 },
27996 ExcerptsEdited {
27997 ids: Vec<ExcerptId>,
27998 },
27999 ExcerptsExpanded {
28000 ids: Vec<ExcerptId>,
28001 },
28002 ExpandExcerptsRequested {
28003 excerpt_ids: Vec<ExcerptId>,
28004 lines: u32,
28005 direction: ExpandExcerptDirection,
28006 },
28007 StageOrUnstageRequested {
28008 stage: bool,
28009 hunks: Vec<MultiBufferDiffHunk>,
28010 },
28011 OpenExcerptsRequested {
28012 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28013 split: bool,
28014 },
28015 RestoreRequested {
28016 hunks: Vec<MultiBufferDiffHunk>,
28017 },
28018 BufferEdited,
28019 Edited {
28020 transaction_id: clock::Lamport,
28021 },
28022 Reparsed(BufferId),
28023 Focused,
28024 FocusedIn,
28025 Blurred,
28026 DirtyChanged,
28027 Saved,
28028 TitleChanged,
28029 SelectionsChanged {
28030 local: bool,
28031 },
28032 ScrollPositionChanged {
28033 local: bool,
28034 autoscroll: bool,
28035 },
28036 TransactionUndone {
28037 transaction_id: clock::Lamport,
28038 },
28039 TransactionBegun {
28040 transaction_id: clock::Lamport,
28041 },
28042 CursorShapeChanged,
28043 BreadcrumbsChanged,
28044 OutlineSymbolsChanged,
28045 PushedToNavHistory {
28046 anchor: Anchor,
28047 is_deactivate: bool,
28048 },
28049}
28050
28051impl EventEmitter<EditorEvent> for Editor {}
28052
28053impl Focusable for Editor {
28054 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28055 self.focus_handle.clone()
28056 }
28057}
28058
28059impl Render for Editor {
28060 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28061 EditorElement::new(&cx.entity(), self.create_style(cx))
28062 }
28063}
28064
28065impl EntityInputHandler for Editor {
28066 fn text_for_range(
28067 &mut self,
28068 range_utf16: Range<usize>,
28069 adjusted_range: &mut Option<Range<usize>>,
28070 _: &mut Window,
28071 cx: &mut Context<Self>,
28072 ) -> Option<String> {
28073 let snapshot = self.buffer.read(cx).read(cx);
28074 let start = snapshot.clip_offset_utf16(
28075 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28076 Bias::Left,
28077 );
28078 let end = snapshot.clip_offset_utf16(
28079 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28080 Bias::Right,
28081 );
28082 if (start.0.0..end.0.0) != range_utf16 {
28083 adjusted_range.replace(start.0.0..end.0.0);
28084 }
28085 Some(snapshot.text_for_range(start..end).collect())
28086 }
28087
28088 fn selected_text_range(
28089 &mut self,
28090 ignore_disabled_input: bool,
28091 _: &mut Window,
28092 cx: &mut Context<Self>,
28093 ) -> Option<UTF16Selection> {
28094 // Prevent the IME menu from appearing when holding down an alphabetic key
28095 // while input is disabled.
28096 if !ignore_disabled_input && !self.input_enabled {
28097 return None;
28098 }
28099
28100 let selection = self
28101 .selections
28102 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28103 let range = selection.range();
28104
28105 Some(UTF16Selection {
28106 range: range.start.0.0..range.end.0.0,
28107 reversed: selection.reversed,
28108 })
28109 }
28110
28111 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28112 let snapshot = self.buffer.read(cx).read(cx);
28113 let range = self
28114 .text_highlights(HighlightKey::InputComposition, cx)?
28115 .1
28116 .first()?;
28117 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28118 }
28119
28120 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28121 self.clear_highlights(HighlightKey::InputComposition, cx);
28122 self.ime_transaction.take();
28123 }
28124
28125 fn replace_text_in_range(
28126 &mut self,
28127 range_utf16: Option<Range<usize>>,
28128 text: &str,
28129 window: &mut Window,
28130 cx: &mut Context<Self>,
28131 ) {
28132 if !self.input_enabled {
28133 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28134 return;
28135 }
28136
28137 self.transact(window, cx, |this, window, cx| {
28138 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28139 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28140 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28141 Some(this.selection_replacement_ranges(range_utf16, cx))
28142 } else {
28143 this.marked_text_ranges(cx)
28144 };
28145
28146 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28147 let newest_selection_id = this.selections.newest_anchor().id;
28148 this.selections
28149 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28150 .iter()
28151 .zip(ranges_to_replace.iter())
28152 .find_map(|(selection, range)| {
28153 if selection.id == newest_selection_id {
28154 Some(
28155 (range.start.0.0 as isize - selection.head().0.0 as isize)
28156 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28157 )
28158 } else {
28159 None
28160 }
28161 })
28162 });
28163
28164 cx.emit(EditorEvent::InputHandled {
28165 utf16_range_to_replace: range_to_replace,
28166 text: text.into(),
28167 });
28168
28169 if let Some(new_selected_ranges) = new_selected_ranges {
28170 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28171 selections.select_ranges(new_selected_ranges)
28172 });
28173 this.backspace(&Default::default(), window, cx);
28174 }
28175
28176 this.handle_input(text, window, cx);
28177 });
28178
28179 if let Some(transaction) = self.ime_transaction {
28180 self.buffer.update(cx, |buffer, cx| {
28181 buffer.group_until_transaction(transaction, cx);
28182 });
28183 }
28184
28185 self.unmark_text(window, cx);
28186 }
28187
28188 fn replace_and_mark_text_in_range(
28189 &mut self,
28190 range_utf16: Option<Range<usize>>,
28191 text: &str,
28192 new_selected_range_utf16: Option<Range<usize>>,
28193 window: &mut Window,
28194 cx: &mut Context<Self>,
28195 ) {
28196 if !self.input_enabled {
28197 return;
28198 }
28199
28200 let transaction = self.transact(window, cx, |this, window, cx| {
28201 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28202 let snapshot = this.buffer.read(cx).read(cx);
28203 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28204 for marked_range in &mut marked_ranges {
28205 marked_range.end = marked_range.start + relative_range_utf16.end;
28206 marked_range.start += relative_range_utf16.start;
28207 marked_range.start =
28208 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28209 marked_range.end =
28210 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28211 }
28212 }
28213 Some(marked_ranges)
28214 } else if let Some(range_utf16) = range_utf16 {
28215 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28216 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28217 Some(this.selection_replacement_ranges(range_utf16, cx))
28218 } else {
28219 None
28220 };
28221
28222 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28223 let newest_selection_id = this.selections.newest_anchor().id;
28224 this.selections
28225 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28226 .iter()
28227 .zip(ranges_to_replace.iter())
28228 .find_map(|(selection, range)| {
28229 if selection.id == newest_selection_id {
28230 Some(
28231 (range.start.0.0 as isize - selection.head().0.0 as isize)
28232 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28233 )
28234 } else {
28235 None
28236 }
28237 })
28238 });
28239
28240 cx.emit(EditorEvent::InputHandled {
28241 utf16_range_to_replace: range_to_replace,
28242 text: text.into(),
28243 });
28244
28245 if let Some(ranges) = ranges_to_replace {
28246 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28247 s.select_ranges(ranges)
28248 });
28249 }
28250
28251 let marked_ranges = {
28252 let snapshot = this.buffer.read(cx).read(cx);
28253 this.selections
28254 .disjoint_anchors_arc()
28255 .iter()
28256 .map(|selection| {
28257 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28258 })
28259 .collect::<Vec<_>>()
28260 };
28261
28262 if text.is_empty() {
28263 this.unmark_text(window, cx);
28264 } else {
28265 this.highlight_text(
28266 HighlightKey::InputComposition,
28267 marked_ranges.clone(),
28268 HighlightStyle {
28269 underline: Some(UnderlineStyle {
28270 thickness: px(1.),
28271 color: None,
28272 wavy: false,
28273 }),
28274 ..Default::default()
28275 },
28276 cx,
28277 );
28278 }
28279
28280 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28281 let use_autoclose = this.use_autoclose;
28282 let use_auto_surround = this.use_auto_surround;
28283 this.set_use_autoclose(false);
28284 this.set_use_auto_surround(false);
28285 this.handle_input(text, window, cx);
28286 this.set_use_autoclose(use_autoclose);
28287 this.set_use_auto_surround(use_auto_surround);
28288
28289 if let Some(new_selected_range) = new_selected_range_utf16 {
28290 let snapshot = this.buffer.read(cx).read(cx);
28291 let new_selected_ranges = marked_ranges
28292 .into_iter()
28293 .map(|marked_range| {
28294 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28295 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28296 insertion_start.0 + new_selected_range.start,
28297 ));
28298 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28299 insertion_start.0 + new_selected_range.end,
28300 ));
28301 snapshot.clip_offset_utf16(new_start, Bias::Left)
28302 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28303 })
28304 .collect::<Vec<_>>();
28305
28306 drop(snapshot);
28307 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28308 selections.select_ranges(new_selected_ranges)
28309 });
28310 }
28311 });
28312
28313 self.ime_transaction = self.ime_transaction.or(transaction);
28314 if let Some(transaction) = self.ime_transaction {
28315 self.buffer.update(cx, |buffer, cx| {
28316 buffer.group_until_transaction(transaction, cx);
28317 });
28318 }
28319
28320 if self
28321 .text_highlights(HighlightKey::InputComposition, cx)
28322 .is_none()
28323 {
28324 self.ime_transaction.take();
28325 }
28326 }
28327
28328 fn bounds_for_range(
28329 &mut self,
28330 range_utf16: Range<usize>,
28331 element_bounds: gpui::Bounds<Pixels>,
28332 window: &mut Window,
28333 cx: &mut Context<Self>,
28334 ) -> Option<gpui::Bounds<Pixels>> {
28335 let text_layout_details = self.text_layout_details(window, cx);
28336 let CharacterDimensions {
28337 em_width,
28338 em_advance,
28339 line_height,
28340 } = self.character_dimensions(window, cx);
28341
28342 let snapshot = self.snapshot(window, cx);
28343 let scroll_position = snapshot.scroll_position();
28344 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28345
28346 let start =
28347 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28348 let x = Pixels::from(
28349 ScrollOffset::from(
28350 snapshot.x_for_display_point(start, &text_layout_details)
28351 + self.gutter_dimensions.full_width(),
28352 ) - scroll_left,
28353 );
28354 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28355
28356 Some(Bounds {
28357 origin: element_bounds.origin + point(x, y),
28358 size: size(em_width, line_height),
28359 })
28360 }
28361
28362 fn character_index_for_point(
28363 &mut self,
28364 point: gpui::Point<Pixels>,
28365 _window: &mut Window,
28366 _cx: &mut Context<Self>,
28367 ) -> Option<usize> {
28368 let position_map = self.last_position_map.as_ref()?;
28369 if !position_map.text_hitbox.contains(&point) {
28370 return None;
28371 }
28372 let display_point = position_map.point_for_position(point).previous_valid;
28373 let anchor = position_map
28374 .snapshot
28375 .display_point_to_anchor(display_point, Bias::Left);
28376 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28377 Some(utf16_offset.0.0)
28378 }
28379
28380 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28381 self.input_enabled
28382 }
28383}
28384
28385trait SelectionExt {
28386 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28387 fn spanned_rows(
28388 &self,
28389 include_end_if_at_line_start: bool,
28390 map: &DisplaySnapshot,
28391 ) -> Range<MultiBufferRow>;
28392}
28393
28394impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28395 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28396 let start = self
28397 .start
28398 .to_point(map.buffer_snapshot())
28399 .to_display_point(map);
28400 let end = self
28401 .end
28402 .to_point(map.buffer_snapshot())
28403 .to_display_point(map);
28404 if self.reversed {
28405 end..start
28406 } else {
28407 start..end
28408 }
28409 }
28410
28411 fn spanned_rows(
28412 &self,
28413 include_end_if_at_line_start: bool,
28414 map: &DisplaySnapshot,
28415 ) -> Range<MultiBufferRow> {
28416 let start = self.start.to_point(map.buffer_snapshot());
28417 let mut end = self.end.to_point(map.buffer_snapshot());
28418 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28419 end.row -= 1;
28420 }
28421
28422 let buffer_start = map.prev_line_boundary(start).0;
28423 let buffer_end = map.next_line_boundary(end).0;
28424 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28425 }
28426}
28427
28428impl<T: InvalidationRegion> InvalidationStack<T> {
28429 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28430 where
28431 S: Clone + ToOffset,
28432 {
28433 while let Some(region) = self.last() {
28434 let all_selections_inside_invalidation_ranges =
28435 if selections.len() == region.ranges().len() {
28436 selections
28437 .iter()
28438 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28439 .all(|(selection, invalidation_range)| {
28440 let head = selection.head().to_offset(buffer);
28441 invalidation_range.start <= head && invalidation_range.end >= head
28442 })
28443 } else {
28444 false
28445 };
28446
28447 if all_selections_inside_invalidation_ranges {
28448 break;
28449 } else {
28450 self.pop();
28451 }
28452 }
28453 }
28454}
28455
28456#[derive(Clone)]
28457struct ErasedEditorImpl(Entity<Editor>);
28458
28459impl ui_input::ErasedEditor for ErasedEditorImpl {
28460 fn text(&self, cx: &App) -> String {
28461 self.0.read(cx).text(cx)
28462 }
28463
28464 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28465 self.0.update(cx, |this, cx| {
28466 this.set_text(text, window, cx);
28467 })
28468 }
28469
28470 fn clear(&self, window: &mut Window, cx: &mut App) {
28471 self.0.update(cx, |this, cx| this.clear(window, cx));
28472 }
28473
28474 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28475 self.0.update(cx, |this, cx| {
28476 this.set_placeholder_text(text, window, cx);
28477 });
28478 }
28479
28480 fn focus_handle(&self, cx: &App) -> FocusHandle {
28481 self.0.read(cx).focus_handle(cx)
28482 }
28483
28484 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28485 let settings = ThemeSettings::get_global(cx);
28486 let theme_color = cx.theme().colors();
28487
28488 let text_style = TextStyle {
28489 font_family: settings.ui_font.family.clone(),
28490 font_features: settings.ui_font.features.clone(),
28491 font_size: rems(0.875).into(),
28492 font_weight: settings.ui_font.weight,
28493 font_style: FontStyle::Normal,
28494 line_height: relative(1.2),
28495 color: theme_color.text,
28496 ..Default::default()
28497 };
28498 let editor_style = EditorStyle {
28499 background: theme_color.ghost_element_background,
28500 local_player: cx.theme().players().local(),
28501 syntax: cx.theme().syntax().clone(),
28502 text: text_style,
28503 ..Default::default()
28504 };
28505 EditorElement::new(&self.0, editor_style).into_any()
28506 }
28507
28508 fn as_any(&self) -> &dyn Any {
28509 &self.0
28510 }
28511
28512 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28513 self.0.update(cx, |editor, cx| {
28514 let editor_offset = editor.buffer().read(cx).len(cx);
28515 editor.change_selections(
28516 SelectionEffects::scroll(Autoscroll::Next),
28517 window,
28518 cx,
28519 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28520 );
28521 });
28522 }
28523
28524 fn subscribe(
28525 &self,
28526 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28527 window: &mut Window,
28528 cx: &mut App,
28529 ) -> Subscription {
28530 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28531 let event = match event {
28532 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28533 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28534 _ => return,
28535 };
28536 (callback)(event, window, cx);
28537 })
28538 }
28539
28540 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28541 self.0.update(cx, |editor, cx| {
28542 editor.set_masked(masked, cx);
28543 });
28544 }
28545}
28546impl<T> Default for InvalidationStack<T> {
28547 fn default() -> Self {
28548 Self(Default::default())
28549 }
28550}
28551
28552impl<T> Deref for InvalidationStack<T> {
28553 type Target = Vec<T>;
28554
28555 fn deref(&self) -> &Self::Target {
28556 &self.0
28557 }
28558}
28559
28560impl<T> DerefMut for InvalidationStack<T> {
28561 fn deref_mut(&mut self) -> &mut Self::Target {
28562 &mut self.0
28563 }
28564}
28565
28566impl InvalidationRegion for SnippetState {
28567 fn ranges(&self) -> &[Range<Anchor>] {
28568 &self.ranges[self.active_index]
28569 }
28570}
28571
28572fn edit_prediction_edit_text(
28573 current_snapshot: &BufferSnapshot,
28574 edits: &[(Range<Anchor>, impl AsRef<str>)],
28575 edit_preview: &EditPreview,
28576 include_deletions: bool,
28577 cx: &App,
28578) -> HighlightedText {
28579 let edits = edits
28580 .iter()
28581 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28582 .collect::<Vec<_>>();
28583
28584 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28585}
28586
28587fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28588 // Fallback for providers that don't provide edit_preview (like Copilot)
28589 // Just show the raw edit text with basic styling
28590 let mut text = String::new();
28591 let mut highlights = Vec::new();
28592
28593 let insertion_highlight_style = HighlightStyle {
28594 color: Some(cx.theme().colors().text),
28595 ..Default::default()
28596 };
28597
28598 for (_, edit_text) in edits {
28599 let start_offset = text.len();
28600 text.push_str(edit_text);
28601 let end_offset = text.len();
28602
28603 if start_offset < end_offset {
28604 highlights.push((start_offset..end_offset, insertion_highlight_style));
28605 }
28606 }
28607
28608 HighlightedText {
28609 text: text.into(),
28610 highlights,
28611 }
28612}
28613
28614pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28615 match severity {
28616 lsp::DiagnosticSeverity::ERROR => colors.error,
28617 lsp::DiagnosticSeverity::WARNING => colors.warning,
28618 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28619 lsp::DiagnosticSeverity::HINT => colors.info,
28620 _ => colors.ignored,
28621 }
28622}
28623
28624pub fn styled_runs_for_code_label<'a>(
28625 label: &'a CodeLabel,
28626 syntax_theme: &'a theme::SyntaxTheme,
28627 local_player: &'a theme::PlayerColor,
28628) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28629 let fade_out = HighlightStyle {
28630 fade_out: Some(0.35),
28631 ..Default::default()
28632 };
28633
28634 let mut prev_end = label.filter_range.end;
28635 label
28636 .runs
28637 .iter()
28638 .enumerate()
28639 .flat_map(move |(ix, (range, highlight_id))| {
28640 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28641 HighlightStyle {
28642 color: Some(local_player.cursor),
28643 ..Default::default()
28644 }
28645 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28646 HighlightStyle {
28647 background_color: Some(local_player.selection),
28648 ..Default::default()
28649 }
28650 } else if let Some(style) = highlight_id.style(syntax_theme) {
28651 style
28652 } else {
28653 return Default::default();
28654 };
28655 let muted_style = style.highlight(fade_out);
28656
28657 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28658 if range.start >= label.filter_range.end {
28659 if range.start > prev_end {
28660 runs.push((prev_end..range.start, fade_out));
28661 }
28662 runs.push((range.clone(), muted_style));
28663 } else if range.end <= label.filter_range.end {
28664 runs.push((range.clone(), style));
28665 } else {
28666 runs.push((range.start..label.filter_range.end, style));
28667 runs.push((label.filter_range.end..range.end, muted_style));
28668 }
28669 prev_end = cmp::max(prev_end, range.end);
28670
28671 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28672 runs.push((prev_end..label.text.len(), fade_out));
28673 }
28674
28675 runs
28676 })
28677}
28678
28679pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28680 let mut prev_index = 0;
28681 let mut prev_codepoint: Option<char> = None;
28682 text.char_indices()
28683 .chain([(text.len(), '\0')])
28684 .filter_map(move |(index, codepoint)| {
28685 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28686 let is_boundary = index == text.len()
28687 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28688 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28689 if is_boundary {
28690 let chunk = &text[prev_index..index];
28691 prev_index = index;
28692 Some(chunk)
28693 } else {
28694 None
28695 }
28696 })
28697}
28698
28699/// Given a string of text immediately before the cursor, iterates over possible
28700/// strings a snippet could match to. More precisely: returns an iterator over
28701/// suffixes of `text` created by splitting at word boundaries (before & after
28702/// every non-word character).
28703///
28704/// Shorter suffixes are returned first.
28705pub(crate) fn snippet_candidate_suffixes<'a>(
28706 text: &'a str,
28707 is_word_char: &'a dyn Fn(char) -> bool,
28708) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28709 let mut prev_index = text.len();
28710 let mut prev_codepoint = None;
28711 text.char_indices()
28712 .rev()
28713 .chain([(0, '\0')])
28714 .filter_map(move |(index, codepoint)| {
28715 let prev_index = std::mem::replace(&mut prev_index, index);
28716 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28717 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28718 None
28719 } else {
28720 let chunk = &text[prev_index..]; // go to end of string
28721 Some(chunk)
28722 }
28723 })
28724}
28725
28726pub trait RangeToAnchorExt: Sized {
28727 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28728
28729 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28730 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28731 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28732 }
28733}
28734
28735impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28736 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28737 let start_offset = self.start.to_offset(snapshot);
28738 let end_offset = self.end.to_offset(snapshot);
28739 if start_offset == end_offset {
28740 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28741 } else {
28742 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28743 }
28744 }
28745}
28746
28747pub trait RowExt {
28748 fn as_f64(&self) -> f64;
28749
28750 fn next_row(&self) -> Self;
28751
28752 fn previous_row(&self) -> Self;
28753
28754 fn minus(&self, other: Self) -> u32;
28755}
28756
28757impl RowExt for DisplayRow {
28758 fn as_f64(&self) -> f64 {
28759 self.0 as _
28760 }
28761
28762 fn next_row(&self) -> Self {
28763 Self(self.0 + 1)
28764 }
28765
28766 fn previous_row(&self) -> Self {
28767 Self(self.0.saturating_sub(1))
28768 }
28769
28770 fn minus(&self, other: Self) -> u32 {
28771 self.0 - other.0
28772 }
28773}
28774
28775impl RowExt for MultiBufferRow {
28776 fn as_f64(&self) -> f64 {
28777 self.0 as _
28778 }
28779
28780 fn next_row(&self) -> Self {
28781 Self(self.0 + 1)
28782 }
28783
28784 fn previous_row(&self) -> Self {
28785 Self(self.0.saturating_sub(1))
28786 }
28787
28788 fn minus(&self, other: Self) -> u32 {
28789 self.0 - other.0
28790 }
28791}
28792
28793trait RowRangeExt {
28794 type Row;
28795
28796 fn len(&self) -> usize;
28797
28798 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28799}
28800
28801impl RowRangeExt for Range<MultiBufferRow> {
28802 type Row = MultiBufferRow;
28803
28804 fn len(&self) -> usize {
28805 (self.end.0 - self.start.0) as usize
28806 }
28807
28808 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28809 (self.start.0..self.end.0).map(MultiBufferRow)
28810 }
28811}
28812
28813impl RowRangeExt for Range<DisplayRow> {
28814 type Row = DisplayRow;
28815
28816 fn len(&self) -> usize {
28817 (self.end.0 - self.start.0) as usize
28818 }
28819
28820 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28821 (self.start.0..self.end.0).map(DisplayRow)
28822 }
28823}
28824
28825/// If select range has more than one line, we
28826/// just point the cursor to range.start.
28827fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28828 if range.start.row == range.end.row {
28829 range
28830 } else {
28831 range.start..range.start
28832 }
28833}
28834pub struct KillRing(ClipboardItem);
28835impl Global for KillRing {}
28836
28837const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28838
28839enum BreakpointPromptEditAction {
28840 Log,
28841 Condition,
28842 HitCondition,
28843}
28844
28845struct BreakpointPromptEditor {
28846 pub(crate) prompt: Entity<Editor>,
28847 editor: WeakEntity<Editor>,
28848 breakpoint_anchor: Anchor,
28849 breakpoint: Breakpoint,
28850 edit_action: BreakpointPromptEditAction,
28851 block_ids: HashSet<CustomBlockId>,
28852 editor_margins: Arc<Mutex<EditorMargins>>,
28853 _subscriptions: Vec<Subscription>,
28854}
28855
28856impl BreakpointPromptEditor {
28857 const MAX_LINES: u8 = 4;
28858
28859 fn new(
28860 editor: WeakEntity<Editor>,
28861 breakpoint_anchor: Anchor,
28862 breakpoint: Breakpoint,
28863 edit_action: BreakpointPromptEditAction,
28864 window: &mut Window,
28865 cx: &mut Context<Self>,
28866 ) -> Self {
28867 let base_text = match edit_action {
28868 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28869 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28870 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28871 }
28872 .map(|msg| msg.to_string())
28873 .unwrap_or_default();
28874
28875 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28876 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28877
28878 let prompt = cx.new(|cx| {
28879 let mut prompt = Editor::new(
28880 EditorMode::AutoHeight {
28881 min_lines: 1,
28882 max_lines: Some(Self::MAX_LINES as usize),
28883 },
28884 buffer,
28885 None,
28886 window,
28887 cx,
28888 );
28889 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28890 prompt.set_show_cursor_when_unfocused(false, cx);
28891 prompt.set_placeholder_text(
28892 match edit_action {
28893 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28894 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28895 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28896 },
28897 window,
28898 cx,
28899 );
28900
28901 prompt
28902 });
28903
28904 Self {
28905 prompt,
28906 editor,
28907 breakpoint_anchor,
28908 breakpoint,
28909 edit_action,
28910 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28911 block_ids: Default::default(),
28912 _subscriptions: vec![],
28913 }
28914 }
28915
28916 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28917 self.block_ids.extend(block_ids)
28918 }
28919
28920 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28921 if let Some(editor) = self.editor.upgrade() {
28922 let message = self
28923 .prompt
28924 .read(cx)
28925 .buffer
28926 .read(cx)
28927 .as_singleton()
28928 .expect("A multi buffer in breakpoint prompt isn't possible")
28929 .read(cx)
28930 .as_rope()
28931 .to_string();
28932
28933 editor.update(cx, |editor, cx| {
28934 editor.edit_breakpoint_at_anchor(
28935 self.breakpoint_anchor,
28936 self.breakpoint.clone(),
28937 match self.edit_action {
28938 BreakpointPromptEditAction::Log => {
28939 BreakpointEditAction::EditLogMessage(message.into())
28940 }
28941 BreakpointPromptEditAction::Condition => {
28942 BreakpointEditAction::EditCondition(message.into())
28943 }
28944 BreakpointPromptEditAction::HitCondition => {
28945 BreakpointEditAction::EditHitCondition(message.into())
28946 }
28947 },
28948 cx,
28949 );
28950
28951 editor.remove_blocks(self.block_ids.clone(), None, cx);
28952 cx.focus_self(window);
28953 });
28954 }
28955 }
28956
28957 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28958 self.editor
28959 .update(cx, |editor, cx| {
28960 editor.remove_blocks(self.block_ids.clone(), None, cx);
28961 window.focus(&editor.focus_handle, cx);
28962 })
28963 .log_err();
28964 }
28965
28966 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28967 let settings = ThemeSettings::get_global(cx);
28968 let text_style = TextStyle {
28969 color: if self.prompt.read(cx).read_only(cx) {
28970 cx.theme().colors().text_disabled
28971 } else {
28972 cx.theme().colors().text
28973 },
28974 font_family: settings.buffer_font.family.clone(),
28975 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28976 font_size: settings.buffer_font_size(cx).into(),
28977 font_weight: settings.buffer_font.weight,
28978 line_height: relative(settings.buffer_line_height.value()),
28979 ..Default::default()
28980 };
28981 EditorElement::new(
28982 &self.prompt,
28983 EditorStyle {
28984 background: cx.theme().colors().editor_background,
28985 local_player: cx.theme().players().local(),
28986 text: text_style,
28987 ..Default::default()
28988 },
28989 )
28990 }
28991}
28992
28993impl Render for BreakpointPromptEditor {
28994 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28995 let editor_margins = *self.editor_margins.lock();
28996 let gutter_dimensions = editor_margins.gutter;
28997 h_flex()
28998 .key_context("Editor")
28999 .bg(cx.theme().colors().editor_background)
29000 .border_y_1()
29001 .border_color(cx.theme().status().info_border)
29002 .size_full()
29003 .py(window.line_height() / 2.5)
29004 .on_action(cx.listener(Self::confirm))
29005 .on_action(cx.listener(Self::cancel))
29006 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
29007 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29008 }
29009}
29010
29011impl Focusable for BreakpointPromptEditor {
29012 fn focus_handle(&self, cx: &App) -> FocusHandle {
29013 self.prompt.focus_handle(cx)
29014 }
29015}
29016
29017fn all_edits_insertions_or_deletions(
29018 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29019 snapshot: &MultiBufferSnapshot,
29020) -> bool {
29021 let mut all_insertions = true;
29022 let mut all_deletions = true;
29023
29024 for (range, new_text) in edits.iter() {
29025 let range_is_empty = range.to_offset(snapshot).is_empty();
29026 let text_is_empty = new_text.is_empty();
29027
29028 if range_is_empty != text_is_empty {
29029 if range_is_empty {
29030 all_deletions = false;
29031 } else {
29032 all_insertions = false;
29033 }
29034 } else {
29035 return false;
29036 }
29037
29038 if !all_insertions && !all_deletions {
29039 return false;
29040 }
29041 }
29042 all_insertions || all_deletions
29043}
29044
29045struct MissingEditPredictionKeybindingTooltip;
29046
29047impl Render for MissingEditPredictionKeybindingTooltip {
29048 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29049 ui::tooltip_container(cx, |container, cx| {
29050 container
29051 .flex_shrink_0()
29052 .max_w_80()
29053 .min_h(rems_from_px(124.))
29054 .justify_between()
29055 .child(
29056 v_flex()
29057 .flex_1()
29058 .text_ui_sm(cx)
29059 .child(Label::new("Conflict with Accept Keybinding"))
29060 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29061 )
29062 .child(
29063 h_flex()
29064 .pb_1()
29065 .gap_1()
29066 .items_end()
29067 .w_full()
29068 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29069 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29070 }))
29071 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29072 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29073 })),
29074 )
29075 })
29076 }
29077}
29078
29079#[derive(Debug, Clone, Copy, PartialEq)]
29080pub struct LineHighlight {
29081 pub background: Background,
29082 pub border: Option<gpui::Hsla>,
29083 pub include_gutter: bool,
29084 pub type_id: Option<TypeId>,
29085}
29086
29087struct LineManipulationResult {
29088 pub new_text: String,
29089 pub line_count_before: usize,
29090 pub line_count_after: usize,
29091}
29092
29093fn render_diff_hunk_controls(
29094 row: u32,
29095 status: &DiffHunkStatus,
29096 hunk_range: Range<Anchor>,
29097 is_created_file: bool,
29098 line_height: Pixels,
29099 editor: &Entity<Editor>,
29100 _window: &mut Window,
29101 cx: &mut App,
29102) -> AnyElement {
29103 h_flex()
29104 .h(line_height)
29105 .mr_1()
29106 .gap_1()
29107 .px_0p5()
29108 .pb_1()
29109 .border_x_1()
29110 .border_b_1()
29111 .border_color(cx.theme().colors().border_variant)
29112 .rounded_b_lg()
29113 .bg(cx.theme().colors().editor_background)
29114 .gap_1()
29115 .block_mouse_except_scroll()
29116 .shadow_md()
29117 .child(if status.has_secondary_hunk() {
29118 Button::new(("stage", row as u64), "Stage")
29119 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29120 .tooltip({
29121 let focus_handle = editor.focus_handle(cx);
29122 move |_window, cx| {
29123 Tooltip::for_action_in(
29124 "Stage Hunk",
29125 &::git::ToggleStaged,
29126 &focus_handle,
29127 cx,
29128 )
29129 }
29130 })
29131 .on_click({
29132 let editor = editor.clone();
29133 move |_event, _window, cx| {
29134 editor.update(cx, |editor, cx| {
29135 editor.stage_or_unstage_diff_hunks(
29136 true,
29137 vec![hunk_range.start..hunk_range.start],
29138 cx,
29139 );
29140 });
29141 }
29142 })
29143 } else {
29144 Button::new(("unstage", row as u64), "Unstage")
29145 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29146 .tooltip({
29147 let focus_handle = editor.focus_handle(cx);
29148 move |_window, cx| {
29149 Tooltip::for_action_in(
29150 "Unstage Hunk",
29151 &::git::ToggleStaged,
29152 &focus_handle,
29153 cx,
29154 )
29155 }
29156 })
29157 .on_click({
29158 let editor = editor.clone();
29159 move |_event, _window, cx| {
29160 editor.update(cx, |editor, cx| {
29161 editor.stage_or_unstage_diff_hunks(
29162 false,
29163 vec![hunk_range.start..hunk_range.start],
29164 cx,
29165 );
29166 });
29167 }
29168 })
29169 })
29170 .child(
29171 Button::new(("restore", row as u64), "Restore")
29172 .tooltip({
29173 let focus_handle = editor.focus_handle(cx);
29174 move |_window, cx| {
29175 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29176 }
29177 })
29178 .on_click({
29179 let editor = editor.clone();
29180 move |_event, window, cx| {
29181 editor.update(cx, |editor, cx| {
29182 let snapshot = editor.snapshot(window, cx);
29183 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29184 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29185 });
29186 }
29187 })
29188 .disabled(is_created_file),
29189 )
29190 .when(
29191 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29192 |el| {
29193 el.child(
29194 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29195 .shape(IconButtonShape::Square)
29196 .icon_size(IconSize::Small)
29197 // .disabled(!has_multiple_hunks)
29198 .tooltip({
29199 let focus_handle = editor.focus_handle(cx);
29200 move |_window, cx| {
29201 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29202 }
29203 })
29204 .on_click({
29205 let editor = editor.clone();
29206 move |_event, window, cx| {
29207 editor.update(cx, |editor, cx| {
29208 let snapshot = editor.snapshot(window, cx);
29209 let position =
29210 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29211 editor.go_to_hunk_before_or_after_position(
29212 &snapshot,
29213 position,
29214 Direction::Next,
29215 window,
29216 cx,
29217 );
29218 editor.expand_selected_diff_hunks(cx);
29219 });
29220 }
29221 }),
29222 )
29223 .child(
29224 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29225 .shape(IconButtonShape::Square)
29226 .icon_size(IconSize::Small)
29227 // .disabled(!has_multiple_hunks)
29228 .tooltip({
29229 let focus_handle = editor.focus_handle(cx);
29230 move |_window, cx| {
29231 Tooltip::for_action_in(
29232 "Previous Hunk",
29233 &GoToPreviousHunk,
29234 &focus_handle,
29235 cx,
29236 )
29237 }
29238 })
29239 .on_click({
29240 let editor = editor.clone();
29241 move |_event, window, cx| {
29242 editor.update(cx, |editor, cx| {
29243 let snapshot = editor.snapshot(window, cx);
29244 let point =
29245 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29246 editor.go_to_hunk_before_or_after_position(
29247 &snapshot,
29248 point,
29249 Direction::Prev,
29250 window,
29251 cx,
29252 );
29253 editor.expand_selected_diff_hunks(cx);
29254 });
29255 }
29256 }),
29257 )
29258 },
29259 )
29260 .into_any_element()
29261}
29262
29263pub fn multibuffer_context_lines(cx: &App) -> u32 {
29264 EditorSettings::try_get(cx)
29265 .map(|settings| settings.excerpt_context_lines)
29266 .unwrap_or(2)
29267 .min(32)
29268}