1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod semantic_tokens;
42mod split;
43pub mod split_editor_view;
44pub mod tasks;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
65 ShowMinimap,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use lsp::CompletionContext;
76pub use lsp_ext::lsp_tasks;
77pub use multi_buffer::{
78 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
79 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
80 ToPoint,
81};
82pub use split::{SplittableEditor, ToggleSplitDiff};
83pub use split_editor_view::SplitEditorView;
84pub use text::Bias;
85
86use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
87use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
88use anyhow::{Context as _, Result, anyhow, bail};
89use blink_manager::BlinkManager;
90use buffer_diff::DiffHunkStatus;
91use client::{Collaborator, ParticipantIndex, parse_zed_link};
92use clock::ReplicaId;
93use code_context_menus::{
94 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
95 CompletionsMenu, ContextMenuOrigin,
96};
97use collections::{BTreeMap, HashMap, HashSet, VecDeque};
98use convert_case::{Case, Casing};
99use dap::TelemetrySpawnLocation;
100use display_map::*;
101use document_colors::LspColorData;
102use edit_prediction_types::{
103 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
104 EditPredictionGranularity, SuggestionDisplayType,
105};
106use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
107use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
108use futures::{
109 FutureExt,
110 future::{self, Shared, join},
111};
112use fuzzy::{StringMatch, StringMatchCandidate};
113use git::blame::{GitBlame, GlobalBlameRenderer};
114use gpui::{
115 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
116 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
117 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
118 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
119 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
120 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
121 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
122 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
123 pulsating_between, px, relative, size,
124};
125use hover_links::{HoverLink, HoveredLinkState, find_file};
126use hover_popover::{HoverState, hide_hover};
127use indent_guides::ActiveIndentGuidesState;
128use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
129use itertools::{Either, Itertools};
130use language::{
131 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
132 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
133 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
134 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
135 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
136 TreeSitterOptions, WordsQuery,
137 language_settings::{
138 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
139 all_language_settings, language_settings,
140 },
141 point_from_lsp, point_to_lsp, text_diff_with_options,
142};
143use linked_editing_ranges::refresh_linked_ranges;
144use lsp::{
145 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
146 LanguageServerId,
147};
148use markdown::Markdown;
149use mouse_context_menu::MouseContextMenu;
150use movement::TextLayoutDetails;
151use multi_buffer::{
152 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
153};
154use parking_lot::Mutex;
155use persistence::DB;
156use project::{
157 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
158 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
159 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
160 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
161 debugger::{
162 breakpoint_store::{
163 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
164 BreakpointStore, BreakpointStoreEvent,
165 },
166 session::{Session, SessionEvent},
167 },
168 git_store::GitStoreEvent,
169 lsp_store::{
170 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
171 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
172 },
173 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
174};
175use rand::seq::SliceRandom;
176use regex::Regex;
177use rpc::{ErrorCode, ErrorExt, proto::PeerId};
178use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
179use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
180use serde::{Deserialize, Serialize};
181use settings::{
182 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
183 update_settings_file,
184};
185use smallvec::{SmallVec, smallvec};
186use snippet::Snippet;
187use std::{
188 any::{Any, TypeId},
189 borrow::Cow,
190 cell::{OnceCell, RefCell},
191 cmp::{self, Ordering, Reverse},
192 collections::hash_map,
193 iter::{self, Peekable},
194 mem,
195 num::NonZeroU32,
196 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
197 path::{Path, PathBuf},
198 rc::Rc,
199 sync::Arc,
200 time::{Duration, Instant},
201};
202use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
203use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
204use theme::{
205 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
206 ThemeSettings, observe_buffer_font_size_adjustment,
207};
208use ui::{
209 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
210 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
211};
212use ui_input::ErasedEditor;
213use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
214use workspace::{
215 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
216 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
217 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
218 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
219 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
220 searchable::SearchEvent,
221};
222use zed_actions::editor::{MoveDown, MoveUp};
223
224use crate::{
225 code_context_menus::CompletionsMenuSource,
226 editor_settings::MultiCursorModifier,
227 hover_links::{find_url, find_url_from_range},
228 inlays::{
229 InlineValueCache,
230 inlay_hints::{LspInlayHintData, inlay_hint_settings},
231 },
232 scroll::{ScrollOffset, ScrollPixelOffset},
233 selections_collection::resolve_selections_wrapping_blocks,
234 semantic_tokens::SemanticTokenState,
235 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
236};
237
238pub const FILE_HEADER_HEIGHT: u32 = 2;
239pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
240pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
241const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
242const MAX_LINE_LEN: usize = 1024;
243const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
244const MAX_SELECTION_HISTORY_LEN: usize = 1024;
245pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
246#[doc(hidden)]
247pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
248pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
249
250pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
251pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
252pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
253pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
254
255pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
256pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
257pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
258
259pub type RenderDiffHunkControlsFn = Arc<
260 dyn Fn(
261 u32,
262 &DiffHunkStatus,
263 Range<Anchor>,
264 bool,
265 Pixels,
266 &Entity<Editor>,
267 &mut Window,
268 &mut App,
269 ) -> AnyElement,
270>;
271
272enum ReportEditorEvent {
273 Saved { auto_saved: bool },
274 EditorOpened,
275 Closed,
276}
277
278impl ReportEditorEvent {
279 pub fn event_type(&self) -> &'static str {
280 match self {
281 Self::Saved { .. } => "Editor Saved",
282 Self::EditorOpened => "Editor Opened",
283 Self::Closed => "Editor Closed",
284 }
285 }
286}
287
288pub enum ActiveDebugLine {}
289pub enum DebugStackFrameLine {}
290
291pub enum ConflictsOuter {}
292pub enum ConflictsOurs {}
293pub enum ConflictsTheirs {}
294pub enum ConflictsOursMarker {}
295pub enum ConflictsTheirsMarker {}
296
297pub struct HunkAddedColor;
298pub struct HunkRemovedColor;
299
300#[derive(Debug, Copy, Clone, PartialEq, Eq)]
301pub enum Navigated {
302 Yes,
303 No,
304}
305
306impl Navigated {
307 pub fn from_bool(yes: bool) -> Navigated {
308 if yes { Navigated::Yes } else { Navigated::No }
309 }
310}
311
312#[derive(Debug, Clone, PartialEq, Eq)]
313enum DisplayDiffHunk {
314 Folded {
315 display_row: DisplayRow,
316 },
317 Unfolded {
318 is_created_file: bool,
319 diff_base_byte_range: Range<usize>,
320 display_row_range: Range<DisplayRow>,
321 multi_buffer_range: Range<Anchor>,
322 status: DiffHunkStatus,
323 word_diffs: Vec<Range<MultiBufferOffset>>,
324 },
325}
326
327pub enum HideMouseCursorOrigin {
328 TypingAction,
329 MovementAction,
330}
331
332pub fn init(cx: &mut App) {
333 cx.set_global(GlobalBlameRenderer(Arc::new(())));
334 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
335
336 workspace::register_project_item::<Editor>(cx);
337 workspace::FollowableViewRegistry::register::<Editor>(cx);
338 workspace::register_serializable_item::<Editor>(cx);
339
340 cx.observe_new(
341 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
342 workspace.register_action(Editor::new_file);
343 workspace.register_action(Editor::new_file_split);
344 workspace.register_action(Editor::new_file_vertical);
345 workspace.register_action(Editor::new_file_horizontal);
346 workspace.register_action(Editor::cancel_language_server_work);
347 workspace.register_action(Editor::toggle_focus);
348 },
349 )
350 .detach();
351
352 cx.on_action(move |_: &workspace::NewFile, cx| {
353 let app_state = workspace::AppState::global(cx);
354 if let Some(app_state) = app_state.upgrade() {
355 workspace::open_new(
356 Default::default(),
357 app_state,
358 cx,
359 |workspace, window, cx| {
360 Editor::new_file(workspace, &Default::default(), window, cx)
361 },
362 )
363 .detach_and_log_err(cx);
364 }
365 })
366 .on_action(move |_: &workspace::NewWindow, cx| {
367 let app_state = workspace::AppState::global(cx);
368 if let Some(app_state) = app_state.upgrade() {
369 workspace::open_new(
370 Default::default(),
371 app_state,
372 cx,
373 |workspace, window, cx| {
374 cx.activate(true);
375 Editor::new_file(workspace, &Default::default(), window, cx)
376 },
377 )
378 .detach_and_log_err(cx);
379 }
380 });
381 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
382 Arc::new(ErasedEditorImpl(
383 cx.new(|cx| Editor::single_line(window, cx)),
384 )) as Arc<dyn ErasedEditor>
385 });
386 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
387}
388
389pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
390 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
391}
392
393pub trait DiagnosticRenderer {
394 fn render_group(
395 &self,
396 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
397 buffer_id: BufferId,
398 snapshot: EditorSnapshot,
399 editor: WeakEntity<Editor>,
400 language_registry: Option<Arc<LanguageRegistry>>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 language_registry: Option<Arc<LanguageRegistry>>,
410 cx: &mut App,
411 ) -> Option<Entity<markdown::Markdown>>;
412
413 fn open_link(
414 &self,
415 editor: &mut Editor,
416 link: SharedString,
417 window: &mut Window,
418 cx: &mut Context<Editor>,
419 );
420}
421
422pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
423
424impl GlobalDiagnosticRenderer {
425 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
426 cx.try_global::<Self>().map(|g| g.0.clone())
427 }
428}
429
430impl gpui::Global for GlobalDiagnosticRenderer {}
431pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
432 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
433}
434
435pub struct SearchWithinRange;
436
437trait InvalidationRegion {
438 fn ranges(&self) -> &[Range<Anchor>];
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum SelectPhase {
443 Begin {
444 position: DisplayPoint,
445 add: bool,
446 click_count: usize,
447 },
448 BeginColumnar {
449 position: DisplayPoint,
450 reset: bool,
451 mode: ColumnarMode,
452 goal_column: u32,
453 },
454 Extend {
455 position: DisplayPoint,
456 click_count: usize,
457 },
458 Update {
459 position: DisplayPoint,
460 goal_column: u32,
461 scroll_delta: gpui::Point<f32>,
462 },
463 End,
464}
465
466#[derive(Clone, Debug, PartialEq)]
467pub enum ColumnarMode {
468 FromMouse,
469 FromSelection,
470}
471
472#[derive(Clone, Debug)]
473pub enum SelectMode {
474 Character,
475 Word(Range<Anchor>),
476 Line(Range<Anchor>),
477 All,
478}
479
480#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
481pub enum SizingBehavior {
482 /// The editor will layout itself using `size_full` and will include the vertical
483 /// scroll margin as requested by user settings.
484 #[default]
485 Default,
486 /// The editor will layout itself using `size_full`, but will not have any
487 /// vertical overscroll.
488 ExcludeOverscrollMargin,
489 /// The editor will request a vertical size according to its content and will be
490 /// layouted without a vertical scroll margin.
491 SizeByContent,
492}
493
494#[derive(Clone, PartialEq, Eq, Debug)]
495pub enum EditorMode {
496 SingleLine,
497 AutoHeight {
498 min_lines: usize,
499 max_lines: Option<usize>,
500 },
501 Full {
502 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
503 scale_ui_elements_with_buffer_font_size: bool,
504 /// When set to `true`, the editor will render a background for the active line.
505 show_active_line_background: bool,
506 /// Determines the sizing behavior for this editor
507 sizing_behavior: SizingBehavior,
508 },
509 Minimap {
510 parent: WeakEntity<Editor>,
511 },
512}
513
514impl EditorMode {
515 pub fn full() -> Self {
516 Self::Full {
517 scale_ui_elements_with_buffer_font_size: true,
518 show_active_line_background: true,
519 sizing_behavior: SizingBehavior::Default,
520 }
521 }
522
523 #[inline]
524 pub fn is_full(&self) -> bool {
525 matches!(self, Self::Full { .. })
526 }
527
528 #[inline]
529 pub fn is_single_line(&self) -> bool {
530 matches!(self, Self::SingleLine { .. })
531 }
532
533 #[inline]
534 fn is_minimap(&self) -> bool {
535 matches!(self, Self::Minimap { .. })
536 }
537}
538
539#[derive(Copy, Clone, Debug)]
540pub enum SoftWrap {
541 /// Prefer not to wrap at all.
542 ///
543 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
544 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
545 GitDiff,
546 /// Prefer a single line generally, unless an overly long line is encountered.
547 None,
548 /// Soft wrap lines that exceed the editor width.
549 EditorWidth,
550 /// Soft wrap lines at the preferred line length.
551 Column(u32),
552 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
553 Bounded(u32),
554}
555
556#[derive(Clone)]
557pub struct EditorStyle {
558 pub background: Hsla,
559 pub border: Hsla,
560 pub local_player: PlayerColor,
561 pub text: TextStyle,
562 pub scrollbar_width: Pixels,
563 pub syntax: Arc<SyntaxTheme>,
564 pub status: StatusColors,
565 pub inlay_hints_style: HighlightStyle,
566 pub edit_prediction_styles: EditPredictionStyles,
567 pub unnecessary_code_fade: f32,
568 pub show_underlines: bool,
569}
570
571impl Default for EditorStyle {
572 fn default() -> Self {
573 Self {
574 background: Hsla::default(),
575 border: Hsla::default(),
576 local_player: PlayerColor::default(),
577 text: TextStyle::default(),
578 scrollbar_width: Pixels::default(),
579 syntax: Default::default(),
580 // HACK: Status colors don't have a real default.
581 // We should look into removing the status colors from the editor
582 // style and retrieve them directly from the theme.
583 status: StatusColors::dark(),
584 inlay_hints_style: HighlightStyle::default(),
585 edit_prediction_styles: EditPredictionStyles {
586 insertion: HighlightStyle::default(),
587 whitespace: HighlightStyle::default(),
588 },
589 unnecessary_code_fade: Default::default(),
590 show_underlines: true,
591 }
592 }
593}
594
595pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
596 let show_background = language_settings::language_settings(None, None, cx)
597 .inlay_hints
598 .show_background;
599
600 let mut style = cx.theme().syntax().get("hint");
601
602 if style.color.is_none() {
603 style.color = Some(cx.theme().status().hint);
604 }
605
606 if !show_background {
607 style.background_color = None;
608 return style;
609 }
610
611 if style.background_color.is_none() {
612 style.background_color = Some(cx.theme().status().hint_background);
613 }
614
615 style
616}
617
618pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
619 EditPredictionStyles {
620 insertion: HighlightStyle {
621 color: Some(cx.theme().status().predictive),
622 ..HighlightStyle::default()
623 },
624 whitespace: HighlightStyle {
625 background_color: Some(cx.theme().status().created_background),
626 ..HighlightStyle::default()
627 },
628 }
629}
630
631type CompletionId = usize;
632
633pub(crate) enum EditDisplayMode {
634 TabAccept,
635 DiffPopover,
636 Inline,
637}
638
639enum EditPrediction {
640 Edit {
641 edits: Vec<(Range<Anchor>, Arc<str>)>,
642 /// Predicted cursor position as (anchor, offset_from_anchor).
643 /// The anchor is in multibuffer coordinates; after applying edits,
644 /// resolve the anchor and add the offset to get the final cursor position.
645 cursor_position: Option<(Anchor, usize)>,
646 edit_preview: Option<EditPreview>,
647 display_mode: EditDisplayMode,
648 snapshot: BufferSnapshot,
649 },
650 /// Move to a specific location in the active editor
651 MoveWithin {
652 target: Anchor,
653 snapshot: BufferSnapshot,
654 },
655 /// Move to a specific location in a different editor (not the active one)
656 MoveOutside {
657 target: language::Anchor,
658 snapshot: BufferSnapshot,
659 },
660}
661
662struct EditPredictionState {
663 inlay_ids: Vec<InlayId>,
664 completion: EditPrediction,
665 completion_id: Option<SharedString>,
666 invalidation_range: Option<Range<Anchor>>,
667}
668
669enum EditPredictionSettings {
670 Disabled,
671 Enabled {
672 show_in_menu: bool,
673 preview_requires_modifier: bool,
674 },
675}
676
677#[derive(Debug, Clone)]
678struct InlineDiagnostic {
679 message: SharedString,
680 group_id: usize,
681 is_primary: bool,
682 start: Point,
683 severity: lsp::DiagnosticSeverity,
684}
685
686pub enum MenuEditPredictionsPolicy {
687 Never,
688 ByProvider,
689}
690
691pub enum EditPredictionPreview {
692 /// Modifier is not pressed
693 Inactive { released_too_fast: bool },
694 /// Modifier pressed
695 Active {
696 since: Instant,
697 previous_scroll_position: Option<SharedScrollAnchor>,
698 },
699}
700
701impl EditPredictionPreview {
702 pub fn released_too_fast(&self) -> bool {
703 match self {
704 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
705 EditPredictionPreview::Active { .. } => false,
706 }
707 }
708
709 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
710 if let EditPredictionPreview::Active {
711 previous_scroll_position,
712 ..
713 } = self
714 {
715 *previous_scroll_position = scroll_position;
716 }
717 }
718}
719
720pub struct ContextMenuOptions {
721 pub min_entries_visible: usize,
722 pub max_entries_visible: usize,
723 pub placement: Option<ContextMenuPlacement>,
724}
725
726#[derive(Debug, Clone, PartialEq, Eq)]
727pub enum ContextMenuPlacement {
728 Above,
729 Below,
730}
731
732#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
733struct EditorActionId(usize);
734
735impl EditorActionId {
736 pub fn post_inc(&mut self) -> Self {
737 let answer = self.0;
738
739 *self = Self(answer + 1);
740
741 Self(answer)
742 }
743}
744
745// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
746// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
747
748type BackgroundHighlight = (
749 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
750 Arc<[Range<Anchor>]>,
751);
752type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
753
754#[derive(Default)]
755struct ScrollbarMarkerState {
756 scrollbar_size: Size<Pixels>,
757 dirty: bool,
758 markers: Arc<[PaintQuad]>,
759 pending_refresh: Option<Task<Result<()>>>,
760}
761
762impl ScrollbarMarkerState {
763 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
764 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
765 }
766}
767
768#[derive(Clone, Copy, PartialEq, Eq)]
769pub enum MinimapVisibility {
770 Disabled,
771 Enabled {
772 /// The configuration currently present in the users settings.
773 setting_configuration: bool,
774 /// Whether to override the currently set visibility from the users setting.
775 toggle_override: bool,
776 },
777}
778
779impl MinimapVisibility {
780 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
781 if mode.is_full() {
782 Self::Enabled {
783 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
784 toggle_override: false,
785 }
786 } else {
787 Self::Disabled
788 }
789 }
790
791 fn hidden(&self) -> Self {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 ..
796 } => Self::Enabled {
797 setting_configuration,
798 toggle_override: setting_configuration,
799 },
800 Self::Disabled => Self::Disabled,
801 }
802 }
803
804 fn disabled(&self) -> bool {
805 matches!(*self, Self::Disabled)
806 }
807
808 fn settings_visibility(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 ..
813 } => setting_configuration,
814 _ => false,
815 }
816 }
817
818 fn visible(&self) -> bool {
819 match *self {
820 Self::Enabled {
821 setting_configuration,
822 toggle_override,
823 } => setting_configuration ^ toggle_override,
824 _ => false,
825 }
826 }
827
828 fn toggle_visibility(&self) -> Self {
829 match *self {
830 Self::Enabled {
831 toggle_override,
832 setting_configuration,
833 } => Self::Enabled {
834 setting_configuration,
835 toggle_override: !toggle_override,
836 },
837 Self::Disabled => Self::Disabled,
838 }
839 }
840}
841
842#[derive(Debug, Clone, Copy, PartialEq, Eq)]
843pub enum BufferSerialization {
844 All,
845 NonDirtyBuffers,
846}
847
848impl BufferSerialization {
849 fn new(restore_unsaved_buffers: bool) -> Self {
850 if restore_unsaved_buffers {
851 Self::All
852 } else {
853 Self::NonDirtyBuffers
854 }
855 }
856}
857
858#[derive(Clone, Debug)]
859struct RunnableTasks {
860 templates: Vec<(TaskSourceKind, TaskTemplate)>,
861 offset: multi_buffer::Anchor,
862 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
863 column: u32,
864 // Values of all named captures, including those starting with '_'
865 extra_variables: HashMap<String, String>,
866 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
867 context_range: Range<BufferOffset>,
868}
869
870impl RunnableTasks {
871 fn resolve<'a>(
872 &'a self,
873 cx: &'a task::TaskContext,
874 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
875 self.templates.iter().filter_map(|(kind, template)| {
876 template
877 .resolve_task(&kind.to_id_base(), cx)
878 .map(|task| (kind.clone(), task))
879 })
880 }
881}
882
883#[derive(Clone)]
884pub struct ResolvedTasks {
885 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
886 position: Anchor,
887}
888
889/// Addons allow storing per-editor state in other crates (e.g. Vim)
890pub trait Addon: 'static {
891 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
892
893 fn render_buffer_header_controls(
894 &self,
895 _: &ExcerptInfo,
896 _: &Window,
897 _: &App,
898 ) -> Option<AnyElement> {
899 None
900 }
901
902 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
903 None
904 }
905
906 fn to_any(&self) -> &dyn std::any::Any;
907
908 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
909 None
910 }
911}
912
913struct ChangeLocation {
914 current: Option<Vec<Anchor>>,
915 original: Vec<Anchor>,
916}
917impl ChangeLocation {
918 fn locations(&self) -> &[Anchor] {
919 self.current.as_ref().unwrap_or(&self.original)
920 }
921}
922
923/// A set of caret positions, registered when the editor was edited.
924pub struct ChangeList {
925 changes: Vec<ChangeLocation>,
926 /// Currently "selected" change.
927 position: Option<usize>,
928}
929
930impl ChangeList {
931 pub fn new() -> Self {
932 Self {
933 changes: Vec::new(),
934 position: None,
935 }
936 }
937
938 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
939 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
940 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
941 if self.changes.is_empty() {
942 return None;
943 }
944
945 let prev = self.position.unwrap_or(self.changes.len());
946 let next = if direction == Direction::Prev {
947 prev.saturating_sub(count)
948 } else {
949 (prev + count).min(self.changes.len() - 1)
950 };
951 self.position = Some(next);
952 self.changes.get(next).map(|change| change.locations())
953 }
954
955 /// Adds a new change to the list, resetting the change list position.
956 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
957 self.position.take();
958 if let Some(last) = self.changes.last_mut()
959 && group
960 {
961 last.current = Some(new_positions)
962 } else {
963 self.changes.push(ChangeLocation {
964 original: new_positions,
965 current: None,
966 });
967 }
968 }
969
970 pub fn last(&self) -> Option<&[Anchor]> {
971 self.changes.last().map(|change| change.locations())
972 }
973
974 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
975 self.changes.last().map(|change| change.original.as_slice())
976 }
977
978 pub fn invert_last_group(&mut self) {
979 if let Some(last) = self.changes.last_mut()
980 && let Some(current) = last.current.as_mut()
981 {
982 mem::swap(&mut last.original, current);
983 }
984 }
985}
986
987#[derive(Clone)]
988struct InlineBlamePopoverState {
989 scroll_handle: ScrollHandle,
990 commit_message: Option<ParsedCommitMessage>,
991 markdown: Entity<Markdown>,
992}
993
994struct InlineBlamePopover {
995 position: gpui::Point<Pixels>,
996 hide_task: Option<Task<()>>,
997 popover_bounds: Option<Bounds<Pixels>>,
998 popover_state: InlineBlamePopoverState,
999 keyboard_grace: bool,
1000}
1001
1002enum SelectionDragState {
1003 /// State when no drag related activity is detected.
1004 None,
1005 /// State when the mouse is down on a selection that is about to be dragged.
1006 ReadyToDrag {
1007 selection: Selection<Anchor>,
1008 click_position: gpui::Point<Pixels>,
1009 mouse_down_time: Instant,
1010 },
1011 /// State when the mouse is dragging the selection in the editor.
1012 Dragging {
1013 selection: Selection<Anchor>,
1014 drop_cursor: Selection<Anchor>,
1015 hide_drop_cursor: bool,
1016 },
1017}
1018
1019enum ColumnarSelectionState {
1020 FromMouse {
1021 selection_tail: Anchor,
1022 display_point: Option<DisplayPoint>,
1023 },
1024 FromSelection {
1025 selection_tail: Anchor,
1026 },
1027}
1028
1029/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1030/// a breakpoint on them.
1031#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1032struct PhantomBreakpointIndicator {
1033 display_row: DisplayRow,
1034 /// There's a small debounce between hovering over the line and showing the indicator.
1035 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1036 is_active: bool,
1037 collides_with_existing_breakpoint: bool,
1038}
1039
1040/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1041/// in diff view mode.
1042#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1043pub(crate) struct PhantomDiffReviewIndicator {
1044 /// The starting anchor of the selection (or the only row if not dragging).
1045 pub start: Anchor,
1046 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1047 pub end: Anchor,
1048 /// There's a small debounce between hovering over the line and showing the indicator.
1049 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1050 pub is_active: bool,
1051}
1052
1053#[derive(Clone, Debug)]
1054pub(crate) struct DiffReviewDragState {
1055 pub start_anchor: Anchor,
1056 pub current_anchor: Anchor,
1057}
1058
1059impl DiffReviewDragState {
1060 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1061 let start = self.start_anchor.to_display_point(snapshot).row();
1062 let current = self.current_anchor.to_display_point(snapshot).row();
1063
1064 (start..=current).sorted()
1065 }
1066}
1067
1068/// Identifies a specific hunk in the diff buffer.
1069/// Used as a key to group comments by their location.
1070#[derive(Clone, Debug)]
1071pub struct DiffHunkKey {
1072 /// The file path (relative to worktree) this hunk belongs to.
1073 pub file_path: Arc<util::rel_path::RelPath>,
1074 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1075 pub hunk_start_anchor: Anchor,
1076}
1077
1078/// A review comment stored locally before being sent to the Agent panel.
1079#[derive(Clone)]
1080pub struct StoredReviewComment {
1081 /// Unique identifier for this comment (for edit/delete operations).
1082 pub id: usize,
1083 /// The comment text entered by the user.
1084 pub comment: String,
1085 /// Anchors for the code range being reviewed.
1086 pub range: Range<Anchor>,
1087 /// Timestamp when the comment was created (for chronological ordering).
1088 pub created_at: Instant,
1089 /// Whether this comment is currently being edited inline.
1090 pub is_editing: bool,
1091}
1092
1093impl StoredReviewComment {
1094 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1095 Self {
1096 id,
1097 comment,
1098 range: anchor_range,
1099 created_at: Instant::now(),
1100 is_editing: false,
1101 }
1102 }
1103}
1104
1105/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1106pub(crate) struct DiffReviewOverlay {
1107 pub anchor_range: Range<Anchor>,
1108 /// The block ID for the overlay.
1109 pub block_id: CustomBlockId,
1110 /// The editor entity for the review input.
1111 pub prompt_editor: Entity<Editor>,
1112 /// The hunk key this overlay belongs to.
1113 pub hunk_key: DiffHunkKey,
1114 /// Whether the comments section is expanded.
1115 pub comments_expanded: bool,
1116 /// Editors for comments currently being edited inline.
1117 /// Key: comment ID, Value: Editor entity for inline editing.
1118 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1119 /// Subscriptions for inline edit editors' action handlers.
1120 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1121 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1122 /// The current user's avatar URI for display in comment rows.
1123 pub user_avatar_uri: Option<SharedUri>,
1124 /// Subscription to keep the action handler alive.
1125 _subscription: Subscription,
1126}
1127
1128/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1129///
1130/// See the [module level documentation](self) for more information.
1131pub struct Editor {
1132 focus_handle: FocusHandle,
1133 last_focused_descendant: Option<WeakFocusHandle>,
1134 /// The text buffer being edited
1135 buffer: Entity<MultiBuffer>,
1136 /// Map of how text in the buffer should be displayed.
1137 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1138 pub display_map: Entity<DisplayMap>,
1139 placeholder_display_map: Option<Entity<DisplayMap>>,
1140 pub selections: SelectionsCollection,
1141 pub scroll_manager: ScrollManager,
1142 /// When inline assist editors are linked, they all render cursors because
1143 /// typing enters text into each of them, even the ones that aren't focused.
1144 pub(crate) show_cursor_when_unfocused: bool,
1145 columnar_selection_state: Option<ColumnarSelectionState>,
1146 add_selections_state: Option<AddSelectionsState>,
1147 select_next_state: Option<SelectNextState>,
1148 select_prev_state: Option<SelectNextState>,
1149 selection_history: SelectionHistory,
1150 defer_selection_effects: bool,
1151 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1152 autoclose_regions: Vec<AutocloseRegion>,
1153 snippet_stack: InvalidationStack<SnippetState>,
1154 select_syntax_node_history: SelectSyntaxNodeHistory,
1155 ime_transaction: Option<TransactionId>,
1156 pub diagnostics_max_severity: DiagnosticSeverity,
1157 active_diagnostics: ActiveDiagnostic,
1158 show_inline_diagnostics: bool,
1159 inline_diagnostics_update: Task<()>,
1160 inline_diagnostics_enabled: bool,
1161 diagnostics_enabled: bool,
1162 word_completions_enabled: bool,
1163 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1164 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1165 hard_wrap: Option<usize>,
1166 project: Option<Entity<Project>>,
1167 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1168 completion_provider: Option<Rc<dyn CompletionProvider>>,
1169 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1170 blink_manager: Entity<BlinkManager>,
1171 show_cursor_names: bool,
1172 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1173 pub show_local_selections: bool,
1174 mode: EditorMode,
1175 show_breadcrumbs: bool,
1176 show_gutter: bool,
1177 show_scrollbars: ScrollbarAxes,
1178 minimap_visibility: MinimapVisibility,
1179 offset_content: bool,
1180 disable_expand_excerpt_buttons: bool,
1181 delegate_expand_excerpts: bool,
1182 delegate_stage_and_restore: bool,
1183 delegate_open_excerpts: bool,
1184 enable_lsp_data: bool,
1185 enable_runnables: bool,
1186 show_line_numbers: Option<bool>,
1187 use_relative_line_numbers: Option<bool>,
1188 show_git_diff_gutter: Option<bool>,
1189 show_code_actions: Option<bool>,
1190 show_runnables: Option<bool>,
1191 show_breakpoints: Option<bool>,
1192 show_diff_review_button: bool,
1193 show_wrap_guides: Option<bool>,
1194 show_indent_guides: Option<bool>,
1195 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1196 highlight_order: usize,
1197 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1198 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1199 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1200 scrollbar_marker_state: ScrollbarMarkerState,
1201 active_indent_guides_state: ActiveIndentGuidesState,
1202 nav_history: Option<ItemNavHistory>,
1203 context_menu: RefCell<Option<CodeContextMenu>>,
1204 context_menu_options: Option<ContextMenuOptions>,
1205 mouse_context_menu: Option<MouseContextMenu>,
1206 completion_tasks: Vec<(CompletionId, Task<()>)>,
1207 inline_blame_popover: Option<InlineBlamePopover>,
1208 inline_blame_popover_show_task: Option<Task<()>>,
1209 signature_help_state: SignatureHelpState,
1210 auto_signature_help: Option<bool>,
1211 find_all_references_task_sources: Vec<Anchor>,
1212 next_completion_id: CompletionId,
1213 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1214 code_actions_task: Option<Task<Result<()>>>,
1215 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1216 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1217 debounced_selection_highlight_complete: bool,
1218 document_highlights_task: Option<Task<()>>,
1219 linked_editing_range_task: Option<Task<Option<()>>>,
1220 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1221 pending_rename: Option<RenameState>,
1222 searchable: bool,
1223 cursor_shape: CursorShape,
1224 /// Whether the cursor is offset one character to the left when something is
1225 /// selected (needed for vim visual mode)
1226 cursor_offset_on_selection: bool,
1227 current_line_highlight: Option<CurrentLineHighlight>,
1228 /// Whether to collapse search match ranges to just their start position.
1229 /// When true, navigating to a match positions the cursor at the match
1230 /// without selecting the matched text.
1231 collapse_matches: bool,
1232 autoindent_mode: Option<AutoindentMode>,
1233 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1234 input_enabled: bool,
1235 use_modal_editing: bool,
1236 read_only: bool,
1237 leader_id: Option<CollaboratorId>,
1238 remote_id: Option<ViewId>,
1239 pub hover_state: HoverState,
1240 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1241 prev_pressure_stage: Option<PressureStage>,
1242 gutter_hovered: bool,
1243 hovered_link_state: Option<HoveredLinkState>,
1244 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1245 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1246 active_edit_prediction: Option<EditPredictionState>,
1247 /// Used to prevent flickering as the user types while the menu is open
1248 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1249 edit_prediction_settings: EditPredictionSettings,
1250 edit_predictions_hidden_for_vim_mode: bool,
1251 show_edit_predictions_override: Option<bool>,
1252 show_completions_on_input_override: Option<bool>,
1253 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1254 edit_prediction_preview: EditPredictionPreview,
1255 edit_prediction_indent_conflict: bool,
1256 edit_prediction_requires_modifier_in_indent_conflict: bool,
1257 next_inlay_id: usize,
1258 next_color_inlay_id: usize,
1259 _subscriptions: Vec<Subscription>,
1260 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1261 gutter_dimensions: GutterDimensions,
1262 style: Option<EditorStyle>,
1263 text_style_refinement: Option<TextStyleRefinement>,
1264 next_editor_action_id: EditorActionId,
1265 editor_actions: Rc<
1266 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1267 >,
1268 use_autoclose: bool,
1269 use_auto_surround: bool,
1270 auto_replace_emoji_shortcode: bool,
1271 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1272 show_git_blame_gutter: bool,
1273 show_git_blame_inline: bool,
1274 show_git_blame_inline_delay_task: Option<Task<()>>,
1275 git_blame_inline_enabled: bool,
1276 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1277 buffer_serialization: Option<BufferSerialization>,
1278 show_selection_menu: Option<bool>,
1279 blame: Option<Entity<GitBlame>>,
1280 blame_subscription: Option<Subscription>,
1281 custom_context_menu: Option<
1282 Box<
1283 dyn 'static
1284 + Fn(
1285 &mut Self,
1286 DisplayPoint,
1287 &mut Window,
1288 &mut Context<Self>,
1289 ) -> Option<Entity<ui::ContextMenu>>,
1290 >,
1291 >,
1292 last_bounds: Option<Bounds<Pixels>>,
1293 last_position_map: Option<Rc<PositionMap>>,
1294 expect_bounds_change: Option<Bounds<Pixels>>,
1295 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1296 tasks_update_task: Option<Task<()>>,
1297 breakpoint_store: Option<Entity<BreakpointStore>>,
1298 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1299 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1300 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1301 /// Active diff review overlays. Multiple overlays can be open simultaneously
1302 /// when hunks have comments stored.
1303 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1304 /// Stored review comments grouped by hunk.
1305 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1306 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1307 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1308 /// Counter for generating unique comment IDs.
1309 next_review_comment_id: usize,
1310 hovered_diff_hunk_row: Option<DisplayRow>,
1311 pull_diagnostics_task: Task<()>,
1312 in_project_search: bool,
1313 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1314 breadcrumb_header: Option<String>,
1315 focused_block: Option<FocusedBlock>,
1316 next_scroll_position: NextScrollCursorCenterTopBottom,
1317 addons: HashMap<TypeId, Box<dyn Addon>>,
1318 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1319 load_diff_task: Option<Shared<Task<()>>>,
1320 /// Whether we are temporarily displaying a diff other than git's
1321 temporary_diff_override: bool,
1322 selection_mark_mode: bool,
1323 toggle_fold_multiple_buffers: Task<()>,
1324 _scroll_cursor_center_top_bottom_task: Task<()>,
1325 serialize_selections: Task<()>,
1326 serialize_folds: Task<()>,
1327 mouse_cursor_hidden: bool,
1328 minimap: Option<Entity<Self>>,
1329 hide_mouse_mode: HideMouseMode,
1330 pub change_list: ChangeList,
1331 inline_value_cache: InlineValueCache,
1332 number_deleted_lines: bool,
1333
1334 selection_drag_state: SelectionDragState,
1335 colors: Option<LspColorData>,
1336 post_scroll_update: Task<()>,
1337 refresh_colors_task: Task<()>,
1338 use_document_folding_ranges: bool,
1339 refresh_folding_ranges_task: Task<()>,
1340 inlay_hints: Option<LspInlayHintData>,
1341 folding_newlines: Task<()>,
1342 select_next_is_case_sensitive: Option<bool>,
1343 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1344 on_local_selections_changed:
1345 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1346 suppress_selection_callback: bool,
1347 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1348 accent_data: Option<AccentData>,
1349 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1350 semantic_token_state: SemanticTokenState,
1351 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1352 refresh_document_symbols_task: Shared<Task<()>>,
1353 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1354 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1355 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1356 sticky_headers_task: Task<()>,
1357 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1358}
1359
1360#[derive(Debug, PartialEq)]
1361struct AccentData {
1362 colors: AccentColors,
1363 overrides: Vec<SharedString>,
1364}
1365
1366fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1367 if debounce_ms > 0 {
1368 Some(Duration::from_millis(debounce_ms))
1369 } else {
1370 None
1371 }
1372}
1373
1374#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1375enum NextScrollCursorCenterTopBottom {
1376 #[default]
1377 Center,
1378 Top,
1379 Bottom,
1380}
1381
1382impl NextScrollCursorCenterTopBottom {
1383 fn next(&self) -> Self {
1384 match self {
1385 Self::Center => Self::Top,
1386 Self::Top => Self::Bottom,
1387 Self::Bottom => Self::Center,
1388 }
1389 }
1390}
1391
1392#[derive(Clone)]
1393pub struct EditorSnapshot {
1394 pub mode: EditorMode,
1395 show_gutter: bool,
1396 offset_content: bool,
1397 show_line_numbers: Option<bool>,
1398 number_deleted_lines: bool,
1399 show_git_diff_gutter: Option<bool>,
1400 show_code_actions: Option<bool>,
1401 show_runnables: Option<bool>,
1402 show_breakpoints: Option<bool>,
1403 git_blame_gutter_max_author_length: Option<usize>,
1404 pub display_snapshot: DisplaySnapshot,
1405 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1406 is_focused: bool,
1407 scroll_anchor: SharedScrollAnchor,
1408 ongoing_scroll: OngoingScroll,
1409 current_line_highlight: CurrentLineHighlight,
1410 gutter_hovered: bool,
1411 semantic_tokens_enabled: bool,
1412}
1413
1414#[derive(Default, Debug, Clone, Copy)]
1415pub struct GutterDimensions {
1416 pub left_padding: Pixels,
1417 pub right_padding: Pixels,
1418 pub width: Pixels,
1419 pub margin: Pixels,
1420 pub git_blame_entries_width: Option<Pixels>,
1421}
1422
1423impl GutterDimensions {
1424 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1425 Self {
1426 margin: Self::default_gutter_margin(font_id, font_size, cx),
1427 ..Default::default()
1428 }
1429 }
1430
1431 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1432 -cx.text_system().descent(font_id, font_size)
1433 }
1434 /// The full width of the space taken up by the gutter.
1435 pub fn full_width(&self) -> Pixels {
1436 self.margin + self.width
1437 }
1438
1439 /// The width of the space reserved for the fold indicators,
1440 /// use alongside 'justify_end' and `gutter_width` to
1441 /// right align content with the line numbers
1442 pub fn fold_area_width(&self) -> Pixels {
1443 self.margin + self.right_padding
1444 }
1445}
1446
1447struct CharacterDimensions {
1448 em_width: Pixels,
1449 em_advance: Pixels,
1450 line_height: Pixels,
1451}
1452
1453#[derive(Debug)]
1454pub struct RemoteSelection {
1455 pub replica_id: ReplicaId,
1456 pub selection: Selection<Anchor>,
1457 pub cursor_shape: CursorShape,
1458 pub collaborator_id: CollaboratorId,
1459 pub line_mode: bool,
1460 pub user_name: Option<SharedString>,
1461 pub color: PlayerColor,
1462}
1463
1464#[derive(Clone, Debug)]
1465struct SelectionHistoryEntry {
1466 selections: Arc<[Selection<Anchor>]>,
1467 select_next_state: Option<SelectNextState>,
1468 select_prev_state: Option<SelectNextState>,
1469 add_selections_state: Option<AddSelectionsState>,
1470}
1471
1472#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1473enum SelectionHistoryMode {
1474 #[default]
1475 Normal,
1476 Undoing,
1477 Redoing,
1478 Skipping,
1479}
1480
1481#[derive(Clone, PartialEq, Eq, Hash)]
1482struct HoveredCursor {
1483 replica_id: ReplicaId,
1484 selection_id: usize,
1485}
1486
1487#[derive(Debug)]
1488/// SelectionEffects controls the side-effects of updating the selection.
1489///
1490/// The default behaviour does "what you mostly want":
1491/// - it pushes to the nav history if the cursor moved by >10 lines
1492/// - it re-triggers completion requests
1493/// - it scrolls to fit
1494///
1495/// You might want to modify these behaviours. For example when doing a "jump"
1496/// like go to definition, we always want to add to nav history; but when scrolling
1497/// in vim mode we never do.
1498///
1499/// Similarly, you might want to disable scrolling if you don't want the viewport to
1500/// move.
1501#[derive(Clone)]
1502pub struct SelectionEffects {
1503 nav_history: Option<bool>,
1504 completions: bool,
1505 scroll: Option<Autoscroll>,
1506}
1507
1508impl Default for SelectionEffects {
1509 fn default() -> Self {
1510 Self {
1511 nav_history: None,
1512 completions: true,
1513 scroll: Some(Autoscroll::fit()),
1514 }
1515 }
1516}
1517impl SelectionEffects {
1518 pub fn scroll(scroll: Autoscroll) -> Self {
1519 Self {
1520 scroll: Some(scroll),
1521 ..Default::default()
1522 }
1523 }
1524
1525 pub fn no_scroll() -> Self {
1526 Self {
1527 scroll: None,
1528 ..Default::default()
1529 }
1530 }
1531
1532 pub fn completions(self, completions: bool) -> Self {
1533 Self {
1534 completions,
1535 ..self
1536 }
1537 }
1538
1539 pub fn nav_history(self, nav_history: bool) -> Self {
1540 Self {
1541 nav_history: Some(nav_history),
1542 ..self
1543 }
1544 }
1545}
1546
1547struct DeferredSelectionEffectsState {
1548 changed: bool,
1549 effects: SelectionEffects,
1550 old_cursor_position: Anchor,
1551 history_entry: SelectionHistoryEntry,
1552}
1553
1554#[derive(Default)]
1555struct SelectionHistory {
1556 #[allow(clippy::type_complexity)]
1557 selections_by_transaction:
1558 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1559 mode: SelectionHistoryMode,
1560 undo_stack: VecDeque<SelectionHistoryEntry>,
1561 redo_stack: VecDeque<SelectionHistoryEntry>,
1562}
1563
1564impl SelectionHistory {
1565 #[track_caller]
1566 fn insert_transaction(
1567 &mut self,
1568 transaction_id: TransactionId,
1569 selections: Arc<[Selection<Anchor>]>,
1570 ) {
1571 if selections.is_empty() {
1572 log::error!(
1573 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1574 std::panic::Location::caller()
1575 );
1576 return;
1577 }
1578 self.selections_by_transaction
1579 .insert(transaction_id, (selections, None));
1580 }
1581
1582 #[allow(clippy::type_complexity)]
1583 fn transaction(
1584 &self,
1585 transaction_id: TransactionId,
1586 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1587 self.selections_by_transaction.get(&transaction_id)
1588 }
1589
1590 #[allow(clippy::type_complexity)]
1591 fn transaction_mut(
1592 &mut self,
1593 transaction_id: TransactionId,
1594 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1595 self.selections_by_transaction.get_mut(&transaction_id)
1596 }
1597
1598 fn push(&mut self, entry: SelectionHistoryEntry) {
1599 if !entry.selections.is_empty() {
1600 match self.mode {
1601 SelectionHistoryMode::Normal => {
1602 self.push_undo(entry);
1603 self.redo_stack.clear();
1604 }
1605 SelectionHistoryMode::Undoing => self.push_redo(entry),
1606 SelectionHistoryMode::Redoing => self.push_undo(entry),
1607 SelectionHistoryMode::Skipping => {}
1608 }
1609 }
1610 }
1611
1612 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1613 if self
1614 .undo_stack
1615 .back()
1616 .is_none_or(|e| e.selections != entry.selections)
1617 {
1618 self.undo_stack.push_back(entry);
1619 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1620 self.undo_stack.pop_front();
1621 }
1622 }
1623 }
1624
1625 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1626 if self
1627 .redo_stack
1628 .back()
1629 .is_none_or(|e| e.selections != entry.selections)
1630 {
1631 self.redo_stack.push_back(entry);
1632 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1633 self.redo_stack.pop_front();
1634 }
1635 }
1636 }
1637}
1638
1639#[derive(Clone, Copy)]
1640pub struct RowHighlightOptions {
1641 pub autoscroll: bool,
1642 pub include_gutter: bool,
1643}
1644
1645impl Default for RowHighlightOptions {
1646 fn default() -> Self {
1647 Self {
1648 autoscroll: Default::default(),
1649 include_gutter: true,
1650 }
1651 }
1652}
1653
1654struct RowHighlight {
1655 index: usize,
1656 range: Range<Anchor>,
1657 color: Hsla,
1658 options: RowHighlightOptions,
1659 type_id: TypeId,
1660}
1661
1662#[derive(Clone, Debug)]
1663struct AddSelectionsState {
1664 groups: Vec<AddSelectionsGroup>,
1665}
1666
1667#[derive(Clone, Debug)]
1668struct AddSelectionsGroup {
1669 above: bool,
1670 stack: Vec<usize>,
1671}
1672
1673#[derive(Clone)]
1674struct SelectNextState {
1675 query: AhoCorasick,
1676 wordwise: bool,
1677 done: bool,
1678}
1679
1680impl std::fmt::Debug for SelectNextState {
1681 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1682 f.debug_struct(std::any::type_name::<Self>())
1683 .field("wordwise", &self.wordwise)
1684 .field("done", &self.done)
1685 .finish()
1686 }
1687}
1688
1689#[derive(Debug)]
1690struct AutocloseRegion {
1691 selection_id: usize,
1692 range: Range<Anchor>,
1693 pair: BracketPair,
1694}
1695
1696#[derive(Debug)]
1697struct SnippetState {
1698 ranges: Vec<Vec<Range<Anchor>>>,
1699 active_index: usize,
1700 choices: Vec<Option<Vec<String>>>,
1701}
1702
1703#[doc(hidden)]
1704pub struct RenameState {
1705 pub range: Range<Anchor>,
1706 pub old_name: Arc<str>,
1707 pub editor: Entity<Editor>,
1708 block_id: CustomBlockId,
1709}
1710
1711struct InvalidationStack<T>(Vec<T>);
1712
1713struct RegisteredEditPredictionDelegate {
1714 provider: Arc<dyn EditPredictionDelegateHandle>,
1715 _subscription: Subscription,
1716}
1717
1718#[derive(Debug, PartialEq, Eq)]
1719pub struct ActiveDiagnosticGroup {
1720 pub active_range: Range<Anchor>,
1721 pub active_message: String,
1722 pub group_id: usize,
1723 pub blocks: HashSet<CustomBlockId>,
1724}
1725
1726#[derive(Debug, PartialEq, Eq)]
1727
1728pub(crate) enum ActiveDiagnostic {
1729 None,
1730 All,
1731 Group(ActiveDiagnosticGroup),
1732}
1733
1734#[derive(Serialize, Deserialize, Clone, Debug)]
1735pub struct ClipboardSelection {
1736 /// The number of bytes in this selection.
1737 pub len: usize,
1738 /// Whether this was a full-line selection.
1739 pub is_entire_line: bool,
1740 /// The indentation of the first line when this content was originally copied.
1741 pub first_line_indent: u32,
1742 #[serde(default)]
1743 pub file_path: Option<PathBuf>,
1744 #[serde(default)]
1745 pub line_range: Option<RangeInclusive<u32>>,
1746}
1747
1748impl ClipboardSelection {
1749 pub fn for_buffer(
1750 len: usize,
1751 is_entire_line: bool,
1752 range: Range<Point>,
1753 buffer: &MultiBufferSnapshot,
1754 project: Option<&Entity<Project>>,
1755 cx: &App,
1756 ) -> Self {
1757 let first_line_indent = buffer
1758 .indent_size_for_line(MultiBufferRow(range.start.row))
1759 .len;
1760
1761 let file_path = util::maybe!({
1762 let project = project?.read(cx);
1763 let file = buffer.file_at(range.start)?;
1764 let project_path = ProjectPath {
1765 worktree_id: file.worktree_id(cx),
1766 path: file.path().clone(),
1767 };
1768 project.absolute_path(&project_path, cx)
1769 });
1770
1771 let line_range = file_path.as_ref().and_then(|_| {
1772 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1773 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1774 if start_excerpt_id == end_excerpt_id {
1775 Some(start_point.row..=end_point.row)
1776 } else {
1777 None
1778 }
1779 });
1780
1781 Self {
1782 len,
1783 is_entire_line,
1784 first_line_indent,
1785 file_path,
1786 line_range,
1787 }
1788 }
1789}
1790
1791// selections, scroll behavior, was newest selection reversed
1792type SelectSyntaxNodeHistoryState = (
1793 Box<[Selection<MultiBufferOffset>]>,
1794 SelectSyntaxNodeScrollBehavior,
1795 bool,
1796);
1797
1798#[derive(Default)]
1799struct SelectSyntaxNodeHistory {
1800 stack: Vec<SelectSyntaxNodeHistoryState>,
1801 // disable temporarily to allow changing selections without losing the stack
1802 pub disable_clearing: bool,
1803}
1804
1805impl SelectSyntaxNodeHistory {
1806 pub fn try_clear(&mut self) {
1807 if !self.disable_clearing {
1808 self.stack.clear();
1809 }
1810 }
1811
1812 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1813 self.stack.push(selection);
1814 }
1815
1816 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1817 self.stack.pop()
1818 }
1819}
1820
1821enum SelectSyntaxNodeScrollBehavior {
1822 CursorTop,
1823 FitSelection,
1824 CursorBottom,
1825}
1826
1827#[derive(Debug, Clone, Copy)]
1828pub(crate) struct NavigationData {
1829 cursor_anchor: Anchor,
1830 cursor_position: Point,
1831 scroll_anchor: ScrollAnchor,
1832 scroll_top_row: u32,
1833}
1834
1835#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1836pub enum GotoDefinitionKind {
1837 Symbol,
1838 Declaration,
1839 Type,
1840 Implementation,
1841}
1842
1843pub enum FormatTarget {
1844 Buffers(HashSet<Entity<Buffer>>),
1845 Ranges(Vec<Range<MultiBufferPoint>>),
1846}
1847
1848pub(crate) struct FocusedBlock {
1849 id: BlockId,
1850 focus_handle: WeakFocusHandle,
1851}
1852
1853#[derive(Clone, Debug)]
1854pub enum JumpData {
1855 MultiBufferRow {
1856 row: MultiBufferRow,
1857 line_offset_from_top: u32,
1858 },
1859 MultiBufferPoint {
1860 excerpt_id: ExcerptId,
1861 position: Point,
1862 anchor: text::Anchor,
1863 line_offset_from_top: u32,
1864 },
1865}
1866
1867pub enum MultibufferSelectionMode {
1868 First,
1869 All,
1870}
1871
1872#[derive(Clone, Copy, Debug, Default)]
1873pub struct RewrapOptions {
1874 pub override_language_settings: bool,
1875 pub preserve_existing_whitespace: bool,
1876}
1877
1878impl Editor {
1879 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1880 let buffer = cx.new(|cx| Buffer::local("", cx));
1881 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1882 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1883 }
1884
1885 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1886 let buffer = cx.new(|cx| Buffer::local("", cx));
1887 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1888 Self::new(EditorMode::full(), buffer, None, window, cx)
1889 }
1890
1891 pub fn auto_height(
1892 min_lines: usize,
1893 max_lines: usize,
1894 window: &mut Window,
1895 cx: &mut Context<Self>,
1896 ) -> Self {
1897 let buffer = cx.new(|cx| Buffer::local("", cx));
1898 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1899 Self::new(
1900 EditorMode::AutoHeight {
1901 min_lines,
1902 max_lines: Some(max_lines),
1903 },
1904 buffer,
1905 None,
1906 window,
1907 cx,
1908 )
1909 }
1910
1911 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1912 /// The editor grows as tall as needed to fit its content.
1913 pub fn auto_height_unbounded(
1914 min_lines: usize,
1915 window: &mut Window,
1916 cx: &mut Context<Self>,
1917 ) -> Self {
1918 let buffer = cx.new(|cx| Buffer::local("", cx));
1919 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1920 Self::new(
1921 EditorMode::AutoHeight {
1922 min_lines,
1923 max_lines: None,
1924 },
1925 buffer,
1926 None,
1927 window,
1928 cx,
1929 )
1930 }
1931
1932 pub fn for_buffer(
1933 buffer: Entity<Buffer>,
1934 project: Option<Entity<Project>>,
1935 window: &mut Window,
1936 cx: &mut Context<Self>,
1937 ) -> Self {
1938 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1939 Self::new(EditorMode::full(), buffer, project, window, cx)
1940 }
1941
1942 pub fn for_multibuffer(
1943 buffer: Entity<MultiBuffer>,
1944 project: Option<Entity<Project>>,
1945 window: &mut Window,
1946 cx: &mut Context<Self>,
1947 ) -> Self {
1948 Self::new(EditorMode::full(), buffer, project, window, cx)
1949 }
1950
1951 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1952 let mut clone = Self::new(
1953 self.mode.clone(),
1954 self.buffer.clone(),
1955 self.project.clone(),
1956 window,
1957 cx,
1958 );
1959 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1960 let snapshot = display_map.snapshot(cx);
1961 clone.display_map.update(cx, |display_map, cx| {
1962 display_map.set_state(&snapshot, cx);
1963 });
1964 snapshot
1965 });
1966 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1967 clone.folds_did_change(cx);
1968 clone.selections.clone_state(&self.selections);
1969 clone
1970 .scroll_manager
1971 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1972 clone.searchable = self.searchable;
1973 clone.read_only = self.read_only;
1974 clone
1975 }
1976
1977 pub fn new(
1978 mode: EditorMode,
1979 buffer: Entity<MultiBuffer>,
1980 project: Option<Entity<Project>>,
1981 window: &mut Window,
1982 cx: &mut Context<Self>,
1983 ) -> Self {
1984 Editor::new_internal(mode, buffer, project, None, window, cx)
1985 }
1986
1987 pub fn refresh_sticky_headers(
1988 &mut self,
1989 display_snapshot: &DisplaySnapshot,
1990 cx: &mut Context<Editor>,
1991 ) {
1992 if !self.mode.is_full() {
1993 return;
1994 }
1995 let multi_buffer = display_snapshot.buffer_snapshot();
1996 let scroll_anchor = self
1997 .scroll_manager
1998 .native_anchor(display_snapshot, cx)
1999 .anchor;
2000 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2001 return;
2002 };
2003 let buffer = buffer.clone();
2004
2005 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2006 let max_row = buffer.max_point().row;
2007 let start_row = buffer_visible_start.row.min(max_row);
2008 let end_row = (buffer_visible_start.row + 10).min(max_row);
2009
2010 let syntax = self.style(cx).syntax.clone();
2011 let background_task = cx.background_spawn(async move {
2012 buffer
2013 .outline_items_containing(
2014 Point::new(start_row, 0)..Point::new(end_row, 0),
2015 true,
2016 Some(syntax.as_ref()),
2017 )
2018 .into_iter()
2019 .map(|outline_item| OutlineItem {
2020 depth: outline_item.depth,
2021 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2022 source_range_for_text: Anchor::range_in_buffer(
2023 excerpt_id,
2024 outline_item.source_range_for_text,
2025 ),
2026 text: outline_item.text,
2027 highlight_ranges: outline_item.highlight_ranges,
2028 name_ranges: outline_item.name_ranges,
2029 body_range: outline_item
2030 .body_range
2031 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2032 annotation_range: outline_item
2033 .annotation_range
2034 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2035 })
2036 .collect()
2037 });
2038 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2039 let sticky_headers = background_task.await;
2040 this.update(cx, |this, cx| {
2041 this.sticky_headers = Some(sticky_headers);
2042 cx.notify();
2043 })
2044 .ok();
2045 });
2046 }
2047
2048 fn new_internal(
2049 mode: EditorMode,
2050 multi_buffer: Entity<MultiBuffer>,
2051 project: Option<Entity<Project>>,
2052 display_map: Option<Entity<DisplayMap>>,
2053 window: &mut Window,
2054 cx: &mut Context<Self>,
2055 ) -> Self {
2056 debug_assert!(
2057 display_map.is_none() || mode.is_minimap(),
2058 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2059 );
2060
2061 let full_mode = mode.is_full();
2062 let is_minimap = mode.is_minimap();
2063 let diagnostics_max_severity = if full_mode {
2064 EditorSettings::get_global(cx)
2065 .diagnostics_max_severity
2066 .unwrap_or(DiagnosticSeverity::Hint)
2067 } else {
2068 DiagnosticSeverity::Off
2069 };
2070 let style = window.text_style();
2071 let font_size = style.font_size.to_pixels(window.rem_size());
2072 let editor = cx.entity().downgrade();
2073 let fold_placeholder = FoldPlaceholder {
2074 constrain_width: false,
2075 render: Arc::new(move |fold_id, fold_range, cx| {
2076 let editor = editor.clone();
2077 FoldPlaceholder::fold_element(fold_id, cx)
2078 .cursor_pointer()
2079 .child("⋯")
2080 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2081 .on_click(move |_, _window, cx| {
2082 editor
2083 .update(cx, |editor, cx| {
2084 editor.unfold_ranges(
2085 &[fold_range.start..fold_range.end],
2086 true,
2087 false,
2088 cx,
2089 );
2090 cx.stop_propagation();
2091 })
2092 .ok();
2093 })
2094 .into_any()
2095 }),
2096 merge_adjacent: true,
2097 ..FoldPlaceholder::default()
2098 };
2099 let display_map = display_map.unwrap_or_else(|| {
2100 cx.new(|cx| {
2101 DisplayMap::new(
2102 multi_buffer.clone(),
2103 style.font(),
2104 font_size,
2105 None,
2106 FILE_HEADER_HEIGHT,
2107 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2108 fold_placeholder,
2109 diagnostics_max_severity,
2110 cx,
2111 )
2112 })
2113 });
2114
2115 let selections = SelectionsCollection::new();
2116
2117 let blink_manager = cx.new(|cx| {
2118 let mut blink_manager = BlinkManager::new(
2119 CURSOR_BLINK_INTERVAL,
2120 |cx| EditorSettings::get_global(cx).cursor_blink,
2121 cx,
2122 );
2123 if is_minimap {
2124 blink_manager.disable(cx);
2125 }
2126 blink_manager
2127 });
2128
2129 let soft_wrap_mode_override =
2130 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2131
2132 let mut project_subscriptions = Vec::new();
2133 if full_mode && let Some(project) = project.as_ref() {
2134 project_subscriptions.push(cx.subscribe_in(
2135 project,
2136 window,
2137 |editor, _, event, window, cx| match event {
2138 project::Event::RefreshCodeLens => {
2139 // we always query lens with actions, without storing them, always refreshing them
2140 }
2141 project::Event::RefreshInlayHints {
2142 server_id,
2143 request_id,
2144 } => {
2145 editor.refresh_inlay_hints(
2146 InlayHintRefreshReason::RefreshRequested {
2147 server_id: *server_id,
2148 request_id: *request_id,
2149 },
2150 cx,
2151 );
2152 }
2153 project::Event::RefreshSemanticTokens {
2154 server_id,
2155 request_id,
2156 } => {
2157 editor.refresh_semantic_tokens(
2158 None,
2159 Some(RefreshForServer {
2160 server_id: *server_id,
2161 request_id: *request_id,
2162 }),
2163 cx,
2164 );
2165 }
2166 project::Event::LanguageServerRemoved(_) => {
2167 editor.registered_buffers.clear();
2168 editor.register_visible_buffers(cx);
2169 editor.invalidate_semantic_tokens(None);
2170 editor.update_lsp_data(None, window, cx);
2171 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2172 if editor.tasks_update_task.is_none() {
2173 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2174 }
2175 }
2176 project::Event::LanguageServerAdded(..) => {
2177 if editor.tasks_update_task.is_none() {
2178 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2179 }
2180 }
2181 project::Event::SnippetEdit(id, snippet_edits) => {
2182 // todo(lw): Non singletons
2183 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2184 let snapshot = buffer.read(cx).snapshot();
2185 let focus_handle = editor.focus_handle(cx);
2186 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2187 for (range, snippet) in snippet_edits {
2188 let buffer_range =
2189 language::range_from_lsp(*range).to_offset(&snapshot);
2190 editor
2191 .insert_snippet(
2192 &[MultiBufferOffset(buffer_range.start)
2193 ..MultiBufferOffset(buffer_range.end)],
2194 snippet.clone(),
2195 window,
2196 cx,
2197 )
2198 .ok();
2199 }
2200 }
2201 }
2202 }
2203 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2204 let buffer_id = *buffer_id;
2205 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2206 editor.register_buffer(buffer_id, cx);
2207 editor.update_lsp_data(Some(buffer_id), window, cx);
2208 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2209 refresh_linked_ranges(editor, window, cx);
2210 editor.refresh_code_actions(window, cx);
2211 editor.refresh_document_highlights(cx);
2212 }
2213 }
2214
2215 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2216 let Some(workspace) = editor.workspace() else {
2217 return;
2218 };
2219 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2220 else {
2221 return;
2222 };
2223
2224 if active_editor.entity_id() == cx.entity_id() {
2225 let entity_id = cx.entity_id();
2226 workspace.update(cx, |this, cx| {
2227 this.panes_mut()
2228 .iter_mut()
2229 .filter(|pane| pane.entity_id() != entity_id)
2230 .for_each(|p| {
2231 p.update(cx, |pane, _| {
2232 pane.nav_history_mut().rename_item(
2233 entity_id,
2234 project_path.clone(),
2235 abs_path.clone().into(),
2236 );
2237 })
2238 });
2239 });
2240
2241 Self::open_transaction_for_hidden_buffers(
2242 workspace,
2243 transaction.clone(),
2244 "Rename".to_string(),
2245 window,
2246 cx,
2247 );
2248 }
2249 }
2250
2251 project::Event::WorkspaceEditApplied(transaction) => {
2252 let Some(workspace) = editor.workspace() else {
2253 return;
2254 };
2255 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2256 else {
2257 return;
2258 };
2259
2260 if active_editor.entity_id() == cx.entity_id() {
2261 Self::open_transaction_for_hidden_buffers(
2262 workspace,
2263 transaction.clone(),
2264 "LSP Edit".to_string(),
2265 window,
2266 cx,
2267 );
2268 }
2269 }
2270
2271 _ => {}
2272 },
2273 ));
2274 if let Some(task_inventory) = project
2275 .read(cx)
2276 .task_store()
2277 .read(cx)
2278 .task_inventory()
2279 .cloned()
2280 {
2281 project_subscriptions.push(cx.observe_in(
2282 &task_inventory,
2283 window,
2284 |editor, _, window, cx| {
2285 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2286 },
2287 ));
2288 };
2289
2290 project_subscriptions.push(cx.subscribe_in(
2291 &project.read(cx).breakpoint_store(),
2292 window,
2293 |editor, _, event, window, cx| match event {
2294 BreakpointStoreEvent::ClearDebugLines => {
2295 editor.clear_row_highlights::<ActiveDebugLine>();
2296 editor.refresh_inline_values(cx);
2297 }
2298 BreakpointStoreEvent::SetDebugLine => {
2299 if editor.go_to_active_debug_line(window, cx) {
2300 cx.stop_propagation();
2301 }
2302
2303 editor.refresh_inline_values(cx);
2304 }
2305 _ => {}
2306 },
2307 ));
2308 let git_store = project.read(cx).git_store().clone();
2309 let project = project.clone();
2310 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2311 if let GitStoreEvent::RepositoryAdded = event {
2312 this.load_diff_task = Some(
2313 update_uncommitted_diff_for_buffer(
2314 cx.entity(),
2315 &project,
2316 this.buffer.read(cx).all_buffers(),
2317 this.buffer.clone(),
2318 cx,
2319 )
2320 .shared(),
2321 );
2322 }
2323 }));
2324 }
2325
2326 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2327
2328 let inlay_hint_settings =
2329 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2330 let focus_handle = cx.focus_handle();
2331 if !is_minimap {
2332 cx.on_focus(&focus_handle, window, Self::handle_focus)
2333 .detach();
2334 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2335 .detach();
2336 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2337 .detach();
2338 cx.on_blur(&focus_handle, window, Self::handle_blur)
2339 .detach();
2340 cx.observe_pending_input(window, Self::observe_pending_input)
2341 .detach();
2342 }
2343
2344 let show_indent_guides =
2345 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2346 Some(false)
2347 } else {
2348 None
2349 };
2350
2351 let breakpoint_store = match (&mode, project.as_ref()) {
2352 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2353 _ => None,
2354 };
2355
2356 let mut code_action_providers = Vec::new();
2357 let mut load_uncommitted_diff = None;
2358 if let Some(project) = project.clone() {
2359 load_uncommitted_diff = Some(
2360 update_uncommitted_diff_for_buffer(
2361 cx.entity(),
2362 &project,
2363 multi_buffer.read(cx).all_buffers(),
2364 multi_buffer.clone(),
2365 cx,
2366 )
2367 .shared(),
2368 );
2369 code_action_providers.push(Rc::new(project) as Rc<_>);
2370 }
2371
2372 let mut editor = Self {
2373 focus_handle,
2374 show_cursor_when_unfocused: false,
2375 last_focused_descendant: None,
2376 buffer: multi_buffer.clone(),
2377 display_map: display_map.clone(),
2378 placeholder_display_map: None,
2379 selections,
2380 scroll_manager: ScrollManager::new(cx),
2381 columnar_selection_state: None,
2382 add_selections_state: None,
2383 select_next_state: None,
2384 select_prev_state: None,
2385 selection_history: SelectionHistory::default(),
2386 defer_selection_effects: false,
2387 deferred_selection_effects_state: None,
2388 autoclose_regions: Vec::new(),
2389 snippet_stack: InvalidationStack::default(),
2390 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2391 ime_transaction: None,
2392 active_diagnostics: ActiveDiagnostic::None,
2393 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2394 inline_diagnostics_update: Task::ready(()),
2395 inline_diagnostics: Vec::new(),
2396 soft_wrap_mode_override,
2397 diagnostics_max_severity,
2398 hard_wrap: None,
2399 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2400 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2401 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2402 project,
2403 blink_manager: blink_manager.clone(),
2404 show_local_selections: true,
2405 show_scrollbars: ScrollbarAxes {
2406 horizontal: full_mode,
2407 vertical: full_mode,
2408 },
2409 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2410 offset_content: !matches!(mode, EditorMode::SingleLine),
2411 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2412 show_gutter: full_mode,
2413 show_line_numbers: (!full_mode).then_some(false),
2414 use_relative_line_numbers: None,
2415 disable_expand_excerpt_buttons: !full_mode,
2416 delegate_expand_excerpts: false,
2417 delegate_stage_and_restore: false,
2418 delegate_open_excerpts: false,
2419 enable_lsp_data: true,
2420 enable_runnables: true,
2421 show_git_diff_gutter: None,
2422 show_code_actions: None,
2423 show_runnables: None,
2424 show_breakpoints: None,
2425 show_diff_review_button: false,
2426 show_wrap_guides: None,
2427 show_indent_guides,
2428 buffers_with_disabled_indent_guides: HashSet::default(),
2429 highlight_order: 0,
2430 highlighted_rows: HashMap::default(),
2431 background_highlights: HashMap::default(),
2432 gutter_highlights: HashMap::default(),
2433 scrollbar_marker_state: ScrollbarMarkerState::default(),
2434 active_indent_guides_state: ActiveIndentGuidesState::default(),
2435 nav_history: None,
2436 context_menu: RefCell::new(None),
2437 context_menu_options: None,
2438 mouse_context_menu: None,
2439 completion_tasks: Vec::new(),
2440 inline_blame_popover: None,
2441 inline_blame_popover_show_task: None,
2442 signature_help_state: SignatureHelpState::default(),
2443 auto_signature_help: None,
2444 find_all_references_task_sources: Vec::new(),
2445 next_completion_id: 0,
2446 next_inlay_id: 0,
2447 code_action_providers,
2448 available_code_actions: None,
2449 code_actions_task: None,
2450 quick_selection_highlight_task: None,
2451 debounced_selection_highlight_task: None,
2452 debounced_selection_highlight_complete: false,
2453 document_highlights_task: None,
2454 linked_editing_range_task: None,
2455 pending_rename: None,
2456 searchable: !is_minimap,
2457 cursor_shape: EditorSettings::get_global(cx)
2458 .cursor_shape
2459 .unwrap_or_default(),
2460 cursor_offset_on_selection: false,
2461 current_line_highlight: None,
2462 autoindent_mode: Some(AutoindentMode::EachLine),
2463 collapse_matches: false,
2464 workspace: None,
2465 input_enabled: !is_minimap,
2466 use_modal_editing: full_mode,
2467 read_only: is_minimap,
2468 use_autoclose: true,
2469 use_auto_surround: true,
2470 auto_replace_emoji_shortcode: false,
2471 jsx_tag_auto_close_enabled_in_any_buffer: false,
2472 leader_id: None,
2473 remote_id: None,
2474 hover_state: HoverState::default(),
2475 pending_mouse_down: None,
2476 prev_pressure_stage: None,
2477 hovered_link_state: None,
2478 edit_prediction_provider: None,
2479 active_edit_prediction: None,
2480 stale_edit_prediction_in_menu: None,
2481 edit_prediction_preview: EditPredictionPreview::Inactive {
2482 released_too_fast: false,
2483 },
2484 inline_diagnostics_enabled: full_mode,
2485 diagnostics_enabled: full_mode,
2486 word_completions_enabled: full_mode,
2487 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2488 gutter_hovered: false,
2489 pixel_position_of_newest_cursor: None,
2490 last_bounds: None,
2491 last_position_map: None,
2492 expect_bounds_change: None,
2493 gutter_dimensions: GutterDimensions::default(),
2494 style: None,
2495 show_cursor_names: false,
2496 hovered_cursors: HashMap::default(),
2497 next_editor_action_id: EditorActionId::default(),
2498 editor_actions: Rc::default(),
2499 edit_predictions_hidden_for_vim_mode: false,
2500 show_edit_predictions_override: None,
2501 show_completions_on_input_override: None,
2502 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2503 edit_prediction_settings: EditPredictionSettings::Disabled,
2504 edit_prediction_indent_conflict: false,
2505 edit_prediction_requires_modifier_in_indent_conflict: true,
2506 custom_context_menu: None,
2507 show_git_blame_gutter: false,
2508 show_git_blame_inline: false,
2509 show_selection_menu: None,
2510 show_git_blame_inline_delay_task: None,
2511 git_blame_inline_enabled: full_mode
2512 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2513 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2514 buffer_serialization: is_minimap.not().then(|| {
2515 BufferSerialization::new(
2516 ProjectSettings::get_global(cx)
2517 .session
2518 .restore_unsaved_buffers,
2519 )
2520 }),
2521 blame: None,
2522 blame_subscription: None,
2523 tasks: BTreeMap::default(),
2524
2525 breakpoint_store,
2526 gutter_breakpoint_indicator: (None, None),
2527 gutter_diff_review_indicator: (None, None),
2528 diff_review_drag_state: None,
2529 diff_review_overlays: Vec::new(),
2530 stored_review_comments: Vec::new(),
2531 next_review_comment_id: 0,
2532 hovered_diff_hunk_row: None,
2533 _subscriptions: (!is_minimap)
2534 .then(|| {
2535 vec![
2536 cx.observe(&multi_buffer, Self::on_buffer_changed),
2537 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2538 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2539 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2540 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2541 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2542 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2543 cx.observe_window_activation(window, |editor, window, cx| {
2544 let active = window.is_window_active();
2545 editor.blink_manager.update(cx, |blink_manager, cx| {
2546 if active {
2547 blink_manager.enable(cx);
2548 } else {
2549 blink_manager.disable(cx);
2550 }
2551 });
2552 if active {
2553 editor.show_mouse_cursor(cx);
2554 }
2555 }),
2556 ]
2557 })
2558 .unwrap_or_default(),
2559 tasks_update_task: None,
2560 pull_diagnostics_task: Task::ready(()),
2561 colors: None,
2562 refresh_colors_task: Task::ready(()),
2563 use_document_folding_ranges: false,
2564 refresh_folding_ranges_task: Task::ready(()),
2565 inlay_hints: None,
2566 next_color_inlay_id: 0,
2567 post_scroll_update: Task::ready(()),
2568 linked_edit_ranges: Default::default(),
2569 in_project_search: false,
2570 previous_search_ranges: None,
2571 breadcrumb_header: None,
2572 focused_block: None,
2573 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2574 addons: HashMap::default(),
2575 registered_buffers: HashMap::default(),
2576 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2577 selection_mark_mode: false,
2578 toggle_fold_multiple_buffers: Task::ready(()),
2579 serialize_selections: Task::ready(()),
2580 serialize_folds: Task::ready(()),
2581 text_style_refinement: None,
2582 load_diff_task: load_uncommitted_diff,
2583 temporary_diff_override: false,
2584 mouse_cursor_hidden: false,
2585 minimap: None,
2586 hide_mouse_mode: EditorSettings::get_global(cx)
2587 .hide_mouse
2588 .unwrap_or_default(),
2589 change_list: ChangeList::new(),
2590 mode,
2591 selection_drag_state: SelectionDragState::None,
2592 folding_newlines: Task::ready(()),
2593 lookup_key: None,
2594 select_next_is_case_sensitive: None,
2595 on_local_selections_changed: None,
2596 suppress_selection_callback: false,
2597 applicable_language_settings: HashMap::default(),
2598 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2599 accent_data: None,
2600 fetched_tree_sitter_chunks: HashMap::default(),
2601 number_deleted_lines: false,
2602 refresh_matching_bracket_highlights_task: Task::ready(()),
2603 refresh_document_symbols_task: Task::ready(()).shared(),
2604 lsp_document_symbols: HashMap::default(),
2605 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2606 outline_symbols_at_cursor: None,
2607 sticky_headers_task: Task::ready(()),
2608 sticky_headers: None,
2609 };
2610
2611 if is_minimap {
2612 return editor;
2613 }
2614
2615 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2616 editor.accent_data = editor.fetch_accent_data(cx);
2617
2618 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2619 editor
2620 ._subscriptions
2621 .push(cx.observe(breakpoints, |_, _, cx| {
2622 cx.notify();
2623 }));
2624 }
2625 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2626 editor._subscriptions.extend(project_subscriptions);
2627
2628 editor._subscriptions.push(cx.subscribe_in(
2629 &cx.entity(),
2630 window,
2631 |editor, _, e: &EditorEvent, window, cx| match e {
2632 EditorEvent::ScrollPositionChanged { local, .. } => {
2633 if *local {
2634 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2635 editor.inline_blame_popover.take();
2636 let snapshot = editor.snapshot(window, cx);
2637 let new_anchor = editor
2638 .scroll_manager
2639 .native_anchor(&snapshot.display_snapshot, cx);
2640 editor.update_restoration_data(cx, move |data| {
2641 data.scroll_position = (
2642 new_anchor.top_row(snapshot.buffer_snapshot()),
2643 new_anchor.offset,
2644 );
2645 });
2646
2647 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2648 cx.background_executor()
2649 .timer(Duration::from_millis(50))
2650 .await;
2651 editor
2652 .update_in(cx, |editor, window, cx| {
2653 editor.register_visible_buffers(cx);
2654 editor.colorize_brackets(false, cx);
2655 editor.refresh_inlay_hints(
2656 InlayHintRefreshReason::NewLinesShown,
2657 cx,
2658 );
2659 if !editor.buffer().read(cx).is_singleton() {
2660 editor.update_lsp_data(None, window, cx);
2661 }
2662 })
2663 .ok();
2664 });
2665 }
2666 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2667 }
2668 EditorEvent::Edited { .. } => {
2669 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2670 .map(|vim_mode| vim_mode.0)
2671 .unwrap_or(false);
2672 if !vim_mode {
2673 let display_map = editor.display_snapshot(cx);
2674 let selections = editor.selections.all_adjusted_display(&display_map);
2675 let pop_state = editor
2676 .change_list
2677 .last()
2678 .map(|previous| {
2679 previous.len() == selections.len()
2680 && previous.iter().enumerate().all(|(ix, p)| {
2681 p.to_display_point(&display_map).row()
2682 == selections[ix].head().row()
2683 })
2684 })
2685 .unwrap_or(false);
2686 let new_positions = selections
2687 .into_iter()
2688 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2689 .collect();
2690 editor
2691 .change_list
2692 .push_to_change_list(pop_state, new_positions);
2693 }
2694 }
2695 _ => (),
2696 },
2697 ));
2698
2699 if let Some(dap_store) = editor
2700 .project
2701 .as_ref()
2702 .map(|project| project.read(cx).dap_store())
2703 {
2704 let weak_editor = cx.weak_entity();
2705
2706 editor
2707 ._subscriptions
2708 .push(
2709 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2710 let session_entity = cx.entity();
2711 weak_editor
2712 .update(cx, |editor, cx| {
2713 editor._subscriptions.push(
2714 cx.subscribe(&session_entity, Self::on_debug_session_event),
2715 );
2716 })
2717 .ok();
2718 }),
2719 );
2720
2721 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2722 editor
2723 ._subscriptions
2724 .push(cx.subscribe(&session, Self::on_debug_session_event));
2725 }
2726 }
2727
2728 // skip adding the initial selection to selection history
2729 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2730 editor.end_selection(window, cx);
2731 editor.selection_history.mode = SelectionHistoryMode::Normal;
2732
2733 editor.scroll_manager.show_scrollbars(window, cx);
2734 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2735
2736 if full_mode {
2737 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2738 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2739
2740 if editor.git_blame_inline_enabled {
2741 editor.start_git_blame_inline(false, window, cx);
2742 }
2743
2744 editor.go_to_active_debug_line(window, cx);
2745
2746 editor.minimap =
2747 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2748 editor.colors = Some(LspColorData::new(cx));
2749 editor.use_document_folding_ranges = true;
2750 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2751
2752 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2753 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2754 }
2755 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2756 }
2757
2758 editor
2759 }
2760
2761 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2762 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2763 }
2764
2765 pub fn deploy_mouse_context_menu(
2766 &mut self,
2767 position: gpui::Point<Pixels>,
2768 context_menu: Entity<ContextMenu>,
2769 window: &mut Window,
2770 cx: &mut Context<Self>,
2771 ) {
2772 self.mouse_context_menu = Some(MouseContextMenu::new(
2773 self,
2774 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2775 context_menu,
2776 window,
2777 cx,
2778 ));
2779 }
2780
2781 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2782 self.mouse_context_menu
2783 .as_ref()
2784 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2785 }
2786
2787 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2788 if self
2789 .selections
2790 .pending_anchor()
2791 .is_some_and(|pending_selection| {
2792 let snapshot = self.buffer().read(cx).snapshot(cx);
2793 pending_selection.range().includes(range, &snapshot)
2794 })
2795 {
2796 return true;
2797 }
2798
2799 self.selections
2800 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2801 .into_iter()
2802 .any(|selection| {
2803 // This is needed to cover a corner case, if we just check for an existing
2804 // selection in the fold range, having a cursor at the start of the fold
2805 // marks it as selected. Non-empty selections don't cause this.
2806 let length = selection.end - selection.start;
2807 length > 0
2808 })
2809 }
2810
2811 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2812 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2813 }
2814
2815 fn key_context_internal(
2816 &self,
2817 has_active_edit_prediction: bool,
2818 window: &mut Window,
2819 cx: &mut App,
2820 ) -> KeyContext {
2821 let mut key_context = KeyContext::new_with_defaults();
2822 key_context.add("Editor");
2823 let mode = match self.mode {
2824 EditorMode::SingleLine => "single_line",
2825 EditorMode::AutoHeight { .. } => "auto_height",
2826 EditorMode::Minimap { .. } => "minimap",
2827 EditorMode::Full { .. } => "full",
2828 };
2829
2830 if EditorSettings::jupyter_enabled(cx) {
2831 key_context.add("jupyter");
2832 }
2833
2834 key_context.set("mode", mode);
2835 if self.pending_rename.is_some() {
2836 key_context.add("renaming");
2837 }
2838
2839 if let Some(snippet_stack) = self.snippet_stack.last() {
2840 key_context.add("in_snippet");
2841
2842 if snippet_stack.active_index > 0 {
2843 key_context.add("has_previous_tabstop");
2844 }
2845
2846 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2847 key_context.add("has_next_tabstop");
2848 }
2849 }
2850
2851 match self.context_menu.borrow().as_ref() {
2852 Some(CodeContextMenu::Completions(menu)) => {
2853 if menu.visible() {
2854 key_context.add("menu");
2855 key_context.add("showing_completions");
2856 }
2857 }
2858 Some(CodeContextMenu::CodeActions(menu)) => {
2859 if menu.visible() {
2860 key_context.add("menu");
2861 key_context.add("showing_code_actions")
2862 }
2863 }
2864 None => {}
2865 }
2866
2867 if self.signature_help_state.has_multiple_signatures() {
2868 key_context.add("showing_signature_help");
2869 }
2870
2871 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2872 if !self.focus_handle(cx).contains_focused(window, cx)
2873 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2874 {
2875 for addon in self.addons.values() {
2876 addon.extend_key_context(&mut key_context, cx)
2877 }
2878 }
2879
2880 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2881 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2882 Some(
2883 file.full_path(cx)
2884 .extension()?
2885 .to_string_lossy()
2886 .to_lowercase(),
2887 )
2888 }) {
2889 key_context.set("extension", extension);
2890 }
2891 } else {
2892 key_context.add("multibuffer");
2893 }
2894
2895 if has_active_edit_prediction {
2896 if self.edit_prediction_in_conflict() {
2897 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2898 } else {
2899 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2900 key_context.add("copilot_suggestion");
2901 }
2902 }
2903
2904 if self.selection_mark_mode {
2905 key_context.add("selection_mode");
2906 }
2907
2908 let disjoint = self.selections.disjoint_anchors();
2909 let snapshot = self.snapshot(window, cx);
2910 let snapshot = snapshot.buffer_snapshot();
2911 if self.mode == EditorMode::SingleLine
2912 && let [selection] = disjoint
2913 && selection.start == selection.end
2914 && selection.end.to_offset(snapshot) == snapshot.len()
2915 {
2916 key_context.add("end_of_input");
2917 }
2918
2919 if self.has_any_expanded_diff_hunks(cx) {
2920 key_context.add("diffs_expanded");
2921 }
2922
2923 key_context
2924 }
2925
2926 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2927 self.last_bounds.as_ref()
2928 }
2929
2930 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2931 if self.mouse_cursor_hidden {
2932 self.mouse_cursor_hidden = false;
2933 cx.notify();
2934 }
2935 }
2936
2937 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2938 let hide_mouse_cursor = match origin {
2939 HideMouseCursorOrigin::TypingAction => {
2940 matches!(
2941 self.hide_mouse_mode,
2942 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2943 )
2944 }
2945 HideMouseCursorOrigin::MovementAction => {
2946 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2947 }
2948 };
2949 if self.mouse_cursor_hidden != hide_mouse_cursor {
2950 self.mouse_cursor_hidden = hide_mouse_cursor;
2951 cx.notify();
2952 }
2953 }
2954
2955 pub fn edit_prediction_in_conflict(&self) -> bool {
2956 if !self.show_edit_predictions_in_menu() {
2957 return false;
2958 }
2959
2960 let showing_completions = self
2961 .context_menu
2962 .borrow()
2963 .as_ref()
2964 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2965
2966 showing_completions
2967 || self.edit_prediction_requires_modifier()
2968 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2969 // bindings to insert tab characters.
2970 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2971 }
2972
2973 pub fn accept_edit_prediction_keybind(
2974 &self,
2975 granularity: EditPredictionGranularity,
2976 window: &mut Window,
2977 cx: &mut App,
2978 ) -> AcceptEditPredictionBinding {
2979 let key_context = self.key_context_internal(true, window, cx);
2980 let in_conflict = self.edit_prediction_in_conflict();
2981
2982 let bindings =
2983 match granularity {
2984 EditPredictionGranularity::Word => window
2985 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2986 EditPredictionGranularity::Line => window
2987 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2988 EditPredictionGranularity::Full => {
2989 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2990 }
2991 };
2992
2993 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2994 !in_conflict
2995 || binding
2996 .keystrokes()
2997 .first()
2998 .is_some_and(|keystroke| keystroke.modifiers().modified())
2999 }))
3000 }
3001
3002 pub fn new_file(
3003 workspace: &mut Workspace,
3004 _: &workspace::NewFile,
3005 window: &mut Window,
3006 cx: &mut Context<Workspace>,
3007 ) {
3008 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3009 "Failed to create buffer",
3010 window,
3011 cx,
3012 |e, _, _| match e.error_code() {
3013 ErrorCode::RemoteUpgradeRequired => Some(format!(
3014 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3015 e.error_tag("required").unwrap_or("the latest version")
3016 )),
3017 _ => None,
3018 },
3019 );
3020 }
3021
3022 pub fn new_in_workspace(
3023 workspace: &mut Workspace,
3024 window: &mut Window,
3025 cx: &mut Context<Workspace>,
3026 ) -> Task<Result<Entity<Editor>>> {
3027 let project = workspace.project().clone();
3028 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3029
3030 cx.spawn_in(window, async move |workspace, cx| {
3031 let buffer = create.await?;
3032 workspace.update_in(cx, |workspace, window, cx| {
3033 let editor =
3034 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3035 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3036 editor
3037 })
3038 })
3039 }
3040
3041 fn new_file_vertical(
3042 workspace: &mut Workspace,
3043 _: &workspace::NewFileSplitVertical,
3044 window: &mut Window,
3045 cx: &mut Context<Workspace>,
3046 ) {
3047 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3048 }
3049
3050 fn new_file_horizontal(
3051 workspace: &mut Workspace,
3052 _: &workspace::NewFileSplitHorizontal,
3053 window: &mut Window,
3054 cx: &mut Context<Workspace>,
3055 ) {
3056 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3057 }
3058
3059 fn new_file_split(
3060 workspace: &mut Workspace,
3061 action: &workspace::NewFileSplit,
3062 window: &mut Window,
3063 cx: &mut Context<Workspace>,
3064 ) {
3065 Self::new_file_in_direction(workspace, action.0, window, cx)
3066 }
3067
3068 fn new_file_in_direction(
3069 workspace: &mut Workspace,
3070 direction: SplitDirection,
3071 window: &mut Window,
3072 cx: &mut Context<Workspace>,
3073 ) {
3074 let project = workspace.project().clone();
3075 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3076
3077 cx.spawn_in(window, async move |workspace, cx| {
3078 let buffer = create.await?;
3079 workspace.update_in(cx, move |workspace, window, cx| {
3080 workspace.split_item(
3081 direction,
3082 Box::new(
3083 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3084 ),
3085 window,
3086 cx,
3087 )
3088 })?;
3089 anyhow::Ok(())
3090 })
3091 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3092 match e.error_code() {
3093 ErrorCode::RemoteUpgradeRequired => Some(format!(
3094 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3095 e.error_tag("required").unwrap_or("the latest version")
3096 )),
3097 _ => None,
3098 }
3099 });
3100 }
3101
3102 pub fn leader_id(&self) -> Option<CollaboratorId> {
3103 self.leader_id
3104 }
3105
3106 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3107 &self.buffer
3108 }
3109
3110 pub fn project(&self) -> Option<&Entity<Project>> {
3111 self.project.as_ref()
3112 }
3113
3114 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3115 self.workspace.as_ref()?.0.upgrade()
3116 }
3117
3118 /// Detaches a task and shows an error notification in the workspace if available,
3119 /// otherwise just logs the error.
3120 pub fn detach_and_notify_err<R, E>(
3121 &self,
3122 task: Task<Result<R, E>>,
3123 window: &mut Window,
3124 cx: &mut App,
3125 ) where
3126 E: std::fmt::Debug + std::fmt::Display + 'static,
3127 R: 'static,
3128 {
3129 if let Some(workspace) = self.workspace() {
3130 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3131 } else {
3132 task.detach_and_log_err(cx);
3133 }
3134 }
3135
3136 /// Returns the workspace serialization ID if this editor should be serialized.
3137 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3138 self.workspace
3139 .as_ref()
3140 .filter(|_| self.should_serialize_buffer())
3141 .and_then(|workspace| workspace.1)
3142 }
3143
3144 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3145 self.buffer().read(cx).title(cx)
3146 }
3147
3148 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3149 let git_blame_gutter_max_author_length = self
3150 .render_git_blame_gutter(cx)
3151 .then(|| {
3152 if let Some(blame) = self.blame.as_ref() {
3153 let max_author_length =
3154 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3155 Some(max_author_length)
3156 } else {
3157 None
3158 }
3159 })
3160 .flatten();
3161
3162 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3163
3164 EditorSnapshot {
3165 mode: self.mode.clone(),
3166 show_gutter: self.show_gutter,
3167 offset_content: self.offset_content,
3168 show_line_numbers: self.show_line_numbers,
3169 number_deleted_lines: self.number_deleted_lines,
3170 show_git_diff_gutter: self.show_git_diff_gutter,
3171 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3172 show_code_actions: self.show_code_actions,
3173 show_runnables: self.show_runnables,
3174 show_breakpoints: self.show_breakpoints,
3175 git_blame_gutter_max_author_length,
3176 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3177 display_snapshot,
3178 placeholder_display_snapshot: self
3179 .placeholder_display_map
3180 .as_ref()
3181 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3182 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3183 is_focused: self.focus_handle.is_focused(window),
3184 current_line_highlight: self
3185 .current_line_highlight
3186 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3187 gutter_hovered: self.gutter_hovered,
3188 }
3189 }
3190
3191 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3192 self.buffer.read(cx).language_at(point, cx)
3193 }
3194
3195 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3196 self.buffer.read(cx).read(cx).file_at(point).cloned()
3197 }
3198
3199 pub fn active_excerpt(
3200 &self,
3201 cx: &App,
3202 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3203 self.buffer
3204 .read(cx)
3205 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3206 }
3207
3208 pub fn mode(&self) -> &EditorMode {
3209 &self.mode
3210 }
3211
3212 pub fn set_mode(&mut self, mode: EditorMode) {
3213 self.mode = mode;
3214 }
3215
3216 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3217 self.collaboration_hub.as_deref()
3218 }
3219
3220 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3221 self.collaboration_hub = Some(hub);
3222 }
3223
3224 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3225 self.in_project_search = in_project_search;
3226 }
3227
3228 pub fn set_custom_context_menu(
3229 &mut self,
3230 f: impl 'static
3231 + Fn(
3232 &mut Self,
3233 DisplayPoint,
3234 &mut Window,
3235 &mut Context<Self>,
3236 ) -> Option<Entity<ui::ContextMenu>>,
3237 ) {
3238 self.custom_context_menu = Some(Box::new(f))
3239 }
3240
3241 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3242 self.completion_provider = provider;
3243 }
3244
3245 #[cfg(any(test, feature = "test-support"))]
3246 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3247 self.completion_provider.clone()
3248 }
3249
3250 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3251 self.semantics_provider.clone()
3252 }
3253
3254 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3255 self.semantics_provider = provider;
3256 }
3257
3258 pub fn set_edit_prediction_provider<T>(
3259 &mut self,
3260 provider: Option<Entity<T>>,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 ) where
3264 T: EditPredictionDelegate,
3265 {
3266 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3267 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3268 if this.focus_handle.is_focused(window) {
3269 this.update_visible_edit_prediction(window, cx);
3270 }
3271 }),
3272 provider: Arc::new(provider),
3273 });
3274 self.update_edit_prediction_settings(cx);
3275 self.refresh_edit_prediction(false, false, window, cx);
3276 }
3277
3278 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3279 self.placeholder_display_map
3280 .as_ref()
3281 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3282 }
3283
3284 pub fn set_placeholder_text(
3285 &mut self,
3286 placeholder_text: &str,
3287 window: &mut Window,
3288 cx: &mut Context<Self>,
3289 ) {
3290 let multibuffer = cx
3291 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3292
3293 let style = window.text_style();
3294
3295 self.placeholder_display_map = Some(cx.new(|cx| {
3296 DisplayMap::new(
3297 multibuffer,
3298 style.font(),
3299 style.font_size.to_pixels(window.rem_size()),
3300 None,
3301 FILE_HEADER_HEIGHT,
3302 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3303 Default::default(),
3304 DiagnosticSeverity::Off,
3305 cx,
3306 )
3307 }));
3308 cx.notify();
3309 }
3310
3311 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3312 self.cursor_shape = cursor_shape;
3313
3314 // Disrupt blink for immediate user feedback that the cursor shape has changed
3315 self.blink_manager.update(cx, BlinkManager::show_cursor);
3316
3317 cx.notify();
3318 }
3319
3320 pub fn cursor_shape(&self) -> CursorShape {
3321 self.cursor_shape
3322 }
3323
3324 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3325 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3326 }
3327
3328 pub fn set_current_line_highlight(
3329 &mut self,
3330 current_line_highlight: Option<CurrentLineHighlight>,
3331 ) {
3332 self.current_line_highlight = current_line_highlight;
3333 }
3334
3335 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3336 self.collapse_matches = collapse_matches;
3337 }
3338
3339 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3340 if self.collapse_matches {
3341 return range.start..range.start;
3342 }
3343 range.clone()
3344 }
3345
3346 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3347 self.display_map.read(cx).clip_at_line_ends
3348 }
3349
3350 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3351 if self.display_map.read(cx).clip_at_line_ends != clip {
3352 self.display_map
3353 .update(cx, |map, _| map.clip_at_line_ends = clip);
3354 }
3355 }
3356
3357 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3358 self.input_enabled = input_enabled;
3359 }
3360
3361 pub fn set_edit_predictions_hidden_for_vim_mode(
3362 &mut self,
3363 hidden: bool,
3364 window: &mut Window,
3365 cx: &mut Context<Self>,
3366 ) {
3367 if hidden != self.edit_predictions_hidden_for_vim_mode {
3368 self.edit_predictions_hidden_for_vim_mode = hidden;
3369 if hidden {
3370 self.update_visible_edit_prediction(window, cx);
3371 } else {
3372 self.refresh_edit_prediction(true, false, window, cx);
3373 }
3374 }
3375 }
3376
3377 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3378 self.menu_edit_predictions_policy = value;
3379 }
3380
3381 pub fn set_autoindent(&mut self, autoindent: bool) {
3382 if autoindent {
3383 self.autoindent_mode = Some(AutoindentMode::EachLine);
3384 } else {
3385 self.autoindent_mode = None;
3386 }
3387 }
3388
3389 pub fn capability(&self, cx: &App) -> Capability {
3390 if self.read_only {
3391 Capability::ReadOnly
3392 } else {
3393 self.buffer.read(cx).capability()
3394 }
3395 }
3396
3397 pub fn read_only(&self, cx: &App) -> bool {
3398 self.read_only || self.buffer.read(cx).read_only()
3399 }
3400
3401 pub fn set_read_only(&mut self, read_only: bool) {
3402 self.read_only = read_only;
3403 }
3404
3405 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3406 self.use_autoclose = autoclose;
3407 }
3408
3409 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3410 self.use_auto_surround = auto_surround;
3411 }
3412
3413 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3414 self.auto_replace_emoji_shortcode = auto_replace;
3415 }
3416
3417 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3418 self.buffer_serialization = should_serialize.then(|| {
3419 BufferSerialization::new(
3420 ProjectSettings::get_global(cx)
3421 .session
3422 .restore_unsaved_buffers,
3423 )
3424 })
3425 }
3426
3427 fn should_serialize_buffer(&self) -> bool {
3428 self.buffer_serialization.is_some()
3429 }
3430
3431 pub fn toggle_edit_predictions(
3432 &mut self,
3433 _: &ToggleEditPrediction,
3434 window: &mut Window,
3435 cx: &mut Context<Self>,
3436 ) {
3437 if self.show_edit_predictions_override.is_some() {
3438 self.set_show_edit_predictions(None, window, cx);
3439 } else {
3440 let show_edit_predictions = !self.edit_predictions_enabled();
3441 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3442 }
3443 }
3444
3445 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3446 self.show_completions_on_input_override = show_completions_on_input;
3447 }
3448
3449 pub fn set_show_edit_predictions(
3450 &mut self,
3451 show_edit_predictions: Option<bool>,
3452 window: &mut Window,
3453 cx: &mut Context<Self>,
3454 ) {
3455 self.show_edit_predictions_override = show_edit_predictions;
3456 self.update_edit_prediction_settings(cx);
3457
3458 if let Some(false) = show_edit_predictions {
3459 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3460 } else {
3461 self.refresh_edit_prediction(false, true, window, cx);
3462 }
3463 }
3464
3465 fn edit_predictions_disabled_in_scope(
3466 &self,
3467 buffer: &Entity<Buffer>,
3468 buffer_position: language::Anchor,
3469 cx: &App,
3470 ) -> bool {
3471 let snapshot = buffer.read(cx).snapshot();
3472 let settings = snapshot.settings_at(buffer_position, cx);
3473
3474 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3475 return false;
3476 };
3477
3478 scope.override_name().is_some_and(|scope_name| {
3479 settings
3480 .edit_predictions_disabled_in
3481 .iter()
3482 .any(|s| s == scope_name)
3483 })
3484 }
3485
3486 pub fn set_use_modal_editing(&mut self, to: bool) {
3487 self.use_modal_editing = to;
3488 }
3489
3490 pub fn use_modal_editing(&self) -> bool {
3491 self.use_modal_editing
3492 }
3493
3494 fn selections_did_change(
3495 &mut self,
3496 local: bool,
3497 old_cursor_position: &Anchor,
3498 effects: SelectionEffects,
3499 window: &mut Window,
3500 cx: &mut Context<Self>,
3501 ) {
3502 window.invalidate_character_coordinates();
3503
3504 // Copy selections to primary selection buffer
3505 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3506 if local {
3507 let selections = self
3508 .selections
3509 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3510 let buffer_handle = self.buffer.read(cx).read(cx);
3511
3512 let mut text = String::new();
3513 for (index, selection) in selections.iter().enumerate() {
3514 let text_for_selection = buffer_handle
3515 .text_for_range(selection.start..selection.end)
3516 .collect::<String>();
3517
3518 text.push_str(&text_for_selection);
3519 if index != selections.len() - 1 {
3520 text.push('\n');
3521 }
3522 }
3523
3524 if !text.is_empty() {
3525 cx.write_to_primary(ClipboardItem::new_string(text));
3526 }
3527 }
3528
3529 let selection_anchors = self.selections.disjoint_anchors_arc();
3530
3531 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3532 self.buffer.update(cx, |buffer, cx| {
3533 buffer.set_active_selections(
3534 &selection_anchors,
3535 self.selections.line_mode(),
3536 self.cursor_shape,
3537 cx,
3538 )
3539 });
3540 }
3541 let display_map = self
3542 .display_map
3543 .update(cx, |display_map, cx| display_map.snapshot(cx));
3544 let buffer = display_map.buffer_snapshot();
3545 if self.selections.count() == 1 {
3546 self.add_selections_state = None;
3547 }
3548 self.select_next_state = None;
3549 self.select_prev_state = None;
3550 self.select_syntax_node_history.try_clear();
3551 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3552 self.snippet_stack.invalidate(&selection_anchors, buffer);
3553 self.take_rename(false, window, cx);
3554
3555 let newest_selection = self.selections.newest_anchor();
3556 let new_cursor_position = newest_selection.head();
3557 let selection_start = newest_selection.start;
3558
3559 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3560 self.push_to_nav_history(
3561 *old_cursor_position,
3562 Some(new_cursor_position.to_point(buffer)),
3563 false,
3564 effects.nav_history == Some(true),
3565 cx,
3566 );
3567 }
3568
3569 if local {
3570 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3571 self.register_buffer(buffer_id, cx);
3572 }
3573
3574 let mut context_menu = self.context_menu.borrow_mut();
3575 let completion_menu = match context_menu.as_ref() {
3576 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3577 Some(CodeContextMenu::CodeActions(_)) => {
3578 *context_menu = None;
3579 None
3580 }
3581 None => None,
3582 };
3583 let completion_position = completion_menu.map(|menu| menu.initial_position);
3584 drop(context_menu);
3585
3586 if effects.completions
3587 && let Some(completion_position) = completion_position
3588 {
3589 let start_offset = selection_start.to_offset(buffer);
3590 let position_matches = start_offset == completion_position.to_offset(buffer);
3591 let continue_showing = if let Some((snap, ..)) =
3592 buffer.point_to_buffer_offset(completion_position)
3593 && !snap.capability.editable()
3594 {
3595 false
3596 } else if position_matches {
3597 if self.snippet_stack.is_empty() {
3598 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3599 == Some(CharKind::Word)
3600 } else {
3601 // Snippet choices can be shown even when the cursor is in whitespace.
3602 // Dismissing the menu with actions like backspace is handled by
3603 // invalidation regions.
3604 true
3605 }
3606 } else {
3607 false
3608 };
3609
3610 if continue_showing {
3611 self.open_or_update_completions_menu(None, None, false, window, cx);
3612 } else {
3613 self.hide_context_menu(window, cx);
3614 }
3615 }
3616
3617 hide_hover(self, cx);
3618
3619 if old_cursor_position.to_display_point(&display_map).row()
3620 != new_cursor_position.to_display_point(&display_map).row()
3621 {
3622 self.available_code_actions.take();
3623 }
3624 self.refresh_code_actions(window, cx);
3625 self.refresh_document_highlights(cx);
3626 refresh_linked_ranges(self, window, cx);
3627
3628 self.refresh_selected_text_highlights(false, window, cx);
3629 self.refresh_matching_bracket_highlights(&display_map, cx);
3630 self.refresh_outline_symbols_at_cursor(cx);
3631 self.update_visible_edit_prediction(window, cx);
3632 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3633 self.inline_blame_popover.take();
3634 if self.git_blame_inline_enabled {
3635 self.start_inline_blame_timer(window, cx);
3636 }
3637 }
3638
3639 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3640
3641 if local && !self.suppress_selection_callback {
3642 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3643 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3644 callback(cursor_position, window, cx);
3645 }
3646 }
3647
3648 cx.emit(EditorEvent::SelectionsChanged { local });
3649
3650 let selections = &self.selections.disjoint_anchors_arc();
3651 if selections.len() == 1 {
3652 cx.emit(SearchEvent::ActiveMatchChanged)
3653 }
3654 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3655 let inmemory_selections = selections
3656 .iter()
3657 .map(|s| {
3658 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3659 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3660 })
3661 .collect();
3662 self.update_restoration_data(cx, |data| {
3663 data.selections = inmemory_selections;
3664 });
3665
3666 if WorkspaceSettings::get(None, cx).restore_on_startup
3667 != RestoreOnStartupBehavior::EmptyTab
3668 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3669 {
3670 let snapshot = self.buffer().read(cx).snapshot(cx);
3671 let selections = selections.clone();
3672 let background_executor = cx.background_executor().clone();
3673 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3674 self.serialize_selections = cx.background_spawn(async move {
3675 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3676 let db_selections = selections
3677 .iter()
3678 .map(|selection| {
3679 (
3680 selection.start.to_offset(&snapshot).0,
3681 selection.end.to_offset(&snapshot).0,
3682 )
3683 })
3684 .collect();
3685
3686 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3687 .await
3688 .with_context(|| {
3689 format!(
3690 "persisting editor selections for editor {editor_id}, \
3691 workspace {workspace_id:?}"
3692 )
3693 })
3694 .log_err();
3695 });
3696 }
3697 }
3698
3699 cx.notify();
3700 }
3701
3702 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3703 use text::ToOffset as _;
3704 use text::ToPoint as _;
3705
3706 if self.mode.is_minimap()
3707 || WorkspaceSettings::get(None, cx).restore_on_startup
3708 == RestoreOnStartupBehavior::EmptyTab
3709 {
3710 return;
3711 }
3712
3713 if !self.buffer().read(cx).is_singleton() {
3714 return;
3715 }
3716
3717 let display_snapshot = self
3718 .display_map
3719 .update(cx, |display_map, cx| display_map.snapshot(cx));
3720 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3721 return;
3722 };
3723 let inmemory_folds = display_snapshot
3724 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3725 .map(|fold| {
3726 fold.range.start.text_anchor.to_point(&snapshot)
3727 ..fold.range.end.text_anchor.to_point(&snapshot)
3728 })
3729 .collect();
3730 self.update_restoration_data(cx, |data| {
3731 data.folds = inmemory_folds;
3732 });
3733
3734 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3735 return;
3736 };
3737 let background_executor = cx.background_executor().clone();
3738 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3739 const FINGERPRINT_LEN: usize = 32;
3740 let db_folds = display_snapshot
3741 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3742 .map(|fold| {
3743 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3744 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3745
3746 // Extract fingerprints - content at fold boundaries for validation on restore
3747 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3748 // content that might change independently.
3749 // start_fp: first min(32, fold_len) bytes of fold content
3750 // end_fp: last min(32, fold_len) bytes of fold content
3751 // Clip to character boundaries to handle multibyte UTF-8 characters.
3752 let fold_len = end - start;
3753 let start_fp_end = snapshot
3754 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3755 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3756 let end_fp_start = snapshot
3757 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3758 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3759
3760 (start, end, start_fp, end_fp)
3761 })
3762 .collect::<Vec<_>>();
3763 self.serialize_folds = cx.background_spawn(async move {
3764 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3765 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3766 .await
3767 .with_context(|| {
3768 format!(
3769 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3770 )
3771 })
3772 .log_err();
3773 });
3774 }
3775
3776 pub fn sync_selections(
3777 &mut self,
3778 other: Entity<Editor>,
3779 cx: &mut Context<Self>,
3780 ) -> gpui::Subscription {
3781 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3782 if !other_selections.is_empty() {
3783 self.selections
3784 .change_with(&self.display_snapshot(cx), |selections| {
3785 selections.select_anchors(other_selections);
3786 });
3787 }
3788
3789 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3790 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3791 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3792 if other_selections.is_empty() {
3793 return;
3794 }
3795 let snapshot = this.display_snapshot(cx);
3796 this.selections.change_with(&snapshot, |selections| {
3797 selections.select_anchors(other_selections);
3798 });
3799 }
3800 });
3801
3802 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3803 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3804 let these_selections = this.selections.disjoint_anchors().to_vec();
3805 if these_selections.is_empty() {
3806 return;
3807 }
3808 other.update(cx, |other_editor, cx| {
3809 let snapshot = other_editor.display_snapshot(cx);
3810 other_editor
3811 .selections
3812 .change_with(&snapshot, |selections| {
3813 selections.select_anchors(these_selections);
3814 })
3815 });
3816 }
3817 });
3818
3819 Subscription::join(other_subscription, this_subscription)
3820 }
3821
3822 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3823 if self.buffer().read(cx).is_singleton() {
3824 return;
3825 }
3826 let snapshot = self.buffer.read(cx).snapshot(cx);
3827 let buffer_ids: HashSet<BufferId> = self
3828 .selections
3829 .disjoint_anchor_ranges()
3830 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3831 .collect();
3832 for buffer_id in buffer_ids {
3833 self.unfold_buffer(buffer_id, cx);
3834 }
3835 }
3836
3837 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3838 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3839 /// effects of selection change occur at the end of the transaction.
3840 pub fn change_selections<R>(
3841 &mut self,
3842 effects: SelectionEffects,
3843 window: &mut Window,
3844 cx: &mut Context<Self>,
3845 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3846 ) -> R {
3847 let snapshot = self.display_snapshot(cx);
3848 if let Some(state) = &mut self.deferred_selection_effects_state {
3849 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3850 state.effects.completions = effects.completions;
3851 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3852 let (changed, result) = self.selections.change_with(&snapshot, change);
3853 state.changed |= changed;
3854 return result;
3855 }
3856 let mut state = DeferredSelectionEffectsState {
3857 changed: false,
3858 effects,
3859 old_cursor_position: self.selections.newest_anchor().head(),
3860 history_entry: SelectionHistoryEntry {
3861 selections: self.selections.disjoint_anchors_arc(),
3862 select_next_state: self.select_next_state.clone(),
3863 select_prev_state: self.select_prev_state.clone(),
3864 add_selections_state: self.add_selections_state.clone(),
3865 },
3866 };
3867 let (changed, result) = self.selections.change_with(&snapshot, change);
3868 state.changed = state.changed || changed;
3869 if self.defer_selection_effects {
3870 self.deferred_selection_effects_state = Some(state);
3871 } else {
3872 self.apply_selection_effects(state, window, cx);
3873 }
3874 result
3875 }
3876
3877 /// Defers the effects of selection change, so that the effects of multiple calls to
3878 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3879 /// to selection history and the state of popovers based on selection position aren't
3880 /// erroneously updated.
3881 pub fn with_selection_effects_deferred<R>(
3882 &mut self,
3883 window: &mut Window,
3884 cx: &mut Context<Self>,
3885 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3886 ) -> R {
3887 let already_deferred = self.defer_selection_effects;
3888 self.defer_selection_effects = true;
3889 let result = update(self, window, cx);
3890 if !already_deferred {
3891 self.defer_selection_effects = false;
3892 if let Some(state) = self.deferred_selection_effects_state.take() {
3893 self.apply_selection_effects(state, window, cx);
3894 }
3895 }
3896 result
3897 }
3898
3899 fn apply_selection_effects(
3900 &mut self,
3901 state: DeferredSelectionEffectsState,
3902 window: &mut Window,
3903 cx: &mut Context<Self>,
3904 ) {
3905 if state.changed {
3906 self.selection_history.push(state.history_entry);
3907
3908 if let Some(autoscroll) = state.effects.scroll {
3909 self.request_autoscroll(autoscroll, cx);
3910 }
3911
3912 let old_cursor_position = &state.old_cursor_position;
3913
3914 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3915
3916 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3917 self.show_signature_help_auto(window, cx);
3918 }
3919 }
3920 }
3921
3922 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3923 where
3924 I: IntoIterator<Item = (Range<S>, T)>,
3925 S: ToOffset,
3926 T: Into<Arc<str>>,
3927 {
3928 if self.read_only(cx) {
3929 return;
3930 }
3931
3932 self.buffer
3933 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3934 }
3935
3936 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3937 where
3938 I: IntoIterator<Item = (Range<S>, T)>,
3939 S: ToOffset,
3940 T: Into<Arc<str>>,
3941 {
3942 if self.read_only(cx) {
3943 return;
3944 }
3945
3946 self.buffer.update(cx, |buffer, cx| {
3947 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3948 });
3949 }
3950
3951 pub fn edit_with_block_indent<I, S, T>(
3952 &mut self,
3953 edits: I,
3954 original_indent_columns: Vec<Option<u32>>,
3955 cx: &mut Context<Self>,
3956 ) where
3957 I: IntoIterator<Item = (Range<S>, T)>,
3958 S: ToOffset,
3959 T: Into<Arc<str>>,
3960 {
3961 if self.read_only(cx) {
3962 return;
3963 }
3964
3965 self.buffer.update(cx, |buffer, cx| {
3966 buffer.edit(
3967 edits,
3968 Some(AutoindentMode::Block {
3969 original_indent_columns,
3970 }),
3971 cx,
3972 )
3973 });
3974 }
3975
3976 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3977 self.hide_context_menu(window, cx);
3978
3979 match phase {
3980 SelectPhase::Begin {
3981 position,
3982 add,
3983 click_count,
3984 } => self.begin_selection(position, add, click_count, window, cx),
3985 SelectPhase::BeginColumnar {
3986 position,
3987 goal_column,
3988 reset,
3989 mode,
3990 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3991 SelectPhase::Extend {
3992 position,
3993 click_count,
3994 } => self.extend_selection(position, click_count, window, cx),
3995 SelectPhase::Update {
3996 position,
3997 goal_column,
3998 scroll_delta,
3999 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4000 SelectPhase::End => self.end_selection(window, cx),
4001 }
4002 }
4003
4004 fn extend_selection(
4005 &mut self,
4006 position: DisplayPoint,
4007 click_count: usize,
4008 window: &mut Window,
4009 cx: &mut Context<Self>,
4010 ) {
4011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4012 let tail = self
4013 .selections
4014 .newest::<MultiBufferOffset>(&display_map)
4015 .tail();
4016 let click_count = click_count.max(match self.selections.select_mode() {
4017 SelectMode::Character => 1,
4018 SelectMode::Word(_) => 2,
4019 SelectMode::Line(_) => 3,
4020 SelectMode::All => 4,
4021 });
4022 self.begin_selection(position, false, click_count, window, cx);
4023
4024 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4025
4026 let current_selection = match self.selections.select_mode() {
4027 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4028 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4029 };
4030
4031 let mut pending_selection = self
4032 .selections
4033 .pending_anchor()
4034 .cloned()
4035 .expect("extend_selection not called with pending selection");
4036
4037 if pending_selection
4038 .start
4039 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4040 == Ordering::Greater
4041 {
4042 pending_selection.start = current_selection.start;
4043 }
4044 if pending_selection
4045 .end
4046 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4047 == Ordering::Less
4048 {
4049 pending_selection.end = current_selection.end;
4050 pending_selection.reversed = true;
4051 }
4052
4053 let mut pending_mode = self.selections.pending_mode().unwrap();
4054 match &mut pending_mode {
4055 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4056 _ => {}
4057 }
4058
4059 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4060 SelectionEffects::scroll(Autoscroll::fit())
4061 } else {
4062 SelectionEffects::no_scroll()
4063 };
4064
4065 self.change_selections(effects, window, cx, |s| {
4066 s.set_pending(pending_selection.clone(), pending_mode);
4067 s.set_is_extending(true);
4068 });
4069 }
4070
4071 fn begin_selection(
4072 &mut self,
4073 position: DisplayPoint,
4074 add: bool,
4075 click_count: usize,
4076 window: &mut Window,
4077 cx: &mut Context<Self>,
4078 ) {
4079 if !self.focus_handle.is_focused(window) {
4080 self.last_focused_descendant = None;
4081 window.focus(&self.focus_handle, cx);
4082 }
4083
4084 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4085 let buffer = display_map.buffer_snapshot();
4086 let position = display_map.clip_point(position, Bias::Left);
4087
4088 let start;
4089 let end;
4090 let mode;
4091 let mut auto_scroll;
4092 match click_count {
4093 1 => {
4094 start = buffer.anchor_before(position.to_point(&display_map));
4095 end = start;
4096 mode = SelectMode::Character;
4097 auto_scroll = true;
4098 }
4099 2 => {
4100 let position = display_map
4101 .clip_point(position, Bias::Left)
4102 .to_offset(&display_map, Bias::Left);
4103 let (range, _) = buffer.surrounding_word(position, None);
4104 start = buffer.anchor_before(range.start);
4105 end = buffer.anchor_before(range.end);
4106 mode = SelectMode::Word(start..end);
4107 auto_scroll = true;
4108 }
4109 3 => {
4110 let position = display_map
4111 .clip_point(position, Bias::Left)
4112 .to_point(&display_map);
4113 let line_start = display_map.prev_line_boundary(position).0;
4114 let next_line_start = buffer.clip_point(
4115 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4116 Bias::Left,
4117 );
4118 start = buffer.anchor_before(line_start);
4119 end = buffer.anchor_before(next_line_start);
4120 mode = SelectMode::Line(start..end);
4121 auto_scroll = true;
4122 }
4123 _ => {
4124 start = buffer.anchor_before(MultiBufferOffset(0));
4125 end = buffer.anchor_before(buffer.len());
4126 mode = SelectMode::All;
4127 auto_scroll = false;
4128 }
4129 }
4130 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4131
4132 let point_to_delete: Option<usize> = {
4133 let selected_points: Vec<Selection<Point>> =
4134 self.selections.disjoint_in_range(start..end, &display_map);
4135
4136 if !add || click_count > 1 {
4137 None
4138 } else if !selected_points.is_empty() {
4139 Some(selected_points[0].id)
4140 } else {
4141 let clicked_point_already_selected =
4142 self.selections.disjoint_anchors().iter().find(|selection| {
4143 selection.start.to_point(buffer) == start.to_point(buffer)
4144 || selection.end.to_point(buffer) == end.to_point(buffer)
4145 });
4146
4147 clicked_point_already_selected.map(|selection| selection.id)
4148 }
4149 };
4150
4151 let selections_count = self.selections.count();
4152 let effects = if auto_scroll {
4153 SelectionEffects::default()
4154 } else {
4155 SelectionEffects::no_scroll()
4156 };
4157
4158 self.change_selections(effects, window, cx, |s| {
4159 if let Some(point_to_delete) = point_to_delete {
4160 s.delete(point_to_delete);
4161
4162 if selections_count == 1 {
4163 s.set_pending_anchor_range(start..end, mode);
4164 }
4165 } else {
4166 if !add {
4167 s.clear_disjoint();
4168 }
4169
4170 s.set_pending_anchor_range(start..end, mode);
4171 }
4172 });
4173 }
4174
4175 fn begin_columnar_selection(
4176 &mut self,
4177 position: DisplayPoint,
4178 goal_column: u32,
4179 reset: bool,
4180 mode: ColumnarMode,
4181 window: &mut Window,
4182 cx: &mut Context<Self>,
4183 ) {
4184 if !self.focus_handle.is_focused(window) {
4185 self.last_focused_descendant = None;
4186 window.focus(&self.focus_handle, cx);
4187 }
4188
4189 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4190
4191 if reset {
4192 let pointer_position = display_map
4193 .buffer_snapshot()
4194 .anchor_before(position.to_point(&display_map));
4195
4196 self.change_selections(
4197 SelectionEffects::scroll(Autoscroll::newest()),
4198 window,
4199 cx,
4200 |s| {
4201 s.clear_disjoint();
4202 s.set_pending_anchor_range(
4203 pointer_position..pointer_position,
4204 SelectMode::Character,
4205 );
4206 },
4207 );
4208 };
4209
4210 let tail = self.selections.newest::<Point>(&display_map).tail();
4211 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4212 self.columnar_selection_state = match mode {
4213 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4214 selection_tail: selection_anchor,
4215 display_point: if reset {
4216 if position.column() != goal_column {
4217 Some(DisplayPoint::new(position.row(), goal_column))
4218 } else {
4219 None
4220 }
4221 } else {
4222 None
4223 },
4224 }),
4225 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4226 selection_tail: selection_anchor,
4227 }),
4228 };
4229
4230 if !reset {
4231 self.select_columns(position, goal_column, &display_map, window, cx);
4232 }
4233 }
4234
4235 fn update_selection(
4236 &mut self,
4237 position: DisplayPoint,
4238 goal_column: u32,
4239 scroll_delta: gpui::Point<f32>,
4240 window: &mut Window,
4241 cx: &mut Context<Self>,
4242 ) {
4243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4244
4245 if self.columnar_selection_state.is_some() {
4246 self.select_columns(position, goal_column, &display_map, window, cx);
4247 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4248 let buffer = display_map.buffer_snapshot();
4249 let head;
4250 let tail;
4251 let mode = self.selections.pending_mode().unwrap();
4252 match &mode {
4253 SelectMode::Character => {
4254 head = position.to_point(&display_map);
4255 tail = pending.tail().to_point(buffer);
4256 }
4257 SelectMode::Word(original_range) => {
4258 let offset = display_map
4259 .clip_point(position, Bias::Left)
4260 .to_offset(&display_map, Bias::Left);
4261 let original_range = original_range.to_offset(buffer);
4262
4263 let head_offset = if buffer.is_inside_word(offset, None)
4264 || original_range.contains(&offset)
4265 {
4266 let (word_range, _) = buffer.surrounding_word(offset, None);
4267 if word_range.start < original_range.start {
4268 word_range.start
4269 } else {
4270 word_range.end
4271 }
4272 } else {
4273 offset
4274 };
4275
4276 head = head_offset.to_point(buffer);
4277 if head_offset <= original_range.start {
4278 tail = original_range.end.to_point(buffer);
4279 } else {
4280 tail = original_range.start.to_point(buffer);
4281 }
4282 }
4283 SelectMode::Line(original_range) => {
4284 let original_range = original_range.to_point(display_map.buffer_snapshot());
4285
4286 let position = display_map
4287 .clip_point(position, Bias::Left)
4288 .to_point(&display_map);
4289 let line_start = display_map.prev_line_boundary(position).0;
4290 let next_line_start = buffer.clip_point(
4291 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4292 Bias::Left,
4293 );
4294
4295 if line_start < original_range.start {
4296 head = line_start
4297 } else {
4298 head = next_line_start
4299 }
4300
4301 if head <= original_range.start {
4302 tail = original_range.end;
4303 } else {
4304 tail = original_range.start;
4305 }
4306 }
4307 SelectMode::All => {
4308 return;
4309 }
4310 };
4311
4312 if head < tail {
4313 pending.start = buffer.anchor_before(head);
4314 pending.end = buffer.anchor_before(tail);
4315 pending.reversed = true;
4316 } else {
4317 pending.start = buffer.anchor_before(tail);
4318 pending.end = buffer.anchor_before(head);
4319 pending.reversed = false;
4320 }
4321
4322 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4323 s.set_pending(pending.clone(), mode);
4324 });
4325 } else {
4326 log::error!("update_selection dispatched with no pending selection");
4327 return;
4328 }
4329
4330 self.apply_scroll_delta(scroll_delta, window, cx);
4331 cx.notify();
4332 }
4333
4334 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4335 self.columnar_selection_state.take();
4336 if let Some(pending_mode) = self.selections.pending_mode() {
4337 let selections = self
4338 .selections
4339 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4340 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4341 s.select(selections);
4342 s.clear_pending();
4343 if s.is_extending() {
4344 s.set_is_extending(false);
4345 } else {
4346 s.set_select_mode(pending_mode);
4347 }
4348 });
4349 }
4350 }
4351
4352 fn select_columns(
4353 &mut self,
4354 head: DisplayPoint,
4355 goal_column: u32,
4356 display_map: &DisplaySnapshot,
4357 window: &mut Window,
4358 cx: &mut Context<Self>,
4359 ) {
4360 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4361 return;
4362 };
4363
4364 let tail = match columnar_state {
4365 ColumnarSelectionState::FromMouse {
4366 selection_tail,
4367 display_point,
4368 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4369 ColumnarSelectionState::FromSelection { selection_tail } => {
4370 selection_tail.to_display_point(display_map)
4371 }
4372 };
4373
4374 let start_row = cmp::min(tail.row(), head.row());
4375 let end_row = cmp::max(tail.row(), head.row());
4376 let start_column = cmp::min(tail.column(), goal_column);
4377 let end_column = cmp::max(tail.column(), goal_column);
4378 let reversed = start_column < tail.column();
4379
4380 let selection_ranges = (start_row.0..=end_row.0)
4381 .map(DisplayRow)
4382 .filter_map(|row| {
4383 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4384 || start_column <= display_map.line_len(row))
4385 && !display_map.is_block_line(row)
4386 {
4387 let start = display_map
4388 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4389 .to_point(display_map);
4390 let end = display_map
4391 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4392 .to_point(display_map);
4393 if reversed {
4394 Some(end..start)
4395 } else {
4396 Some(start..end)
4397 }
4398 } else {
4399 None
4400 }
4401 })
4402 .collect::<Vec<_>>();
4403 if selection_ranges.is_empty() {
4404 return;
4405 }
4406
4407 let ranges = match columnar_state {
4408 ColumnarSelectionState::FromMouse { .. } => {
4409 let mut non_empty_ranges = selection_ranges
4410 .iter()
4411 .filter(|selection_range| selection_range.start != selection_range.end)
4412 .peekable();
4413 if non_empty_ranges.peek().is_some() {
4414 non_empty_ranges.cloned().collect()
4415 } else {
4416 selection_ranges
4417 }
4418 }
4419 _ => selection_ranges,
4420 };
4421
4422 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4423 s.select_ranges(ranges);
4424 });
4425 cx.notify();
4426 }
4427
4428 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4429 self.selections
4430 .all_adjusted(snapshot)
4431 .iter()
4432 .any(|selection| !selection.is_empty())
4433 }
4434
4435 pub fn has_pending_nonempty_selection(&self) -> bool {
4436 let pending_nonempty_selection = match self.selections.pending_anchor() {
4437 Some(Selection { start, end, .. }) => start != end,
4438 None => false,
4439 };
4440
4441 pending_nonempty_selection
4442 || (self.columnar_selection_state.is_some()
4443 && self.selections.disjoint_anchors().len() > 1)
4444 }
4445
4446 pub fn has_pending_selection(&self) -> bool {
4447 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4448 }
4449
4450 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4451 self.selection_mark_mode = false;
4452 self.selection_drag_state = SelectionDragState::None;
4453
4454 if self.dismiss_menus_and_popups(true, window, cx) {
4455 cx.notify();
4456 return;
4457 }
4458 if self.clear_expanded_diff_hunks(cx) {
4459 cx.notify();
4460 return;
4461 }
4462 if self.show_git_blame_gutter {
4463 self.show_git_blame_gutter = false;
4464 cx.notify();
4465 return;
4466 }
4467
4468 if self.mode.is_full()
4469 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4470 {
4471 cx.notify();
4472 return;
4473 }
4474
4475 cx.propagate();
4476 }
4477
4478 pub fn dismiss_menus_and_popups(
4479 &mut self,
4480 is_user_requested: bool,
4481 window: &mut Window,
4482 cx: &mut Context<Self>,
4483 ) -> bool {
4484 let mut dismissed = false;
4485
4486 dismissed |= self.take_rename(false, window, cx).is_some();
4487 dismissed |= self.hide_blame_popover(true, cx);
4488 dismissed |= hide_hover(self, cx);
4489 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4490 dismissed |= self.hide_context_menu(window, cx).is_some();
4491 dismissed |= self.mouse_context_menu.take().is_some();
4492 dismissed |= is_user_requested
4493 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4494 dismissed |= self.snippet_stack.pop().is_some();
4495 if self.diff_review_drag_state.is_some() {
4496 self.cancel_diff_review_drag(cx);
4497 dismissed = true;
4498 }
4499 if !self.diff_review_overlays.is_empty() {
4500 self.dismiss_all_diff_review_overlays(cx);
4501 dismissed = true;
4502 }
4503
4504 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4505 self.dismiss_diagnostics(cx);
4506 dismissed = true;
4507 }
4508
4509 dismissed
4510 }
4511
4512 fn linked_editing_ranges_for(
4513 &self,
4514 selection: Range<text::Anchor>,
4515 cx: &App,
4516 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4517 if self.linked_edit_ranges.is_empty() {
4518 return None;
4519 }
4520 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4521 selection.end.buffer_id.and_then(|end_buffer_id| {
4522 if selection.start.buffer_id != Some(end_buffer_id) {
4523 return None;
4524 }
4525 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4526 let snapshot = buffer.read(cx).snapshot();
4527 self.linked_edit_ranges
4528 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4529 .map(|ranges| (ranges, snapshot, buffer))
4530 })?;
4531 use text::ToOffset as TO;
4532 // find offset from the start of current range to current cursor position
4533 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4534
4535 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4536 let start_difference = start_offset - start_byte_offset;
4537 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4538 let end_difference = end_offset - start_byte_offset;
4539 // Current range has associated linked ranges.
4540 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4541 for range in linked_ranges.iter() {
4542 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4543 let end_offset = start_offset + end_difference;
4544 let start_offset = start_offset + start_difference;
4545 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4546 continue;
4547 }
4548 if self.selections.disjoint_anchor_ranges().any(|s| {
4549 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4550 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4551 {
4552 return false;
4553 }
4554 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4555 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4556 }) {
4557 continue;
4558 }
4559 let start = buffer_snapshot.anchor_after(start_offset);
4560 let end = buffer_snapshot.anchor_after(end_offset);
4561 linked_edits
4562 .entry(buffer.clone())
4563 .or_default()
4564 .push(start..end);
4565 }
4566 Some(linked_edits)
4567 }
4568
4569 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4570 let text: Arc<str> = text.into();
4571
4572 if self.read_only(cx) {
4573 return;
4574 }
4575
4576 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4577
4578 self.unfold_buffers_with_selections(cx);
4579
4580 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4581 let mut bracket_inserted = false;
4582 let mut edits = Vec::new();
4583 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4584 let mut new_selections = Vec::with_capacity(selections.len());
4585 let mut new_autoclose_regions = Vec::new();
4586 let snapshot = self.buffer.read(cx).read(cx);
4587 let mut clear_linked_edit_ranges = false;
4588 let mut all_selections_read_only = true;
4589 let mut has_adjacent_edits = false;
4590 let mut in_adjacent_group = false;
4591
4592 let mut regions = self
4593 .selections_with_autoclose_regions(selections, &snapshot)
4594 .peekable();
4595
4596 while let Some((selection, autoclose_region)) = regions.next() {
4597 if snapshot
4598 .point_to_buffer_point(selection.head())
4599 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4600 {
4601 continue;
4602 }
4603 if snapshot
4604 .point_to_buffer_point(selection.tail())
4605 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4606 {
4607 // note, ideally we'd clip the tail to the closest writeable region towards the head
4608 continue;
4609 }
4610 all_selections_read_only = false;
4611
4612 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4613 // Determine if the inserted text matches the opening or closing
4614 // bracket of any of this language's bracket pairs.
4615 let mut bracket_pair = None;
4616 let mut is_bracket_pair_start = false;
4617 let mut is_bracket_pair_end = false;
4618 if !text.is_empty() {
4619 let mut bracket_pair_matching_end = None;
4620 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4621 // and they are removing the character that triggered IME popup.
4622 for (pair, enabled) in scope.brackets() {
4623 if !pair.close && !pair.surround {
4624 continue;
4625 }
4626
4627 if enabled && pair.start.ends_with(text.as_ref()) {
4628 let prefix_len = pair.start.len() - text.len();
4629 let preceding_text_matches_prefix = prefix_len == 0
4630 || (selection.start.column >= (prefix_len as u32)
4631 && snapshot.contains_str_at(
4632 Point::new(
4633 selection.start.row,
4634 selection.start.column - (prefix_len as u32),
4635 ),
4636 &pair.start[..prefix_len],
4637 ));
4638 if preceding_text_matches_prefix {
4639 bracket_pair = Some(pair.clone());
4640 is_bracket_pair_start = true;
4641 break;
4642 }
4643 }
4644 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4645 {
4646 // take first bracket pair matching end, but don't break in case a later bracket
4647 // pair matches start
4648 bracket_pair_matching_end = Some(pair.clone());
4649 }
4650 }
4651 if let Some(end) = bracket_pair_matching_end
4652 && bracket_pair.is_none()
4653 {
4654 bracket_pair = Some(end);
4655 is_bracket_pair_end = true;
4656 }
4657 }
4658
4659 if let Some(bracket_pair) = bracket_pair {
4660 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4661 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4662 let auto_surround =
4663 self.use_auto_surround && snapshot_settings.use_auto_surround;
4664 if selection.is_empty() {
4665 if is_bracket_pair_start {
4666 // If the inserted text is a suffix of an opening bracket and the
4667 // selection is preceded by the rest of the opening bracket, then
4668 // insert the closing bracket.
4669 let following_text_allows_autoclose = snapshot
4670 .chars_at(selection.start)
4671 .next()
4672 .is_none_or(|c| scope.should_autoclose_before(c));
4673
4674 let preceding_text_allows_autoclose = selection.start.column == 0
4675 || snapshot
4676 .reversed_chars_at(selection.start)
4677 .next()
4678 .is_none_or(|c| {
4679 bracket_pair.start != bracket_pair.end
4680 || !snapshot
4681 .char_classifier_at(selection.start)
4682 .is_word(c)
4683 });
4684
4685 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4686 && bracket_pair.start.len() == 1
4687 {
4688 let target = bracket_pair.start.chars().next().unwrap();
4689 let mut byte_offset = 0u32;
4690 let current_line_count = snapshot
4691 .reversed_chars_at(selection.start)
4692 .take_while(|&c| c != '\n')
4693 .filter(|c| {
4694 byte_offset += c.len_utf8() as u32;
4695 if *c != target {
4696 return false;
4697 }
4698
4699 let point = Point::new(
4700 selection.start.row,
4701 selection.start.column.saturating_sub(byte_offset),
4702 );
4703
4704 let is_enabled = snapshot
4705 .language_scope_at(point)
4706 .and_then(|scope| {
4707 scope
4708 .brackets()
4709 .find(|(pair, _)| {
4710 pair.start == bracket_pair.start
4711 })
4712 .map(|(_, enabled)| enabled)
4713 })
4714 .unwrap_or(true);
4715
4716 let is_delimiter = snapshot
4717 .language_scope_at(Point::new(
4718 point.row,
4719 point.column + 1,
4720 ))
4721 .and_then(|scope| {
4722 scope
4723 .brackets()
4724 .find(|(pair, _)| {
4725 pair.start == bracket_pair.start
4726 })
4727 .map(|(_, enabled)| !enabled)
4728 })
4729 .unwrap_or(false);
4730
4731 is_enabled && !is_delimiter
4732 })
4733 .count();
4734 current_line_count % 2 == 1
4735 } else {
4736 false
4737 };
4738
4739 if autoclose
4740 && bracket_pair.close
4741 && following_text_allows_autoclose
4742 && preceding_text_allows_autoclose
4743 && !is_closing_quote
4744 {
4745 let anchor = snapshot.anchor_before(selection.end);
4746 new_selections.push((selection.map(|_| anchor), text.len()));
4747 new_autoclose_regions.push((
4748 anchor,
4749 text.len(),
4750 selection.id,
4751 bracket_pair.clone(),
4752 ));
4753 edits.push((
4754 selection.range(),
4755 format!("{}{}", text, bracket_pair.end).into(),
4756 ));
4757 bracket_inserted = true;
4758 continue;
4759 }
4760 }
4761
4762 if let Some(region) = autoclose_region {
4763 // If the selection is followed by an auto-inserted closing bracket,
4764 // then don't insert that closing bracket again; just move the selection
4765 // past the closing bracket.
4766 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4767 && text.as_ref() == region.pair.end.as_str()
4768 && snapshot.contains_str_at(region.range.end, text.as_ref());
4769 if should_skip {
4770 let anchor = snapshot.anchor_after(selection.end);
4771 new_selections
4772 .push((selection.map(|_| anchor), region.pair.end.len()));
4773 continue;
4774 }
4775 }
4776
4777 let always_treat_brackets_as_autoclosed = snapshot
4778 .language_settings_at(selection.start, cx)
4779 .always_treat_brackets_as_autoclosed;
4780 if always_treat_brackets_as_autoclosed
4781 && is_bracket_pair_end
4782 && snapshot.contains_str_at(selection.end, text.as_ref())
4783 {
4784 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4785 // and the inserted text is a closing bracket and the selection is followed
4786 // by the closing bracket then move the selection past the closing bracket.
4787 let anchor = snapshot.anchor_after(selection.end);
4788 new_selections.push((selection.map(|_| anchor), text.len()));
4789 continue;
4790 }
4791 }
4792 // If an opening bracket is 1 character long and is typed while
4793 // text is selected, then surround that text with the bracket pair.
4794 else if auto_surround
4795 && bracket_pair.surround
4796 && is_bracket_pair_start
4797 && bracket_pair.start.chars().count() == 1
4798 {
4799 edits.push((selection.start..selection.start, text.clone()));
4800 edits.push((
4801 selection.end..selection.end,
4802 bracket_pair.end.as_str().into(),
4803 ));
4804 bracket_inserted = true;
4805 new_selections.push((
4806 Selection {
4807 id: selection.id,
4808 start: snapshot.anchor_after(selection.start),
4809 end: snapshot.anchor_before(selection.end),
4810 reversed: selection.reversed,
4811 goal: selection.goal,
4812 },
4813 0,
4814 ));
4815 continue;
4816 }
4817 }
4818 }
4819
4820 if self.auto_replace_emoji_shortcode
4821 && selection.is_empty()
4822 && text.as_ref().ends_with(':')
4823 && let Some(possible_emoji_short_code) =
4824 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4825 && !possible_emoji_short_code.is_empty()
4826 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4827 {
4828 let emoji_shortcode_start = Point::new(
4829 selection.start.row,
4830 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4831 );
4832
4833 // Remove shortcode from buffer
4834 edits.push((
4835 emoji_shortcode_start..selection.start,
4836 "".to_string().into(),
4837 ));
4838 new_selections.push((
4839 Selection {
4840 id: selection.id,
4841 start: snapshot.anchor_after(emoji_shortcode_start),
4842 end: snapshot.anchor_before(selection.start),
4843 reversed: selection.reversed,
4844 goal: selection.goal,
4845 },
4846 0,
4847 ));
4848
4849 // Insert emoji
4850 let selection_start_anchor = snapshot.anchor_after(selection.start);
4851 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4852 edits.push((selection.start..selection.end, emoji.to_string().into()));
4853
4854 continue;
4855 }
4856
4857 let next_is_adjacent = regions
4858 .peek()
4859 .is_some_and(|(next, _)| selection.end == next.start);
4860
4861 // If not handling any auto-close operation, then just replace the selected
4862 // text with the given input and move the selection to the end of the
4863 // newly inserted text.
4864 let anchor = if in_adjacent_group || next_is_adjacent {
4865 // After edits the right bias would shift those anchor to the next visible fragment
4866 // but we want to resolve to the previous one
4867 snapshot.anchor_before(selection.end)
4868 } else {
4869 snapshot.anchor_after(selection.end)
4870 };
4871
4872 if !self.linked_edit_ranges.is_empty() {
4873 let start_anchor = snapshot.anchor_before(selection.start);
4874
4875 let is_word_char = text.chars().next().is_none_or(|char| {
4876 let classifier = snapshot
4877 .char_classifier_at(start_anchor.to_offset(&snapshot))
4878 .scope_context(Some(CharScopeContext::LinkedEdit));
4879 classifier.is_word(char)
4880 });
4881
4882 if is_word_char {
4883 if let Some(ranges) = self
4884 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4885 {
4886 for (buffer, edits) in ranges {
4887 linked_edits
4888 .entry(buffer.clone())
4889 .or_default()
4890 .extend(edits.into_iter().map(|range| (range, text.clone())));
4891 }
4892 }
4893 } else {
4894 clear_linked_edit_ranges = true;
4895 }
4896 }
4897
4898 new_selections.push((selection.map(|_| anchor), 0));
4899 edits.push((selection.start..selection.end, text.clone()));
4900
4901 has_adjacent_edits |= next_is_adjacent;
4902 in_adjacent_group = next_is_adjacent;
4903 }
4904
4905 if all_selections_read_only {
4906 return;
4907 }
4908
4909 drop(regions);
4910 drop(snapshot);
4911
4912 self.transact(window, cx, |this, window, cx| {
4913 if clear_linked_edit_ranges {
4914 this.linked_edit_ranges.clear();
4915 }
4916 let initial_buffer_versions =
4917 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4918
4919 this.buffer.update(cx, |buffer, cx| {
4920 if has_adjacent_edits {
4921 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4922 } else {
4923 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4924 }
4925 });
4926 for (buffer, edits) in linked_edits {
4927 buffer.update(cx, |buffer, cx| {
4928 let snapshot = buffer.snapshot();
4929 let edits = edits
4930 .into_iter()
4931 .map(|(range, text)| {
4932 use text::ToPoint as TP;
4933 let end_point = TP::to_point(&range.end, &snapshot);
4934 let start_point = TP::to_point(&range.start, &snapshot);
4935 (start_point..end_point, text)
4936 })
4937 .sorted_by_key(|(range, _)| range.start);
4938 buffer.edit(edits, None, cx);
4939 })
4940 }
4941 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4942 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4943 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4944 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4945 new_anchor_selections,
4946 &map,
4947 )
4948 .zip(new_selection_deltas)
4949 .map(|(selection, delta)| Selection {
4950 id: selection.id,
4951 start: selection.start + delta,
4952 end: selection.end + delta,
4953 reversed: selection.reversed,
4954 goal: SelectionGoal::None,
4955 })
4956 .collect::<Vec<_>>();
4957
4958 let mut i = 0;
4959 for (position, delta, selection_id, pair) in new_autoclose_regions {
4960 let position = position.to_offset(map.buffer_snapshot()) + delta;
4961 let start = map.buffer_snapshot().anchor_before(position);
4962 let end = map.buffer_snapshot().anchor_after(position);
4963 while let Some(existing_state) = this.autoclose_regions.get(i) {
4964 match existing_state
4965 .range
4966 .start
4967 .cmp(&start, map.buffer_snapshot())
4968 {
4969 Ordering::Less => i += 1,
4970 Ordering::Greater => break,
4971 Ordering::Equal => {
4972 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4973 Ordering::Less => i += 1,
4974 Ordering::Equal => break,
4975 Ordering::Greater => break,
4976 }
4977 }
4978 }
4979 }
4980 this.autoclose_regions.insert(
4981 i,
4982 AutocloseRegion {
4983 selection_id,
4984 range: start..end,
4985 pair,
4986 },
4987 );
4988 }
4989
4990 let had_active_edit_prediction = this.has_active_edit_prediction();
4991 this.change_selections(
4992 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4993 window,
4994 cx,
4995 |s| s.select(new_selections),
4996 );
4997
4998 if !bracket_inserted
4999 && let Some(on_type_format_task) =
5000 this.trigger_on_type_formatting(text.to_string(), window, cx)
5001 {
5002 on_type_format_task.detach_and_log_err(cx);
5003 }
5004
5005 let editor_settings = EditorSettings::get_global(cx);
5006 if bracket_inserted
5007 && (editor_settings.auto_signature_help
5008 || editor_settings.show_signature_help_after_edits)
5009 {
5010 this.show_signature_help(&ShowSignatureHelp, window, cx);
5011 }
5012
5013 let trigger_in_words =
5014 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5015 if this.hard_wrap.is_some() {
5016 let latest: Range<Point> = this.selections.newest(&map).range();
5017 if latest.is_empty()
5018 && this
5019 .buffer()
5020 .read(cx)
5021 .snapshot(cx)
5022 .line_len(MultiBufferRow(latest.start.row))
5023 == latest.start.column
5024 {
5025 this.rewrap_impl(
5026 RewrapOptions {
5027 override_language_settings: true,
5028 preserve_existing_whitespace: true,
5029 },
5030 cx,
5031 )
5032 }
5033 }
5034 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5035 refresh_linked_ranges(this, window, cx);
5036 this.refresh_edit_prediction(true, false, window, cx);
5037 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5038 });
5039 }
5040
5041 fn find_possible_emoji_shortcode_at_position(
5042 snapshot: &MultiBufferSnapshot,
5043 position: Point,
5044 ) -> Option<String> {
5045 let mut chars = Vec::new();
5046 let mut found_colon = false;
5047 for char in snapshot.reversed_chars_at(position).take(100) {
5048 // Found a possible emoji shortcode in the middle of the buffer
5049 if found_colon {
5050 if char.is_whitespace() {
5051 chars.reverse();
5052 return Some(chars.iter().collect());
5053 }
5054 // If the previous character is not a whitespace, we are in the middle of a word
5055 // and we only want to complete the shortcode if the word is made up of other emojis
5056 let mut containing_word = String::new();
5057 for ch in snapshot
5058 .reversed_chars_at(position)
5059 .skip(chars.len() + 1)
5060 .take(100)
5061 {
5062 if ch.is_whitespace() {
5063 break;
5064 }
5065 containing_word.push(ch);
5066 }
5067 let containing_word = containing_word.chars().rev().collect::<String>();
5068 if util::word_consists_of_emojis(containing_word.as_str()) {
5069 chars.reverse();
5070 return Some(chars.iter().collect());
5071 }
5072 }
5073
5074 if char.is_whitespace() || !char.is_ascii() {
5075 return None;
5076 }
5077 if char == ':' {
5078 found_colon = true;
5079 } else {
5080 chars.push(char);
5081 }
5082 }
5083 // Found a possible emoji shortcode at the beginning of the buffer
5084 chars.reverse();
5085 Some(chars.iter().collect())
5086 }
5087
5088 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5090 self.transact(window, cx, |this, window, cx| {
5091 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5092 let selections = this
5093 .selections
5094 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5095 let multi_buffer = this.buffer.read(cx);
5096 let buffer = multi_buffer.snapshot(cx);
5097 selections
5098 .iter()
5099 .map(|selection| {
5100 let start_point = selection.start.to_point(&buffer);
5101 let mut existing_indent =
5102 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5103 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5104 let start = selection.start;
5105 let end = selection.end;
5106 let selection_is_empty = start == end;
5107 let language_scope = buffer.language_scope_at(start);
5108 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5109 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5110 &buffer,
5111 start..end,
5112 language,
5113 )
5114 || NewlineConfig::insert_extra_newline_tree_sitter(
5115 &buffer,
5116 start..end,
5117 );
5118
5119 let mut newline_config = NewlineConfig::Newline {
5120 additional_indent: IndentSize::spaces(0),
5121 extra_line_additional_indent: if needs_extra_newline {
5122 Some(IndentSize::spaces(0))
5123 } else {
5124 None
5125 },
5126 prevent_auto_indent: false,
5127 };
5128
5129 let comment_delimiter = maybe!({
5130 if !selection_is_empty {
5131 return None;
5132 }
5133
5134 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5135 return None;
5136 }
5137
5138 return comment_delimiter_for_newline(
5139 &start_point,
5140 &buffer,
5141 language,
5142 );
5143 });
5144
5145 let doc_delimiter = maybe!({
5146 if !selection_is_empty {
5147 return None;
5148 }
5149
5150 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5151 return None;
5152 }
5153
5154 return documentation_delimiter_for_newline(
5155 &start_point,
5156 &buffer,
5157 language,
5158 &mut newline_config,
5159 );
5160 });
5161
5162 let list_delimiter = maybe!({
5163 if !selection_is_empty {
5164 return None;
5165 }
5166
5167 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5168 return None;
5169 }
5170
5171 return list_delimiter_for_newline(
5172 &start_point,
5173 &buffer,
5174 language,
5175 &mut newline_config,
5176 );
5177 });
5178
5179 (
5180 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5181 newline_config,
5182 )
5183 } else {
5184 (
5185 None,
5186 NewlineConfig::Newline {
5187 additional_indent: IndentSize::spaces(0),
5188 extra_line_additional_indent: None,
5189 prevent_auto_indent: false,
5190 },
5191 )
5192 };
5193
5194 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5195 NewlineConfig::ClearCurrentLine => {
5196 let row_start =
5197 buffer.point_to_offset(Point::new(start_point.row, 0));
5198 (row_start, String::new(), false)
5199 }
5200 NewlineConfig::UnindentCurrentLine { continuation } => {
5201 let row_start =
5202 buffer.point_to_offset(Point::new(start_point.row, 0));
5203 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5204 let tab_size_indent = IndentSize::spaces(tab_size.get());
5205 let reduced_indent =
5206 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5207 let mut new_text = String::new();
5208 new_text.extend(reduced_indent.chars());
5209 new_text.push_str(continuation);
5210 (row_start, new_text, true)
5211 }
5212 NewlineConfig::Newline {
5213 additional_indent,
5214 extra_line_additional_indent,
5215 prevent_auto_indent,
5216 } => {
5217 let capacity_for_delimiter =
5218 delimiter.as_deref().map(str::len).unwrap_or_default();
5219 let extra_line_len = extra_line_additional_indent
5220 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5221 .unwrap_or(0);
5222 let mut new_text = String::with_capacity(
5223 1 + capacity_for_delimiter
5224 + existing_indent.len as usize
5225 + additional_indent.len as usize
5226 + extra_line_len,
5227 );
5228 new_text.push('\n');
5229 new_text.extend(existing_indent.chars());
5230 new_text.extend(additional_indent.chars());
5231 if let Some(delimiter) = &delimiter {
5232 new_text.push_str(delimiter);
5233 }
5234 if let Some(extra_indent) = extra_line_additional_indent {
5235 new_text.push('\n');
5236 new_text.extend(existing_indent.chars());
5237 new_text.extend(extra_indent.chars());
5238 }
5239 (start, new_text, *prevent_auto_indent)
5240 }
5241 };
5242
5243 let anchor = buffer.anchor_after(end);
5244 let new_selection = selection.map(|_| anchor);
5245 (
5246 ((edit_start..end, new_text), prevent_auto_indent),
5247 (newline_config.has_extra_line(), new_selection),
5248 )
5249 })
5250 .unzip()
5251 };
5252
5253 let mut auto_indent_edits = Vec::new();
5254 let mut edits = Vec::new();
5255 for (edit, prevent_auto_indent) in edits_with_flags {
5256 if prevent_auto_indent {
5257 edits.push(edit);
5258 } else {
5259 auto_indent_edits.push(edit);
5260 }
5261 }
5262 if !edits.is_empty() {
5263 this.edit(edits, cx);
5264 }
5265 if !auto_indent_edits.is_empty() {
5266 this.edit_with_autoindent(auto_indent_edits, cx);
5267 }
5268
5269 let buffer = this.buffer.read(cx).snapshot(cx);
5270 let new_selections = selection_info
5271 .into_iter()
5272 .map(|(extra_newline_inserted, new_selection)| {
5273 let mut cursor = new_selection.end.to_point(&buffer);
5274 if extra_newline_inserted {
5275 cursor.row -= 1;
5276 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5277 }
5278 new_selection.map(|_| cursor)
5279 })
5280 .collect();
5281
5282 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5283 this.refresh_edit_prediction(true, false, window, cx);
5284 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5285 task.detach_and_log_err(cx);
5286 }
5287 });
5288 }
5289
5290 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5291 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5292
5293 let buffer = self.buffer.read(cx);
5294 let snapshot = buffer.snapshot(cx);
5295
5296 let mut edits = Vec::new();
5297 let mut rows = Vec::new();
5298
5299 for (rows_inserted, selection) in self
5300 .selections
5301 .all_adjusted(&self.display_snapshot(cx))
5302 .into_iter()
5303 .enumerate()
5304 {
5305 let cursor = selection.head();
5306 let row = cursor.row;
5307
5308 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5309
5310 let newline = "\n".to_string();
5311 edits.push((start_of_line..start_of_line, newline));
5312
5313 rows.push(row + rows_inserted as u32);
5314 }
5315
5316 self.transact(window, cx, |editor, window, cx| {
5317 editor.edit(edits, cx);
5318
5319 editor.change_selections(Default::default(), window, cx, |s| {
5320 let mut index = 0;
5321 s.move_cursors_with(&mut |map, _, _| {
5322 let row = rows[index];
5323 index += 1;
5324
5325 let point = Point::new(row, 0);
5326 let boundary = map.next_line_boundary(point).1;
5327 let clipped = map.clip_point(boundary, Bias::Left);
5328
5329 (clipped, SelectionGoal::None)
5330 });
5331 });
5332
5333 let mut indent_edits = Vec::new();
5334 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5335 for row in rows {
5336 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5337 for (row, indent) in indents {
5338 if indent.len == 0 {
5339 continue;
5340 }
5341
5342 let text = match indent.kind {
5343 IndentKind::Space => " ".repeat(indent.len as usize),
5344 IndentKind::Tab => "\t".repeat(indent.len as usize),
5345 };
5346 let point = Point::new(row.0, 0);
5347 indent_edits.push((point..point, text));
5348 }
5349 }
5350 editor.edit(indent_edits, cx);
5351 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5352 format.detach_and_log_err(cx);
5353 }
5354 });
5355 }
5356
5357 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5358 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5359
5360 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5361 let mut rows = Vec::new();
5362 let mut rows_inserted = 0;
5363
5364 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5365 let cursor = selection.head();
5366 let row = cursor.row;
5367
5368 let point = Point::new(row, 0);
5369 let Some((buffer_handle, buffer_point, _)) =
5370 self.buffer.read(cx).point_to_buffer_point(point, cx)
5371 else {
5372 continue;
5373 };
5374
5375 buffer_edits
5376 .entry(buffer_handle.entity_id())
5377 .or_insert_with(|| (buffer_handle, Vec::new()))
5378 .1
5379 .push(buffer_point);
5380
5381 rows_inserted += 1;
5382 rows.push(row + rows_inserted);
5383 }
5384
5385 self.transact(window, cx, |editor, window, cx| {
5386 for (_, (buffer_handle, points)) in &buffer_edits {
5387 buffer_handle.update(cx, |buffer, cx| {
5388 let edits: Vec<_> = points
5389 .iter()
5390 .map(|point| {
5391 let target = Point::new(point.row + 1, 0);
5392 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5393 (start_of_line..start_of_line, "\n")
5394 })
5395 .collect();
5396 buffer.edit(edits, None, cx);
5397 });
5398 }
5399
5400 editor.change_selections(Default::default(), window, cx, |s| {
5401 let mut index = 0;
5402 s.move_cursors_with(&mut |map, _, _| {
5403 let row = rows[index];
5404 index += 1;
5405
5406 let point = Point::new(row, 0);
5407 let boundary = map.next_line_boundary(point).1;
5408 let clipped = map.clip_point(boundary, Bias::Left);
5409
5410 (clipped, SelectionGoal::None)
5411 });
5412 });
5413
5414 let mut indent_edits = Vec::new();
5415 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5416 for row in rows {
5417 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5418 for (row, indent) in indents {
5419 if indent.len == 0 {
5420 continue;
5421 }
5422
5423 let text = match indent.kind {
5424 IndentKind::Space => " ".repeat(indent.len as usize),
5425 IndentKind::Tab => "\t".repeat(indent.len as usize),
5426 };
5427 let point = Point::new(row.0, 0);
5428 indent_edits.push((point..point, text));
5429 }
5430 }
5431 editor.edit(indent_edits, cx);
5432 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5433 format.detach_and_log_err(cx);
5434 }
5435 });
5436 }
5437
5438 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5439 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5440 original_indent_columns: Vec::new(),
5441 });
5442 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5443 }
5444
5445 fn insert_with_autoindent_mode(
5446 &mut self,
5447 text: &str,
5448 autoindent_mode: Option<AutoindentMode>,
5449 window: &mut Window,
5450 cx: &mut Context<Self>,
5451 ) {
5452 if self.read_only(cx) {
5453 return;
5454 }
5455
5456 let text: Arc<str> = text.into();
5457 self.transact(window, cx, |this, window, cx| {
5458 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5459 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5460 let anchors = {
5461 let snapshot = buffer.read(cx);
5462 old_selections
5463 .iter()
5464 .map(|s| {
5465 let anchor = snapshot.anchor_after(s.head());
5466 s.map(|_| anchor)
5467 })
5468 .collect::<Vec<_>>()
5469 };
5470 buffer.edit(
5471 old_selections
5472 .iter()
5473 .map(|s| (s.start..s.end, text.clone())),
5474 autoindent_mode,
5475 cx,
5476 );
5477 anchors
5478 });
5479
5480 this.change_selections(Default::default(), window, cx, |s| {
5481 s.select_anchors(selection_anchors);
5482 });
5483
5484 cx.notify();
5485 });
5486 }
5487
5488 fn trigger_completion_on_input(
5489 &mut self,
5490 text: &str,
5491 trigger_in_words: bool,
5492 window: &mut Window,
5493 cx: &mut Context<Self>,
5494 ) {
5495 let completions_source = self
5496 .context_menu
5497 .borrow()
5498 .as_ref()
5499 .and_then(|menu| match menu {
5500 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5501 CodeContextMenu::CodeActions(_) => None,
5502 });
5503
5504 match completions_source {
5505 Some(CompletionsMenuSource::Words { .. }) => {
5506 self.open_or_update_completions_menu(
5507 Some(CompletionsMenuSource::Words {
5508 ignore_threshold: false,
5509 }),
5510 None,
5511 trigger_in_words,
5512 window,
5513 cx,
5514 );
5515 }
5516 _ => self.open_or_update_completions_menu(
5517 None,
5518 Some(text.to_owned()).filter(|x| !x.is_empty()),
5519 true,
5520 window,
5521 cx,
5522 ),
5523 }
5524 }
5525
5526 /// If any empty selections is touching the start of its innermost containing autoclose
5527 /// region, expand it to select the brackets.
5528 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5529 let selections = self
5530 .selections
5531 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5532 let buffer = self.buffer.read(cx).read(cx);
5533 let new_selections = self
5534 .selections_with_autoclose_regions(selections, &buffer)
5535 .map(|(mut selection, region)| {
5536 if !selection.is_empty() {
5537 return selection;
5538 }
5539
5540 if let Some(region) = region {
5541 let mut range = region.range.to_offset(&buffer);
5542 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5543 range.start -= region.pair.start.len();
5544 if buffer.contains_str_at(range.start, ®ion.pair.start)
5545 && buffer.contains_str_at(range.end, ®ion.pair.end)
5546 {
5547 range.end += region.pair.end.len();
5548 selection.start = range.start;
5549 selection.end = range.end;
5550
5551 return selection;
5552 }
5553 }
5554 }
5555
5556 let always_treat_brackets_as_autoclosed = buffer
5557 .language_settings_at(selection.start, cx)
5558 .always_treat_brackets_as_autoclosed;
5559
5560 if !always_treat_brackets_as_autoclosed {
5561 return selection;
5562 }
5563
5564 if let Some(scope) = buffer.language_scope_at(selection.start) {
5565 for (pair, enabled) in scope.brackets() {
5566 if !enabled || !pair.close {
5567 continue;
5568 }
5569
5570 if buffer.contains_str_at(selection.start, &pair.end) {
5571 let pair_start_len = pair.start.len();
5572 if buffer.contains_str_at(
5573 selection.start.saturating_sub_usize(pair_start_len),
5574 &pair.start,
5575 ) {
5576 selection.start -= pair_start_len;
5577 selection.end += pair.end.len();
5578
5579 return selection;
5580 }
5581 }
5582 }
5583 }
5584
5585 selection
5586 })
5587 .collect();
5588
5589 drop(buffer);
5590 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5591 selections.select(new_selections)
5592 });
5593 }
5594
5595 /// Iterate the given selections, and for each one, find the smallest surrounding
5596 /// autoclose region. This uses the ordering of the selections and the autoclose
5597 /// regions to avoid repeated comparisons.
5598 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5599 &'a self,
5600 selections: impl IntoIterator<Item = Selection<D>>,
5601 buffer: &'a MultiBufferSnapshot,
5602 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5603 let mut i = 0;
5604 let mut regions = self.autoclose_regions.as_slice();
5605 selections.into_iter().map(move |selection| {
5606 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5607
5608 let mut enclosing = None;
5609 while let Some(pair_state) = regions.get(i) {
5610 if pair_state.range.end.to_offset(buffer) < range.start {
5611 regions = ®ions[i + 1..];
5612 i = 0;
5613 } else if pair_state.range.start.to_offset(buffer) > range.end {
5614 break;
5615 } else {
5616 if pair_state.selection_id == selection.id {
5617 enclosing = Some(pair_state);
5618 }
5619 i += 1;
5620 }
5621 }
5622
5623 (selection, enclosing)
5624 })
5625 }
5626
5627 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5628 fn invalidate_autoclose_regions(
5629 &mut self,
5630 mut selections: &[Selection<Anchor>],
5631 buffer: &MultiBufferSnapshot,
5632 ) {
5633 self.autoclose_regions.retain(|state| {
5634 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5635 return false;
5636 }
5637
5638 let mut i = 0;
5639 while let Some(selection) = selections.get(i) {
5640 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5641 selections = &selections[1..];
5642 continue;
5643 }
5644 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5645 break;
5646 }
5647 if selection.id == state.selection_id {
5648 return true;
5649 } else {
5650 i += 1;
5651 }
5652 }
5653 false
5654 });
5655 }
5656
5657 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5658 let offset = position.to_offset(buffer);
5659 let (word_range, kind) =
5660 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5661 if offset > word_range.start && kind == Some(CharKind::Word) {
5662 Some(
5663 buffer
5664 .text_for_range(word_range.start..offset)
5665 .collect::<String>(),
5666 )
5667 } else {
5668 None
5669 }
5670 }
5671
5672 pub fn visible_excerpts(
5673 &self,
5674 lsp_related_only: bool,
5675 cx: &mut Context<Editor>,
5676 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5677 let project = self.project().cloned();
5678 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5679 let multi_buffer = self.buffer().read(cx);
5680 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5681 let multi_buffer_visible_start = self
5682 .scroll_manager
5683 .native_anchor(&display_snapshot, cx)
5684 .anchor
5685 .to_point(&multi_buffer_snapshot);
5686 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5687 multi_buffer_visible_start
5688 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5689 Bias::Left,
5690 );
5691 multi_buffer_snapshot
5692 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5693 .into_iter()
5694 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5695 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5696 if !lsp_related_only {
5697 return Some((
5698 excerpt_id,
5699 (
5700 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5701 buffer.version().clone(),
5702 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5703 ),
5704 ));
5705 }
5706
5707 let project = project.as_ref()?.read(cx);
5708 let buffer_file = project::File::from_dyn(buffer.file())?;
5709 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5710 let worktree_entry = buffer_worktree
5711 .read(cx)
5712 .entry_for_id(buffer_file.project_entry_id()?)?;
5713 if worktree_entry.is_ignored {
5714 None
5715 } else {
5716 Some((
5717 excerpt_id,
5718 (
5719 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5720 buffer.version().clone(),
5721 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5722 ),
5723 ))
5724 }
5725 })
5726 .collect()
5727 }
5728
5729 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5730 TextLayoutDetails {
5731 text_system: window.text_system().clone(),
5732 editor_style: self.style.clone().unwrap(),
5733 rem_size: window.rem_size(),
5734 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5735 visible_rows: self.visible_line_count(),
5736 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5737 }
5738 }
5739
5740 fn trigger_on_type_formatting(
5741 &self,
5742 input: String,
5743 window: &mut Window,
5744 cx: &mut Context<Self>,
5745 ) -> Option<Task<Result<()>>> {
5746 if input.chars().count() != 1 {
5747 return None;
5748 }
5749
5750 let project = self.project()?;
5751 let position = self.selections.newest_anchor().head();
5752 let (buffer, buffer_position) = self
5753 .buffer
5754 .read(cx)
5755 .text_anchor_for_position(position, cx)?;
5756
5757 let settings = language_settings::language_settings(
5758 buffer
5759 .read(cx)
5760 .language_at(buffer_position)
5761 .map(|l| l.name()),
5762 buffer.read(cx).file(),
5763 cx,
5764 );
5765 if !settings.use_on_type_format {
5766 return None;
5767 }
5768
5769 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5770 // hence we do LSP request & edit on host side only — add formats to host's history.
5771 let push_to_lsp_host_history = true;
5772 // If this is not the host, append its history with new edits.
5773 let push_to_client_history = project.read(cx).is_via_collab();
5774
5775 let on_type_formatting = project.update(cx, |project, cx| {
5776 project.on_type_format(
5777 buffer.clone(),
5778 buffer_position,
5779 input,
5780 push_to_lsp_host_history,
5781 cx,
5782 )
5783 });
5784 Some(cx.spawn_in(window, async move |editor, cx| {
5785 if let Some(transaction) = on_type_formatting.await? {
5786 if push_to_client_history {
5787 buffer.update(cx, |buffer, _| {
5788 buffer.push_transaction(transaction, Instant::now());
5789 buffer.finalize_last_transaction();
5790 });
5791 }
5792 editor.update(cx, |editor, cx| {
5793 editor.refresh_document_highlights(cx);
5794 })?;
5795 }
5796 Ok(())
5797 }))
5798 }
5799
5800 pub fn show_word_completions(
5801 &mut self,
5802 _: &ShowWordCompletions,
5803 window: &mut Window,
5804 cx: &mut Context<Self>,
5805 ) {
5806 self.open_or_update_completions_menu(
5807 Some(CompletionsMenuSource::Words {
5808 ignore_threshold: true,
5809 }),
5810 None,
5811 false,
5812 window,
5813 cx,
5814 );
5815 }
5816
5817 pub fn show_completions(
5818 &mut self,
5819 _: &ShowCompletions,
5820 window: &mut Window,
5821 cx: &mut Context<Self>,
5822 ) {
5823 self.open_or_update_completions_menu(None, None, false, window, cx);
5824 }
5825
5826 fn open_or_update_completions_menu(
5827 &mut self,
5828 requested_source: Option<CompletionsMenuSource>,
5829 trigger: Option<String>,
5830 trigger_in_words: bool,
5831 window: &mut Window,
5832 cx: &mut Context<Self>,
5833 ) {
5834 if self.pending_rename.is_some() {
5835 return;
5836 }
5837
5838 let completions_source = self
5839 .context_menu
5840 .borrow()
5841 .as_ref()
5842 .and_then(|menu| match menu {
5843 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5844 CodeContextMenu::CodeActions(_) => None,
5845 });
5846
5847 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5848
5849 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5850 // inserted and selected. To handle that case, the start of the selection is used so that
5851 // the menu starts with all choices.
5852 let position = self
5853 .selections
5854 .newest_anchor()
5855 .start
5856 .bias_right(&multibuffer_snapshot);
5857 if position.diff_base_anchor.is_some() {
5858 return;
5859 }
5860 let buffer_position = multibuffer_snapshot.anchor_before(position);
5861 let Some(buffer) = buffer_position
5862 .text_anchor
5863 .buffer_id
5864 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5865 else {
5866 return;
5867 };
5868 let buffer_snapshot = buffer.read(cx).snapshot();
5869
5870 let menu_is_open = matches!(
5871 self.context_menu.borrow().as_ref(),
5872 Some(CodeContextMenu::Completions(_))
5873 );
5874
5875 let language = buffer_snapshot
5876 .language_at(buffer_position.text_anchor)
5877 .map(|language| language.name());
5878
5879 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5880 let completion_settings = language_settings.completions.clone();
5881
5882 let show_completions_on_input = self
5883 .show_completions_on_input_override
5884 .unwrap_or(language_settings.show_completions_on_input);
5885 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5886 return;
5887 }
5888
5889 let query: Option<Arc<String>> =
5890 Self::completion_query(&multibuffer_snapshot, buffer_position)
5891 .map(|query| query.into());
5892
5893 drop(multibuffer_snapshot);
5894
5895 // Hide the current completions menu when query is empty. Without this, cached
5896 // completions from before the trigger char may be reused (#32774).
5897 if query.is_none() && menu_is_open {
5898 self.hide_context_menu(window, cx);
5899 }
5900
5901 let mut ignore_word_threshold = false;
5902 let provider = match requested_source {
5903 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5904 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5905 ignore_word_threshold = ignore_threshold;
5906 None
5907 }
5908 Some(CompletionsMenuSource::SnippetChoices)
5909 | Some(CompletionsMenuSource::SnippetsOnly) => {
5910 log::error!("bug: SnippetChoices requested_source is not handled");
5911 None
5912 }
5913 };
5914
5915 let sort_completions = provider
5916 .as_ref()
5917 .is_some_and(|provider| provider.sort_completions());
5918
5919 let filter_completions = provider
5920 .as_ref()
5921 .is_none_or(|provider| provider.filter_completions());
5922
5923 let was_snippets_only = matches!(
5924 completions_source,
5925 Some(CompletionsMenuSource::SnippetsOnly)
5926 );
5927
5928 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5929 if filter_completions {
5930 menu.filter(
5931 query.clone().unwrap_or_default(),
5932 buffer_position.text_anchor,
5933 &buffer,
5934 provider.clone(),
5935 window,
5936 cx,
5937 );
5938 }
5939 // When `is_incomplete` is false, no need to re-query completions when the current query
5940 // is a suffix of the initial query.
5941 let was_complete = !menu.is_incomplete;
5942 if was_complete && !was_snippets_only {
5943 // If the new query is a suffix of the old query (typing more characters) and
5944 // the previous result was complete, the existing completions can be filtered.
5945 //
5946 // Note that snippet completions are always complete.
5947 let query_matches = match (&menu.initial_query, &query) {
5948 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5949 (None, _) => true,
5950 _ => false,
5951 };
5952 if query_matches {
5953 let position_matches = if menu.initial_position == position {
5954 true
5955 } else {
5956 let snapshot = self.buffer.read(cx).read(cx);
5957 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5958 };
5959 if position_matches {
5960 return;
5961 }
5962 }
5963 }
5964 };
5965
5966 let Anchor {
5967 excerpt_id: buffer_excerpt_id,
5968 text_anchor: buffer_position,
5969 ..
5970 } = buffer_position;
5971
5972 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5973 buffer_snapshot.surrounding_word(buffer_position, None)
5974 {
5975 let word_to_exclude = buffer_snapshot
5976 .text_for_range(word_range.clone())
5977 .collect::<String>();
5978 (
5979 buffer_snapshot.anchor_before(word_range.start)
5980 ..buffer_snapshot.anchor_after(buffer_position),
5981 Some(word_to_exclude),
5982 )
5983 } else {
5984 (buffer_position..buffer_position, None)
5985 };
5986
5987 let show_completion_documentation = buffer_snapshot
5988 .settings_at(buffer_position, cx)
5989 .show_completion_documentation;
5990
5991 // The document can be large, so stay in reasonable bounds when searching for words,
5992 // otherwise completion pop-up might be slow to appear.
5993 const WORD_LOOKUP_ROWS: u32 = 5_000;
5994 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5995 let min_word_search = buffer_snapshot.clip_point(
5996 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5997 Bias::Left,
5998 );
5999 let max_word_search = buffer_snapshot.clip_point(
6000 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6001 Bias::Right,
6002 );
6003 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6004 ..buffer_snapshot.point_to_offset(max_word_search);
6005
6006 let skip_digits = query
6007 .as_ref()
6008 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6009
6010 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6011 trigger.as_ref().is_none_or(|trigger| {
6012 provider.is_completion_trigger(
6013 &buffer,
6014 position.text_anchor,
6015 trigger,
6016 trigger_in_words,
6017 cx,
6018 )
6019 })
6020 });
6021
6022 let provider_responses = if let Some(provider) = &provider
6023 && load_provider_completions
6024 {
6025 let trigger_character =
6026 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6027 let completion_context = CompletionContext {
6028 trigger_kind: match &trigger_character {
6029 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6030 None => CompletionTriggerKind::INVOKED,
6031 },
6032 trigger_character,
6033 };
6034
6035 provider.completions(
6036 buffer_excerpt_id,
6037 &buffer,
6038 buffer_position,
6039 completion_context,
6040 window,
6041 cx,
6042 )
6043 } else {
6044 Task::ready(Ok(Vec::new()))
6045 };
6046
6047 let load_word_completions = if !self.word_completions_enabled {
6048 false
6049 } else if requested_source
6050 == Some(CompletionsMenuSource::Words {
6051 ignore_threshold: true,
6052 })
6053 {
6054 true
6055 } else {
6056 load_provider_completions
6057 && completion_settings.words != WordsCompletionMode::Disabled
6058 && (ignore_word_threshold || {
6059 let words_min_length = completion_settings.words_min_length;
6060 // check whether word has at least `words_min_length` characters
6061 let query_chars = query.iter().flat_map(|q| q.chars());
6062 query_chars.take(words_min_length).count() == words_min_length
6063 })
6064 };
6065
6066 let mut words = if load_word_completions {
6067 cx.background_spawn({
6068 let buffer_snapshot = buffer_snapshot.clone();
6069 async move {
6070 buffer_snapshot.words_in_range(WordsQuery {
6071 fuzzy_contents: None,
6072 range: word_search_range,
6073 skip_digits,
6074 })
6075 }
6076 })
6077 } else {
6078 Task::ready(BTreeMap::default())
6079 };
6080
6081 let snippets = if let Some(provider) = &provider
6082 && provider.show_snippets()
6083 && let Some(project) = self.project()
6084 {
6085 let char_classifier = buffer_snapshot
6086 .char_classifier_at(buffer_position)
6087 .scope_context(Some(CharScopeContext::Completion));
6088 project.update(cx, |project, cx| {
6089 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6090 })
6091 } else {
6092 Task::ready(Ok(CompletionResponse {
6093 completions: Vec::new(),
6094 display_options: Default::default(),
6095 is_incomplete: false,
6096 }))
6097 };
6098
6099 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6100
6101 let id = post_inc(&mut self.next_completion_id);
6102 let task = cx.spawn_in(window, async move |editor, cx| {
6103 let Ok(()) = editor.update(cx, |this, _| {
6104 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6105 }) else {
6106 return;
6107 };
6108
6109 // TODO: Ideally completions from different sources would be selectively re-queried, so
6110 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6111 let mut completions = Vec::new();
6112 let mut is_incomplete = false;
6113 let mut display_options: Option<CompletionDisplayOptions> = None;
6114 if let Some(provider_responses) = provider_responses.await.log_err()
6115 && !provider_responses.is_empty()
6116 {
6117 for response in provider_responses {
6118 completions.extend(response.completions);
6119 is_incomplete = is_incomplete || response.is_incomplete;
6120 match display_options.as_mut() {
6121 None => {
6122 display_options = Some(response.display_options);
6123 }
6124 Some(options) => options.merge(&response.display_options),
6125 }
6126 }
6127 if completion_settings.words == WordsCompletionMode::Fallback {
6128 words = Task::ready(BTreeMap::default());
6129 }
6130 }
6131 let display_options = display_options.unwrap_or_default();
6132
6133 let mut words = words.await;
6134 if let Some(word_to_exclude) = &word_to_exclude {
6135 words.remove(word_to_exclude);
6136 }
6137 for lsp_completion in &completions {
6138 words.remove(&lsp_completion.new_text);
6139 }
6140 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6141 replace_range: word_replace_range.clone(),
6142 new_text: word.clone(),
6143 label: CodeLabel::plain(word, None),
6144 match_start: None,
6145 snippet_deduplication_key: None,
6146 icon_path: None,
6147 documentation: None,
6148 source: CompletionSource::BufferWord {
6149 word_range,
6150 resolved: false,
6151 },
6152 insert_text_mode: Some(InsertTextMode::AS_IS),
6153 confirm: None,
6154 }));
6155
6156 completions.extend(
6157 snippets
6158 .await
6159 .into_iter()
6160 .flat_map(|response| response.completions),
6161 );
6162
6163 let menu = if completions.is_empty() {
6164 None
6165 } else {
6166 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6167 let languages = editor
6168 .workspace
6169 .as_ref()
6170 .and_then(|(workspace, _)| workspace.upgrade())
6171 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6172 let menu = CompletionsMenu::new(
6173 id,
6174 requested_source.unwrap_or(if load_provider_completions {
6175 CompletionsMenuSource::Normal
6176 } else {
6177 CompletionsMenuSource::SnippetsOnly
6178 }),
6179 sort_completions,
6180 show_completion_documentation,
6181 position,
6182 query.clone(),
6183 is_incomplete,
6184 buffer.clone(),
6185 completions.into(),
6186 editor
6187 .context_menu()
6188 .borrow_mut()
6189 .as_ref()
6190 .map(|menu| menu.primary_scroll_handle()),
6191 display_options,
6192 snippet_sort_order,
6193 languages,
6194 language,
6195 cx,
6196 );
6197
6198 let query = if filter_completions { query } else { None };
6199 let matches_task = menu.do_async_filtering(
6200 query.unwrap_or_default(),
6201 buffer_position,
6202 &buffer,
6203 cx,
6204 );
6205 (menu, matches_task)
6206 }) else {
6207 return;
6208 };
6209
6210 let matches = matches_task.await;
6211
6212 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6213 // Newer menu already set, so exit.
6214 if let Some(CodeContextMenu::Completions(prev_menu)) =
6215 editor.context_menu.borrow().as_ref()
6216 && prev_menu.id > id
6217 {
6218 return;
6219 };
6220
6221 // Only valid to take prev_menu because either the new menu is immediately set
6222 // below, or the menu is hidden.
6223 if let Some(CodeContextMenu::Completions(prev_menu)) =
6224 editor.context_menu.borrow_mut().take()
6225 {
6226 let position_matches =
6227 if prev_menu.initial_position == menu.initial_position {
6228 true
6229 } else {
6230 let snapshot = editor.buffer.read(cx).read(cx);
6231 prev_menu.initial_position.to_offset(&snapshot)
6232 == menu.initial_position.to_offset(&snapshot)
6233 };
6234 if position_matches {
6235 // Preserve markdown cache before `set_filter_results` because it will
6236 // try to populate the documentation cache.
6237 menu.preserve_markdown_cache(prev_menu);
6238 }
6239 };
6240
6241 menu.set_filter_results(matches, provider, window, cx);
6242 }) else {
6243 return;
6244 };
6245
6246 menu.visible().then_some(menu)
6247 };
6248
6249 editor
6250 .update_in(cx, |editor, window, cx| {
6251 if editor.focus_handle.is_focused(window)
6252 && let Some(menu) = menu
6253 {
6254 *editor.context_menu.borrow_mut() =
6255 Some(CodeContextMenu::Completions(menu));
6256
6257 crate::hover_popover::hide_hover(editor, cx);
6258 if editor.show_edit_predictions_in_menu() {
6259 editor.update_visible_edit_prediction(window, cx);
6260 } else {
6261 editor
6262 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6263 }
6264
6265 cx.notify();
6266 return;
6267 }
6268
6269 if editor.completion_tasks.len() <= 1 {
6270 // If there are no more completion tasks and the last menu was empty, we should hide it.
6271 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6272 // If it was already hidden and we don't show edit predictions in the menu,
6273 // we should also show the edit prediction when available.
6274 if was_hidden && editor.show_edit_predictions_in_menu() {
6275 editor.update_visible_edit_prediction(window, cx);
6276 }
6277 }
6278 })
6279 .ok();
6280 });
6281
6282 self.completion_tasks.push((id, task));
6283 }
6284
6285 #[cfg(feature = "test-support")]
6286 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6287 let menu = self.context_menu.borrow();
6288 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6289 let completions = menu.completions.borrow();
6290 Some(completions.to_vec())
6291 } else {
6292 None
6293 }
6294 }
6295
6296 pub fn with_completions_menu_matching_id<R>(
6297 &self,
6298 id: CompletionId,
6299 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6300 ) -> R {
6301 let mut context_menu = self.context_menu.borrow_mut();
6302 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6303 return f(None);
6304 };
6305 if completions_menu.id != id {
6306 return f(None);
6307 }
6308 f(Some(completions_menu))
6309 }
6310
6311 pub fn confirm_completion(
6312 &mut self,
6313 action: &ConfirmCompletion,
6314 window: &mut Window,
6315 cx: &mut Context<Self>,
6316 ) -> Option<Task<Result<()>>> {
6317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6318 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6319 }
6320
6321 pub fn confirm_completion_insert(
6322 &mut self,
6323 _: &ConfirmCompletionInsert,
6324 window: &mut Window,
6325 cx: &mut Context<Self>,
6326 ) -> Option<Task<Result<()>>> {
6327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6328 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6329 }
6330
6331 pub fn confirm_completion_replace(
6332 &mut self,
6333 _: &ConfirmCompletionReplace,
6334 window: &mut Window,
6335 cx: &mut Context<Self>,
6336 ) -> Option<Task<Result<()>>> {
6337 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6338 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6339 }
6340
6341 pub fn compose_completion(
6342 &mut self,
6343 action: &ComposeCompletion,
6344 window: &mut Window,
6345 cx: &mut Context<Self>,
6346 ) -> Option<Task<Result<()>>> {
6347 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6348 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6349 }
6350
6351 fn do_completion(
6352 &mut self,
6353 item_ix: Option<usize>,
6354 intent: CompletionIntent,
6355 window: &mut Window,
6356 cx: &mut Context<Editor>,
6357 ) -> Option<Task<Result<()>>> {
6358 use language::ToOffset as _;
6359
6360 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6361 else {
6362 return None;
6363 };
6364
6365 let candidate_id = {
6366 let entries = completions_menu.entries.borrow();
6367 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6368 if self.show_edit_predictions_in_menu() {
6369 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6370 }
6371 mat.candidate_id
6372 };
6373
6374 let completion = completions_menu
6375 .completions
6376 .borrow()
6377 .get(candidate_id)?
6378 .clone();
6379 cx.stop_propagation();
6380
6381 let buffer_handle = completions_menu.buffer.clone();
6382
6383 let CompletionEdit {
6384 new_text,
6385 snippet,
6386 replace_range,
6387 } = process_completion_for_edit(
6388 &completion,
6389 intent,
6390 &buffer_handle,
6391 &completions_menu.initial_position.text_anchor,
6392 cx,
6393 );
6394
6395 let buffer = buffer_handle.read(cx);
6396 let snapshot = self.buffer.read(cx).snapshot(cx);
6397 let newest_anchor = self.selections.newest_anchor();
6398 let replace_range_multibuffer = {
6399 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6400 excerpt.map_range_from_buffer(replace_range.clone())
6401 };
6402 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6403 return None;
6404 }
6405
6406 let old_text = buffer
6407 .text_for_range(replace_range.clone())
6408 .collect::<String>();
6409 let lookbehind = newest_anchor
6410 .start
6411 .text_anchor
6412 .to_offset(buffer)
6413 .saturating_sub(replace_range.start.0);
6414 let lookahead = replace_range
6415 .end
6416 .0
6417 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6418 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6419 let suffix = &old_text[lookbehind.min(old_text.len())..];
6420
6421 let selections = self
6422 .selections
6423 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6424 let mut ranges = Vec::new();
6425 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6426
6427 for selection in &selections {
6428 let range = if selection.id == newest_anchor.id {
6429 replace_range_multibuffer.clone()
6430 } else {
6431 let mut range = selection.range();
6432
6433 // if prefix is present, don't duplicate it
6434 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6435 range.start = range.start.saturating_sub_usize(lookbehind);
6436
6437 // if suffix is also present, mimic the newest cursor and replace it
6438 if selection.id != newest_anchor.id
6439 && snapshot.contains_str_at(range.end, suffix)
6440 {
6441 range.end += lookahead;
6442 }
6443 }
6444 range
6445 };
6446
6447 ranges.push(range.clone());
6448
6449 if !self.linked_edit_ranges.is_empty() {
6450 let start_anchor = snapshot.anchor_before(range.start);
6451 let end_anchor = snapshot.anchor_after(range.end);
6452 if let Some(ranges) = self
6453 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6454 {
6455 for (buffer, edits) in ranges {
6456 linked_edits
6457 .entry(buffer.clone())
6458 .or_default()
6459 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6460 }
6461 }
6462 }
6463 }
6464
6465 let common_prefix_len = old_text
6466 .chars()
6467 .zip(new_text.chars())
6468 .take_while(|(a, b)| a == b)
6469 .map(|(a, _)| a.len_utf8())
6470 .sum::<usize>();
6471
6472 cx.emit(EditorEvent::InputHandled {
6473 utf16_range_to_replace: None,
6474 text: new_text[common_prefix_len..].into(),
6475 });
6476
6477 self.transact(window, cx, |editor, window, cx| {
6478 if let Some(mut snippet) = snippet {
6479 snippet.text = new_text.to_string();
6480 editor
6481 .insert_snippet(&ranges, snippet, window, cx)
6482 .log_err();
6483 } else {
6484 editor.buffer.update(cx, |multi_buffer, cx| {
6485 let auto_indent = match completion.insert_text_mode {
6486 Some(InsertTextMode::AS_IS) => None,
6487 _ => editor.autoindent_mode.clone(),
6488 };
6489 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6490 multi_buffer.edit(edits, auto_indent, cx);
6491 });
6492 }
6493 for (buffer, edits) in linked_edits {
6494 buffer.update(cx, |buffer, cx| {
6495 let snapshot = buffer.snapshot();
6496 let edits = edits
6497 .into_iter()
6498 .map(|(range, text)| {
6499 use text::ToPoint as TP;
6500 let end_point = TP::to_point(&range.end, &snapshot);
6501 let start_point = TP::to_point(&range.start, &snapshot);
6502 (start_point..end_point, text)
6503 })
6504 .sorted_by_key(|(range, _)| range.start);
6505 buffer.edit(edits, None, cx);
6506 })
6507 }
6508
6509 editor.refresh_edit_prediction(true, false, window, cx);
6510 });
6511 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6512
6513 let show_new_completions_on_confirm = completion
6514 .confirm
6515 .as_ref()
6516 .is_some_and(|confirm| confirm(intent, window, cx));
6517 if show_new_completions_on_confirm {
6518 self.open_or_update_completions_menu(None, None, false, window, cx);
6519 }
6520
6521 let provider = self.completion_provider.as_ref()?;
6522
6523 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6524 let command = lsp_store.as_ref().and_then(|lsp_store| {
6525 let CompletionSource::Lsp {
6526 lsp_completion,
6527 server_id,
6528 ..
6529 } = &completion.source
6530 else {
6531 return None;
6532 };
6533 let lsp_command = lsp_completion.command.as_ref()?;
6534 let available_commands = lsp_store
6535 .read(cx)
6536 .lsp_server_capabilities
6537 .get(server_id)
6538 .and_then(|server_capabilities| {
6539 server_capabilities
6540 .execute_command_provider
6541 .as_ref()
6542 .map(|options| options.commands.as_slice())
6543 })?;
6544 if available_commands.contains(&lsp_command.command) {
6545 Some(CodeAction {
6546 server_id: *server_id,
6547 range: language::Anchor::MIN..language::Anchor::MIN,
6548 lsp_action: LspAction::Command(lsp_command.clone()),
6549 resolved: false,
6550 })
6551 } else {
6552 None
6553 }
6554 });
6555
6556 drop(completion);
6557 let apply_edits = provider.apply_additional_edits_for_completion(
6558 buffer_handle.clone(),
6559 completions_menu.completions.clone(),
6560 candidate_id,
6561 true,
6562 cx,
6563 );
6564
6565 let editor_settings = EditorSettings::get_global(cx);
6566 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6567 // After the code completion is finished, users often want to know what signatures are needed.
6568 // so we should automatically call signature_help
6569 self.show_signature_help(&ShowSignatureHelp, window, cx);
6570 }
6571
6572 Some(cx.spawn_in(window, async move |editor, cx| {
6573 apply_edits.await?;
6574
6575 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6576 let title = command.lsp_action.title().to_owned();
6577 let project_transaction = lsp_store
6578 .update(cx, |lsp_store, cx| {
6579 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6580 })
6581 .await
6582 .context("applying post-completion command")?;
6583 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6584 Self::open_project_transaction(
6585 &editor,
6586 workspace.downgrade(),
6587 project_transaction,
6588 title,
6589 cx,
6590 )
6591 .await?;
6592 }
6593 }
6594
6595 Ok(())
6596 }))
6597 }
6598
6599 pub fn toggle_code_actions(
6600 &mut self,
6601 action: &ToggleCodeActions,
6602 window: &mut Window,
6603 cx: &mut Context<Self>,
6604 ) {
6605 let quick_launch = action.quick_launch;
6606 let mut context_menu = self.context_menu.borrow_mut();
6607 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6608 if code_actions.deployed_from == action.deployed_from {
6609 // Toggle if we're selecting the same one
6610 *context_menu = None;
6611 cx.notify();
6612 return;
6613 } else {
6614 // Otherwise, clear it and start a new one
6615 *context_menu = None;
6616 cx.notify();
6617 }
6618 }
6619 drop(context_menu);
6620 let snapshot = self.snapshot(window, cx);
6621 let deployed_from = action.deployed_from.clone();
6622 let action = action.clone();
6623 self.completion_tasks.clear();
6624 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6625
6626 let multibuffer_point = match &action.deployed_from {
6627 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6628 DisplayPoint::new(*row, 0).to_point(&snapshot)
6629 }
6630 _ => self
6631 .selections
6632 .newest::<Point>(&snapshot.display_snapshot)
6633 .head(),
6634 };
6635 let Some((buffer, buffer_row)) = snapshot
6636 .buffer_snapshot()
6637 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6638 .and_then(|(buffer_snapshot, range)| {
6639 self.buffer()
6640 .read(cx)
6641 .buffer(buffer_snapshot.remote_id())
6642 .map(|buffer| (buffer, range.start.row))
6643 })
6644 else {
6645 return;
6646 };
6647 let buffer_id = buffer.read(cx).remote_id();
6648 let tasks = self
6649 .tasks
6650 .get(&(buffer_id, buffer_row))
6651 .map(|t| Arc::new(t.to_owned()));
6652
6653 if !self.focus_handle.is_focused(window) {
6654 return;
6655 }
6656 let project = self.project.clone();
6657
6658 let code_actions_task = match deployed_from {
6659 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6660 _ => self.code_actions(buffer_row, window, cx),
6661 };
6662
6663 let runnable_task = match deployed_from {
6664 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6665 _ => {
6666 let mut task_context_task = Task::ready(None);
6667 if let Some(tasks) = &tasks
6668 && let Some(project) = project
6669 {
6670 task_context_task =
6671 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6672 }
6673
6674 cx.spawn_in(window, {
6675 let buffer = buffer.clone();
6676 async move |editor, cx| {
6677 let task_context = task_context_task.await;
6678
6679 let resolved_tasks =
6680 tasks
6681 .zip(task_context.clone())
6682 .map(|(tasks, task_context)| ResolvedTasks {
6683 templates: tasks.resolve(&task_context).collect(),
6684 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6685 multibuffer_point.row,
6686 tasks.column,
6687 )),
6688 });
6689 let debug_scenarios = editor
6690 .update(cx, |editor, cx| {
6691 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6692 })?
6693 .await;
6694 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6695 }
6696 })
6697 }
6698 };
6699
6700 cx.spawn_in(window, async move |editor, cx| {
6701 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6702 let code_actions = code_actions_task.await;
6703 let spawn_straight_away = quick_launch
6704 && resolved_tasks
6705 .as_ref()
6706 .is_some_and(|tasks| tasks.templates.len() == 1)
6707 && code_actions
6708 .as_ref()
6709 .is_none_or(|actions| actions.is_empty())
6710 && debug_scenarios.is_empty();
6711
6712 editor.update_in(cx, |editor, window, cx| {
6713 crate::hover_popover::hide_hover(editor, cx);
6714 let actions = CodeActionContents::new(
6715 resolved_tasks,
6716 code_actions,
6717 debug_scenarios,
6718 task_context.unwrap_or_default(),
6719 );
6720
6721 // Don't show the menu if there are no actions available
6722 if actions.is_empty() {
6723 cx.notify();
6724 return Task::ready(Ok(()));
6725 }
6726
6727 *editor.context_menu.borrow_mut() =
6728 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6729 buffer,
6730 actions,
6731 selected_item: Default::default(),
6732 scroll_handle: UniformListScrollHandle::default(),
6733 deployed_from,
6734 }));
6735 cx.notify();
6736 if spawn_straight_away
6737 && let Some(task) = editor.confirm_code_action(
6738 &ConfirmCodeAction { item_ix: Some(0) },
6739 window,
6740 cx,
6741 )
6742 {
6743 return task;
6744 }
6745
6746 Task::ready(Ok(()))
6747 })
6748 })
6749 .detach_and_log_err(cx);
6750 }
6751
6752 fn debug_scenarios(
6753 &mut self,
6754 resolved_tasks: &Option<ResolvedTasks>,
6755 buffer: &Entity<Buffer>,
6756 cx: &mut App,
6757 ) -> Task<Vec<task::DebugScenario>> {
6758 maybe!({
6759 let project = self.project()?;
6760 let dap_store = project.read(cx).dap_store();
6761 let mut scenarios = vec![];
6762 let resolved_tasks = resolved_tasks.as_ref()?;
6763 let buffer = buffer.read(cx);
6764 let language = buffer.language()?;
6765 let file = buffer.file();
6766 let debug_adapter = language_settings(language.name().into(), file, cx)
6767 .debuggers
6768 .first()
6769 .map(SharedString::from)
6770 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6771
6772 dap_store.update(cx, |dap_store, cx| {
6773 for (_, task) in &resolved_tasks.templates {
6774 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6775 task.original_task().clone(),
6776 debug_adapter.clone().into(),
6777 task.display_label().to_owned().into(),
6778 cx,
6779 );
6780 scenarios.push(maybe_scenario);
6781 }
6782 });
6783 Some(cx.background_spawn(async move {
6784 futures::future::join_all(scenarios)
6785 .await
6786 .into_iter()
6787 .flatten()
6788 .collect::<Vec<_>>()
6789 }))
6790 })
6791 .unwrap_or_else(|| Task::ready(vec![]))
6792 }
6793
6794 fn code_actions(
6795 &mut self,
6796 buffer_row: u32,
6797 window: &mut Window,
6798 cx: &mut Context<Self>,
6799 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6800 let mut task = self.code_actions_task.take();
6801 cx.spawn_in(window, async move |editor, cx| {
6802 while let Some(prev_task) = task {
6803 prev_task.await.log_err();
6804 task = editor
6805 .update(cx, |this, _| this.code_actions_task.take())
6806 .ok()?;
6807 }
6808
6809 editor
6810 .update(cx, |editor, cx| {
6811 editor
6812 .available_code_actions
6813 .clone()
6814 .and_then(|(location, code_actions)| {
6815 let snapshot = location.buffer.read(cx).snapshot();
6816 let point_range = location.range.to_point(&snapshot);
6817 let point_range = point_range.start.row..=point_range.end.row;
6818 if point_range.contains(&buffer_row) {
6819 Some(code_actions)
6820 } else {
6821 None
6822 }
6823 })
6824 })
6825 .ok()
6826 .flatten()
6827 })
6828 }
6829
6830 pub fn confirm_code_action(
6831 &mut self,
6832 action: &ConfirmCodeAction,
6833 window: &mut Window,
6834 cx: &mut Context<Self>,
6835 ) -> Option<Task<Result<()>>> {
6836 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6837
6838 let actions_menu =
6839 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6840 menu
6841 } else {
6842 return None;
6843 };
6844
6845 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6846 let action = actions_menu.actions.get(action_ix)?;
6847 let title = action.label();
6848 let buffer = actions_menu.buffer;
6849 let workspace = self.workspace()?;
6850
6851 match action {
6852 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6853 workspace.update(cx, |workspace, cx| {
6854 workspace.schedule_resolved_task(
6855 task_source_kind,
6856 resolved_task,
6857 false,
6858 window,
6859 cx,
6860 );
6861
6862 Some(Task::ready(Ok(())))
6863 })
6864 }
6865 CodeActionsItem::CodeAction {
6866 excerpt_id,
6867 action,
6868 provider,
6869 } => {
6870 let apply_code_action =
6871 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6872 let workspace = workspace.downgrade();
6873 Some(cx.spawn_in(window, async move |editor, cx| {
6874 let project_transaction = apply_code_action.await?;
6875 Self::open_project_transaction(
6876 &editor,
6877 workspace,
6878 project_transaction,
6879 title,
6880 cx,
6881 )
6882 .await
6883 }))
6884 }
6885 CodeActionsItem::DebugScenario(scenario) => {
6886 let context = actions_menu.actions.context.into();
6887
6888 workspace.update(cx, |workspace, cx| {
6889 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6890 workspace.start_debug_session(
6891 scenario,
6892 context,
6893 Some(buffer),
6894 None,
6895 window,
6896 cx,
6897 );
6898 });
6899 Some(Task::ready(Ok(())))
6900 }
6901 }
6902 }
6903
6904 fn open_transaction_for_hidden_buffers(
6905 workspace: Entity<Workspace>,
6906 transaction: ProjectTransaction,
6907 title: String,
6908 window: &mut Window,
6909 cx: &mut Context<Self>,
6910 ) {
6911 if transaction.0.is_empty() {
6912 return;
6913 }
6914
6915 let edited_buffers_already_open = {
6916 let other_editors: Vec<Entity<Editor>> = workspace
6917 .read(cx)
6918 .panes()
6919 .iter()
6920 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6921 .filter(|editor| editor.entity_id() != cx.entity_id())
6922 .collect();
6923
6924 transaction.0.keys().all(|buffer| {
6925 other_editors.iter().any(|editor| {
6926 let multi_buffer = editor.read(cx).buffer();
6927 multi_buffer.read(cx).is_singleton()
6928 && multi_buffer
6929 .read(cx)
6930 .as_singleton()
6931 .map_or(false, |singleton| {
6932 singleton.entity_id() == buffer.entity_id()
6933 })
6934 })
6935 })
6936 };
6937 if !edited_buffers_already_open {
6938 let workspace = workspace.downgrade();
6939 cx.defer_in(window, move |_, window, cx| {
6940 cx.spawn_in(window, async move |editor, cx| {
6941 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6942 .await
6943 .ok()
6944 })
6945 .detach();
6946 });
6947 }
6948 }
6949
6950 pub async fn open_project_transaction(
6951 editor: &WeakEntity<Editor>,
6952 workspace: WeakEntity<Workspace>,
6953 transaction: ProjectTransaction,
6954 title: String,
6955 cx: &mut AsyncWindowContext,
6956 ) -> Result<()> {
6957 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6958 cx.update(|_, cx| {
6959 entries.sort_unstable_by_key(|(buffer, _)| {
6960 buffer.read(cx).file().map(|f| f.path().clone())
6961 });
6962 })?;
6963 if entries.is_empty() {
6964 return Ok(());
6965 }
6966
6967 // If the project transaction's edits are all contained within this editor, then
6968 // avoid opening a new editor to display them.
6969
6970 if let [(buffer, transaction)] = &*entries {
6971 let excerpt = editor.update(cx, |editor, cx| {
6972 editor
6973 .buffer()
6974 .read(cx)
6975 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6976 })?;
6977 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6978 && excerpted_buffer == *buffer
6979 {
6980 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6981 let excerpt_range = excerpt_range.to_offset(buffer);
6982 buffer
6983 .edited_ranges_for_transaction::<usize>(transaction)
6984 .all(|range| {
6985 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6986 })
6987 });
6988
6989 if all_edits_within_excerpt {
6990 return Ok(());
6991 }
6992 }
6993 }
6994
6995 let mut ranges_to_highlight = Vec::new();
6996 let excerpt_buffer = cx.new(|cx| {
6997 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6998 for (buffer_handle, transaction) in &entries {
6999 let edited_ranges = buffer_handle
7000 .read(cx)
7001 .edited_ranges_for_transaction::<Point>(transaction)
7002 .collect::<Vec<_>>();
7003 let (ranges, _) = multibuffer.set_excerpts_for_path(
7004 PathKey::for_buffer(buffer_handle, cx),
7005 buffer_handle.clone(),
7006 edited_ranges,
7007 multibuffer_context_lines(cx),
7008 cx,
7009 );
7010
7011 ranges_to_highlight.extend(ranges);
7012 }
7013 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7014 multibuffer
7015 });
7016
7017 workspace.update_in(cx, |workspace, window, cx| {
7018 let project = workspace.project().clone();
7019 let editor =
7020 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7021 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7022 editor.update(cx, |editor, cx| {
7023 editor.highlight_background(
7024 HighlightKey::Editor,
7025 &ranges_to_highlight,
7026 |_, theme| theme.colors().editor_highlighted_line_background,
7027 cx,
7028 );
7029 });
7030 })?;
7031
7032 Ok(())
7033 }
7034
7035 pub fn clear_code_action_providers(&mut self) {
7036 self.code_action_providers.clear();
7037 self.available_code_actions.take();
7038 }
7039
7040 pub fn add_code_action_provider(
7041 &mut self,
7042 provider: Rc<dyn CodeActionProvider>,
7043 window: &mut Window,
7044 cx: &mut Context<Self>,
7045 ) {
7046 if self
7047 .code_action_providers
7048 .iter()
7049 .any(|existing_provider| existing_provider.id() == provider.id())
7050 {
7051 return;
7052 }
7053
7054 self.code_action_providers.push(provider);
7055 self.refresh_code_actions(window, cx);
7056 }
7057
7058 pub fn remove_code_action_provider(
7059 &mut self,
7060 id: Arc<str>,
7061 window: &mut Window,
7062 cx: &mut Context<Self>,
7063 ) {
7064 self.code_action_providers
7065 .retain(|provider| provider.id() != id);
7066 self.refresh_code_actions(window, cx);
7067 }
7068
7069 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7070 !self.code_action_providers.is_empty()
7071 && EditorSettings::get_global(cx).toolbar.code_actions
7072 }
7073
7074 pub fn has_available_code_actions(&self) -> bool {
7075 self.available_code_actions
7076 .as_ref()
7077 .is_some_and(|(_, actions)| !actions.is_empty())
7078 }
7079
7080 fn render_inline_code_actions(
7081 &self,
7082 icon_size: ui::IconSize,
7083 display_row: DisplayRow,
7084 is_active: bool,
7085 cx: &mut Context<Self>,
7086 ) -> AnyElement {
7087 let show_tooltip = !self.context_menu_visible();
7088 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7089 .icon_size(icon_size)
7090 .shape(ui::IconButtonShape::Square)
7091 .icon_color(ui::Color::Hidden)
7092 .toggle_state(is_active)
7093 .when(show_tooltip, |this| {
7094 this.tooltip({
7095 let focus_handle = self.focus_handle.clone();
7096 move |_window, cx| {
7097 Tooltip::for_action_in(
7098 "Toggle Code Actions",
7099 &ToggleCodeActions {
7100 deployed_from: None,
7101 quick_launch: false,
7102 },
7103 &focus_handle,
7104 cx,
7105 )
7106 }
7107 })
7108 })
7109 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7110 window.focus(&editor.focus_handle(cx), cx);
7111 editor.toggle_code_actions(
7112 &crate::actions::ToggleCodeActions {
7113 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7114 display_row,
7115 )),
7116 quick_launch: false,
7117 },
7118 window,
7119 cx,
7120 );
7121 }))
7122 .into_any_element()
7123 }
7124
7125 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7126 &self.context_menu
7127 }
7128
7129 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7130 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7131 cx.background_executor()
7132 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7133 .await;
7134
7135 let (start_buffer, start, _, end, newest_selection) = this
7136 .update(cx, |this, cx| {
7137 let newest_selection = this.selections.newest_anchor().clone();
7138 if newest_selection.head().diff_base_anchor.is_some() {
7139 return None;
7140 }
7141 let display_snapshot = this.display_snapshot(cx);
7142 let newest_selection_adjusted =
7143 this.selections.newest_adjusted(&display_snapshot);
7144 let buffer = this.buffer.read(cx);
7145
7146 let (start_buffer, start) =
7147 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7148 let (end_buffer, end) =
7149 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7150
7151 Some((start_buffer, start, end_buffer, end, newest_selection))
7152 })?
7153 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7154 .context(
7155 "Expected selection to lie in a single buffer when refreshing code actions",
7156 )?;
7157 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7158 let providers = this.code_action_providers.clone();
7159 let tasks = this
7160 .code_action_providers
7161 .iter()
7162 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7163 .collect::<Vec<_>>();
7164 (providers, tasks)
7165 })?;
7166
7167 let mut actions = Vec::new();
7168 for (provider, provider_actions) in
7169 providers.into_iter().zip(future::join_all(tasks).await)
7170 {
7171 if let Some(provider_actions) = provider_actions.log_err() {
7172 actions.extend(provider_actions.into_iter().map(|action| {
7173 AvailableCodeAction {
7174 excerpt_id: newest_selection.start.excerpt_id,
7175 action,
7176 provider: provider.clone(),
7177 }
7178 }));
7179 }
7180 }
7181
7182 this.update(cx, |this, cx| {
7183 this.available_code_actions = if actions.is_empty() {
7184 None
7185 } else {
7186 Some((
7187 Location {
7188 buffer: start_buffer,
7189 range: start..end,
7190 },
7191 actions.into(),
7192 ))
7193 };
7194 cx.notify();
7195 })
7196 }));
7197 }
7198
7199 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7200 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7201 self.show_git_blame_inline = false;
7202
7203 self.show_git_blame_inline_delay_task =
7204 Some(cx.spawn_in(window, async move |this, cx| {
7205 cx.background_executor().timer(delay).await;
7206
7207 this.update(cx, |this, cx| {
7208 this.show_git_blame_inline = true;
7209 cx.notify();
7210 })
7211 .log_err();
7212 }));
7213 }
7214 }
7215
7216 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7217 let snapshot = self.snapshot(window, cx);
7218 let cursor = self
7219 .selections
7220 .newest::<Point>(&snapshot.display_snapshot)
7221 .head();
7222 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7223 else {
7224 return;
7225 };
7226
7227 if self.blame.is_none() {
7228 self.start_git_blame(true, window, cx);
7229 }
7230 let Some(blame) = self.blame.as_ref() else {
7231 return;
7232 };
7233
7234 let row_info = RowInfo {
7235 buffer_id: Some(buffer.remote_id()),
7236 buffer_row: Some(point.row),
7237 ..Default::default()
7238 };
7239 let Some((buffer, blame_entry)) = blame
7240 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7241 .flatten()
7242 else {
7243 return;
7244 };
7245
7246 let anchor = self.selections.newest_anchor().head();
7247 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7248 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7249 self.show_blame_popover(
7250 buffer,
7251 &blame_entry,
7252 position + last_bounds.origin,
7253 true,
7254 cx,
7255 );
7256 };
7257 }
7258
7259 fn show_blame_popover(
7260 &mut self,
7261 buffer: BufferId,
7262 blame_entry: &BlameEntry,
7263 position: gpui::Point<Pixels>,
7264 ignore_timeout: bool,
7265 cx: &mut Context<Self>,
7266 ) {
7267 if let Some(state) = &mut self.inline_blame_popover {
7268 state.hide_task.take();
7269 } else {
7270 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7271 let blame_entry = blame_entry.clone();
7272 let show_task = cx.spawn(async move |editor, cx| {
7273 if !ignore_timeout {
7274 cx.background_executor()
7275 .timer(std::time::Duration::from_millis(blame_popover_delay))
7276 .await;
7277 }
7278 editor
7279 .update(cx, |editor, cx| {
7280 editor.inline_blame_popover_show_task.take();
7281 let Some(blame) = editor.blame.as_ref() else {
7282 return;
7283 };
7284 let blame = blame.read(cx);
7285 let details = blame.details_for_entry(buffer, &blame_entry);
7286 let markdown = cx.new(|cx| {
7287 Markdown::new(
7288 details
7289 .as_ref()
7290 .map(|message| message.message.clone())
7291 .unwrap_or_default(),
7292 None,
7293 None,
7294 cx,
7295 )
7296 });
7297 editor.inline_blame_popover = Some(InlineBlamePopover {
7298 position,
7299 hide_task: None,
7300 popover_bounds: None,
7301 popover_state: InlineBlamePopoverState {
7302 scroll_handle: ScrollHandle::new(),
7303 commit_message: details,
7304 markdown,
7305 },
7306 keyboard_grace: ignore_timeout,
7307 });
7308 cx.notify();
7309 })
7310 .ok();
7311 });
7312 self.inline_blame_popover_show_task = Some(show_task);
7313 }
7314 }
7315
7316 pub fn has_mouse_context_menu(&self) -> bool {
7317 self.mouse_context_menu.is_some()
7318 }
7319
7320 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7321 self.inline_blame_popover_show_task.take();
7322 if let Some(state) = &mut self.inline_blame_popover {
7323 let hide_task = cx.spawn(async move |editor, cx| {
7324 if !ignore_timeout {
7325 cx.background_executor()
7326 .timer(std::time::Duration::from_millis(100))
7327 .await;
7328 }
7329 editor
7330 .update(cx, |editor, cx| {
7331 editor.inline_blame_popover.take();
7332 cx.notify();
7333 })
7334 .ok();
7335 });
7336 state.hide_task = Some(hide_task);
7337 true
7338 } else {
7339 false
7340 }
7341 }
7342
7343 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7344 if self.pending_rename.is_some() {
7345 return None;
7346 }
7347
7348 let provider = self.semantics_provider.clone()?;
7349 let buffer = self.buffer.read(cx);
7350 let newest_selection = self.selections.newest_anchor().clone();
7351 let cursor_position = newest_selection.head();
7352 let (cursor_buffer, cursor_buffer_position) =
7353 buffer.text_anchor_for_position(cursor_position, cx)?;
7354 let (tail_buffer, tail_buffer_position) =
7355 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7356 if cursor_buffer != tail_buffer {
7357 return None;
7358 }
7359
7360 let snapshot = cursor_buffer.read(cx).snapshot();
7361 let word_ranges = cx.background_spawn(async move {
7362 // this might look odd to put on the background thread, but
7363 // `surrounding_word` can be quite expensive as it calls into
7364 // tree-sitter language scopes
7365 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7366 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7367 (start_word_range, end_word_range)
7368 });
7369
7370 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7371 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7372 let (start_word_range, end_word_range) = word_ranges.await;
7373 if start_word_range != end_word_range {
7374 this.update(cx, |this, cx| {
7375 this.document_highlights_task.take();
7376 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7377 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7378 })
7379 .ok();
7380 return;
7381 }
7382 cx.background_executor()
7383 .timer(Duration::from_millis(debounce))
7384 .await;
7385
7386 let highlights = if let Some(highlights) = cx.update(|cx| {
7387 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7388 }) {
7389 highlights.await.log_err()
7390 } else {
7391 None
7392 };
7393
7394 if let Some(highlights) = highlights {
7395 this.update(cx, |this, cx| {
7396 if this.pending_rename.is_some() {
7397 return;
7398 }
7399
7400 let buffer = this.buffer.read(cx);
7401 if buffer
7402 .text_anchor_for_position(cursor_position, cx)
7403 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7404 {
7405 return;
7406 }
7407
7408 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7409 let mut write_ranges = Vec::new();
7410 let mut read_ranges = Vec::new();
7411 for highlight in highlights {
7412 let buffer_id = cursor_buffer.read(cx).remote_id();
7413 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7414 {
7415 let start = highlight
7416 .range
7417 .start
7418 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7419 let end = highlight
7420 .range
7421 .end
7422 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7423 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7424 continue;
7425 }
7426
7427 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7428 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7429 write_ranges.push(range);
7430 } else {
7431 read_ranges.push(range);
7432 }
7433 }
7434 }
7435
7436 this.highlight_background(
7437 HighlightKey::DocumentHighlightRead,
7438 &read_ranges,
7439 |_, theme| theme.colors().editor_document_highlight_read_background,
7440 cx,
7441 );
7442 this.highlight_background(
7443 HighlightKey::DocumentHighlightWrite,
7444 &write_ranges,
7445 |_, theme| theme.colors().editor_document_highlight_write_background,
7446 cx,
7447 );
7448 cx.notify();
7449 })
7450 .log_err();
7451 }
7452 }));
7453 None
7454 }
7455
7456 fn prepare_highlight_query_from_selection(
7457 &mut self,
7458 window: &Window,
7459 cx: &mut Context<Editor>,
7460 ) -> Option<(String, Range<Anchor>)> {
7461 if matches!(self.mode, EditorMode::SingleLine) {
7462 return None;
7463 }
7464 if !EditorSettings::get_global(cx).selection_highlight {
7465 return None;
7466 }
7467 if self.selections.count() != 1 || self.selections.line_mode() {
7468 return None;
7469 }
7470 let snapshot = self.snapshot(window, cx);
7471 let selection = self.selections.newest::<Point>(&snapshot);
7472 // If the selection spans multiple rows OR it is empty
7473 if selection.start.row != selection.end.row
7474 || selection.start.column == selection.end.column
7475 {
7476 return None;
7477 }
7478 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7479 let query = snapshot
7480 .buffer_snapshot()
7481 .text_for_range(selection_anchor_range.clone())
7482 .collect::<String>();
7483 if query.trim().is_empty() {
7484 return None;
7485 }
7486 Some((query, selection_anchor_range))
7487 }
7488
7489 #[ztracing::instrument(skip_all)]
7490 fn update_selection_occurrence_highlights(
7491 &mut self,
7492 query_text: String,
7493 query_range: Range<Anchor>,
7494 multi_buffer_range_to_query: Range<Point>,
7495 use_debounce: bool,
7496 window: &mut Window,
7497 cx: &mut Context<Editor>,
7498 ) -> Task<()> {
7499 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7500 cx.spawn_in(window, async move |editor, cx| {
7501 if use_debounce {
7502 cx.background_executor()
7503 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7504 .await;
7505 }
7506 let match_task = cx.background_spawn(async move {
7507 let buffer_ranges = multi_buffer_snapshot
7508 .range_to_buffer_ranges(
7509 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7510 )
7511 .into_iter()
7512 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7513 let mut match_ranges = Vec::new();
7514 let Ok(regex) = project::search::SearchQuery::text(
7515 query_text.clone(),
7516 false,
7517 false,
7518 false,
7519 Default::default(),
7520 Default::default(),
7521 false,
7522 None,
7523 ) else {
7524 return Vec::default();
7525 };
7526 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7527 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7528 match_ranges.extend(
7529 regex
7530 .search(
7531 buffer_snapshot,
7532 Some(search_range.start.0..search_range.end.0),
7533 )
7534 .await
7535 .into_iter()
7536 .filter_map(|match_range| {
7537 let match_start = buffer_snapshot
7538 .anchor_after(search_range.start + match_range.start);
7539 let match_end = buffer_snapshot
7540 .anchor_before(search_range.start + match_range.end);
7541 let match_anchor_range =
7542 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7543 (match_anchor_range != query_range).then_some(match_anchor_range)
7544 }),
7545 );
7546 }
7547 match_ranges
7548 });
7549 let match_ranges = match_task.await;
7550 editor
7551 .update_in(cx, |editor, _, cx| {
7552 if use_debounce {
7553 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7554 editor.debounced_selection_highlight_complete = true;
7555 } else if editor.debounced_selection_highlight_complete {
7556 return;
7557 }
7558 if !match_ranges.is_empty() {
7559 editor.highlight_background(
7560 HighlightKey::SelectedTextHighlight,
7561 &match_ranges,
7562 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7563 cx,
7564 )
7565 }
7566 })
7567 .log_err();
7568 })
7569 }
7570
7571 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7572 struct NewlineFold;
7573 let type_id = std::any::TypeId::of::<NewlineFold>();
7574 if !self.mode.is_single_line() {
7575 return;
7576 }
7577 let snapshot = self.snapshot(window, cx);
7578 if snapshot.buffer_snapshot().max_point().row == 0 {
7579 return;
7580 }
7581 let task = cx.background_spawn(async move {
7582 let new_newlines = snapshot
7583 .buffer_chars_at(MultiBufferOffset(0))
7584 .filter_map(|(c, i)| {
7585 if c == '\n' {
7586 Some(
7587 snapshot.buffer_snapshot().anchor_after(i)
7588 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7589 )
7590 } else {
7591 None
7592 }
7593 })
7594 .collect::<Vec<_>>();
7595 let existing_newlines = snapshot
7596 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7597 .filter_map(|fold| {
7598 if fold.placeholder.type_tag == Some(type_id) {
7599 Some(fold.range.start..fold.range.end)
7600 } else {
7601 None
7602 }
7603 })
7604 .collect::<Vec<_>>();
7605
7606 (new_newlines, existing_newlines)
7607 });
7608 self.folding_newlines = cx.spawn(async move |this, cx| {
7609 let (new_newlines, existing_newlines) = task.await;
7610 if new_newlines == existing_newlines {
7611 return;
7612 }
7613 let placeholder = FoldPlaceholder {
7614 render: Arc::new(move |_, _, cx| {
7615 div()
7616 .bg(cx.theme().status().hint_background)
7617 .border_b_1()
7618 .size_full()
7619 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7620 .border_color(cx.theme().status().hint)
7621 .child("\\n")
7622 .into_any()
7623 }),
7624 constrain_width: false,
7625 merge_adjacent: false,
7626 type_tag: Some(type_id),
7627 collapsed_text: None,
7628 };
7629 let creases = new_newlines
7630 .into_iter()
7631 .map(|range| Crease::simple(range, placeholder.clone()))
7632 .collect();
7633 this.update(cx, |this, cx| {
7634 this.display_map.update(cx, |display_map, cx| {
7635 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7636 display_map.fold(creases, cx);
7637 });
7638 })
7639 .ok();
7640 });
7641 }
7642
7643 #[ztracing::instrument(skip_all)]
7644 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7645 if !self.mode.is_full() {
7646 return;
7647 }
7648 let cursor = self.selections.newest_anchor().head();
7649 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7650
7651 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7652 self.outline_symbols_at_cursor =
7653 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7654 cx.emit(EditorEvent::OutlineSymbolsChanged);
7655 cx.notify();
7656 } else {
7657 let syntax = cx.theme().syntax().clone();
7658 let background_task = cx.background_spawn(async move {
7659 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7660 });
7661 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7662 cx.spawn(async move |this, cx| {
7663 let symbols = background_task.await;
7664 this.update(cx, |this, cx| {
7665 this.outline_symbols_at_cursor = symbols;
7666 cx.emit(EditorEvent::OutlineSymbolsChanged);
7667 cx.notify();
7668 })
7669 .ok();
7670 });
7671 }
7672 }
7673
7674 #[ztracing::instrument(skip_all)]
7675 fn refresh_selected_text_highlights(
7676 &mut self,
7677 on_buffer_edit: bool,
7678 window: &mut Window,
7679 cx: &mut Context<Editor>,
7680 ) {
7681 let Some((query_text, query_range)) =
7682 self.prepare_highlight_query_from_selection(window, cx)
7683 else {
7684 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7685 self.quick_selection_highlight_task.take();
7686 self.debounced_selection_highlight_task.take();
7687 self.debounced_selection_highlight_complete = false;
7688 return;
7689 };
7690 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7691 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7692 let query_changed = self
7693 .quick_selection_highlight_task
7694 .as_ref()
7695 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7696 if query_changed {
7697 self.debounced_selection_highlight_complete = false;
7698 }
7699 if on_buffer_edit || query_changed {
7700 let multi_buffer_visible_start = self
7701 .scroll_manager
7702 .native_anchor(&display_snapshot, cx)
7703 .anchor
7704 .to_point(&multi_buffer_snapshot);
7705 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7706 multi_buffer_visible_start
7707 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7708 Bias::Left,
7709 );
7710 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7711 self.quick_selection_highlight_task = Some((
7712 query_range.clone(),
7713 self.update_selection_occurrence_highlights(
7714 query_text.clone(),
7715 query_range.clone(),
7716 multi_buffer_visible_range,
7717 false,
7718 window,
7719 cx,
7720 ),
7721 ));
7722 }
7723 if on_buffer_edit
7724 || self
7725 .debounced_selection_highlight_task
7726 .as_ref()
7727 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7728 {
7729 let multi_buffer_start = multi_buffer_snapshot
7730 .anchor_before(MultiBufferOffset(0))
7731 .to_point(&multi_buffer_snapshot);
7732 let multi_buffer_end = multi_buffer_snapshot
7733 .anchor_after(multi_buffer_snapshot.len())
7734 .to_point(&multi_buffer_snapshot);
7735 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7736 self.debounced_selection_highlight_task = Some((
7737 query_range.clone(),
7738 self.update_selection_occurrence_highlights(
7739 query_text,
7740 query_range,
7741 multi_buffer_full_range,
7742 true,
7743 window,
7744 cx,
7745 ),
7746 ));
7747 }
7748 }
7749
7750 pub fn refresh_edit_prediction(
7751 &mut self,
7752 debounce: bool,
7753 user_requested: bool,
7754 window: &mut Window,
7755 cx: &mut Context<Self>,
7756 ) -> Option<()> {
7757 let provider = self.edit_prediction_provider()?;
7758 let cursor = self.selections.newest_anchor().head();
7759 let (buffer, cursor_buffer_position) =
7760 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7761
7762 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7763 return None;
7764 }
7765
7766 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7767 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7768 return None;
7769 }
7770
7771 self.update_visible_edit_prediction(window, cx);
7772
7773 if !user_requested
7774 && (!self.should_show_edit_predictions()
7775 || !self.is_focused(window)
7776 || buffer.read(cx).is_empty())
7777 {
7778 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7779 return None;
7780 }
7781
7782 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7783 Some(())
7784 }
7785
7786 fn show_edit_predictions_in_menu(&self) -> bool {
7787 match self.edit_prediction_settings {
7788 EditPredictionSettings::Disabled => false,
7789 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7790 }
7791 }
7792
7793 pub fn edit_predictions_enabled(&self) -> bool {
7794 match self.edit_prediction_settings {
7795 EditPredictionSettings::Disabled => false,
7796 EditPredictionSettings::Enabled { .. } => true,
7797 }
7798 }
7799
7800 fn edit_prediction_requires_modifier(&self) -> bool {
7801 match self.edit_prediction_settings {
7802 EditPredictionSettings::Disabled => false,
7803 EditPredictionSettings::Enabled {
7804 preview_requires_modifier,
7805 ..
7806 } => preview_requires_modifier,
7807 }
7808 }
7809
7810 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7811 if self.edit_prediction_provider.is_none() {
7812 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7813 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7814 return;
7815 }
7816
7817 let selection = self.selections.newest_anchor();
7818 let cursor = selection.head();
7819
7820 if let Some((buffer, cursor_buffer_position)) =
7821 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7822 {
7823 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7824 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7825 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7826 return;
7827 }
7828 self.edit_prediction_settings =
7829 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7830 }
7831 }
7832
7833 fn edit_prediction_settings_at_position(
7834 &self,
7835 buffer: &Entity<Buffer>,
7836 buffer_position: language::Anchor,
7837 cx: &App,
7838 ) -> EditPredictionSettings {
7839 if !self.mode.is_full()
7840 || !self.show_edit_predictions_override.unwrap_or(true)
7841 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7842 {
7843 return EditPredictionSettings::Disabled;
7844 }
7845
7846 let buffer = buffer.read(cx);
7847
7848 let file = buffer.file();
7849
7850 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7851 return EditPredictionSettings::Disabled;
7852 };
7853
7854 let by_provider = matches!(
7855 self.menu_edit_predictions_policy,
7856 MenuEditPredictionsPolicy::ByProvider
7857 );
7858
7859 let show_in_menu = by_provider
7860 && self
7861 .edit_prediction_provider
7862 .as_ref()
7863 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7864
7865 let preview_requires_modifier =
7866 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7867
7868 EditPredictionSettings::Enabled {
7869 show_in_menu,
7870 preview_requires_modifier,
7871 }
7872 }
7873
7874 fn should_show_edit_predictions(&self) -> bool {
7875 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7876 }
7877
7878 pub fn edit_prediction_preview_is_active(&self) -> bool {
7879 matches!(
7880 self.edit_prediction_preview,
7881 EditPredictionPreview::Active { .. }
7882 )
7883 }
7884
7885 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7886 let cursor = self.selections.newest_anchor().head();
7887 if let Some((buffer, cursor_position)) =
7888 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7889 {
7890 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7891 } else {
7892 false
7893 }
7894 }
7895
7896 pub fn supports_minimap(&self, cx: &App) -> bool {
7897 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7898 }
7899
7900 fn edit_predictions_enabled_in_buffer(
7901 &self,
7902 buffer: &Entity<Buffer>,
7903 buffer_position: language::Anchor,
7904 cx: &App,
7905 ) -> bool {
7906 maybe!({
7907 if self.read_only(cx) {
7908 return Some(false);
7909 }
7910 let provider = self.edit_prediction_provider()?;
7911 if !provider.is_enabled(buffer, buffer_position, cx) {
7912 return Some(false);
7913 }
7914 let buffer = buffer.read(cx);
7915 let Some(file) = buffer.file() else {
7916 return Some(true);
7917 };
7918 let settings = all_language_settings(Some(file), cx);
7919 Some(settings.edit_predictions_enabled_for_file(file, cx))
7920 })
7921 .unwrap_or(false)
7922 }
7923
7924 pub fn show_edit_prediction(
7925 &mut self,
7926 _: &ShowEditPrediction,
7927 window: &mut Window,
7928 cx: &mut Context<Self>,
7929 ) {
7930 if !self.has_active_edit_prediction() {
7931 self.refresh_edit_prediction(false, true, window, cx);
7932 return;
7933 }
7934
7935 self.update_visible_edit_prediction(window, cx);
7936 }
7937
7938 pub fn display_cursor_names(
7939 &mut self,
7940 _: &DisplayCursorNames,
7941 window: &mut Window,
7942 cx: &mut Context<Self>,
7943 ) {
7944 self.show_cursor_names(window, cx);
7945 }
7946
7947 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7948 self.show_cursor_names = true;
7949 cx.notify();
7950 cx.spawn_in(window, async move |this, cx| {
7951 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7952 this.update(cx, |this, cx| {
7953 this.show_cursor_names = false;
7954 cx.notify()
7955 })
7956 .ok()
7957 })
7958 .detach();
7959 }
7960
7961 pub fn accept_partial_edit_prediction(
7962 &mut self,
7963 granularity: EditPredictionGranularity,
7964 window: &mut Window,
7965 cx: &mut Context<Self>,
7966 ) {
7967 if self.show_edit_predictions_in_menu() {
7968 self.hide_context_menu(window, cx);
7969 }
7970
7971 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7972 return;
7973 };
7974
7975 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7976 return;
7977 }
7978
7979 match &active_edit_prediction.completion {
7980 EditPrediction::MoveWithin { target, .. } => {
7981 let target = *target;
7982
7983 if matches!(granularity, EditPredictionGranularity::Full) {
7984 if let Some(position_map) = &self.last_position_map {
7985 let target_row = target.to_display_point(&position_map.snapshot).row();
7986 let is_visible = position_map.visible_row_range.contains(&target_row);
7987
7988 if is_visible || !self.edit_prediction_requires_modifier() {
7989 self.unfold_ranges(&[target..target], true, false, cx);
7990 self.change_selections(
7991 SelectionEffects::scroll(Autoscroll::newest()),
7992 window,
7993 cx,
7994 |selections| {
7995 selections.select_anchor_ranges([target..target]);
7996 },
7997 );
7998 self.clear_row_highlights::<EditPredictionPreview>();
7999 self.edit_prediction_preview
8000 .set_previous_scroll_position(None);
8001 } else {
8002 // Highlight and request scroll
8003 self.edit_prediction_preview
8004 .set_previous_scroll_position(Some(
8005 position_map.snapshot.scroll_anchor,
8006 ));
8007 self.highlight_rows::<EditPredictionPreview>(
8008 target..target,
8009 cx.theme().colors().editor_highlighted_line_background,
8010 RowHighlightOptions {
8011 autoscroll: true,
8012 ..Default::default()
8013 },
8014 cx,
8015 );
8016 self.request_autoscroll(Autoscroll::fit(), cx);
8017 }
8018 }
8019 } else {
8020 self.change_selections(
8021 SelectionEffects::scroll(Autoscroll::newest()),
8022 window,
8023 cx,
8024 |selections| {
8025 selections.select_anchor_ranges([target..target]);
8026 },
8027 );
8028 }
8029 }
8030 EditPrediction::MoveOutside { snapshot, target } => {
8031 if let Some(workspace) = self.workspace() {
8032 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8033 .detach_and_log_err(cx);
8034 }
8035 }
8036 EditPrediction::Edit {
8037 edits,
8038 cursor_position,
8039 ..
8040 } => {
8041 self.report_edit_prediction_event(
8042 active_edit_prediction.completion_id.clone(),
8043 true,
8044 cx,
8045 );
8046
8047 match granularity {
8048 EditPredictionGranularity::Full => {
8049 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8050
8051 // Compute fallback cursor position BEFORE applying the edit,
8052 // so the anchor tracks through the edit correctly
8053 let fallback_cursor_target = {
8054 let snapshot = self.buffer.read(cx).snapshot(cx);
8055 edits.last().unwrap().0.end.bias_right(&snapshot)
8056 };
8057
8058 self.buffer.update(cx, |buffer, cx| {
8059 buffer.edit(edits.iter().cloned(), None, cx)
8060 });
8061
8062 if let Some(provider) = self.edit_prediction_provider() {
8063 provider.accept(cx);
8064 }
8065
8066 // Resolve cursor position after the edit is applied
8067 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8068 // The anchor tracks through the edit, then we add the offset
8069 let snapshot = self.buffer.read(cx).snapshot(cx);
8070 let base_offset = anchor.to_offset(&snapshot).0;
8071 let target_offset =
8072 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8073 snapshot.anchor_after(target_offset)
8074 } else {
8075 fallback_cursor_target
8076 };
8077
8078 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8079 s.select_anchor_ranges([cursor_target..cursor_target]);
8080 });
8081
8082 let selections = self.selections.disjoint_anchors_arc();
8083 if let Some(transaction_id_now) =
8084 self.buffer.read(cx).last_transaction_id(cx)
8085 {
8086 if transaction_id_prev != Some(transaction_id_now) {
8087 self.selection_history
8088 .insert_transaction(transaction_id_now, selections);
8089 }
8090 }
8091
8092 self.update_visible_edit_prediction(window, cx);
8093 if self.active_edit_prediction.is_none() {
8094 self.refresh_edit_prediction(true, true, window, cx);
8095 }
8096 cx.notify();
8097 }
8098 _ => {
8099 let snapshot = self.buffer.read(cx).snapshot(cx);
8100 let cursor_offset = self
8101 .selections
8102 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8103 .head();
8104
8105 let insertion = edits.iter().find_map(|(range, text)| {
8106 let range = range.to_offset(&snapshot);
8107 if range.is_empty() && range.start == cursor_offset {
8108 Some(text)
8109 } else {
8110 None
8111 }
8112 });
8113
8114 if let Some(text) = insertion {
8115 let text_to_insert = match granularity {
8116 EditPredictionGranularity::Word => {
8117 let mut partial = text
8118 .chars()
8119 .by_ref()
8120 .take_while(|c| c.is_alphabetic())
8121 .collect::<String>();
8122 if partial.is_empty() {
8123 partial = text
8124 .chars()
8125 .by_ref()
8126 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8127 .collect::<String>();
8128 }
8129 partial
8130 }
8131 EditPredictionGranularity::Line => {
8132 if let Some(line) = text.split_inclusive('\n').next() {
8133 line.to_string()
8134 } else {
8135 text.to_string()
8136 }
8137 }
8138 EditPredictionGranularity::Full => unreachable!(),
8139 };
8140
8141 cx.emit(EditorEvent::InputHandled {
8142 utf16_range_to_replace: None,
8143 text: text_to_insert.clone().into(),
8144 });
8145
8146 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
8147 self.refresh_edit_prediction(true, true, window, cx);
8148 cx.notify();
8149 } else {
8150 self.accept_partial_edit_prediction(
8151 EditPredictionGranularity::Full,
8152 window,
8153 cx,
8154 );
8155 }
8156 }
8157 }
8158 }
8159 }
8160
8161 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8162 }
8163
8164 pub fn accept_next_word_edit_prediction(
8165 &mut self,
8166 _: &AcceptNextWordEditPrediction,
8167 window: &mut Window,
8168 cx: &mut Context<Self>,
8169 ) {
8170 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8171 }
8172
8173 pub fn accept_next_line_edit_prediction(
8174 &mut self,
8175 _: &AcceptNextLineEditPrediction,
8176 window: &mut Window,
8177 cx: &mut Context<Self>,
8178 ) {
8179 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8180 }
8181
8182 pub fn accept_edit_prediction(
8183 &mut self,
8184 _: &AcceptEditPrediction,
8185 window: &mut Window,
8186 cx: &mut Context<Self>,
8187 ) {
8188 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8189 }
8190
8191 fn discard_edit_prediction(
8192 &mut self,
8193 reason: EditPredictionDiscardReason,
8194 cx: &mut Context<Self>,
8195 ) -> bool {
8196 if reason == EditPredictionDiscardReason::Rejected {
8197 let completion_id = self
8198 .active_edit_prediction
8199 .as_ref()
8200 .and_then(|active_completion| active_completion.completion_id.clone());
8201
8202 self.report_edit_prediction_event(completion_id, false, cx);
8203 }
8204
8205 if let Some(provider) = self.edit_prediction_provider() {
8206 provider.discard(reason, cx);
8207 }
8208
8209 self.take_active_edit_prediction(cx)
8210 }
8211
8212 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8213 let Some(provider) = self.edit_prediction_provider() else {
8214 return;
8215 };
8216
8217 let Some((_, buffer, _)) = self
8218 .buffer
8219 .read(cx)
8220 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8221 else {
8222 return;
8223 };
8224
8225 let extension = buffer
8226 .read(cx)
8227 .file()
8228 .and_then(|file| Some(file.path().extension()?.to_string()));
8229
8230 let event_type = match accepted {
8231 true => "Edit Prediction Accepted",
8232 false => "Edit Prediction Discarded",
8233 };
8234 telemetry::event!(
8235 event_type,
8236 provider = provider.name(),
8237 prediction_id = id,
8238 suggestion_accepted = accepted,
8239 file_extension = extension,
8240 );
8241 }
8242
8243 fn open_editor_at_anchor(
8244 snapshot: &language::BufferSnapshot,
8245 target: language::Anchor,
8246 workspace: &Entity<Workspace>,
8247 window: &mut Window,
8248 cx: &mut App,
8249 ) -> Task<Result<()>> {
8250 workspace.update(cx, |workspace, cx| {
8251 let path = snapshot.file().map(|file| file.full_path(cx));
8252 let Some(path) =
8253 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8254 else {
8255 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8256 };
8257 let target = text::ToPoint::to_point(&target, snapshot);
8258 let item = workspace.open_path(path, None, true, window, cx);
8259 window.spawn(cx, async move |cx| {
8260 let Some(editor) = item.await?.downcast::<Editor>() else {
8261 return Ok(());
8262 };
8263 editor
8264 .update_in(cx, |editor, window, cx| {
8265 editor.go_to_singleton_buffer_point(target, window, cx);
8266 })
8267 .ok();
8268 anyhow::Ok(())
8269 })
8270 })
8271 }
8272
8273 pub fn has_active_edit_prediction(&self) -> bool {
8274 self.active_edit_prediction.is_some()
8275 }
8276
8277 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8278 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8279 return false;
8280 };
8281
8282 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8283 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8284 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8285 true
8286 }
8287
8288 /// Returns true when we're displaying the edit prediction popover below the cursor
8289 /// like we are not previewing and the LSP autocomplete menu is visible
8290 /// or we are in `when_holding_modifier` mode.
8291 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8292 if self.edit_prediction_preview_is_active()
8293 || !self.show_edit_predictions_in_menu()
8294 || !self.edit_predictions_enabled()
8295 {
8296 return false;
8297 }
8298
8299 if self.has_visible_completions_menu() {
8300 return true;
8301 }
8302
8303 has_completion && self.edit_prediction_requires_modifier()
8304 }
8305
8306 fn handle_modifiers_changed(
8307 &mut self,
8308 modifiers: Modifiers,
8309 position_map: &PositionMap,
8310 window: &mut Window,
8311 cx: &mut Context<Self>,
8312 ) {
8313 // Ensure that the edit prediction preview is updated, even when not
8314 // enabled, if there's an active edit prediction preview.
8315 if self.show_edit_predictions_in_menu()
8316 || matches!(
8317 self.edit_prediction_preview,
8318 EditPredictionPreview::Active { .. }
8319 )
8320 {
8321 self.update_edit_prediction_preview(&modifiers, window, cx);
8322 }
8323
8324 self.update_selection_mode(&modifiers, position_map, window, cx);
8325
8326 let mouse_position = window.mouse_position();
8327 if !position_map.text_hitbox.is_hovered(window) {
8328 return;
8329 }
8330
8331 self.update_hovered_link(
8332 position_map.point_for_position(mouse_position),
8333 &position_map.snapshot,
8334 modifiers,
8335 window,
8336 cx,
8337 )
8338 }
8339
8340 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8341 match EditorSettings::get_global(cx).multi_cursor_modifier {
8342 MultiCursorModifier::Alt => modifiers.secondary(),
8343 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8344 }
8345 }
8346
8347 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8348 match EditorSettings::get_global(cx).multi_cursor_modifier {
8349 MultiCursorModifier::Alt => modifiers.alt,
8350 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8351 }
8352 }
8353
8354 fn columnar_selection_mode(
8355 modifiers: &Modifiers,
8356 cx: &mut Context<Self>,
8357 ) -> Option<ColumnarMode> {
8358 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8359 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8360 Some(ColumnarMode::FromMouse)
8361 } else if Self::is_alt_pressed(modifiers, cx) {
8362 Some(ColumnarMode::FromSelection)
8363 } else {
8364 None
8365 }
8366 } else {
8367 None
8368 }
8369 }
8370
8371 fn update_selection_mode(
8372 &mut self,
8373 modifiers: &Modifiers,
8374 position_map: &PositionMap,
8375 window: &mut Window,
8376 cx: &mut Context<Self>,
8377 ) {
8378 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8379 return;
8380 };
8381 if self.selections.pending_anchor().is_none() {
8382 return;
8383 }
8384
8385 let mouse_position = window.mouse_position();
8386 let point_for_position = position_map.point_for_position(mouse_position);
8387 let position = point_for_position.previous_valid;
8388
8389 self.select(
8390 SelectPhase::BeginColumnar {
8391 position,
8392 reset: false,
8393 mode,
8394 goal_column: point_for_position.exact_unclipped.column(),
8395 },
8396 window,
8397 cx,
8398 );
8399 }
8400
8401 fn update_edit_prediction_preview(
8402 &mut self,
8403 modifiers: &Modifiers,
8404 window: &mut Window,
8405 cx: &mut Context<Self>,
8406 ) {
8407 let mut modifiers_held = false;
8408
8409 // Check bindings for all granularities.
8410 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8411 let granularities = [
8412 EditPredictionGranularity::Full,
8413 EditPredictionGranularity::Line,
8414 EditPredictionGranularity::Word,
8415 ];
8416
8417 for granularity in granularities {
8418 if let Some(keystroke) = self
8419 .accept_edit_prediction_keybind(granularity, window, cx)
8420 .keystroke()
8421 {
8422 modifiers_held = modifiers_held
8423 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8424 }
8425 }
8426
8427 if modifiers_held {
8428 if matches!(
8429 self.edit_prediction_preview,
8430 EditPredictionPreview::Inactive { .. }
8431 ) {
8432 self.edit_prediction_preview = EditPredictionPreview::Active {
8433 previous_scroll_position: None,
8434 since: Instant::now(),
8435 };
8436
8437 self.update_visible_edit_prediction(window, cx);
8438 cx.notify();
8439 }
8440 } else if let EditPredictionPreview::Active {
8441 previous_scroll_position,
8442 since,
8443 } = self.edit_prediction_preview
8444 {
8445 if let (Some(previous_scroll_position), Some(position_map)) =
8446 (previous_scroll_position, self.last_position_map.as_ref())
8447 {
8448 self.set_scroll_position(
8449 previous_scroll_position
8450 .scroll_position(&position_map.snapshot.display_snapshot),
8451 window,
8452 cx,
8453 );
8454 }
8455
8456 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8457 released_too_fast: since.elapsed() < Duration::from_millis(200),
8458 };
8459 self.clear_row_highlights::<EditPredictionPreview>();
8460 self.update_visible_edit_prediction(window, cx);
8461 cx.notify();
8462 }
8463 }
8464
8465 fn update_visible_edit_prediction(
8466 &mut self,
8467 _window: &mut Window,
8468 cx: &mut Context<Self>,
8469 ) -> Option<()> {
8470 if self.ime_transaction.is_some() {
8471 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8472 return None;
8473 }
8474
8475 let selection = self.selections.newest_anchor();
8476 let cursor = selection.head();
8477 let multibuffer = self.buffer.read(cx).snapshot(cx);
8478
8479 // Check project-level disable_ai setting for the current buffer
8480 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8481 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8482 return None;
8483 }
8484 }
8485 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8486 let excerpt_id = cursor.excerpt_id;
8487
8488 let show_in_menu = self.show_edit_predictions_in_menu();
8489 let completions_menu_has_precedence = !show_in_menu
8490 && (self.context_menu.borrow().is_some()
8491 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8492
8493 if completions_menu_has_precedence
8494 || !offset_selection.is_empty()
8495 || self
8496 .active_edit_prediction
8497 .as_ref()
8498 .is_some_and(|completion| {
8499 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8500 return false;
8501 };
8502 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8503 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8504 !invalidation_range.contains(&offset_selection.head())
8505 })
8506 {
8507 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8508 return None;
8509 }
8510
8511 self.take_active_edit_prediction(cx);
8512 let Some(provider) = self.edit_prediction_provider() else {
8513 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8514 return None;
8515 };
8516
8517 let (buffer, cursor_buffer_position) =
8518 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8519
8520 self.edit_prediction_settings =
8521 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8522
8523 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8524
8525 if self.edit_prediction_indent_conflict {
8526 let cursor_point = cursor.to_point(&multibuffer);
8527 let mut suggested_indent = None;
8528 multibuffer.suggested_indents_callback(
8529 cursor_point.row..cursor_point.row + 1,
8530 &mut |_, indent| {
8531 suggested_indent = Some(indent);
8532 ControlFlow::Break(())
8533 },
8534 cx,
8535 );
8536
8537 if let Some(indent) = suggested_indent
8538 && indent.len == cursor_point.column
8539 {
8540 self.edit_prediction_indent_conflict = false;
8541 }
8542 }
8543
8544 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8545
8546 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8547 {
8548 edit_prediction_types::EditPrediction::Local {
8549 id,
8550 edits,
8551 cursor_position,
8552 edit_preview,
8553 } => (id, edits, cursor_position, edit_preview),
8554 edit_prediction_types::EditPrediction::Jump {
8555 id,
8556 snapshot,
8557 target,
8558 } => {
8559 if let Some(provider) = &self.edit_prediction_provider {
8560 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8561 }
8562 self.stale_edit_prediction_in_menu = None;
8563 self.active_edit_prediction = Some(EditPredictionState {
8564 inlay_ids: vec![],
8565 completion: EditPrediction::MoveOutside { snapshot, target },
8566 completion_id: id,
8567 invalidation_range: None,
8568 });
8569 cx.notify();
8570 return Some(());
8571 }
8572 };
8573
8574 let edits = edits
8575 .into_iter()
8576 .flat_map(|(range, new_text)| {
8577 Some((
8578 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8579 new_text,
8580 ))
8581 })
8582 .collect::<Vec<_>>();
8583 if edits.is_empty() {
8584 return None;
8585 }
8586
8587 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8588 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8589 Some((anchor, predicted.offset))
8590 });
8591
8592 let first_edit_start = edits.first().unwrap().0.start;
8593 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8594 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8595
8596 let last_edit_end = edits.last().unwrap().0.end;
8597 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8598 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8599
8600 let cursor_row = cursor.to_point(&multibuffer).row;
8601
8602 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8603
8604 let mut inlay_ids = Vec::new();
8605 let invalidation_row_range;
8606 let move_invalidation_row_range = if cursor_row < edit_start_row {
8607 Some(cursor_row..edit_end_row)
8608 } else if cursor_row > edit_end_row {
8609 Some(edit_start_row..cursor_row)
8610 } else {
8611 None
8612 };
8613 let supports_jump = self
8614 .edit_prediction_provider
8615 .as_ref()
8616 .map(|provider| provider.provider.supports_jump_to_edit())
8617 .unwrap_or(true);
8618
8619 let is_move = supports_jump
8620 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8621 let completion = if is_move {
8622 if let Some(provider) = &self.edit_prediction_provider {
8623 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8624 }
8625 invalidation_row_range =
8626 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8627 let target = first_edit_start;
8628 EditPrediction::MoveWithin { target, snapshot }
8629 } else {
8630 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8631 && !self.edit_predictions_hidden_for_vim_mode;
8632
8633 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8634 if provider.show_tab_accept_marker() {
8635 EditDisplayMode::TabAccept
8636 } else {
8637 EditDisplayMode::Inline
8638 }
8639 } else {
8640 EditDisplayMode::DiffPopover
8641 };
8642
8643 if show_completions_in_buffer {
8644 if let Some(provider) = &self.edit_prediction_provider {
8645 let suggestion_display_type = match display_mode {
8646 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8647 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8648 SuggestionDisplayType::GhostText
8649 }
8650 };
8651 provider.provider.did_show(suggestion_display_type, cx);
8652 }
8653 if edits
8654 .iter()
8655 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8656 {
8657 let mut inlays = Vec::new();
8658 for (range, new_text) in &edits {
8659 let inlay = Inlay::edit_prediction(
8660 post_inc(&mut self.next_inlay_id),
8661 range.start,
8662 new_text.as_ref(),
8663 );
8664 inlay_ids.push(inlay.id);
8665 inlays.push(inlay);
8666 }
8667
8668 self.splice_inlays(&[], inlays, cx);
8669 } else {
8670 let background_color = cx.theme().status().deleted_background;
8671 self.highlight_text(
8672 HighlightKey::EditPredictionHighlight,
8673 edits.iter().map(|(range, _)| range.clone()).collect(),
8674 HighlightStyle {
8675 background_color: Some(background_color),
8676 ..Default::default()
8677 },
8678 cx,
8679 );
8680 }
8681 }
8682
8683 invalidation_row_range = edit_start_row..edit_end_row;
8684
8685 EditPrediction::Edit {
8686 edits,
8687 cursor_position,
8688 edit_preview,
8689 display_mode,
8690 snapshot,
8691 }
8692 };
8693
8694 let invalidation_range = multibuffer
8695 .anchor_before(Point::new(invalidation_row_range.start, 0))
8696 ..multibuffer.anchor_after(Point::new(
8697 invalidation_row_range.end,
8698 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8699 ));
8700
8701 self.stale_edit_prediction_in_menu = None;
8702 self.active_edit_prediction = Some(EditPredictionState {
8703 inlay_ids,
8704 completion,
8705 completion_id,
8706 invalidation_range: Some(invalidation_range),
8707 });
8708
8709 cx.notify();
8710
8711 Some(())
8712 }
8713
8714 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8715 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8716 }
8717
8718 fn clear_tasks(&mut self) {
8719 self.tasks.clear()
8720 }
8721
8722 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8723 if self.tasks.insert(key, value).is_some() {
8724 // This case should hopefully be rare, but just in case...
8725 log::error!(
8726 "multiple different run targets found on a single line, only the last target will be rendered"
8727 )
8728 }
8729 }
8730
8731 /// Get all display points of breakpoints that will be rendered within editor
8732 ///
8733 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8734 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8735 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8736 fn active_breakpoints(
8737 &self,
8738 range: Range<DisplayRow>,
8739 window: &mut Window,
8740 cx: &mut Context<Self>,
8741 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8742 let mut breakpoint_display_points = HashMap::default();
8743
8744 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8745 return breakpoint_display_points;
8746 };
8747
8748 let snapshot = self.snapshot(window, cx);
8749
8750 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8751 let Some(project) = self.project() else {
8752 return breakpoint_display_points;
8753 };
8754
8755 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8756 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8757
8758 for (buffer_snapshot, range, excerpt_id) in
8759 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8760 {
8761 let Some(buffer) = project
8762 .read(cx)
8763 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8764 else {
8765 continue;
8766 };
8767 let breakpoints = breakpoint_store.read(cx).breakpoints(
8768 &buffer,
8769 Some(
8770 buffer_snapshot.anchor_before(range.start)
8771 ..buffer_snapshot.anchor_after(range.end),
8772 ),
8773 buffer_snapshot,
8774 cx,
8775 );
8776 for (breakpoint, state) in breakpoints {
8777 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8778 let position = multi_buffer_anchor
8779 .to_point(&multi_buffer_snapshot)
8780 .to_display_point(&snapshot);
8781
8782 breakpoint_display_points.insert(
8783 position.row(),
8784 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8785 );
8786 }
8787 }
8788
8789 breakpoint_display_points
8790 }
8791
8792 fn breakpoint_context_menu(
8793 &self,
8794 anchor: Anchor,
8795 window: &mut Window,
8796 cx: &mut Context<Self>,
8797 ) -> Entity<ui::ContextMenu> {
8798 let weak_editor = cx.weak_entity();
8799 let focus_handle = self.focus_handle(cx);
8800
8801 let row = self
8802 .buffer
8803 .read(cx)
8804 .snapshot(cx)
8805 .summary_for_anchor::<Point>(&anchor)
8806 .row;
8807
8808 let breakpoint = self
8809 .breakpoint_at_row(row, window, cx)
8810 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8811
8812 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8813 "Edit Log Breakpoint"
8814 } else {
8815 "Set Log Breakpoint"
8816 };
8817
8818 let condition_breakpoint_msg = if breakpoint
8819 .as_ref()
8820 .is_some_and(|bp| bp.1.condition.is_some())
8821 {
8822 "Edit Condition Breakpoint"
8823 } else {
8824 "Set Condition Breakpoint"
8825 };
8826
8827 let hit_condition_breakpoint_msg = if breakpoint
8828 .as_ref()
8829 .is_some_and(|bp| bp.1.hit_condition.is_some())
8830 {
8831 "Edit Hit Condition Breakpoint"
8832 } else {
8833 "Set Hit Condition Breakpoint"
8834 };
8835
8836 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8837 "Unset Breakpoint"
8838 } else {
8839 "Set Breakpoint"
8840 };
8841
8842 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8843
8844 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8845 BreakpointState::Enabled => Some("Disable"),
8846 BreakpointState::Disabled => Some("Enable"),
8847 });
8848
8849 let (anchor, breakpoint) =
8850 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8851
8852 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8853 menu.on_blur_subscription(Subscription::new(|| {}))
8854 .context(focus_handle)
8855 .when(run_to_cursor, |this| {
8856 let weak_editor = weak_editor.clone();
8857 this.entry("Run to cursor", None, move |window, cx| {
8858 weak_editor
8859 .update(cx, |editor, cx| {
8860 editor.change_selections(
8861 SelectionEffects::no_scroll(),
8862 window,
8863 cx,
8864 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8865 );
8866 })
8867 .ok();
8868
8869 window.dispatch_action(Box::new(RunToCursor), cx);
8870 })
8871 .separator()
8872 })
8873 .when_some(toggle_state_msg, |this, msg| {
8874 this.entry(msg, None, {
8875 let weak_editor = weak_editor.clone();
8876 let breakpoint = breakpoint.clone();
8877 move |_window, cx| {
8878 weak_editor
8879 .update(cx, |this, cx| {
8880 this.edit_breakpoint_at_anchor(
8881 anchor,
8882 breakpoint.as_ref().clone(),
8883 BreakpointEditAction::InvertState,
8884 cx,
8885 );
8886 })
8887 .log_err();
8888 }
8889 })
8890 })
8891 .entry(set_breakpoint_msg, None, {
8892 let weak_editor = weak_editor.clone();
8893 let breakpoint = breakpoint.clone();
8894 move |_window, cx| {
8895 weak_editor
8896 .update(cx, |this, cx| {
8897 this.edit_breakpoint_at_anchor(
8898 anchor,
8899 breakpoint.as_ref().clone(),
8900 BreakpointEditAction::Toggle,
8901 cx,
8902 );
8903 })
8904 .log_err();
8905 }
8906 })
8907 .entry(log_breakpoint_msg, None, {
8908 let breakpoint = breakpoint.clone();
8909 let weak_editor = weak_editor.clone();
8910 move |window, cx| {
8911 weak_editor
8912 .update(cx, |this, cx| {
8913 this.add_edit_breakpoint_block(
8914 anchor,
8915 breakpoint.as_ref(),
8916 BreakpointPromptEditAction::Log,
8917 window,
8918 cx,
8919 );
8920 })
8921 .log_err();
8922 }
8923 })
8924 .entry(condition_breakpoint_msg, None, {
8925 let breakpoint = breakpoint.clone();
8926 let weak_editor = weak_editor.clone();
8927 move |window, cx| {
8928 weak_editor
8929 .update(cx, |this, cx| {
8930 this.add_edit_breakpoint_block(
8931 anchor,
8932 breakpoint.as_ref(),
8933 BreakpointPromptEditAction::Condition,
8934 window,
8935 cx,
8936 );
8937 })
8938 .log_err();
8939 }
8940 })
8941 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8942 weak_editor
8943 .update(cx, |this, cx| {
8944 this.add_edit_breakpoint_block(
8945 anchor,
8946 breakpoint.as_ref(),
8947 BreakpointPromptEditAction::HitCondition,
8948 window,
8949 cx,
8950 );
8951 })
8952 .log_err();
8953 })
8954 })
8955 }
8956
8957 fn render_breakpoint(
8958 &self,
8959 position: Anchor,
8960 row: DisplayRow,
8961 breakpoint: &Breakpoint,
8962 state: Option<BreakpointSessionState>,
8963 cx: &mut Context<Self>,
8964 ) -> IconButton {
8965 let is_rejected = state.is_some_and(|s| !s.verified);
8966 // Is it a breakpoint that shows up when hovering over gutter?
8967 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8968 (false, false),
8969 |PhantomBreakpointIndicator {
8970 is_active,
8971 display_row,
8972 collides_with_existing_breakpoint,
8973 }| {
8974 (
8975 is_active && display_row == row,
8976 collides_with_existing_breakpoint,
8977 )
8978 },
8979 );
8980
8981 let (color, icon) = {
8982 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8983 (false, false) => ui::IconName::DebugBreakpoint,
8984 (true, false) => ui::IconName::DebugLogBreakpoint,
8985 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8986 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8987 };
8988
8989 let theme_colors = cx.theme().colors();
8990
8991 let color = if is_phantom {
8992 if collides_with_existing {
8993 Color::Custom(
8994 theme_colors
8995 .debugger_accent
8996 .blend(theme_colors.text.opacity(0.6)),
8997 )
8998 } else {
8999 Color::Hint
9000 }
9001 } else if is_rejected {
9002 Color::Disabled
9003 } else {
9004 Color::Debugger
9005 };
9006
9007 (color, icon)
9008 };
9009
9010 let breakpoint = Arc::from(breakpoint.clone());
9011
9012 let alt_as_text = gpui::Keystroke {
9013 modifiers: Modifiers::secondary_key(),
9014 ..Default::default()
9015 };
9016 let primary_action_text = if breakpoint.is_disabled() {
9017 "Enable breakpoint"
9018 } else if is_phantom && !collides_with_existing {
9019 "Set breakpoint"
9020 } else {
9021 "Unset breakpoint"
9022 };
9023 let focus_handle = self.focus_handle.clone();
9024
9025 let meta = if is_rejected {
9026 SharedString::from("No executable code is associated with this line.")
9027 } else if collides_with_existing && !breakpoint.is_disabled() {
9028 SharedString::from(format!(
9029 "{alt_as_text}-click to disable,\nright-click for more options."
9030 ))
9031 } else {
9032 SharedString::from("Right-click for more options.")
9033 };
9034 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9035 .icon_size(IconSize::XSmall)
9036 .size(ui::ButtonSize::None)
9037 .when(is_rejected, |this| {
9038 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9039 })
9040 .icon_color(color)
9041 .style(ButtonStyle::Transparent)
9042 .on_click(cx.listener({
9043 move |editor, event: &ClickEvent, window, cx| {
9044 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9045 BreakpointEditAction::InvertState
9046 } else {
9047 BreakpointEditAction::Toggle
9048 };
9049
9050 window.focus(&editor.focus_handle(cx), cx);
9051 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9052 editor.edit_breakpoint_at_anchor(
9053 position,
9054 breakpoint.as_ref().clone(),
9055 edit_action,
9056 cx,
9057 );
9058 }
9059 }))
9060 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9061 editor.set_breakpoint_context_menu(
9062 row,
9063 Some(position),
9064 event.position(),
9065 window,
9066 cx,
9067 );
9068 }))
9069 .tooltip(move |_window, cx| {
9070 Tooltip::with_meta_in(
9071 primary_action_text,
9072 Some(&ToggleBreakpoint),
9073 meta.clone(),
9074 &focus_handle,
9075 cx,
9076 )
9077 })
9078 }
9079
9080 fn build_tasks_context(
9081 project: &Entity<Project>,
9082 buffer: &Entity<Buffer>,
9083 buffer_row: u32,
9084 tasks: &Arc<RunnableTasks>,
9085 cx: &mut Context<Self>,
9086 ) -> Task<Option<task::TaskContext>> {
9087 let position = Point::new(buffer_row, tasks.column);
9088 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9089 let location = Location {
9090 buffer: buffer.clone(),
9091 range: range_start..range_start,
9092 };
9093 // Fill in the environmental variables from the tree-sitter captures
9094 let mut captured_task_variables = TaskVariables::default();
9095 for (capture_name, value) in tasks.extra_variables.clone() {
9096 captured_task_variables.insert(
9097 task::VariableName::Custom(capture_name.into()),
9098 value.clone(),
9099 );
9100 }
9101 project.update(cx, |project, cx| {
9102 project.task_store().update(cx, |task_store, cx| {
9103 task_store.task_context_for_location(captured_task_variables, location, cx)
9104 })
9105 })
9106 }
9107
9108 pub fn spawn_nearest_task(
9109 &mut self,
9110 action: &SpawnNearestTask,
9111 window: &mut Window,
9112 cx: &mut Context<Self>,
9113 ) {
9114 let Some((workspace, _)) = self.workspace.clone() else {
9115 return;
9116 };
9117 let Some(project) = self.project.clone() else {
9118 return;
9119 };
9120
9121 // Try to find a closest, enclosing node using tree-sitter that has a task
9122 let Some((buffer, buffer_row, tasks)) = self
9123 .find_enclosing_node_task(cx)
9124 // Or find the task that's closest in row-distance.
9125 .or_else(|| self.find_closest_task(cx))
9126 else {
9127 return;
9128 };
9129
9130 let reveal_strategy = action.reveal;
9131 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
9132 cx.spawn_in(window, async move |_, cx| {
9133 let context = task_context.await?;
9134 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
9135
9136 let resolved = &mut resolved_task.resolved;
9137 resolved.reveal = reveal_strategy;
9138
9139 workspace
9140 .update_in(cx, |workspace, window, cx| {
9141 workspace.schedule_resolved_task(
9142 task_source_kind,
9143 resolved_task,
9144 false,
9145 window,
9146 cx,
9147 );
9148 })
9149 .ok()
9150 })
9151 .detach();
9152 }
9153
9154 fn find_closest_task(
9155 &mut self,
9156 cx: &mut Context<Self>,
9157 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9158 let cursor_row = self
9159 .selections
9160 .newest_adjusted(&self.display_snapshot(cx))
9161 .head()
9162 .row;
9163
9164 let ((buffer_id, row), tasks) = self
9165 .tasks
9166 .iter()
9167 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
9168
9169 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
9170 let tasks = Arc::new(tasks.to_owned());
9171 Some((buffer, *row, tasks))
9172 }
9173
9174 fn find_enclosing_node_task(
9175 &mut self,
9176 cx: &mut Context<Self>,
9177 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9178 let snapshot = self.buffer.read(cx).snapshot(cx);
9179 let offset = self
9180 .selections
9181 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
9182 .head();
9183 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
9184 let offset = excerpt.map_offset_to_buffer(offset);
9185 let buffer_id = excerpt.buffer().remote_id();
9186
9187 let layer = excerpt.buffer().syntax_layer_at(offset)?;
9188 let mut cursor = layer.node().walk();
9189
9190 while cursor.goto_first_child_for_byte(offset.0).is_some() {
9191 if cursor.node().end_byte() == offset.0 {
9192 cursor.goto_next_sibling();
9193 }
9194 }
9195
9196 // Ascend to the smallest ancestor that contains the range and has a task.
9197 loop {
9198 let node = cursor.node();
9199 let node_range = node.byte_range();
9200 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9201
9202 // Check if this node contains our offset
9203 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9204 // If it contains offset, check for task
9205 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9206 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9207 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9208 }
9209 }
9210
9211 if !cursor.goto_parent() {
9212 break;
9213 }
9214 }
9215 None
9216 }
9217
9218 fn render_run_indicator(
9219 &self,
9220 _style: &EditorStyle,
9221 is_active: bool,
9222 row: DisplayRow,
9223 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9224 cx: &mut Context<Self>,
9225 ) -> IconButton {
9226 let color = Color::Muted;
9227 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9228
9229 IconButton::new(
9230 ("run_indicator", row.0 as usize),
9231 ui::IconName::PlayOutlined,
9232 )
9233 .shape(ui::IconButtonShape::Square)
9234 .icon_size(IconSize::XSmall)
9235 .icon_color(color)
9236 .toggle_state(is_active)
9237 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9238 let quick_launch = match e {
9239 ClickEvent::Keyboard(_) => true,
9240 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9241 };
9242
9243 window.focus(&editor.focus_handle(cx), cx);
9244 editor.toggle_code_actions(
9245 &ToggleCodeActions {
9246 deployed_from: Some(CodeActionSource::RunMenu(row)),
9247 quick_launch,
9248 },
9249 window,
9250 cx,
9251 );
9252 }))
9253 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9254 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9255 }))
9256 }
9257
9258 pub fn context_menu_visible(&self) -> bool {
9259 !self.edit_prediction_preview_is_active()
9260 && self
9261 .context_menu
9262 .borrow()
9263 .as_ref()
9264 .is_some_and(|menu| menu.visible())
9265 }
9266
9267 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9268 self.context_menu
9269 .borrow()
9270 .as_ref()
9271 .map(|menu| menu.origin())
9272 }
9273
9274 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9275 self.context_menu_options = Some(options);
9276 }
9277
9278 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9279 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9280
9281 fn render_edit_prediction_popover(
9282 &mut self,
9283 text_bounds: &Bounds<Pixels>,
9284 content_origin: gpui::Point<Pixels>,
9285 right_margin: Pixels,
9286 editor_snapshot: &EditorSnapshot,
9287 visible_row_range: Range<DisplayRow>,
9288 scroll_top: ScrollOffset,
9289 scroll_bottom: ScrollOffset,
9290 line_layouts: &[LineWithInvisibles],
9291 line_height: Pixels,
9292 scroll_position: gpui::Point<ScrollOffset>,
9293 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9294 newest_selection_head: Option<DisplayPoint>,
9295 editor_width: Pixels,
9296 style: &EditorStyle,
9297 window: &mut Window,
9298 cx: &mut App,
9299 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9300 if self.mode().is_minimap() {
9301 return None;
9302 }
9303 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9304
9305 if self.edit_prediction_visible_in_cursor_popover(true) {
9306 return None;
9307 }
9308
9309 match &active_edit_prediction.completion {
9310 EditPrediction::MoveWithin { target, .. } => {
9311 let target_display_point = target.to_display_point(editor_snapshot);
9312
9313 if self.edit_prediction_requires_modifier() {
9314 if !self.edit_prediction_preview_is_active() {
9315 return None;
9316 }
9317
9318 self.render_edit_prediction_modifier_jump_popover(
9319 text_bounds,
9320 content_origin,
9321 visible_row_range,
9322 line_layouts,
9323 line_height,
9324 scroll_pixel_position,
9325 newest_selection_head,
9326 target_display_point,
9327 window,
9328 cx,
9329 )
9330 } else {
9331 self.render_edit_prediction_eager_jump_popover(
9332 text_bounds,
9333 content_origin,
9334 editor_snapshot,
9335 visible_row_range,
9336 scroll_top,
9337 scroll_bottom,
9338 line_height,
9339 scroll_pixel_position,
9340 target_display_point,
9341 editor_width,
9342 window,
9343 cx,
9344 )
9345 }
9346 }
9347 EditPrediction::Edit {
9348 display_mode: EditDisplayMode::Inline,
9349 ..
9350 } => None,
9351 EditPrediction::Edit {
9352 display_mode: EditDisplayMode::TabAccept,
9353 edits,
9354 ..
9355 } => {
9356 let range = &edits.first()?.0;
9357 let target_display_point = range.end.to_display_point(editor_snapshot);
9358
9359 self.render_edit_prediction_end_of_line_popover(
9360 "Accept",
9361 editor_snapshot,
9362 visible_row_range,
9363 target_display_point,
9364 line_height,
9365 scroll_pixel_position,
9366 content_origin,
9367 editor_width,
9368 window,
9369 cx,
9370 )
9371 }
9372 EditPrediction::Edit {
9373 edits,
9374 edit_preview,
9375 display_mode: EditDisplayMode::DiffPopover,
9376 snapshot,
9377 ..
9378 } => self.render_edit_prediction_diff_popover(
9379 text_bounds,
9380 content_origin,
9381 right_margin,
9382 editor_snapshot,
9383 visible_row_range,
9384 line_layouts,
9385 line_height,
9386 scroll_position,
9387 scroll_pixel_position,
9388 newest_selection_head,
9389 editor_width,
9390 style,
9391 edits,
9392 edit_preview,
9393 snapshot,
9394 window,
9395 cx,
9396 ),
9397 EditPrediction::MoveOutside { snapshot, .. } => {
9398 let mut element = self
9399 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9400 .into_any();
9401
9402 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9403 let origin_x = text_bounds.size.width - size.width - px(30.);
9404 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9405 element.prepaint_at(origin, window, cx);
9406
9407 Some((element, origin))
9408 }
9409 }
9410 }
9411
9412 fn render_edit_prediction_modifier_jump_popover(
9413 &mut self,
9414 text_bounds: &Bounds<Pixels>,
9415 content_origin: gpui::Point<Pixels>,
9416 visible_row_range: Range<DisplayRow>,
9417 line_layouts: &[LineWithInvisibles],
9418 line_height: Pixels,
9419 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9420 newest_selection_head: Option<DisplayPoint>,
9421 target_display_point: DisplayPoint,
9422 window: &mut Window,
9423 cx: &mut App,
9424 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9425 let scrolled_content_origin =
9426 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9427
9428 const SCROLL_PADDING_Y: Pixels = px(12.);
9429
9430 if target_display_point.row() < visible_row_range.start {
9431 return self.render_edit_prediction_scroll_popover(
9432 |_| SCROLL_PADDING_Y,
9433 IconName::ArrowUp,
9434 visible_row_range,
9435 line_layouts,
9436 newest_selection_head,
9437 scrolled_content_origin,
9438 window,
9439 cx,
9440 );
9441 } else if target_display_point.row() >= visible_row_range.end {
9442 return self.render_edit_prediction_scroll_popover(
9443 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9444 IconName::ArrowDown,
9445 visible_row_range,
9446 line_layouts,
9447 newest_selection_head,
9448 scrolled_content_origin,
9449 window,
9450 cx,
9451 );
9452 }
9453
9454 const POLE_WIDTH: Pixels = px(2.);
9455
9456 let line_layout =
9457 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9458 let target_column = target_display_point.column() as usize;
9459
9460 let target_x = line_layout.x_for_index(target_column);
9461 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9462 - scroll_pixel_position.y;
9463
9464 let flag_on_right = target_x < text_bounds.size.width / 2.;
9465
9466 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9467 border_color.l += 0.001;
9468
9469 let mut element = v_flex()
9470 .items_end()
9471 .when(flag_on_right, |el| el.items_start())
9472 .child(if flag_on_right {
9473 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9474 .rounded_bl(px(0.))
9475 .rounded_tl(px(0.))
9476 .border_l_2()
9477 .border_color(border_color)
9478 } else {
9479 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9480 .rounded_br(px(0.))
9481 .rounded_tr(px(0.))
9482 .border_r_2()
9483 .border_color(border_color)
9484 })
9485 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9486 .into_any();
9487
9488 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9489
9490 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9491 - point(
9492 if flag_on_right {
9493 POLE_WIDTH
9494 } else {
9495 size.width - POLE_WIDTH
9496 },
9497 size.height - line_height,
9498 );
9499
9500 origin.x = origin.x.max(content_origin.x);
9501
9502 element.prepaint_at(origin, window, cx);
9503
9504 Some((element, origin))
9505 }
9506
9507 fn render_edit_prediction_scroll_popover(
9508 &mut self,
9509 to_y: impl Fn(Size<Pixels>) -> Pixels,
9510 scroll_icon: IconName,
9511 visible_row_range: Range<DisplayRow>,
9512 line_layouts: &[LineWithInvisibles],
9513 newest_selection_head: Option<DisplayPoint>,
9514 scrolled_content_origin: gpui::Point<Pixels>,
9515 window: &mut Window,
9516 cx: &mut App,
9517 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9518 let mut element = self
9519 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9520 .into_any();
9521
9522 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9523
9524 let cursor = newest_selection_head?;
9525 let cursor_row_layout =
9526 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9527 let cursor_column = cursor.column() as usize;
9528
9529 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9530
9531 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9532
9533 element.prepaint_at(origin, window, cx);
9534 Some((element, origin))
9535 }
9536
9537 fn render_edit_prediction_eager_jump_popover(
9538 &mut self,
9539 text_bounds: &Bounds<Pixels>,
9540 content_origin: gpui::Point<Pixels>,
9541 editor_snapshot: &EditorSnapshot,
9542 visible_row_range: Range<DisplayRow>,
9543 scroll_top: ScrollOffset,
9544 scroll_bottom: ScrollOffset,
9545 line_height: Pixels,
9546 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9547 target_display_point: DisplayPoint,
9548 editor_width: Pixels,
9549 window: &mut Window,
9550 cx: &mut App,
9551 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9552 if target_display_point.row().as_f64() < scroll_top {
9553 let mut element = self
9554 .render_edit_prediction_line_popover(
9555 "Jump to Edit",
9556 Some(IconName::ArrowUp),
9557 window,
9558 cx,
9559 )
9560 .into_any();
9561
9562 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9563 let offset = point(
9564 (text_bounds.size.width - size.width) / 2.,
9565 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9566 );
9567
9568 let origin = text_bounds.origin + offset;
9569 element.prepaint_at(origin, window, cx);
9570 Some((element, origin))
9571 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9572 let mut element = self
9573 .render_edit_prediction_line_popover(
9574 "Jump to Edit",
9575 Some(IconName::ArrowDown),
9576 window,
9577 cx,
9578 )
9579 .into_any();
9580
9581 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9582 let offset = point(
9583 (text_bounds.size.width - size.width) / 2.,
9584 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9585 );
9586
9587 let origin = text_bounds.origin + offset;
9588 element.prepaint_at(origin, window, cx);
9589 Some((element, origin))
9590 } else {
9591 self.render_edit_prediction_end_of_line_popover(
9592 "Jump to Edit",
9593 editor_snapshot,
9594 visible_row_range,
9595 target_display_point,
9596 line_height,
9597 scroll_pixel_position,
9598 content_origin,
9599 editor_width,
9600 window,
9601 cx,
9602 )
9603 }
9604 }
9605
9606 fn render_edit_prediction_end_of_line_popover(
9607 self: &mut Editor,
9608 label: &'static str,
9609 editor_snapshot: &EditorSnapshot,
9610 visible_row_range: Range<DisplayRow>,
9611 target_display_point: DisplayPoint,
9612 line_height: Pixels,
9613 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9614 content_origin: gpui::Point<Pixels>,
9615 editor_width: Pixels,
9616 window: &mut Window,
9617 cx: &mut App,
9618 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9619 let target_line_end = DisplayPoint::new(
9620 target_display_point.row(),
9621 editor_snapshot.line_len(target_display_point.row()),
9622 );
9623
9624 let mut element = self
9625 .render_edit_prediction_line_popover(label, None, window, cx)
9626 .into_any();
9627
9628 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9629
9630 let line_origin =
9631 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9632
9633 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9634 let mut origin = start_point
9635 + line_origin
9636 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9637 origin.x = origin.x.max(content_origin.x);
9638
9639 let max_x = content_origin.x + editor_width - size.width;
9640
9641 if origin.x > max_x {
9642 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9643
9644 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9645 origin.y += offset;
9646 IconName::ArrowUp
9647 } else {
9648 origin.y -= offset;
9649 IconName::ArrowDown
9650 };
9651
9652 element = self
9653 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9654 .into_any();
9655
9656 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9657
9658 origin.x = content_origin.x + editor_width - size.width - px(2.);
9659 }
9660
9661 element.prepaint_at(origin, window, cx);
9662 Some((element, origin))
9663 }
9664
9665 fn render_edit_prediction_diff_popover(
9666 self: &Editor,
9667 text_bounds: &Bounds<Pixels>,
9668 content_origin: gpui::Point<Pixels>,
9669 right_margin: Pixels,
9670 editor_snapshot: &EditorSnapshot,
9671 visible_row_range: Range<DisplayRow>,
9672 line_layouts: &[LineWithInvisibles],
9673 line_height: Pixels,
9674 scroll_position: gpui::Point<ScrollOffset>,
9675 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9676 newest_selection_head: Option<DisplayPoint>,
9677 editor_width: Pixels,
9678 style: &EditorStyle,
9679 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9680 edit_preview: &Option<language::EditPreview>,
9681 snapshot: &language::BufferSnapshot,
9682 window: &mut Window,
9683 cx: &mut App,
9684 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9685 let edit_start = edits
9686 .first()
9687 .unwrap()
9688 .0
9689 .start
9690 .to_display_point(editor_snapshot);
9691 let edit_end = edits
9692 .last()
9693 .unwrap()
9694 .0
9695 .end
9696 .to_display_point(editor_snapshot);
9697
9698 let is_visible = visible_row_range.contains(&edit_start.row())
9699 || visible_row_range.contains(&edit_end.row());
9700 if !is_visible {
9701 return None;
9702 }
9703
9704 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9705 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9706 } else {
9707 // Fallback for providers without edit_preview
9708 crate::edit_prediction_fallback_text(edits, cx)
9709 };
9710
9711 let styled_text = highlighted_edits.to_styled_text(&style.text);
9712 let line_count = highlighted_edits.text.lines().count();
9713
9714 const BORDER_WIDTH: Pixels = px(1.);
9715
9716 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9717 let has_keybind = keybind.is_some();
9718
9719 let mut element = h_flex()
9720 .items_start()
9721 .child(
9722 h_flex()
9723 .bg(cx.theme().colors().editor_background)
9724 .border(BORDER_WIDTH)
9725 .shadow_xs()
9726 .border_color(cx.theme().colors().border)
9727 .rounded_l_lg()
9728 .when(line_count > 1, |el| el.rounded_br_lg())
9729 .pr_1()
9730 .child(styled_text),
9731 )
9732 .child(
9733 h_flex()
9734 .h(line_height + BORDER_WIDTH * 2.)
9735 .px_1p5()
9736 .gap_1()
9737 // Workaround: For some reason, there's a gap if we don't do this
9738 .ml(-BORDER_WIDTH)
9739 .shadow(vec![gpui::BoxShadow {
9740 color: gpui::black().opacity(0.05),
9741 offset: point(px(1.), px(1.)),
9742 blur_radius: px(2.),
9743 spread_radius: px(0.),
9744 }])
9745 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9746 .border(BORDER_WIDTH)
9747 .border_color(cx.theme().colors().border)
9748 .rounded_r_lg()
9749 .id("edit_prediction_diff_popover_keybind")
9750 .when(!has_keybind, |el| {
9751 let status_colors = cx.theme().status();
9752
9753 el.bg(status_colors.error_background)
9754 .border_color(status_colors.error.opacity(0.6))
9755 .child(Icon::new(IconName::Info).color(Color::Error))
9756 .cursor_default()
9757 .hoverable_tooltip(move |_window, cx| {
9758 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9759 })
9760 })
9761 .children(keybind),
9762 )
9763 .into_any();
9764
9765 let longest_row =
9766 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9767 let longest_line_width = if visible_row_range.contains(&longest_row) {
9768 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9769 } else {
9770 layout_line(
9771 longest_row,
9772 editor_snapshot,
9773 style,
9774 editor_width,
9775 |_| false,
9776 window,
9777 cx,
9778 )
9779 .width
9780 };
9781
9782 let viewport_bounds =
9783 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9784 right: -right_margin,
9785 ..Default::default()
9786 });
9787
9788 let x_after_longest = Pixels::from(
9789 ScrollPixelOffset::from(
9790 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9791 ) - scroll_pixel_position.x,
9792 );
9793
9794 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9795
9796 // Fully visible if it can be displayed within the window (allow overlapping other
9797 // panes). However, this is only allowed if the popover starts within text_bounds.
9798 let can_position_to_the_right = x_after_longest < text_bounds.right()
9799 && x_after_longest + element_bounds.width < viewport_bounds.right();
9800
9801 let mut origin = if can_position_to_the_right {
9802 point(
9803 x_after_longest,
9804 text_bounds.origin.y
9805 + Pixels::from(
9806 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9807 - scroll_pixel_position.y,
9808 ),
9809 )
9810 } else {
9811 let cursor_row = newest_selection_head.map(|head| head.row());
9812 let above_edit = edit_start
9813 .row()
9814 .0
9815 .checked_sub(line_count as u32)
9816 .map(DisplayRow);
9817 let below_edit = Some(edit_end.row() + 1);
9818 let above_cursor =
9819 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9820 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9821
9822 // Place the edit popover adjacent to the edit if there is a location
9823 // available that is onscreen and does not obscure the cursor. Otherwise,
9824 // place it adjacent to the cursor.
9825 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9826 .into_iter()
9827 .flatten()
9828 .find(|&start_row| {
9829 let end_row = start_row + line_count as u32;
9830 visible_row_range.contains(&start_row)
9831 && visible_row_range.contains(&end_row)
9832 && cursor_row
9833 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9834 })?;
9835
9836 content_origin
9837 + point(
9838 Pixels::from(-scroll_pixel_position.x),
9839 Pixels::from(
9840 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9841 ),
9842 )
9843 };
9844
9845 origin.x -= BORDER_WIDTH;
9846
9847 window.defer_draw(element, origin, 1);
9848
9849 // Do not return an element, since it will already be drawn due to defer_draw.
9850 None
9851 }
9852
9853 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9854 px(30.)
9855 }
9856
9857 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9858 if self.read_only(cx) {
9859 cx.theme().players().read_only()
9860 } else {
9861 self.style.as_ref().unwrap().local_player
9862 }
9863 }
9864
9865 fn render_edit_prediction_accept_keybind(
9866 &self,
9867 window: &mut Window,
9868 cx: &mut App,
9869 ) -> Option<AnyElement> {
9870 let accept_binding =
9871 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9872 let accept_keystroke = accept_binding.keystroke()?;
9873
9874 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9875
9876 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9877 Color::Accent
9878 } else {
9879 Color::Muted
9880 };
9881
9882 h_flex()
9883 .px_0p5()
9884 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9885 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9886 .text_size(TextSize::XSmall.rems(cx))
9887 .child(h_flex().children(ui::render_modifiers(
9888 accept_keystroke.modifiers(),
9889 PlatformStyle::platform(),
9890 Some(modifiers_color),
9891 Some(IconSize::XSmall.rems().into()),
9892 true,
9893 )))
9894 .when(is_platform_style_mac, |parent| {
9895 parent.child(accept_keystroke.key().to_string())
9896 })
9897 .when(!is_platform_style_mac, |parent| {
9898 parent.child(
9899 Key::new(
9900 util::capitalize(accept_keystroke.key()),
9901 Some(Color::Default),
9902 )
9903 .size(Some(IconSize::XSmall.rems().into())),
9904 )
9905 })
9906 .into_any()
9907 .into()
9908 }
9909
9910 fn render_edit_prediction_line_popover(
9911 &self,
9912 label: impl Into<SharedString>,
9913 icon: Option<IconName>,
9914 window: &mut Window,
9915 cx: &mut App,
9916 ) -> Stateful<Div> {
9917 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9918
9919 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9920 let has_keybind = keybind.is_some();
9921 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9922
9923 h_flex()
9924 .id("ep-line-popover")
9925 .py_0p5()
9926 .pl_1()
9927 .pr(padding_right)
9928 .gap_1()
9929 .rounded_md()
9930 .border_1()
9931 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9932 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9933 .shadow_xs()
9934 .when(!has_keybind, |el| {
9935 let status_colors = cx.theme().status();
9936
9937 el.bg(status_colors.error_background)
9938 .border_color(status_colors.error.opacity(0.6))
9939 .pl_2()
9940 .child(Icon::new(icons.error).color(Color::Error))
9941 .cursor_default()
9942 .hoverable_tooltip(move |_window, cx| {
9943 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9944 })
9945 })
9946 .children(keybind)
9947 .child(
9948 Label::new(label)
9949 .size(LabelSize::Small)
9950 .when(!has_keybind, |el| {
9951 el.color(cx.theme().status().error.into()).strikethrough()
9952 }),
9953 )
9954 .when(!has_keybind, |el| {
9955 el.child(
9956 h_flex().ml_1().child(
9957 Icon::new(IconName::Info)
9958 .size(IconSize::Small)
9959 .color(cx.theme().status().error.into()),
9960 ),
9961 )
9962 })
9963 .when_some(icon, |element, icon| {
9964 element.child(
9965 div()
9966 .mt(px(1.5))
9967 .child(Icon::new(icon).size(IconSize::Small)),
9968 )
9969 })
9970 }
9971
9972 fn render_edit_prediction_jump_outside_popover(
9973 &self,
9974 snapshot: &BufferSnapshot,
9975 window: &mut Window,
9976 cx: &mut App,
9977 ) -> Stateful<Div> {
9978 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9979 let has_keybind = keybind.is_some();
9980 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9981
9982 let file_name = snapshot
9983 .file()
9984 .map(|file| SharedString::new(file.file_name(cx)))
9985 .unwrap_or(SharedString::new_static("untitled"));
9986
9987 h_flex()
9988 .id("ep-jump-outside-popover")
9989 .py_1()
9990 .px_2()
9991 .gap_1()
9992 .rounded_md()
9993 .border_1()
9994 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9995 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9996 .shadow_xs()
9997 .when(!has_keybind, |el| {
9998 let status_colors = cx.theme().status();
9999
10000 el.bg(status_colors.error_background)
10001 .border_color(status_colors.error.opacity(0.6))
10002 .pl_2()
10003 .child(Icon::new(icons.error).color(Color::Error))
10004 .cursor_default()
10005 .hoverable_tooltip(move |_window, cx| {
10006 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10007 })
10008 })
10009 .children(keybind)
10010 .child(
10011 Label::new(file_name)
10012 .size(LabelSize::Small)
10013 .buffer_font(cx)
10014 .when(!has_keybind, |el| {
10015 el.color(cx.theme().status().error.into()).strikethrough()
10016 }),
10017 )
10018 .when(!has_keybind, |el| {
10019 el.child(
10020 h_flex().ml_1().child(
10021 Icon::new(IconName::Info)
10022 .size(IconSize::Small)
10023 .color(cx.theme().status().error.into()),
10024 ),
10025 )
10026 })
10027 .child(
10028 div()
10029 .mt(px(1.5))
10030 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10031 )
10032 }
10033
10034 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10035 let accent_color = cx.theme().colors().text_accent;
10036 let editor_bg_color = cx.theme().colors().editor_background;
10037 editor_bg_color.blend(accent_color.opacity(0.1))
10038 }
10039
10040 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10041 let accent_color = cx.theme().colors().text_accent;
10042 let editor_bg_color = cx.theme().colors().editor_background;
10043 editor_bg_color.blend(accent_color.opacity(0.6))
10044 }
10045 fn get_prediction_provider_icons(
10046 provider: &Option<RegisteredEditPredictionDelegate>,
10047 cx: &App,
10048 ) -> edit_prediction_types::EditPredictionIconSet {
10049 match provider {
10050 Some(provider) => provider.provider.icons(cx),
10051 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10052 }
10053 }
10054
10055 fn render_edit_prediction_cursor_popover(
10056 &self,
10057 min_width: Pixels,
10058 max_width: Pixels,
10059 cursor_point: Point,
10060 style: &EditorStyle,
10061 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
10062 _window: &Window,
10063 cx: &mut Context<Editor>,
10064 ) -> Option<AnyElement> {
10065 let provider = self.edit_prediction_provider.as_ref()?;
10066 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10067
10068 let is_refreshing = provider.provider.is_refreshing(cx);
10069
10070 fn pending_completion_container(icon: IconName) -> Div {
10071 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10072 }
10073
10074 let completion = match &self.active_edit_prediction {
10075 Some(prediction) => {
10076 if !self.has_visible_completions_menu() {
10077 const RADIUS: Pixels = px(6.);
10078 const BORDER_WIDTH: Pixels = px(1.);
10079
10080 return Some(
10081 h_flex()
10082 .elevation_2(cx)
10083 .border(BORDER_WIDTH)
10084 .border_color(cx.theme().colors().border)
10085 .when(accept_keystroke.is_none(), |el| {
10086 el.border_color(cx.theme().status().error)
10087 })
10088 .rounded(RADIUS)
10089 .rounded_tl(px(0.))
10090 .overflow_hidden()
10091 .child(div().px_1p5().child(match &prediction.completion {
10092 EditPrediction::MoveWithin { target, snapshot } => {
10093 use text::ToPoint as _;
10094 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10095 {
10096 Icon::new(icons.down)
10097 } else {
10098 Icon::new(icons.up)
10099 }
10100 }
10101 EditPrediction::MoveOutside { .. } => {
10102 // TODO [zeta2] custom icon for external jump?
10103 Icon::new(icons.base)
10104 }
10105 EditPrediction::Edit { .. } => Icon::new(icons.base),
10106 }))
10107 .child(
10108 h_flex()
10109 .gap_1()
10110 .py_1()
10111 .px_2()
10112 .rounded_r(RADIUS - BORDER_WIDTH)
10113 .border_l_1()
10114 .border_color(cx.theme().colors().border)
10115 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10116 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10117 el.child(
10118 Label::new("Hold")
10119 .size(LabelSize::Small)
10120 .when(accept_keystroke.is_none(), |el| {
10121 el.strikethrough()
10122 })
10123 .line_height_style(LineHeightStyle::UiLabel),
10124 )
10125 })
10126 .id("edit_prediction_cursor_popover_keybind")
10127 .when(accept_keystroke.is_none(), |el| {
10128 let status_colors = cx.theme().status();
10129
10130 el.bg(status_colors.error_background)
10131 .border_color(status_colors.error.opacity(0.6))
10132 .child(Icon::new(IconName::Info).color(Color::Error))
10133 .cursor_default()
10134 .hoverable_tooltip(move |_window, cx| {
10135 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10136 .into()
10137 })
10138 })
10139 .when_some(
10140 accept_keystroke.as_ref(),
10141 |el, accept_keystroke| {
10142 el.child(h_flex().children(ui::render_modifiers(
10143 accept_keystroke.modifiers(),
10144 PlatformStyle::platform(),
10145 Some(Color::Default),
10146 Some(IconSize::XSmall.rems().into()),
10147 false,
10148 )))
10149 },
10150 ),
10151 )
10152 .into_any(),
10153 );
10154 }
10155
10156 self.render_edit_prediction_cursor_popover_preview(
10157 prediction,
10158 cursor_point,
10159 style,
10160 cx,
10161 )?
10162 }
10163
10164 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10165 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10166 stale_completion,
10167 cursor_point,
10168 style,
10169 cx,
10170 )?,
10171
10172 None => pending_completion_container(icons.base)
10173 .child(Label::new("...").size(LabelSize::Small)),
10174 },
10175
10176 None => pending_completion_container(icons.base)
10177 .child(Label::new("...").size(LabelSize::Small)),
10178 };
10179
10180 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10181 completion
10182 .with_animation(
10183 "loading-completion",
10184 Animation::new(Duration::from_secs(2))
10185 .repeat()
10186 .with_easing(pulsating_between(0.4, 0.8)),
10187 |label, delta| label.opacity(delta),
10188 )
10189 .into_any_element()
10190 } else {
10191 completion.into_any_element()
10192 };
10193
10194 let has_completion = self.active_edit_prediction.is_some();
10195
10196 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10197 Some(
10198 h_flex()
10199 .min_w(min_width)
10200 .max_w(max_width)
10201 .flex_1()
10202 .elevation_2(cx)
10203 .border_color(cx.theme().colors().border)
10204 .child(
10205 div()
10206 .flex_1()
10207 .py_1()
10208 .px_2()
10209 .overflow_hidden()
10210 .child(completion),
10211 )
10212 .when_some(accept_keystroke, |el, accept_keystroke| {
10213 if !accept_keystroke.modifiers().modified() {
10214 return el;
10215 }
10216
10217 el.child(
10218 h_flex()
10219 .h_full()
10220 .border_l_1()
10221 .rounded_r_lg()
10222 .border_color(cx.theme().colors().border)
10223 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10224 .gap_1()
10225 .py_1()
10226 .px_2()
10227 .child(
10228 h_flex()
10229 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10230 .when(is_platform_style_mac, |parent| parent.gap_1())
10231 .child(h_flex().children(ui::render_modifiers(
10232 accept_keystroke.modifiers(),
10233 PlatformStyle::platform(),
10234 Some(if !has_completion {
10235 Color::Muted
10236 } else {
10237 Color::Default
10238 }),
10239 None,
10240 false,
10241 ))),
10242 )
10243 .child(Label::new("Preview").into_any_element())
10244 .opacity(if has_completion { 1.0 } else { 0.4 }),
10245 )
10246 })
10247 .into_any(),
10248 )
10249 }
10250
10251 fn render_edit_prediction_cursor_popover_preview(
10252 &self,
10253 completion: &EditPredictionState,
10254 cursor_point: Point,
10255 style: &EditorStyle,
10256 cx: &mut Context<Editor>,
10257 ) -> Option<Div> {
10258 use text::ToPoint as _;
10259
10260 fn render_relative_row_jump(
10261 prefix: impl Into<String>,
10262 current_row: u32,
10263 target_row: u32,
10264 ) -> Div {
10265 let (row_diff, arrow) = if target_row < current_row {
10266 (current_row - target_row, IconName::ArrowUp)
10267 } else {
10268 (target_row - current_row, IconName::ArrowDown)
10269 };
10270
10271 h_flex()
10272 .child(
10273 Label::new(format!("{}{}", prefix.into(), row_diff))
10274 .color(Color::Muted)
10275 .size(LabelSize::Small),
10276 )
10277 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10278 }
10279
10280 let supports_jump = self
10281 .edit_prediction_provider
10282 .as_ref()
10283 .map(|provider| provider.provider.supports_jump_to_edit())
10284 .unwrap_or(true);
10285
10286 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10287
10288 match &completion.completion {
10289 EditPrediction::MoveWithin {
10290 target, snapshot, ..
10291 } => {
10292 if !supports_jump {
10293 return None;
10294 }
10295
10296 Some(
10297 h_flex()
10298 .px_2()
10299 .gap_2()
10300 .flex_1()
10301 .child(
10302 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10303 Icon::new(icons.down)
10304 } else {
10305 Icon::new(icons.up)
10306 },
10307 )
10308 .child(Label::new("Jump to Edit")),
10309 )
10310 }
10311 EditPrediction::MoveOutside { snapshot, .. } => {
10312 let file_name = snapshot
10313 .file()
10314 .map(|file| file.file_name(cx))
10315 .unwrap_or("untitled");
10316 Some(
10317 h_flex()
10318 .px_2()
10319 .gap_2()
10320 .flex_1()
10321 .child(Icon::new(icons.base))
10322 .child(Label::new(format!("Jump to {file_name}"))),
10323 )
10324 }
10325 EditPrediction::Edit {
10326 edits,
10327 edit_preview,
10328 snapshot,
10329 ..
10330 } => {
10331 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10332
10333 let (highlighted_edits, has_more_lines) =
10334 if let Some(edit_preview) = edit_preview.as_ref() {
10335 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10336 .first_line_preview()
10337 } else {
10338 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10339 };
10340
10341 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10342 .with_default_highlights(&style.text, highlighted_edits.highlights);
10343
10344 let preview = h_flex()
10345 .gap_1()
10346 .min_w_16()
10347 .child(styled_text)
10348 .when(has_more_lines, |parent| parent.child("…"));
10349
10350 let left = if supports_jump && first_edit_row != cursor_point.row {
10351 render_relative_row_jump("", cursor_point.row, first_edit_row)
10352 .into_any_element()
10353 } else {
10354 Icon::new(icons.base).into_any_element()
10355 };
10356
10357 Some(
10358 h_flex()
10359 .h_full()
10360 .flex_1()
10361 .gap_2()
10362 .pr_1()
10363 .overflow_x_hidden()
10364 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10365 .child(left)
10366 .child(preview),
10367 )
10368 }
10369 }
10370 }
10371
10372 pub fn render_context_menu(
10373 &mut self,
10374 max_height_in_lines: u32,
10375 window: &mut Window,
10376 cx: &mut Context<Editor>,
10377 ) -> Option<AnyElement> {
10378 let menu = self.context_menu.borrow();
10379 let menu = menu.as_ref()?;
10380 if !menu.visible() {
10381 return None;
10382 };
10383 self.style
10384 .as_ref()
10385 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10386 }
10387
10388 fn render_context_menu_aside(
10389 &mut self,
10390 max_size: Size<Pixels>,
10391 window: &mut Window,
10392 cx: &mut Context<Editor>,
10393 ) -> Option<AnyElement> {
10394 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10395 if menu.visible() {
10396 menu.render_aside(max_size, window, cx)
10397 } else {
10398 None
10399 }
10400 })
10401 }
10402
10403 fn hide_context_menu(
10404 &mut self,
10405 window: &mut Window,
10406 cx: &mut Context<Self>,
10407 ) -> Option<CodeContextMenu> {
10408 cx.notify();
10409 self.completion_tasks.clear();
10410 let context_menu = self.context_menu.borrow_mut().take();
10411 self.stale_edit_prediction_in_menu.take();
10412 self.update_visible_edit_prediction(window, cx);
10413 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10414 && let Some(completion_provider) = &self.completion_provider
10415 {
10416 completion_provider.selection_changed(None, window, cx);
10417 }
10418 context_menu
10419 }
10420
10421 fn show_snippet_choices(
10422 &mut self,
10423 choices: &Vec<String>,
10424 selection: Range<Anchor>,
10425 cx: &mut Context<Self>,
10426 ) {
10427 let Some((_, buffer, _)) = self
10428 .buffer()
10429 .read(cx)
10430 .excerpt_containing(selection.start, cx)
10431 else {
10432 return;
10433 };
10434 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10435 else {
10436 return;
10437 };
10438 if buffer != end_buffer {
10439 log::error!("expected anchor range to have matching buffer IDs");
10440 return;
10441 }
10442
10443 let id = post_inc(&mut self.next_completion_id);
10444 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10445 let mut context_menu = self.context_menu.borrow_mut();
10446 let old_menu = context_menu.take();
10447 *context_menu = Some(CodeContextMenu::Completions(
10448 CompletionsMenu::new_snippet_choices(
10449 id,
10450 true,
10451 choices,
10452 selection,
10453 buffer,
10454 old_menu.map(|menu| menu.primary_scroll_handle()),
10455 snippet_sort_order,
10456 ),
10457 ));
10458 }
10459
10460 pub fn insert_snippet(
10461 &mut self,
10462 insertion_ranges: &[Range<MultiBufferOffset>],
10463 snippet: Snippet,
10464 window: &mut Window,
10465 cx: &mut Context<Self>,
10466 ) -> Result<()> {
10467 struct Tabstop<T> {
10468 is_end_tabstop: bool,
10469 ranges: Vec<Range<T>>,
10470 choices: Option<Vec<String>>,
10471 }
10472
10473 let tabstops = self.buffer.update(cx, |buffer, cx| {
10474 let snippet_text: Arc<str> = snippet.text.clone().into();
10475 let edits = insertion_ranges
10476 .iter()
10477 .cloned()
10478 .map(|range| (range, snippet_text.clone()));
10479 let autoindent_mode = AutoindentMode::Block {
10480 original_indent_columns: Vec::new(),
10481 };
10482 buffer.edit(edits, Some(autoindent_mode), cx);
10483
10484 let snapshot = &*buffer.read(cx);
10485 let snippet = &snippet;
10486 snippet
10487 .tabstops
10488 .iter()
10489 .map(|tabstop| {
10490 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10491 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10492 });
10493 let mut tabstop_ranges = tabstop
10494 .ranges
10495 .iter()
10496 .flat_map(|tabstop_range| {
10497 let mut delta = 0_isize;
10498 insertion_ranges.iter().map(move |insertion_range| {
10499 let insertion_start = insertion_range.start + delta;
10500 delta += snippet.text.len() as isize
10501 - (insertion_range.end - insertion_range.start) as isize;
10502
10503 let start =
10504 (insertion_start + tabstop_range.start).min(snapshot.len());
10505 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10506 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10507 })
10508 })
10509 .collect::<Vec<_>>();
10510 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10511
10512 Tabstop {
10513 is_end_tabstop,
10514 ranges: tabstop_ranges,
10515 choices: tabstop.choices.clone(),
10516 }
10517 })
10518 .collect::<Vec<_>>()
10519 });
10520 if let Some(tabstop) = tabstops.first() {
10521 self.change_selections(Default::default(), window, cx, |s| {
10522 // Reverse order so that the first range is the newest created selection.
10523 // Completions will use it and autoscroll will prioritize it.
10524 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10525 });
10526
10527 if let Some(choices) = &tabstop.choices
10528 && let Some(selection) = tabstop.ranges.first()
10529 {
10530 self.show_snippet_choices(choices, selection.clone(), cx)
10531 }
10532
10533 // If we're already at the last tabstop and it's at the end of the snippet,
10534 // we're done, we don't need to keep the state around.
10535 if !tabstop.is_end_tabstop {
10536 let choices = tabstops
10537 .iter()
10538 .map(|tabstop| tabstop.choices.clone())
10539 .collect();
10540
10541 let ranges = tabstops
10542 .into_iter()
10543 .map(|tabstop| tabstop.ranges)
10544 .collect::<Vec<_>>();
10545
10546 self.snippet_stack.push(SnippetState {
10547 active_index: 0,
10548 ranges,
10549 choices,
10550 });
10551 }
10552
10553 // Check whether the just-entered snippet ends with an auto-closable bracket.
10554 if self.autoclose_regions.is_empty() {
10555 let snapshot = self.buffer.read(cx).snapshot(cx);
10556 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10557 let selection_head = selection.head();
10558 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10559 continue;
10560 };
10561
10562 let mut bracket_pair = None;
10563 let max_lookup_length = scope
10564 .brackets()
10565 .map(|(pair, _)| {
10566 pair.start
10567 .as_str()
10568 .chars()
10569 .count()
10570 .max(pair.end.as_str().chars().count())
10571 })
10572 .max();
10573 if let Some(max_lookup_length) = max_lookup_length {
10574 let next_text = snapshot
10575 .chars_at(selection_head)
10576 .take(max_lookup_length)
10577 .collect::<String>();
10578 let prev_text = snapshot
10579 .reversed_chars_at(selection_head)
10580 .take(max_lookup_length)
10581 .collect::<String>();
10582
10583 for (pair, enabled) in scope.brackets() {
10584 if enabled
10585 && pair.close
10586 && prev_text.starts_with(pair.start.as_str())
10587 && next_text.starts_with(pair.end.as_str())
10588 {
10589 bracket_pair = Some(pair.clone());
10590 break;
10591 }
10592 }
10593 }
10594
10595 if let Some(pair) = bracket_pair {
10596 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10597 let autoclose_enabled =
10598 self.use_autoclose && snapshot_settings.use_autoclose;
10599 if autoclose_enabled {
10600 let start = snapshot.anchor_after(selection_head);
10601 let end = snapshot.anchor_after(selection_head);
10602 self.autoclose_regions.push(AutocloseRegion {
10603 selection_id: selection.id,
10604 range: start..end,
10605 pair,
10606 });
10607 }
10608 }
10609 }
10610 }
10611 }
10612 Ok(())
10613 }
10614
10615 pub fn move_to_next_snippet_tabstop(
10616 &mut self,
10617 window: &mut Window,
10618 cx: &mut Context<Self>,
10619 ) -> bool {
10620 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10621 }
10622
10623 pub fn move_to_prev_snippet_tabstop(
10624 &mut self,
10625 window: &mut Window,
10626 cx: &mut Context<Self>,
10627 ) -> bool {
10628 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10629 }
10630
10631 pub fn move_to_snippet_tabstop(
10632 &mut self,
10633 bias: Bias,
10634 window: &mut Window,
10635 cx: &mut Context<Self>,
10636 ) -> bool {
10637 if let Some(mut snippet) = self.snippet_stack.pop() {
10638 match bias {
10639 Bias::Left => {
10640 if snippet.active_index > 0 {
10641 snippet.active_index -= 1;
10642 } else {
10643 self.snippet_stack.push(snippet);
10644 return false;
10645 }
10646 }
10647 Bias::Right => {
10648 if snippet.active_index + 1 < snippet.ranges.len() {
10649 snippet.active_index += 1;
10650 } else {
10651 self.snippet_stack.push(snippet);
10652 return false;
10653 }
10654 }
10655 }
10656 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10657 self.change_selections(Default::default(), window, cx, |s| {
10658 // Reverse order so that the first range is the newest created selection.
10659 // Completions will use it and autoscroll will prioritize it.
10660 s.select_ranges(current_ranges.iter().rev().cloned())
10661 });
10662
10663 if let Some(choices) = &snippet.choices[snippet.active_index]
10664 && let Some(selection) = current_ranges.first()
10665 {
10666 self.show_snippet_choices(choices, selection.clone(), cx);
10667 }
10668
10669 // If snippet state is not at the last tabstop, push it back on the stack
10670 if snippet.active_index + 1 < snippet.ranges.len() {
10671 self.snippet_stack.push(snippet);
10672 }
10673 return true;
10674 }
10675 }
10676
10677 false
10678 }
10679
10680 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10681 self.transact(window, cx, |this, window, cx| {
10682 this.select_all(&SelectAll, window, cx);
10683 this.insert("", window, cx);
10684 });
10685 }
10686
10687 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10688 if self.read_only(cx) {
10689 return;
10690 }
10691 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10692 self.transact(window, cx, |this, window, cx| {
10693 this.select_autoclose_pair(window, cx);
10694
10695 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10696
10697 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10698 if !this.linked_edit_ranges.is_empty() {
10699 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10700 let snapshot = this.buffer.read(cx).snapshot(cx);
10701
10702 for selection in selections.iter() {
10703 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10704 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10705 if selection_start.buffer_id != selection_end.buffer_id {
10706 continue;
10707 }
10708 if let Some(ranges) =
10709 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10710 {
10711 for (buffer, entries) in ranges {
10712 linked_ranges.entry(buffer).or_default().extend(entries);
10713 }
10714 }
10715 }
10716 }
10717
10718 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10719 for selection in &mut selections {
10720 if selection.is_empty() {
10721 let old_head = selection.head();
10722 let mut new_head =
10723 movement::left(&display_map, old_head.to_display_point(&display_map))
10724 .to_point(&display_map);
10725 if let Some((buffer, line_buffer_range)) = display_map
10726 .buffer_snapshot()
10727 .buffer_line_for_row(MultiBufferRow(old_head.row))
10728 {
10729 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10730 let indent_len = match indent_size.kind {
10731 IndentKind::Space => {
10732 buffer.settings_at(line_buffer_range.start, cx).tab_size
10733 }
10734 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10735 };
10736 if old_head.column <= indent_size.len && old_head.column > 0 {
10737 let indent_len = indent_len.get();
10738 new_head = cmp::min(
10739 new_head,
10740 MultiBufferPoint::new(
10741 old_head.row,
10742 ((old_head.column - 1) / indent_len) * indent_len,
10743 ),
10744 );
10745 }
10746 }
10747
10748 selection.set_head(new_head, SelectionGoal::None);
10749 }
10750 }
10751
10752 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10753 this.insert("", window, cx);
10754 let empty_str: Arc<str> = Arc::from("");
10755 for (buffer, edits) in linked_ranges {
10756 let snapshot = buffer.read(cx).snapshot();
10757 use text::ToPoint as TP;
10758
10759 let edits = edits
10760 .into_iter()
10761 .map(|range| {
10762 let end_point = TP::to_point(&range.end, &snapshot);
10763 let mut start_point = TP::to_point(&range.start, &snapshot);
10764
10765 if end_point == start_point {
10766 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10767 .saturating_sub(1);
10768 start_point =
10769 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10770 };
10771
10772 (start_point..end_point, empty_str.clone())
10773 })
10774 .sorted_by_key(|(range, _)| range.start)
10775 .collect::<Vec<_>>();
10776 buffer.update(cx, |this, cx| {
10777 this.edit(edits, None, cx);
10778 })
10779 }
10780 this.refresh_edit_prediction(true, false, window, cx);
10781 refresh_linked_ranges(this, window, cx);
10782 });
10783 }
10784
10785 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10786 if self.read_only(cx) {
10787 return;
10788 }
10789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10790 self.transact(window, cx, |this, window, cx| {
10791 this.change_selections(Default::default(), window, cx, |s| {
10792 s.move_with(&mut |map, selection| {
10793 if selection.is_empty() {
10794 let cursor = movement::right(map, selection.head());
10795 selection.end = cursor;
10796 selection.reversed = true;
10797 selection.goal = SelectionGoal::None;
10798 }
10799 })
10800 });
10801 this.insert("", window, cx);
10802 this.refresh_edit_prediction(true, false, window, cx);
10803 });
10804 }
10805
10806 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10807 if self.mode.is_single_line() {
10808 cx.propagate();
10809 return;
10810 }
10811
10812 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10813 if self.move_to_prev_snippet_tabstop(window, cx) {
10814 return;
10815 }
10816 self.outdent(&Outdent, window, cx);
10817 }
10818
10819 pub fn next_snippet_tabstop(
10820 &mut self,
10821 _: &NextSnippetTabstop,
10822 window: &mut Window,
10823 cx: &mut Context<Self>,
10824 ) {
10825 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10826 cx.propagate();
10827 return;
10828 }
10829
10830 if self.move_to_next_snippet_tabstop(window, cx) {
10831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10832 return;
10833 }
10834 cx.propagate();
10835 }
10836
10837 pub fn previous_snippet_tabstop(
10838 &mut self,
10839 _: &PreviousSnippetTabstop,
10840 window: &mut Window,
10841 cx: &mut Context<Self>,
10842 ) {
10843 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10844 cx.propagate();
10845 return;
10846 }
10847
10848 if self.move_to_prev_snippet_tabstop(window, cx) {
10849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10850 return;
10851 }
10852 cx.propagate();
10853 }
10854
10855 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10856 if self.mode.is_single_line() {
10857 cx.propagate();
10858 return;
10859 }
10860
10861 if self.move_to_next_snippet_tabstop(window, cx) {
10862 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10863 return;
10864 }
10865 if self.read_only(cx) {
10866 return;
10867 }
10868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10869 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10870 let buffer = self.buffer.read(cx);
10871 let snapshot = buffer.snapshot(cx);
10872 let rows_iter = selections.iter().map(|s| s.head().row);
10873 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10874
10875 let has_some_cursor_in_whitespace = selections
10876 .iter()
10877 .filter(|selection| selection.is_empty())
10878 .any(|selection| {
10879 let cursor = selection.head();
10880 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10881 cursor.column < current_indent.len
10882 });
10883
10884 let mut edits = Vec::new();
10885 let mut prev_edited_row = 0;
10886 let mut row_delta = 0;
10887 for selection in &mut selections {
10888 if selection.start.row != prev_edited_row {
10889 row_delta = 0;
10890 }
10891 prev_edited_row = selection.end.row;
10892
10893 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10894 if selection.is_empty() {
10895 let cursor = selection.head();
10896 let settings = buffer.language_settings_at(cursor, cx);
10897 if settings.indent_list_on_tab {
10898 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10899 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10900 row_delta = Self::indent_selection(
10901 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10902 );
10903 continue;
10904 }
10905 }
10906 }
10907 }
10908
10909 // If the selection is non-empty, then increase the indentation of the selected lines.
10910 if !selection.is_empty() {
10911 row_delta =
10912 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10913 continue;
10914 }
10915
10916 let cursor = selection.head();
10917 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10918 if let Some(suggested_indent) =
10919 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10920 {
10921 // Don't do anything if already at suggested indent
10922 // and there is any other cursor which is not
10923 if has_some_cursor_in_whitespace
10924 && cursor.column == current_indent.len
10925 && current_indent.len == suggested_indent.len
10926 {
10927 continue;
10928 }
10929
10930 // Adjust line and move cursor to suggested indent
10931 // if cursor is not at suggested indent
10932 if cursor.column < suggested_indent.len
10933 && cursor.column <= current_indent.len
10934 && current_indent.len <= suggested_indent.len
10935 {
10936 selection.start = Point::new(cursor.row, suggested_indent.len);
10937 selection.end = selection.start;
10938 if row_delta == 0 {
10939 edits.extend(Buffer::edit_for_indent_size_adjustment(
10940 cursor.row,
10941 current_indent,
10942 suggested_indent,
10943 ));
10944 row_delta = suggested_indent.len - current_indent.len;
10945 }
10946 continue;
10947 }
10948
10949 // If current indent is more than suggested indent
10950 // only move cursor to current indent and skip indent
10951 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10952 selection.start = Point::new(cursor.row, current_indent.len);
10953 selection.end = selection.start;
10954 continue;
10955 }
10956 }
10957
10958 // Otherwise, insert a hard or soft tab.
10959 let settings = buffer.language_settings_at(cursor, cx);
10960 let tab_size = if settings.hard_tabs {
10961 IndentSize::tab()
10962 } else {
10963 let tab_size = settings.tab_size.get();
10964 let indent_remainder = snapshot
10965 .text_for_range(Point::new(cursor.row, 0)..cursor)
10966 .flat_map(str::chars)
10967 .fold(row_delta % tab_size, |counter: u32, c| {
10968 if c == '\t' {
10969 0
10970 } else {
10971 (counter + 1) % tab_size
10972 }
10973 });
10974
10975 let chars_to_next_tab_stop = tab_size - indent_remainder;
10976 IndentSize::spaces(chars_to_next_tab_stop)
10977 };
10978 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10979 selection.end = selection.start;
10980 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10981 row_delta += tab_size.len;
10982 }
10983
10984 self.transact(window, cx, |this, window, cx| {
10985 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10986 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10987 this.refresh_edit_prediction(true, false, window, cx);
10988 });
10989 }
10990
10991 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10992 if self.read_only(cx) {
10993 return;
10994 }
10995 if self.mode.is_single_line() {
10996 cx.propagate();
10997 return;
10998 }
10999
11000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11001 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11002 let mut prev_edited_row = 0;
11003 let mut row_delta = 0;
11004 let mut edits = Vec::new();
11005 let buffer = self.buffer.read(cx);
11006 let snapshot = buffer.snapshot(cx);
11007 for selection in &mut selections {
11008 if selection.start.row != prev_edited_row {
11009 row_delta = 0;
11010 }
11011 prev_edited_row = selection.end.row;
11012
11013 row_delta =
11014 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11015 }
11016
11017 self.transact(window, cx, |this, window, cx| {
11018 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11019 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11020 });
11021 }
11022
11023 fn indent_selection(
11024 buffer: &MultiBuffer,
11025 snapshot: &MultiBufferSnapshot,
11026 selection: &mut Selection<Point>,
11027 edits: &mut Vec<(Range<Point>, String)>,
11028 delta_for_start_row: u32,
11029 cx: &App,
11030 ) -> u32 {
11031 let settings = buffer.language_settings_at(selection.start, cx);
11032 let tab_size = settings.tab_size.get();
11033 let indent_kind = if settings.hard_tabs {
11034 IndentKind::Tab
11035 } else {
11036 IndentKind::Space
11037 };
11038 let mut start_row = selection.start.row;
11039 let mut end_row = selection.end.row + 1;
11040
11041 // If a selection ends at the beginning of a line, don't indent
11042 // that last line.
11043 if selection.end.column == 0 && selection.end.row > selection.start.row {
11044 end_row -= 1;
11045 }
11046
11047 // Avoid re-indenting a row that has already been indented by a
11048 // previous selection, but still update this selection's column
11049 // to reflect that indentation.
11050 if delta_for_start_row > 0 {
11051 start_row += 1;
11052 selection.start.column += delta_for_start_row;
11053 if selection.end.row == selection.start.row {
11054 selection.end.column += delta_for_start_row;
11055 }
11056 }
11057
11058 let mut delta_for_end_row = 0;
11059 let has_multiple_rows = start_row + 1 != end_row;
11060 for row in start_row..end_row {
11061 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11062 let indent_delta = match (current_indent.kind, indent_kind) {
11063 (IndentKind::Space, IndentKind::Space) => {
11064 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11065 IndentSize::spaces(columns_to_next_tab_stop)
11066 }
11067 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11068 (_, IndentKind::Tab) => IndentSize::tab(),
11069 };
11070
11071 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11072 0
11073 } else {
11074 selection.start.column
11075 };
11076 let row_start = Point::new(row, start);
11077 edits.push((
11078 row_start..row_start,
11079 indent_delta.chars().collect::<String>(),
11080 ));
11081
11082 // Update this selection's endpoints to reflect the indentation.
11083 if row == selection.start.row {
11084 selection.start.column += indent_delta.len;
11085 }
11086 if row == selection.end.row {
11087 selection.end.column += indent_delta.len;
11088 delta_for_end_row = indent_delta.len;
11089 }
11090 }
11091
11092 if selection.start.row == selection.end.row {
11093 delta_for_start_row + delta_for_end_row
11094 } else {
11095 delta_for_end_row
11096 }
11097 }
11098
11099 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11100 if self.read_only(cx) {
11101 return;
11102 }
11103 if self.mode.is_single_line() {
11104 cx.propagate();
11105 return;
11106 }
11107
11108 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11109 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11110 let selections = self.selections.all::<Point>(&display_map);
11111 let mut deletion_ranges = Vec::new();
11112 let mut last_outdent = None;
11113 {
11114 let buffer = self.buffer.read(cx);
11115 let snapshot = buffer.snapshot(cx);
11116 for selection in &selections {
11117 let settings = buffer.language_settings_at(selection.start, cx);
11118 let tab_size = settings.tab_size.get();
11119 let mut rows = selection.spanned_rows(false, &display_map);
11120
11121 // Avoid re-outdenting a row that has already been outdented by a
11122 // previous selection.
11123 if let Some(last_row) = last_outdent
11124 && last_row == rows.start
11125 {
11126 rows.start = rows.start.next_row();
11127 }
11128 let has_multiple_rows = rows.len() > 1;
11129 for row in rows.iter_rows() {
11130 let indent_size = snapshot.indent_size_for_line(row);
11131 if indent_size.len > 0 {
11132 let deletion_len = match indent_size.kind {
11133 IndentKind::Space => {
11134 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11135 if columns_to_prev_tab_stop == 0 {
11136 tab_size
11137 } else {
11138 columns_to_prev_tab_stop
11139 }
11140 }
11141 IndentKind::Tab => 1,
11142 };
11143 let start = if has_multiple_rows
11144 || deletion_len > selection.start.column
11145 || indent_size.len < selection.start.column
11146 {
11147 0
11148 } else {
11149 selection.start.column - deletion_len
11150 };
11151 deletion_ranges.push(
11152 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11153 );
11154 last_outdent = Some(row);
11155 }
11156 }
11157 }
11158 }
11159
11160 self.transact(window, cx, |this, window, cx| {
11161 this.buffer.update(cx, |buffer, cx| {
11162 let empty_str: Arc<str> = Arc::default();
11163 buffer.edit(
11164 deletion_ranges
11165 .into_iter()
11166 .map(|range| (range, empty_str.clone())),
11167 None,
11168 cx,
11169 );
11170 });
11171 let selections = this
11172 .selections
11173 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11174 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11175 });
11176 }
11177
11178 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11179 if self.read_only(cx) {
11180 return;
11181 }
11182 if self.mode.is_single_line() {
11183 cx.propagate();
11184 return;
11185 }
11186
11187 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11188 let selections = self
11189 .selections
11190 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11191 .into_iter()
11192 .map(|s| s.range());
11193
11194 self.transact(window, cx, |this, window, cx| {
11195 this.buffer.update(cx, |buffer, cx| {
11196 buffer.autoindent_ranges(selections, cx);
11197 });
11198 let selections = this
11199 .selections
11200 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11201 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11202 });
11203 }
11204
11205 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11207 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11208 let selections = self.selections.all::<Point>(&display_map);
11209
11210 let mut new_cursors = Vec::new();
11211 let mut edit_ranges = Vec::new();
11212 let mut selections = selections.iter().peekable();
11213 while let Some(selection) = selections.next() {
11214 let mut rows = selection.spanned_rows(false, &display_map);
11215
11216 // Accumulate contiguous regions of rows that we want to delete.
11217 while let Some(next_selection) = selections.peek() {
11218 let next_rows = next_selection.spanned_rows(false, &display_map);
11219 if next_rows.start <= rows.end {
11220 rows.end = next_rows.end;
11221 selections.next().unwrap();
11222 } else {
11223 break;
11224 }
11225 }
11226
11227 let buffer = display_map.buffer_snapshot();
11228 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11229 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11230 // If there's a line after the range, delete the \n from the end of the row range
11231 (
11232 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11233 rows.end,
11234 )
11235 } else {
11236 // If there isn't a line after the range, delete the \n from the line before the
11237 // start of the row range
11238 edit_start = edit_start.saturating_sub_usize(1);
11239 (buffer.len(), rows.start.previous_row())
11240 };
11241
11242 let text_layout_details = self.text_layout_details(window, cx);
11243 let x = display_map.x_for_display_point(
11244 selection.head().to_display_point(&display_map),
11245 &text_layout_details,
11246 );
11247 let row = Point::new(target_row.0, 0)
11248 .to_display_point(&display_map)
11249 .row();
11250 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11251
11252 new_cursors.push((
11253 selection.id,
11254 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11255 SelectionGoal::None,
11256 ));
11257 edit_ranges.push(edit_start..edit_end);
11258 }
11259
11260 self.transact(window, cx, |this, window, cx| {
11261 let buffer = this.buffer.update(cx, |buffer, cx| {
11262 let empty_str: Arc<str> = Arc::default();
11263 buffer.edit(
11264 edit_ranges
11265 .into_iter()
11266 .map(|range| (range, empty_str.clone())),
11267 None,
11268 cx,
11269 );
11270 buffer.snapshot(cx)
11271 });
11272 let new_selections = new_cursors
11273 .into_iter()
11274 .map(|(id, cursor, goal)| {
11275 let cursor = cursor.to_point(&buffer);
11276 Selection {
11277 id,
11278 start: cursor,
11279 end: cursor,
11280 reversed: false,
11281 goal,
11282 }
11283 })
11284 .collect();
11285
11286 this.change_selections(Default::default(), window, cx, |s| {
11287 s.select(new_selections);
11288 });
11289 });
11290 }
11291
11292 pub fn join_lines_impl(
11293 &mut self,
11294 insert_whitespace: bool,
11295 window: &mut Window,
11296 cx: &mut Context<Self>,
11297 ) {
11298 if self.read_only(cx) {
11299 return;
11300 }
11301 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11302 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11303 let start = MultiBufferRow(selection.start.row);
11304 // Treat single line selections as if they include the next line. Otherwise this action
11305 // would do nothing for single line selections individual cursors.
11306 let end = if selection.start.row == selection.end.row {
11307 MultiBufferRow(selection.start.row + 1)
11308 } else {
11309 MultiBufferRow(selection.end.row)
11310 };
11311
11312 if let Some(last_row_range) = row_ranges.last_mut()
11313 && start <= last_row_range.end
11314 {
11315 last_row_range.end = end;
11316 continue;
11317 }
11318 row_ranges.push(start..end);
11319 }
11320
11321 let snapshot = self.buffer.read(cx).snapshot(cx);
11322 let mut cursor_positions = Vec::new();
11323 for row_range in &row_ranges {
11324 let anchor = snapshot.anchor_before(Point::new(
11325 row_range.end.previous_row().0,
11326 snapshot.line_len(row_range.end.previous_row()),
11327 ));
11328 cursor_positions.push(anchor..anchor);
11329 }
11330
11331 self.transact(window, cx, |this, window, cx| {
11332 for row_range in row_ranges.into_iter().rev() {
11333 for row in row_range.iter_rows().rev() {
11334 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11335 let next_line_row = row.next_row();
11336 let indent = snapshot.indent_size_for_line(next_line_row);
11337 let mut join_start_column = indent.len;
11338
11339 if let Some(language_scope) =
11340 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11341 {
11342 let line_end =
11343 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11344 let line_text_after_indent = snapshot
11345 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11346 .collect::<String>();
11347
11348 if !line_text_after_indent.is_empty() {
11349 let block_prefix = language_scope
11350 .block_comment()
11351 .map(|c| c.prefix.as_ref())
11352 .filter(|p| !p.is_empty());
11353 let doc_prefix = language_scope
11354 .documentation_comment()
11355 .map(|c| c.prefix.as_ref())
11356 .filter(|p| !p.is_empty());
11357 let all_prefixes = language_scope
11358 .line_comment_prefixes()
11359 .iter()
11360 .map(|p| p.as_ref())
11361 .chain(block_prefix)
11362 .chain(doc_prefix)
11363 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11364
11365 let mut longest_prefix_len = None;
11366 for prefix in all_prefixes {
11367 let trimmed = prefix.trim_end();
11368 if line_text_after_indent.starts_with(trimmed) {
11369 let candidate_len =
11370 if line_text_after_indent.starts_with(prefix) {
11371 prefix.len()
11372 } else {
11373 trimmed.len()
11374 };
11375 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11376 longest_prefix_len = Some(candidate_len);
11377 }
11378 }
11379 }
11380
11381 if let Some(prefix_len) = longest_prefix_len {
11382 join_start_column =
11383 join_start_column.saturating_add(prefix_len as u32);
11384 }
11385 }
11386 }
11387
11388 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11389
11390 let replace = if snapshot.line_len(next_line_row) > join_start_column
11391 && insert_whitespace
11392 {
11393 " "
11394 } else {
11395 ""
11396 };
11397
11398 this.buffer.update(cx, |buffer, cx| {
11399 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11400 });
11401 }
11402 }
11403
11404 this.change_selections(Default::default(), window, cx, |s| {
11405 s.select_anchor_ranges(cursor_positions)
11406 });
11407 });
11408 }
11409
11410 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11412 self.join_lines_impl(true, window, cx);
11413 }
11414
11415 pub fn sort_lines_case_sensitive(
11416 &mut self,
11417 _: &SortLinesCaseSensitive,
11418 window: &mut Window,
11419 cx: &mut Context<Self>,
11420 ) {
11421 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11422 }
11423
11424 pub fn sort_lines_by_length(
11425 &mut self,
11426 _: &SortLinesByLength,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) {
11430 self.manipulate_immutable_lines(window, cx, |lines| {
11431 lines.sort_by_key(|&line| line.chars().count())
11432 })
11433 }
11434
11435 pub fn sort_lines_case_insensitive(
11436 &mut self,
11437 _: &SortLinesCaseInsensitive,
11438 window: &mut Window,
11439 cx: &mut Context<Self>,
11440 ) {
11441 self.manipulate_immutable_lines(window, cx, |lines| {
11442 lines.sort_by_key(|line| line.to_lowercase())
11443 })
11444 }
11445
11446 pub fn unique_lines_case_insensitive(
11447 &mut self,
11448 _: &UniqueLinesCaseInsensitive,
11449 window: &mut Window,
11450 cx: &mut Context<Self>,
11451 ) {
11452 self.manipulate_immutable_lines(window, cx, |lines| {
11453 let mut seen = HashSet::default();
11454 lines.retain(|line| seen.insert(line.to_lowercase()));
11455 })
11456 }
11457
11458 pub fn unique_lines_case_sensitive(
11459 &mut self,
11460 _: &UniqueLinesCaseSensitive,
11461 window: &mut Window,
11462 cx: &mut Context<Self>,
11463 ) {
11464 self.manipulate_immutable_lines(window, cx, |lines| {
11465 let mut seen = HashSet::default();
11466 lines.retain(|line| seen.insert(*line));
11467 })
11468 }
11469
11470 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11471 let snapshot = self.buffer.read(cx).snapshot(cx);
11472 for selection in self.selections.disjoint_anchors_arc().iter() {
11473 if snapshot
11474 .language_at(selection.start)
11475 .and_then(|lang| lang.config().wrap_characters.as_ref())
11476 .is_some()
11477 {
11478 return true;
11479 }
11480 }
11481 false
11482 }
11483
11484 fn wrap_selections_in_tag(
11485 &mut self,
11486 _: &WrapSelectionsInTag,
11487 window: &mut Window,
11488 cx: &mut Context<Self>,
11489 ) {
11490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11491
11492 let snapshot = self.buffer.read(cx).snapshot(cx);
11493
11494 let mut edits = Vec::new();
11495 let mut boundaries = Vec::new();
11496
11497 for selection in self
11498 .selections
11499 .all_adjusted(&self.display_snapshot(cx))
11500 .iter()
11501 {
11502 let Some(wrap_config) = snapshot
11503 .language_at(selection.start)
11504 .and_then(|lang| lang.config().wrap_characters.clone())
11505 else {
11506 continue;
11507 };
11508
11509 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11510 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11511
11512 let start_before = snapshot.anchor_before(selection.start);
11513 let end_after = snapshot.anchor_after(selection.end);
11514
11515 edits.push((start_before..start_before, open_tag));
11516 edits.push((end_after..end_after, close_tag));
11517
11518 boundaries.push((
11519 start_before,
11520 end_after,
11521 wrap_config.start_prefix.len(),
11522 wrap_config.end_suffix.len(),
11523 ));
11524 }
11525
11526 if edits.is_empty() {
11527 return;
11528 }
11529
11530 self.transact(window, cx, |this, window, cx| {
11531 let buffer = this.buffer.update(cx, |buffer, cx| {
11532 buffer.edit(edits, None, cx);
11533 buffer.snapshot(cx)
11534 });
11535
11536 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11537 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11538 boundaries.into_iter()
11539 {
11540 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11541 let close_offset = end_after
11542 .to_offset(&buffer)
11543 .saturating_sub_usize(end_suffix_len);
11544 new_selections.push(open_offset..open_offset);
11545 new_selections.push(close_offset..close_offset);
11546 }
11547
11548 this.change_selections(Default::default(), window, cx, |s| {
11549 s.select_ranges(new_selections);
11550 });
11551
11552 this.request_autoscroll(Autoscroll::fit(), cx);
11553 });
11554 }
11555
11556 pub fn toggle_read_only(
11557 &mut self,
11558 _: &workspace::ToggleReadOnlyFile,
11559 _: &mut Window,
11560 cx: &mut Context<Self>,
11561 ) {
11562 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11563 buffer.update(cx, |buffer, cx| {
11564 buffer.set_capability(
11565 match buffer.capability() {
11566 Capability::ReadWrite => Capability::Read,
11567 Capability::Read => Capability::ReadWrite,
11568 Capability::ReadOnly => Capability::ReadOnly,
11569 },
11570 cx,
11571 );
11572 })
11573 }
11574 }
11575
11576 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11577 let Some(project) = self.project.clone() else {
11578 return;
11579 };
11580 let task = self.reload(project, window, cx);
11581 self.detach_and_notify_err(task, window, cx);
11582 }
11583
11584 pub fn restore_file(
11585 &mut self,
11586 _: &::git::RestoreFile,
11587 window: &mut Window,
11588 cx: &mut Context<Self>,
11589 ) {
11590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11591 let mut buffer_ids = HashSet::default();
11592 let snapshot = self.buffer().read(cx).snapshot(cx);
11593 for selection in self
11594 .selections
11595 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11596 {
11597 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11598 }
11599
11600 let buffer = self.buffer().read(cx);
11601 let ranges = buffer_ids
11602 .into_iter()
11603 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11604 .collect::<Vec<_>>();
11605
11606 self.restore_hunks_in_ranges(ranges, window, cx);
11607 }
11608
11609 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11611 let selections = self
11612 .selections
11613 .all(&self.display_snapshot(cx))
11614 .into_iter()
11615 .map(|s| s.range())
11616 .collect();
11617 self.restore_hunks_in_ranges(selections, window, cx);
11618 }
11619
11620 pub fn restore_hunks_in_ranges(
11621 &mut self,
11622 ranges: Vec<Range<Point>>,
11623 window: &mut Window,
11624 cx: &mut Context<Editor>,
11625 ) {
11626 if self.delegate_stage_and_restore {
11627 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11628 if !hunks.is_empty() {
11629 cx.emit(EditorEvent::RestoreRequested { hunks });
11630 }
11631 return;
11632 }
11633 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11634 self.transact(window, cx, |editor, window, cx| {
11635 editor.restore_diff_hunks(hunks, cx);
11636 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11637 selections.refresh()
11638 });
11639 });
11640 }
11641
11642 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11643 let mut revert_changes = HashMap::default();
11644 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11645 for (buffer_id, hunks) in &chunk_by {
11646 let hunks = hunks.collect::<Vec<_>>();
11647 for hunk in &hunks {
11648 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11649 }
11650 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11651 }
11652 if !revert_changes.is_empty() {
11653 self.buffer().update(cx, |multi_buffer, cx| {
11654 for (buffer_id, changes) in revert_changes {
11655 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11656 buffer.update(cx, |buffer, cx| {
11657 buffer.edit(
11658 changes
11659 .into_iter()
11660 .map(|(range, text)| (range, text.to_string())),
11661 None,
11662 cx,
11663 );
11664 });
11665 }
11666 }
11667 });
11668 }
11669 }
11670
11671 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11672 if let Some(status) = self
11673 .addons
11674 .iter()
11675 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11676 {
11677 return Some(status);
11678 }
11679 self.project
11680 .as_ref()?
11681 .read(cx)
11682 .status_for_buffer_id(buffer_id, cx)
11683 }
11684
11685 pub fn open_active_item_in_terminal(
11686 &mut self,
11687 _: &OpenInTerminal,
11688 window: &mut Window,
11689 cx: &mut Context<Self>,
11690 ) {
11691 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11692 let project_path = buffer.read(cx).project_path(cx)?;
11693 let project = self.project()?.read(cx);
11694 let entry = project.entry_for_path(&project_path, cx)?;
11695 let parent = match &entry.canonical_path {
11696 Some(canonical_path) => canonical_path.to_path_buf(),
11697 None => project.absolute_path(&project_path, cx)?,
11698 }
11699 .parent()?
11700 .to_path_buf();
11701 Some(parent)
11702 }) {
11703 window.dispatch_action(
11704 OpenTerminal {
11705 working_directory,
11706 local: false,
11707 }
11708 .boxed_clone(),
11709 cx,
11710 );
11711 }
11712 }
11713
11714 fn set_breakpoint_context_menu(
11715 &mut self,
11716 display_row: DisplayRow,
11717 position: Option<Anchor>,
11718 clicked_point: gpui::Point<Pixels>,
11719 window: &mut Window,
11720 cx: &mut Context<Self>,
11721 ) {
11722 let source = self
11723 .buffer
11724 .read(cx)
11725 .snapshot(cx)
11726 .anchor_before(Point::new(display_row.0, 0u32));
11727
11728 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11729
11730 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11731 self,
11732 source,
11733 clicked_point,
11734 context_menu,
11735 window,
11736 cx,
11737 );
11738 }
11739
11740 fn add_edit_breakpoint_block(
11741 &mut self,
11742 anchor: Anchor,
11743 breakpoint: &Breakpoint,
11744 edit_action: BreakpointPromptEditAction,
11745 window: &mut Window,
11746 cx: &mut Context<Self>,
11747 ) {
11748 let weak_editor = cx.weak_entity();
11749 let bp_prompt = cx.new(|cx| {
11750 BreakpointPromptEditor::new(
11751 weak_editor,
11752 anchor,
11753 breakpoint.clone(),
11754 edit_action,
11755 window,
11756 cx,
11757 )
11758 });
11759
11760 let height = bp_prompt.update(cx, |this, cx| {
11761 this.prompt
11762 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11763 });
11764 let cloned_prompt = bp_prompt.clone();
11765 let blocks = vec![BlockProperties {
11766 style: BlockStyle::Sticky,
11767 placement: BlockPlacement::Above(anchor),
11768 height: Some(height),
11769 render: Arc::new(move |cx| {
11770 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11771 cloned_prompt.clone().into_any_element()
11772 }),
11773 priority: 0,
11774 }];
11775
11776 let focus_handle = bp_prompt.focus_handle(cx);
11777 window.focus(&focus_handle, cx);
11778
11779 let block_ids = self.insert_blocks(blocks, None, cx);
11780 bp_prompt.update(cx, |prompt, _| {
11781 prompt.add_block_ids(block_ids);
11782 });
11783 }
11784
11785 pub(crate) fn breakpoint_at_row(
11786 &self,
11787 row: u32,
11788 window: &mut Window,
11789 cx: &mut Context<Self>,
11790 ) -> Option<(Anchor, Breakpoint)> {
11791 let snapshot = self.snapshot(window, cx);
11792 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11793
11794 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11795 }
11796
11797 pub(crate) fn breakpoint_at_anchor(
11798 &self,
11799 breakpoint_position: Anchor,
11800 snapshot: &EditorSnapshot,
11801 cx: &mut Context<Self>,
11802 ) -> Option<(Anchor, Breakpoint)> {
11803 let buffer = self
11804 .buffer
11805 .read(cx)
11806 .buffer_for_anchor(breakpoint_position, cx)?;
11807
11808 let enclosing_excerpt = breakpoint_position.excerpt_id;
11809 let buffer_snapshot = buffer.read(cx).snapshot();
11810
11811 let row = buffer_snapshot
11812 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11813 .row;
11814
11815 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11816 let anchor_end = snapshot
11817 .buffer_snapshot()
11818 .anchor_after(Point::new(row, line_len));
11819
11820 self.breakpoint_store
11821 .as_ref()?
11822 .read_with(cx, |breakpoint_store, cx| {
11823 breakpoint_store
11824 .breakpoints(
11825 &buffer,
11826 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11827 &buffer_snapshot,
11828 cx,
11829 )
11830 .next()
11831 .and_then(|(bp, _)| {
11832 let breakpoint_row = buffer_snapshot
11833 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11834 .row;
11835
11836 if breakpoint_row == row {
11837 snapshot
11838 .buffer_snapshot()
11839 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11840 .map(|position| (position, bp.bp.clone()))
11841 } else {
11842 None
11843 }
11844 })
11845 })
11846 }
11847
11848 pub fn edit_log_breakpoint(
11849 &mut self,
11850 _: &EditLogBreakpoint,
11851 window: &mut Window,
11852 cx: &mut Context<Self>,
11853 ) {
11854 if self.breakpoint_store.is_none() {
11855 return;
11856 }
11857
11858 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11859 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11860 message: None,
11861 state: BreakpointState::Enabled,
11862 condition: None,
11863 hit_condition: None,
11864 });
11865
11866 self.add_edit_breakpoint_block(
11867 anchor,
11868 &breakpoint,
11869 BreakpointPromptEditAction::Log,
11870 window,
11871 cx,
11872 );
11873 }
11874 }
11875
11876 fn breakpoints_at_cursors(
11877 &self,
11878 window: &mut Window,
11879 cx: &mut Context<Self>,
11880 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11881 let snapshot = self.snapshot(window, cx);
11882 let cursors = self
11883 .selections
11884 .disjoint_anchors_arc()
11885 .iter()
11886 .map(|selection| {
11887 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11888
11889 let breakpoint_position = self
11890 .breakpoint_at_row(cursor_position.row, window, cx)
11891 .map(|bp| bp.0)
11892 .unwrap_or_else(|| {
11893 snapshot
11894 .display_snapshot
11895 .buffer_snapshot()
11896 .anchor_after(Point::new(cursor_position.row, 0))
11897 });
11898
11899 let breakpoint = self
11900 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11901 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11902
11903 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11904 })
11905 // 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.
11906 .collect::<HashMap<Anchor, _>>();
11907
11908 cursors.into_iter().collect()
11909 }
11910
11911 pub fn enable_breakpoint(
11912 &mut self,
11913 _: &crate::actions::EnableBreakpoint,
11914 window: &mut Window,
11915 cx: &mut Context<Self>,
11916 ) {
11917 if self.breakpoint_store.is_none() {
11918 return;
11919 }
11920
11921 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11922 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11923 continue;
11924 };
11925 self.edit_breakpoint_at_anchor(
11926 anchor,
11927 breakpoint,
11928 BreakpointEditAction::InvertState,
11929 cx,
11930 );
11931 }
11932 }
11933
11934 pub fn disable_breakpoint(
11935 &mut self,
11936 _: &crate::actions::DisableBreakpoint,
11937 window: &mut Window,
11938 cx: &mut Context<Self>,
11939 ) {
11940 if self.breakpoint_store.is_none() {
11941 return;
11942 }
11943
11944 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11945 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11946 continue;
11947 };
11948 self.edit_breakpoint_at_anchor(
11949 anchor,
11950 breakpoint,
11951 BreakpointEditAction::InvertState,
11952 cx,
11953 );
11954 }
11955 }
11956
11957 pub fn toggle_breakpoint(
11958 &mut self,
11959 _: &crate::actions::ToggleBreakpoint,
11960 window: &mut Window,
11961 cx: &mut Context<Self>,
11962 ) {
11963 if self.breakpoint_store.is_none() {
11964 return;
11965 }
11966
11967 let snapshot = self.snapshot(window, cx);
11968 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11969 if self.gutter_breakpoint_indicator.0.is_some() {
11970 let display_row = anchor
11971 .to_point(snapshot.buffer_snapshot())
11972 .to_display_point(&snapshot.display_snapshot)
11973 .row();
11974 self.update_breakpoint_collision_on_toggle(
11975 display_row,
11976 &BreakpointEditAction::Toggle,
11977 );
11978 }
11979
11980 if let Some(breakpoint) = breakpoint {
11981 self.edit_breakpoint_at_anchor(
11982 anchor,
11983 breakpoint,
11984 BreakpointEditAction::Toggle,
11985 cx,
11986 );
11987 } else {
11988 self.edit_breakpoint_at_anchor(
11989 anchor,
11990 Breakpoint::new_standard(),
11991 BreakpointEditAction::Toggle,
11992 cx,
11993 );
11994 }
11995 }
11996 }
11997
11998 fn update_breakpoint_collision_on_toggle(
11999 &mut self,
12000 display_row: DisplayRow,
12001 edit_action: &BreakpointEditAction,
12002 ) {
12003 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12004 if breakpoint_indicator.display_row == display_row
12005 && matches!(edit_action, BreakpointEditAction::Toggle)
12006 {
12007 breakpoint_indicator.collides_with_existing_breakpoint =
12008 !breakpoint_indicator.collides_with_existing_breakpoint;
12009 }
12010 }
12011 }
12012
12013 pub fn edit_breakpoint_at_anchor(
12014 &mut self,
12015 breakpoint_position: Anchor,
12016 breakpoint: Breakpoint,
12017 edit_action: BreakpointEditAction,
12018 cx: &mut Context<Self>,
12019 ) {
12020 let Some(breakpoint_store) = &self.breakpoint_store else {
12021 return;
12022 };
12023
12024 let Some(buffer) = self
12025 .buffer
12026 .read(cx)
12027 .buffer_for_anchor(breakpoint_position, cx)
12028 else {
12029 return;
12030 };
12031
12032 breakpoint_store.update(cx, |breakpoint_store, cx| {
12033 breakpoint_store.toggle_breakpoint(
12034 buffer,
12035 BreakpointWithPosition {
12036 position: breakpoint_position.text_anchor,
12037 bp: breakpoint,
12038 },
12039 edit_action,
12040 cx,
12041 );
12042 });
12043
12044 cx.notify();
12045 }
12046
12047 #[cfg(any(test, feature = "test-support"))]
12048 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12049 self.breakpoint_store.clone()
12050 }
12051
12052 pub fn prepare_restore_change(
12053 &self,
12054 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12055 hunk: &MultiBufferDiffHunk,
12056 cx: &mut App,
12057 ) -> Option<()> {
12058 if hunk.is_created_file() {
12059 return None;
12060 }
12061 let buffer = self.buffer.read(cx);
12062 let diff = buffer.diff_for(hunk.buffer_id)?;
12063 let buffer = buffer.buffer(hunk.buffer_id)?;
12064 let buffer = buffer.read(cx);
12065 let original_text = diff
12066 .read(cx)
12067 .base_text(cx)
12068 .as_rope()
12069 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12070 let buffer_snapshot = buffer.snapshot();
12071 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12072 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12073 probe
12074 .0
12075 .start
12076 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12077 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12078 }) {
12079 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12080 Some(())
12081 } else {
12082 None
12083 }
12084 }
12085
12086 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12087 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12088 }
12089
12090 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12091 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12092 }
12093
12094 pub fn rotate_selections_forward(
12095 &mut self,
12096 _: &RotateSelectionsForward,
12097 window: &mut Window,
12098 cx: &mut Context<Self>,
12099 ) {
12100 self.rotate_selections(window, cx, false)
12101 }
12102
12103 pub fn rotate_selections_backward(
12104 &mut self,
12105 _: &RotateSelectionsBackward,
12106 window: &mut Window,
12107 cx: &mut Context<Self>,
12108 ) {
12109 self.rotate_selections(window, cx, true)
12110 }
12111
12112 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12113 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12114 let display_snapshot = self.display_snapshot(cx);
12115 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12116
12117 if selections.len() < 2 {
12118 return;
12119 }
12120
12121 let (edits, new_selections) = {
12122 let buffer = self.buffer.read(cx).read(cx);
12123 let has_selections = selections.iter().any(|s| !s.is_empty());
12124 if has_selections {
12125 let mut selected_texts: Vec<String> = selections
12126 .iter()
12127 .map(|selection| {
12128 buffer
12129 .text_for_range(selection.start..selection.end)
12130 .collect()
12131 })
12132 .collect();
12133
12134 if reverse {
12135 selected_texts.rotate_left(1);
12136 } else {
12137 selected_texts.rotate_right(1);
12138 }
12139
12140 let mut offset_delta: i64 = 0;
12141 let mut new_selections = Vec::new();
12142 let edits: Vec<_> = selections
12143 .iter()
12144 .zip(selected_texts.iter())
12145 .map(|(selection, new_text)| {
12146 let old_len = (selection.end.0 - selection.start.0) as i64;
12147 let new_len = new_text.len() as i64;
12148 let adjusted_start =
12149 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12150 let adjusted_end =
12151 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12152
12153 new_selections.push(Selection {
12154 id: selection.id,
12155 start: adjusted_start,
12156 end: adjusted_end,
12157 reversed: selection.reversed,
12158 goal: selection.goal,
12159 });
12160
12161 offset_delta += new_len - old_len;
12162 (selection.start..selection.end, new_text.clone())
12163 })
12164 .collect();
12165 (edits, new_selections)
12166 } else {
12167 let mut all_rows: Vec<u32> = selections
12168 .iter()
12169 .map(|selection| buffer.offset_to_point(selection.start).row)
12170 .collect();
12171 all_rows.sort_unstable();
12172 all_rows.dedup();
12173
12174 if all_rows.len() < 2 {
12175 return;
12176 }
12177
12178 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12179 .iter()
12180 .map(|&row| {
12181 let start = Point::new(row, 0);
12182 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12183 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12184 })
12185 .collect();
12186
12187 let mut line_texts: Vec<String> = line_ranges
12188 .iter()
12189 .map(|range| buffer.text_for_range(range.clone()).collect())
12190 .collect();
12191
12192 if reverse {
12193 line_texts.rotate_left(1);
12194 } else {
12195 line_texts.rotate_right(1);
12196 }
12197
12198 let edits = line_ranges
12199 .iter()
12200 .zip(line_texts.iter())
12201 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12202 .collect();
12203
12204 let num_rows = all_rows.len();
12205 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12206 .iter()
12207 .enumerate()
12208 .map(|(i, &row)| (row, i))
12209 .collect();
12210
12211 // Compute new line start offsets after rotation (handles CRLF)
12212 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12213 let first_line_start = line_ranges[0].start.0;
12214 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12215 for text in line_texts.iter().take(num_rows - 1) {
12216 let prev_start = *new_line_starts.last().unwrap();
12217 new_line_starts.push(prev_start + text.len() + newline_len);
12218 }
12219
12220 let new_selections = selections
12221 .iter()
12222 .map(|selection| {
12223 let point = buffer.offset_to_point(selection.start);
12224 let old_index = row_to_index[&point.row];
12225 let new_index = if reverse {
12226 (old_index + num_rows - 1) % num_rows
12227 } else {
12228 (old_index + 1) % num_rows
12229 };
12230 let new_offset =
12231 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12232 Selection {
12233 id: selection.id,
12234 start: new_offset,
12235 end: new_offset,
12236 reversed: selection.reversed,
12237 goal: selection.goal,
12238 }
12239 })
12240 .collect();
12241
12242 (edits, new_selections)
12243 }
12244 };
12245
12246 self.transact(window, cx, |this, window, cx| {
12247 this.buffer.update(cx, |buffer, cx| {
12248 buffer.edit(edits, None, cx);
12249 });
12250 this.change_selections(Default::default(), window, cx, |s| {
12251 s.select(new_selections);
12252 });
12253 });
12254 }
12255
12256 fn manipulate_lines<M>(
12257 &mut self,
12258 window: &mut Window,
12259 cx: &mut Context<Self>,
12260 mut manipulate: M,
12261 ) where
12262 M: FnMut(&str) -> LineManipulationResult,
12263 {
12264 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12265
12266 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12267 let buffer = self.buffer.read(cx).snapshot(cx);
12268
12269 let mut edits = Vec::new();
12270
12271 let selections = self.selections.all::<Point>(&display_map);
12272 let mut selections = selections.iter().peekable();
12273 let mut contiguous_row_selections = Vec::new();
12274 let mut new_selections = Vec::new();
12275 let mut added_lines = 0;
12276 let mut removed_lines = 0;
12277
12278 while let Some(selection) = selections.next() {
12279 let (start_row, end_row) = consume_contiguous_rows(
12280 &mut contiguous_row_selections,
12281 selection,
12282 &display_map,
12283 &mut selections,
12284 );
12285
12286 let start_point = Point::new(start_row.0, 0);
12287 let end_point = Point::new(
12288 end_row.previous_row().0,
12289 buffer.line_len(end_row.previous_row()),
12290 );
12291 let text = buffer
12292 .text_for_range(start_point..end_point)
12293 .collect::<String>();
12294
12295 let LineManipulationResult {
12296 new_text,
12297 line_count_before,
12298 line_count_after,
12299 } = manipulate(&text);
12300
12301 edits.push((start_point..end_point, new_text));
12302
12303 // Selections must change based on added and removed line count
12304 let start_row =
12305 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12306 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12307 new_selections.push(Selection {
12308 id: selection.id,
12309 start: start_row,
12310 end: end_row,
12311 goal: SelectionGoal::None,
12312 reversed: selection.reversed,
12313 });
12314
12315 if line_count_after > line_count_before {
12316 added_lines += line_count_after - line_count_before;
12317 } else if line_count_before > line_count_after {
12318 removed_lines += line_count_before - line_count_after;
12319 }
12320 }
12321
12322 self.transact(window, cx, |this, window, cx| {
12323 let buffer = this.buffer.update(cx, |buffer, cx| {
12324 buffer.edit(edits, None, cx);
12325 buffer.snapshot(cx)
12326 });
12327
12328 // Recalculate offsets on newly edited buffer
12329 let new_selections = new_selections
12330 .iter()
12331 .map(|s| {
12332 let start_point = Point::new(s.start.0, 0);
12333 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12334 Selection {
12335 id: s.id,
12336 start: buffer.point_to_offset(start_point),
12337 end: buffer.point_to_offset(end_point),
12338 goal: s.goal,
12339 reversed: s.reversed,
12340 }
12341 })
12342 .collect();
12343
12344 this.change_selections(Default::default(), window, cx, |s| {
12345 s.select(new_selections);
12346 });
12347
12348 this.request_autoscroll(Autoscroll::fit(), cx);
12349 });
12350 }
12351
12352 fn manipulate_immutable_lines<Fn>(
12353 &mut self,
12354 window: &mut Window,
12355 cx: &mut Context<Self>,
12356 mut callback: Fn,
12357 ) where
12358 Fn: FnMut(&mut Vec<&str>),
12359 {
12360 self.manipulate_lines(window, cx, |text| {
12361 let mut lines: Vec<&str> = text.split('\n').collect();
12362 let line_count_before = lines.len();
12363
12364 callback(&mut lines);
12365
12366 LineManipulationResult {
12367 new_text: lines.join("\n"),
12368 line_count_before,
12369 line_count_after: lines.len(),
12370 }
12371 });
12372 }
12373
12374 fn manipulate_mutable_lines<Fn>(
12375 &mut self,
12376 window: &mut Window,
12377 cx: &mut Context<Self>,
12378 mut callback: Fn,
12379 ) where
12380 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12381 {
12382 self.manipulate_lines(window, cx, |text| {
12383 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12384 let line_count_before = lines.len();
12385
12386 callback(&mut lines);
12387
12388 LineManipulationResult {
12389 new_text: lines.join("\n"),
12390 line_count_before,
12391 line_count_after: lines.len(),
12392 }
12393 });
12394 }
12395
12396 pub fn convert_indentation_to_spaces(
12397 &mut self,
12398 _: &ConvertIndentationToSpaces,
12399 window: &mut Window,
12400 cx: &mut Context<Self>,
12401 ) {
12402 let settings = self.buffer.read(cx).language_settings(cx);
12403 let tab_size = settings.tab_size.get() as usize;
12404
12405 self.manipulate_mutable_lines(window, cx, |lines| {
12406 // Allocates a reasonably sized scratch buffer once for the whole loop
12407 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12408 // Avoids recomputing spaces that could be inserted many times
12409 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12410 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12411 .collect();
12412
12413 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12414 let mut chars = line.as_ref().chars();
12415 let mut col = 0;
12416 let mut changed = false;
12417
12418 for ch in chars.by_ref() {
12419 match ch {
12420 ' ' => {
12421 reindented_line.push(' ');
12422 col += 1;
12423 }
12424 '\t' => {
12425 // \t are converted to spaces depending on the current column
12426 let spaces_len = tab_size - (col % tab_size);
12427 reindented_line.extend(&space_cache[spaces_len - 1]);
12428 col += spaces_len;
12429 changed = true;
12430 }
12431 _ => {
12432 // If we dont append before break, the character is consumed
12433 reindented_line.push(ch);
12434 break;
12435 }
12436 }
12437 }
12438
12439 if !changed {
12440 reindented_line.clear();
12441 continue;
12442 }
12443 // Append the rest of the line and replace old reference with new one
12444 reindented_line.extend(chars);
12445 *line = Cow::Owned(reindented_line.clone());
12446 reindented_line.clear();
12447 }
12448 });
12449 }
12450
12451 pub fn convert_indentation_to_tabs(
12452 &mut self,
12453 _: &ConvertIndentationToTabs,
12454 window: &mut Window,
12455 cx: &mut Context<Self>,
12456 ) {
12457 let settings = self.buffer.read(cx).language_settings(cx);
12458 let tab_size = settings.tab_size.get() as usize;
12459
12460 self.manipulate_mutable_lines(window, cx, |lines| {
12461 // Allocates a reasonably sized buffer once for the whole loop
12462 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12463 // Avoids recomputing spaces that could be inserted many times
12464 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12465 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12466 .collect();
12467
12468 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12469 let mut chars = line.chars();
12470 let mut spaces_count = 0;
12471 let mut first_non_indent_char = None;
12472 let mut changed = false;
12473
12474 for ch in chars.by_ref() {
12475 match ch {
12476 ' ' => {
12477 // Keep track of spaces. Append \t when we reach tab_size
12478 spaces_count += 1;
12479 changed = true;
12480 if spaces_count == tab_size {
12481 reindented_line.push('\t');
12482 spaces_count = 0;
12483 }
12484 }
12485 '\t' => {
12486 reindented_line.push('\t');
12487 spaces_count = 0;
12488 }
12489 _ => {
12490 // Dont append it yet, we might have remaining spaces
12491 first_non_indent_char = Some(ch);
12492 break;
12493 }
12494 }
12495 }
12496
12497 if !changed {
12498 reindented_line.clear();
12499 continue;
12500 }
12501 // Remaining spaces that didn't make a full tab stop
12502 if spaces_count > 0 {
12503 reindented_line.extend(&space_cache[spaces_count - 1]);
12504 }
12505 // If we consume an extra character that was not indentation, add it back
12506 if let Some(extra_char) = first_non_indent_char {
12507 reindented_line.push(extra_char);
12508 }
12509 // Append the rest of the line and replace old reference with new one
12510 reindented_line.extend(chars);
12511 *line = Cow::Owned(reindented_line.clone());
12512 reindented_line.clear();
12513 }
12514 });
12515 }
12516
12517 pub fn convert_to_upper_case(
12518 &mut self,
12519 _: &ConvertToUpperCase,
12520 window: &mut Window,
12521 cx: &mut Context<Self>,
12522 ) {
12523 self.manipulate_text(window, cx, |text| text.to_uppercase())
12524 }
12525
12526 pub fn convert_to_lower_case(
12527 &mut self,
12528 _: &ConvertToLowerCase,
12529 window: &mut Window,
12530 cx: &mut Context<Self>,
12531 ) {
12532 self.manipulate_text(window, cx, |text| text.to_lowercase())
12533 }
12534
12535 pub fn convert_to_title_case(
12536 &mut self,
12537 _: &ConvertToTitleCase,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 self.manipulate_text(window, cx, |text| {
12542 text.split('\n')
12543 .map(|line| line.to_case(Case::Title))
12544 .join("\n")
12545 })
12546 }
12547
12548 pub fn convert_to_snake_case(
12549 &mut self,
12550 _: &ConvertToSnakeCase,
12551 window: &mut Window,
12552 cx: &mut Context<Self>,
12553 ) {
12554 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12555 }
12556
12557 pub fn convert_to_kebab_case(
12558 &mut self,
12559 _: &ConvertToKebabCase,
12560 window: &mut Window,
12561 cx: &mut Context<Self>,
12562 ) {
12563 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12564 }
12565
12566 pub fn convert_to_upper_camel_case(
12567 &mut self,
12568 _: &ConvertToUpperCamelCase,
12569 window: &mut Window,
12570 cx: &mut Context<Self>,
12571 ) {
12572 self.manipulate_text(window, cx, |text| {
12573 text.split('\n')
12574 .map(|line| line.to_case(Case::UpperCamel))
12575 .join("\n")
12576 })
12577 }
12578
12579 pub fn convert_to_lower_camel_case(
12580 &mut self,
12581 _: &ConvertToLowerCamelCase,
12582 window: &mut Window,
12583 cx: &mut Context<Self>,
12584 ) {
12585 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12586 }
12587
12588 pub fn convert_to_opposite_case(
12589 &mut self,
12590 _: &ConvertToOppositeCase,
12591 window: &mut Window,
12592 cx: &mut Context<Self>,
12593 ) {
12594 self.manipulate_text(window, cx, |text| {
12595 text.chars()
12596 .fold(String::with_capacity(text.len()), |mut t, c| {
12597 if c.is_uppercase() {
12598 t.extend(c.to_lowercase());
12599 } else {
12600 t.extend(c.to_uppercase());
12601 }
12602 t
12603 })
12604 })
12605 }
12606
12607 pub fn convert_to_sentence_case(
12608 &mut self,
12609 _: &ConvertToSentenceCase,
12610 window: &mut Window,
12611 cx: &mut Context<Self>,
12612 ) {
12613 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12614 }
12615
12616 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12617 self.manipulate_text(window, cx, |text| {
12618 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12619 if has_upper_case_characters {
12620 text.to_lowercase()
12621 } else {
12622 text.to_uppercase()
12623 }
12624 })
12625 }
12626
12627 pub fn convert_to_rot13(
12628 &mut self,
12629 _: &ConvertToRot13,
12630 window: &mut Window,
12631 cx: &mut Context<Self>,
12632 ) {
12633 self.manipulate_text(window, cx, |text| {
12634 text.chars()
12635 .map(|c| match c {
12636 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12637 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12638 _ => c,
12639 })
12640 .collect()
12641 })
12642 }
12643
12644 pub fn convert_to_rot47(
12645 &mut self,
12646 _: &ConvertToRot47,
12647 window: &mut Window,
12648 cx: &mut Context<Self>,
12649 ) {
12650 self.manipulate_text(window, cx, |text| {
12651 text.chars()
12652 .map(|c| {
12653 let code_point = c as u32;
12654 if code_point >= 33 && code_point <= 126 {
12655 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12656 }
12657 c
12658 })
12659 .collect()
12660 })
12661 }
12662
12663 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12664 where
12665 Fn: FnMut(&str) -> String,
12666 {
12667 let buffer = self.buffer.read(cx).snapshot(cx);
12668
12669 let mut new_selections = Vec::new();
12670 let mut edits = Vec::new();
12671 let mut selection_adjustment = 0isize;
12672
12673 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12674 let selection_is_empty = selection.is_empty();
12675
12676 let (start, end) = if selection_is_empty {
12677 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12678 (word_range.start, word_range.end)
12679 } else {
12680 (
12681 buffer.point_to_offset(selection.start),
12682 buffer.point_to_offset(selection.end),
12683 )
12684 };
12685
12686 let text = buffer.text_for_range(start..end).collect::<String>();
12687 let old_length = text.len() as isize;
12688 let text = callback(&text);
12689
12690 new_selections.push(Selection {
12691 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12692 end: MultiBufferOffset(
12693 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12694 ),
12695 goal: SelectionGoal::None,
12696 id: selection.id,
12697 reversed: selection.reversed,
12698 });
12699
12700 selection_adjustment += old_length - text.len() as isize;
12701
12702 edits.push((start..end, text));
12703 }
12704
12705 self.transact(window, cx, |this, window, cx| {
12706 this.buffer.update(cx, |buffer, cx| {
12707 buffer.edit(edits, None, cx);
12708 });
12709
12710 this.change_selections(Default::default(), window, cx, |s| {
12711 s.select(new_selections);
12712 });
12713
12714 this.request_autoscroll(Autoscroll::fit(), cx);
12715 });
12716 }
12717
12718 pub fn move_selection_on_drop(
12719 &mut self,
12720 selection: &Selection<Anchor>,
12721 target: DisplayPoint,
12722 is_cut: bool,
12723 window: &mut Window,
12724 cx: &mut Context<Self>,
12725 ) {
12726 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12727 let buffer = display_map.buffer_snapshot();
12728 let mut edits = Vec::new();
12729 let insert_point = display_map
12730 .clip_point(target, Bias::Left)
12731 .to_point(&display_map);
12732 let text = buffer
12733 .text_for_range(selection.start..selection.end)
12734 .collect::<String>();
12735 if is_cut {
12736 edits.push(((selection.start..selection.end), String::new()));
12737 }
12738 let insert_anchor = buffer.anchor_before(insert_point);
12739 edits.push(((insert_anchor..insert_anchor), text));
12740 let last_edit_start = insert_anchor.bias_left(buffer);
12741 let last_edit_end = insert_anchor.bias_right(buffer);
12742 self.transact(window, cx, |this, window, cx| {
12743 this.buffer.update(cx, |buffer, cx| {
12744 buffer.edit(edits, None, cx);
12745 });
12746 this.change_selections(Default::default(), window, cx, |s| {
12747 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12748 });
12749 });
12750 }
12751
12752 pub fn clear_selection_drag_state(&mut self) {
12753 self.selection_drag_state = SelectionDragState::None;
12754 }
12755
12756 pub fn duplicate(
12757 &mut self,
12758 upwards: bool,
12759 whole_lines: bool,
12760 window: &mut Window,
12761 cx: &mut Context<Self>,
12762 ) {
12763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12764
12765 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12766 let buffer = display_map.buffer_snapshot();
12767 let selections = self.selections.all::<Point>(&display_map);
12768
12769 let mut edits = Vec::new();
12770 let mut selections_iter = selections.iter().peekable();
12771 while let Some(selection) = selections_iter.next() {
12772 let mut rows = selection.spanned_rows(false, &display_map);
12773 // duplicate line-wise
12774 if whole_lines || selection.start == selection.end {
12775 // Avoid duplicating the same lines twice.
12776 while let Some(next_selection) = selections_iter.peek() {
12777 let next_rows = next_selection.spanned_rows(false, &display_map);
12778 if next_rows.start < rows.end {
12779 rows.end = next_rows.end;
12780 selections_iter.next().unwrap();
12781 } else {
12782 break;
12783 }
12784 }
12785
12786 // Copy the text from the selected row region and splice it either at the start
12787 // or end of the region.
12788 let start = Point::new(rows.start.0, 0);
12789 let end = Point::new(
12790 rows.end.previous_row().0,
12791 buffer.line_len(rows.end.previous_row()),
12792 );
12793
12794 let mut text = buffer.text_for_range(start..end).collect::<String>();
12795
12796 let insert_location = if upwards {
12797 // When duplicating upward, we need to insert before the current line.
12798 // If we're on the last line and it doesn't end with a newline,
12799 // we need to add a newline before the duplicated content.
12800 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12801 && buffer.max_point().column > 0
12802 && !text.ends_with('\n');
12803
12804 if needs_leading_newline {
12805 text.insert(0, '\n');
12806 end
12807 } else {
12808 text.push('\n');
12809 Point::new(rows.start.0, 0)
12810 }
12811 } else {
12812 text.push('\n');
12813 start
12814 };
12815 edits.push((insert_location..insert_location, text));
12816 } else {
12817 // duplicate character-wise
12818 let start = selection.start;
12819 let end = selection.end;
12820 let text = buffer.text_for_range(start..end).collect::<String>();
12821 edits.push((selection.end..selection.end, text));
12822 }
12823 }
12824
12825 self.transact(window, cx, |this, window, cx| {
12826 this.buffer.update(cx, |buffer, cx| {
12827 buffer.edit(edits, None, cx);
12828 });
12829
12830 // When duplicating upward with whole lines, move the cursor to the duplicated line
12831 if upwards && whole_lines {
12832 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12833
12834 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12835 let mut new_ranges = Vec::new();
12836 let selections = s.all::<Point>(&display_map);
12837 let mut selections_iter = selections.iter().peekable();
12838
12839 while let Some(first_selection) = selections_iter.next() {
12840 // Group contiguous selections together to find the total row span
12841 let mut group_selections = vec![first_selection];
12842 let mut rows = first_selection.spanned_rows(false, &display_map);
12843
12844 while let Some(next_selection) = selections_iter.peek() {
12845 let next_rows = next_selection.spanned_rows(false, &display_map);
12846 if next_rows.start < rows.end {
12847 rows.end = next_rows.end;
12848 group_selections.push(selections_iter.next().unwrap());
12849 } else {
12850 break;
12851 }
12852 }
12853
12854 let row_count = rows.end.0 - rows.start.0;
12855
12856 // Move all selections in this group up by the total number of duplicated rows
12857 for selection in group_selections {
12858 let new_start = Point::new(
12859 selection.start.row.saturating_sub(row_count),
12860 selection.start.column,
12861 );
12862
12863 let new_end = Point::new(
12864 selection.end.row.saturating_sub(row_count),
12865 selection.end.column,
12866 );
12867
12868 new_ranges.push(new_start..new_end);
12869 }
12870 }
12871
12872 s.select_ranges(new_ranges);
12873 });
12874 }
12875
12876 this.request_autoscroll(Autoscroll::fit(), cx);
12877 });
12878 }
12879
12880 pub fn duplicate_line_up(
12881 &mut self,
12882 _: &DuplicateLineUp,
12883 window: &mut Window,
12884 cx: &mut Context<Self>,
12885 ) {
12886 self.duplicate(true, true, window, cx);
12887 }
12888
12889 pub fn duplicate_line_down(
12890 &mut self,
12891 _: &DuplicateLineDown,
12892 window: &mut Window,
12893 cx: &mut Context<Self>,
12894 ) {
12895 self.duplicate(false, true, window, cx);
12896 }
12897
12898 pub fn duplicate_selection(
12899 &mut self,
12900 _: &DuplicateSelection,
12901 window: &mut Window,
12902 cx: &mut Context<Self>,
12903 ) {
12904 self.duplicate(false, false, window, cx);
12905 }
12906
12907 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12909 if self.mode.is_single_line() {
12910 cx.propagate();
12911 return;
12912 }
12913
12914 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12915 let buffer = self.buffer.read(cx).snapshot(cx);
12916
12917 let mut edits = Vec::new();
12918 let mut unfold_ranges = Vec::new();
12919 let mut refold_creases = Vec::new();
12920
12921 let selections = self.selections.all::<Point>(&display_map);
12922 let mut selections = selections.iter().peekable();
12923 let mut contiguous_row_selections = Vec::new();
12924 let mut new_selections = Vec::new();
12925
12926 while let Some(selection) = selections.next() {
12927 // Find all the selections that span a contiguous row range
12928 let (start_row, end_row) = consume_contiguous_rows(
12929 &mut contiguous_row_selections,
12930 selection,
12931 &display_map,
12932 &mut selections,
12933 );
12934
12935 // Move the text spanned by the row range to be before the line preceding the row range
12936 if start_row.0 > 0 {
12937 let range_to_move = Point::new(
12938 start_row.previous_row().0,
12939 buffer.line_len(start_row.previous_row()),
12940 )
12941 ..Point::new(
12942 end_row.previous_row().0,
12943 buffer.line_len(end_row.previous_row()),
12944 );
12945 let insertion_point = display_map
12946 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12947 .0;
12948
12949 // Don't move lines across excerpts
12950 if buffer
12951 .excerpt_containing(insertion_point..range_to_move.end)
12952 .is_some()
12953 {
12954 let text = buffer
12955 .text_for_range(range_to_move.clone())
12956 .flat_map(|s| s.chars())
12957 .skip(1)
12958 .chain(['\n'])
12959 .collect::<String>();
12960
12961 edits.push((
12962 buffer.anchor_after(range_to_move.start)
12963 ..buffer.anchor_before(range_to_move.end),
12964 String::new(),
12965 ));
12966 let insertion_anchor = buffer.anchor_after(insertion_point);
12967 edits.push((insertion_anchor..insertion_anchor, text));
12968
12969 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12970
12971 // Move selections up
12972 new_selections.extend(contiguous_row_selections.drain(..).map(
12973 |mut selection| {
12974 selection.start.row -= row_delta;
12975 selection.end.row -= row_delta;
12976 selection
12977 },
12978 ));
12979
12980 // Move folds up
12981 unfold_ranges.push(range_to_move.clone());
12982 for fold in display_map.folds_in_range(
12983 buffer.anchor_before(range_to_move.start)
12984 ..buffer.anchor_after(range_to_move.end),
12985 ) {
12986 let mut start = fold.range.start.to_point(&buffer);
12987 let mut end = fold.range.end.to_point(&buffer);
12988 start.row -= row_delta;
12989 end.row -= row_delta;
12990 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12991 }
12992 }
12993 }
12994
12995 // If we didn't move line(s), preserve the existing selections
12996 new_selections.append(&mut contiguous_row_selections);
12997 }
12998
12999 self.transact(window, cx, |this, window, cx| {
13000 this.unfold_ranges(&unfold_ranges, true, true, cx);
13001 this.buffer.update(cx, |buffer, cx| {
13002 for (range, text) in edits {
13003 buffer.edit([(range, text)], None, cx);
13004 }
13005 });
13006 this.fold_creases(refold_creases, true, window, cx);
13007 this.change_selections(Default::default(), window, cx, |s| {
13008 s.select(new_selections);
13009 })
13010 });
13011 }
13012
13013 pub fn move_line_down(
13014 &mut self,
13015 _: &MoveLineDown,
13016 window: &mut Window,
13017 cx: &mut Context<Self>,
13018 ) {
13019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13020 if self.mode.is_single_line() {
13021 cx.propagate();
13022 return;
13023 }
13024
13025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13026 let buffer = self.buffer.read(cx).snapshot(cx);
13027
13028 let mut edits = Vec::new();
13029 let mut unfold_ranges = Vec::new();
13030 let mut refold_creases = Vec::new();
13031
13032 let selections = self.selections.all::<Point>(&display_map);
13033 let mut selections = selections.iter().peekable();
13034 let mut contiguous_row_selections = Vec::new();
13035 let mut new_selections = Vec::new();
13036
13037 while let Some(selection) = selections.next() {
13038 // Find all the selections that span a contiguous row range
13039 let (start_row, end_row) = consume_contiguous_rows(
13040 &mut contiguous_row_selections,
13041 selection,
13042 &display_map,
13043 &mut selections,
13044 );
13045
13046 // Move the text spanned by the row range to be after the last line of the row range
13047 if end_row.0 <= buffer.max_point().row {
13048 let range_to_move =
13049 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13050 let insertion_point = display_map
13051 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13052 .0;
13053
13054 // Don't move lines across excerpt boundaries
13055 if buffer
13056 .excerpt_containing(range_to_move.start..insertion_point)
13057 .is_some()
13058 {
13059 let mut text = String::from("\n");
13060 text.extend(buffer.text_for_range(range_to_move.clone()));
13061 text.pop(); // Drop trailing newline
13062 edits.push((
13063 buffer.anchor_after(range_to_move.start)
13064 ..buffer.anchor_before(range_to_move.end),
13065 String::new(),
13066 ));
13067 let insertion_anchor = buffer.anchor_after(insertion_point);
13068 edits.push((insertion_anchor..insertion_anchor, text));
13069
13070 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13071
13072 // Move selections down
13073 new_selections.extend(contiguous_row_selections.drain(..).map(
13074 |mut selection| {
13075 selection.start.row += row_delta;
13076 selection.end.row += row_delta;
13077 selection
13078 },
13079 ));
13080
13081 // Move folds down
13082 unfold_ranges.push(range_to_move.clone());
13083 for fold in display_map.folds_in_range(
13084 buffer.anchor_before(range_to_move.start)
13085 ..buffer.anchor_after(range_to_move.end),
13086 ) {
13087 let mut start = fold.range.start.to_point(&buffer);
13088 let mut end = fold.range.end.to_point(&buffer);
13089 start.row += row_delta;
13090 end.row += row_delta;
13091 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13092 }
13093 }
13094 }
13095
13096 // If we didn't move line(s), preserve the existing selections
13097 new_selections.append(&mut contiguous_row_selections);
13098 }
13099
13100 self.transact(window, cx, |this, window, cx| {
13101 this.unfold_ranges(&unfold_ranges, true, true, cx);
13102 this.buffer.update(cx, |buffer, cx| {
13103 for (range, text) in edits {
13104 buffer.edit([(range, text)], None, cx);
13105 }
13106 });
13107 this.fold_creases(refold_creases, true, window, cx);
13108 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13109 });
13110 }
13111
13112 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13113 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13114 let text_layout_details = &self.text_layout_details(window, cx);
13115 self.transact(window, cx, |this, window, cx| {
13116 let edits = this.change_selections(Default::default(), window, cx, |s| {
13117 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13118 s.move_with(&mut |display_map, selection| {
13119 if !selection.is_empty() {
13120 return;
13121 }
13122
13123 let mut head = selection.head();
13124 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13125 if head.column() == display_map.line_len(head.row()) {
13126 transpose_offset = display_map
13127 .buffer_snapshot()
13128 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13129 }
13130
13131 if transpose_offset == MultiBufferOffset(0) {
13132 return;
13133 }
13134
13135 *head.column_mut() += 1;
13136 head = display_map.clip_point(head, Bias::Right);
13137 let goal = SelectionGoal::HorizontalPosition(
13138 display_map
13139 .x_for_display_point(head, text_layout_details)
13140 .into(),
13141 );
13142 selection.collapse_to(head, goal);
13143
13144 let transpose_start = display_map
13145 .buffer_snapshot()
13146 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13147 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13148 let transpose_end = display_map
13149 .buffer_snapshot()
13150 .clip_offset(transpose_offset + 1usize, Bias::Right);
13151 if let Some(ch) = display_map
13152 .buffer_snapshot()
13153 .chars_at(transpose_start)
13154 .next()
13155 {
13156 edits.push((transpose_start..transpose_offset, String::new()));
13157 edits.push((transpose_end..transpose_end, ch.to_string()));
13158 }
13159 }
13160 });
13161 edits
13162 });
13163 this.buffer
13164 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13165 let selections = this
13166 .selections
13167 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13168 this.change_selections(Default::default(), window, cx, |s| {
13169 s.select(selections);
13170 });
13171 });
13172 }
13173
13174 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13175 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13176 if self.mode.is_single_line() {
13177 cx.propagate();
13178 return;
13179 }
13180
13181 self.rewrap_impl(RewrapOptions::default(), cx)
13182 }
13183
13184 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13185 let buffer = self.buffer.read(cx).snapshot(cx);
13186 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13187
13188 #[derive(Clone, Debug, PartialEq)]
13189 enum CommentFormat {
13190 /// single line comment, with prefix for line
13191 Line(String),
13192 /// single line within a block comment, with prefix for line
13193 BlockLine(String),
13194 /// a single line of a block comment that includes the initial delimiter
13195 BlockCommentWithStart(BlockCommentConfig),
13196 /// a single line of a block comment that includes the ending delimiter
13197 BlockCommentWithEnd(BlockCommentConfig),
13198 }
13199
13200 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13201 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13202 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13203 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13204 .peekable();
13205
13206 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13207 row
13208 } else {
13209 return Vec::new();
13210 };
13211
13212 let language_settings = buffer.language_settings_at(selection.head(), cx);
13213 let language_scope = buffer.language_scope_at(selection.head());
13214
13215 let indent_and_prefix_for_row =
13216 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13217 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13218 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13219 &language_scope
13220 {
13221 let indent_end = Point::new(row, indent.len);
13222 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13223 let line_text_after_indent = buffer
13224 .text_for_range(indent_end..line_end)
13225 .collect::<String>();
13226
13227 let is_within_comment_override = buffer
13228 .language_scope_at(indent_end)
13229 .is_some_and(|scope| scope.override_name() == Some("comment"));
13230 let comment_delimiters = if is_within_comment_override {
13231 // we are within a comment syntax node, but we don't
13232 // yet know what kind of comment: block, doc or line
13233 match (
13234 language_scope.documentation_comment(),
13235 language_scope.block_comment(),
13236 ) {
13237 (Some(config), _) | (_, Some(config))
13238 if buffer.contains_str_at(indent_end, &config.start) =>
13239 {
13240 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13241 }
13242 (Some(config), _) | (_, Some(config))
13243 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13244 {
13245 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13246 }
13247 (Some(config), _) | (_, Some(config))
13248 if buffer.contains_str_at(indent_end, &config.prefix) =>
13249 {
13250 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13251 }
13252 (_, _) => language_scope
13253 .line_comment_prefixes()
13254 .iter()
13255 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13256 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13257 }
13258 } else {
13259 // we not in an overridden comment node, but we may
13260 // be within a non-overridden line comment node
13261 language_scope
13262 .line_comment_prefixes()
13263 .iter()
13264 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13265 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13266 };
13267
13268 let rewrap_prefix = language_scope
13269 .rewrap_prefixes()
13270 .iter()
13271 .find_map(|prefix_regex| {
13272 prefix_regex.find(&line_text_after_indent).map(|mat| {
13273 if mat.start() == 0 {
13274 Some(mat.as_str().to_string())
13275 } else {
13276 None
13277 }
13278 })
13279 })
13280 .flatten();
13281 (comment_delimiters, rewrap_prefix)
13282 } else {
13283 (None, None)
13284 };
13285 (indent, comment_prefix, rewrap_prefix)
13286 };
13287
13288 let mut ranges = Vec::new();
13289 let from_empty_selection = selection.is_empty();
13290
13291 let mut current_range_start = first_row;
13292 let mut prev_row = first_row;
13293 let (
13294 mut current_range_indent,
13295 mut current_range_comment_delimiters,
13296 mut current_range_rewrap_prefix,
13297 ) = indent_and_prefix_for_row(first_row);
13298
13299 for row in non_blank_rows_iter.skip(1) {
13300 let has_paragraph_break = row > prev_row + 1;
13301
13302 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13303 indent_and_prefix_for_row(row);
13304
13305 let has_indent_change = row_indent != current_range_indent;
13306 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13307
13308 let has_boundary_change = has_comment_change
13309 || row_rewrap_prefix.is_some()
13310 || (has_indent_change && current_range_comment_delimiters.is_some());
13311
13312 if has_paragraph_break || has_boundary_change {
13313 ranges.push((
13314 language_settings.clone(),
13315 Point::new(current_range_start, 0)
13316 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13317 current_range_indent,
13318 current_range_comment_delimiters.clone(),
13319 current_range_rewrap_prefix.clone(),
13320 from_empty_selection,
13321 ));
13322 current_range_start = row;
13323 current_range_indent = row_indent;
13324 current_range_comment_delimiters = row_comment_delimiters;
13325 current_range_rewrap_prefix = row_rewrap_prefix;
13326 }
13327 prev_row = row;
13328 }
13329
13330 ranges.push((
13331 language_settings.clone(),
13332 Point::new(current_range_start, 0)
13333 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13334 current_range_indent,
13335 current_range_comment_delimiters,
13336 current_range_rewrap_prefix,
13337 from_empty_selection,
13338 ));
13339
13340 ranges
13341 });
13342
13343 let mut edits = Vec::new();
13344 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13345
13346 for (
13347 language_settings,
13348 wrap_range,
13349 mut indent_size,
13350 comment_prefix,
13351 rewrap_prefix,
13352 from_empty_selection,
13353 ) in wrap_ranges
13354 {
13355 let mut start_row = wrap_range.start.row;
13356 let mut end_row = wrap_range.end.row;
13357
13358 // Skip selections that overlap with a range that has already been rewrapped.
13359 let selection_range = start_row..end_row;
13360 if rewrapped_row_ranges
13361 .iter()
13362 .any(|range| range.overlaps(&selection_range))
13363 {
13364 continue;
13365 }
13366
13367 let tab_size = language_settings.tab_size;
13368
13369 let (line_prefix, inside_comment) = match &comment_prefix {
13370 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13371 (Some(prefix.as_str()), true)
13372 }
13373 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13374 (Some(prefix.as_ref()), true)
13375 }
13376 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13377 start: _,
13378 end: _,
13379 prefix,
13380 tab_size,
13381 })) => {
13382 indent_size.len += tab_size;
13383 (Some(prefix.as_ref()), true)
13384 }
13385 None => (None, false),
13386 };
13387 let indent_prefix = indent_size.chars().collect::<String>();
13388 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13389
13390 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13391 RewrapBehavior::InComments => inside_comment,
13392 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13393 RewrapBehavior::Anywhere => true,
13394 };
13395
13396 let should_rewrap = options.override_language_settings
13397 || allow_rewrap_based_on_language
13398 || self.hard_wrap.is_some();
13399 if !should_rewrap {
13400 continue;
13401 }
13402
13403 if from_empty_selection {
13404 'expand_upwards: while start_row > 0 {
13405 let prev_row = start_row - 1;
13406 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13407 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13408 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13409 {
13410 start_row = prev_row;
13411 } else {
13412 break 'expand_upwards;
13413 }
13414 }
13415
13416 'expand_downwards: while end_row < buffer.max_point().row {
13417 let next_row = end_row + 1;
13418 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13419 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13420 && !buffer.is_line_blank(MultiBufferRow(next_row))
13421 {
13422 end_row = next_row;
13423 } else {
13424 break 'expand_downwards;
13425 }
13426 }
13427 }
13428
13429 let start = Point::new(start_row, 0);
13430 let start_offset = ToOffset::to_offset(&start, &buffer);
13431 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13432 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13433 let mut first_line_delimiter = None;
13434 let mut last_line_delimiter = None;
13435 let Some(lines_without_prefixes) = selection_text
13436 .lines()
13437 .enumerate()
13438 .map(|(ix, line)| {
13439 let line_trimmed = line.trim_start();
13440 if rewrap_prefix.is_some() && ix > 0 {
13441 Ok(line_trimmed)
13442 } else if let Some(
13443 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13444 start,
13445 prefix,
13446 end,
13447 tab_size,
13448 })
13449 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13450 start,
13451 prefix,
13452 end,
13453 tab_size,
13454 }),
13455 ) = &comment_prefix
13456 {
13457 let line_trimmed = line_trimmed
13458 .strip_prefix(start.as_ref())
13459 .map(|s| {
13460 let mut indent_size = indent_size;
13461 indent_size.len -= tab_size;
13462 let indent_prefix: String = indent_size.chars().collect();
13463 first_line_delimiter = Some((indent_prefix, start));
13464 s.trim_start()
13465 })
13466 .unwrap_or(line_trimmed);
13467 let line_trimmed = line_trimmed
13468 .strip_suffix(end.as_ref())
13469 .map(|s| {
13470 last_line_delimiter = Some(end);
13471 s.trim_end()
13472 })
13473 .unwrap_or(line_trimmed);
13474 let line_trimmed = line_trimmed
13475 .strip_prefix(prefix.as_ref())
13476 .unwrap_or(line_trimmed);
13477 Ok(line_trimmed)
13478 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13479 line_trimmed.strip_prefix(prefix).with_context(|| {
13480 format!("line did not start with prefix {prefix:?}: {line:?}")
13481 })
13482 } else {
13483 line_trimmed
13484 .strip_prefix(&line_prefix.trim_start())
13485 .with_context(|| {
13486 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13487 })
13488 }
13489 })
13490 .collect::<Result<Vec<_>, _>>()
13491 .log_err()
13492 else {
13493 continue;
13494 };
13495
13496 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13497 buffer
13498 .language_settings_at(Point::new(start_row, 0), cx)
13499 .preferred_line_length as usize
13500 });
13501
13502 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13503 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13504 } else {
13505 line_prefix.clone()
13506 };
13507
13508 let wrapped_text = {
13509 let mut wrapped_text = wrap_with_prefix(
13510 line_prefix,
13511 subsequent_lines_prefix,
13512 lines_without_prefixes.join("\n"),
13513 wrap_column,
13514 tab_size,
13515 options.preserve_existing_whitespace,
13516 );
13517
13518 if let Some((indent, delimiter)) = first_line_delimiter {
13519 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13520 }
13521 if let Some(last_line) = last_line_delimiter {
13522 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13523 }
13524
13525 wrapped_text
13526 };
13527
13528 // TODO: should always use char-based diff while still supporting cursor behavior that
13529 // matches vim.
13530 let mut diff_options = DiffOptions::default();
13531 if options.override_language_settings {
13532 diff_options.max_word_diff_len = 0;
13533 diff_options.max_word_diff_line_count = 0;
13534 } else {
13535 diff_options.max_word_diff_len = usize::MAX;
13536 diff_options.max_word_diff_line_count = usize::MAX;
13537 }
13538
13539 for (old_range, new_text) in
13540 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13541 {
13542 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13543 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13544 edits.push((edit_start..edit_end, new_text));
13545 }
13546
13547 rewrapped_row_ranges.push(start_row..=end_row);
13548 }
13549
13550 self.buffer
13551 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13552 }
13553
13554 pub fn cut_common(
13555 &mut self,
13556 cut_no_selection_line: bool,
13557 window: &mut Window,
13558 cx: &mut Context<Self>,
13559 ) -> ClipboardItem {
13560 let mut text = String::new();
13561 let buffer = self.buffer.read(cx).snapshot(cx);
13562 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13563 let mut clipboard_selections = Vec::with_capacity(selections.len());
13564 {
13565 let max_point = buffer.max_point();
13566 let mut is_first = true;
13567 let mut prev_selection_was_entire_line = false;
13568 for selection in &mut selections {
13569 let is_entire_line =
13570 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13571 if is_entire_line {
13572 selection.start = Point::new(selection.start.row, 0);
13573 if !selection.is_empty() && selection.end.column == 0 {
13574 selection.end = cmp::min(max_point, selection.end);
13575 } else {
13576 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13577 }
13578 selection.goal = SelectionGoal::None;
13579 }
13580 if is_first {
13581 is_first = false;
13582 } else if !prev_selection_was_entire_line {
13583 text += "\n";
13584 }
13585 prev_selection_was_entire_line = is_entire_line;
13586 let mut len = 0;
13587 for chunk in buffer.text_for_range(selection.start..selection.end) {
13588 text.push_str(chunk);
13589 len += chunk.len();
13590 }
13591
13592 clipboard_selections.push(ClipboardSelection::for_buffer(
13593 len,
13594 is_entire_line,
13595 selection.range(),
13596 &buffer,
13597 self.project.as_ref(),
13598 cx,
13599 ));
13600 }
13601 }
13602
13603 self.transact(window, cx, |this, window, cx| {
13604 this.change_selections(Default::default(), window, cx, |s| {
13605 s.select(selections);
13606 });
13607 this.insert("", window, cx);
13608 });
13609 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13610 }
13611
13612 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13613 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13614 let item = self.cut_common(true, window, cx);
13615 cx.write_to_clipboard(item);
13616 }
13617
13618 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13619 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13620 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13621 s.move_with(&mut |snapshot, sel| {
13622 if sel.is_empty() {
13623 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13624 }
13625 if sel.is_empty() {
13626 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13627 }
13628 });
13629 });
13630 let item = self.cut_common(false, window, cx);
13631 cx.set_global(KillRing(item))
13632 }
13633
13634 pub fn kill_ring_yank(
13635 &mut self,
13636 _: &KillRingYank,
13637 window: &mut Window,
13638 cx: &mut Context<Self>,
13639 ) {
13640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13641 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13642 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13643 (kill_ring.text().to_string(), kill_ring.metadata_json())
13644 } else {
13645 return;
13646 }
13647 } else {
13648 return;
13649 };
13650 self.do_paste(&text, metadata, false, window, cx);
13651 }
13652
13653 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13654 self.do_copy(true, cx);
13655 }
13656
13657 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13658 self.do_copy(false, cx);
13659 }
13660
13661 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13662 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13663 let buffer = self.buffer.read(cx).read(cx);
13664 let mut text = String::new();
13665
13666 let mut clipboard_selections = Vec::with_capacity(selections.len());
13667 {
13668 let max_point = buffer.max_point();
13669 let mut is_first = true;
13670 let mut prev_selection_was_entire_line = false;
13671 for selection in &selections {
13672 let mut start = selection.start;
13673 let mut end = selection.end;
13674 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13675 let mut add_trailing_newline = false;
13676 if is_entire_line {
13677 start = Point::new(start.row, 0);
13678 let next_line_start = Point::new(end.row + 1, 0);
13679 if next_line_start <= max_point {
13680 end = next_line_start;
13681 } else {
13682 // We're on the last line without a trailing newline.
13683 // Copy to the end of the line and add a newline afterwards.
13684 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13685 add_trailing_newline = true;
13686 }
13687 }
13688
13689 let mut trimmed_selections = Vec::new();
13690 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13691 let row = MultiBufferRow(start.row);
13692 let first_indent = buffer.indent_size_for_line(row);
13693 if first_indent.len == 0 || start.column > first_indent.len {
13694 trimmed_selections.push(start..end);
13695 } else {
13696 trimmed_selections.push(
13697 Point::new(row.0, first_indent.len)
13698 ..Point::new(row.0, buffer.line_len(row)),
13699 );
13700 for row in start.row + 1..=end.row {
13701 let mut line_len = buffer.line_len(MultiBufferRow(row));
13702 if row == end.row {
13703 line_len = end.column;
13704 }
13705 if line_len == 0 {
13706 trimmed_selections
13707 .push(Point::new(row, 0)..Point::new(row, line_len));
13708 continue;
13709 }
13710 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13711 if row_indent_size.len >= first_indent.len {
13712 trimmed_selections.push(
13713 Point::new(row, first_indent.len)..Point::new(row, line_len),
13714 );
13715 } else {
13716 trimmed_selections.clear();
13717 trimmed_selections.push(start..end);
13718 break;
13719 }
13720 }
13721 }
13722 } else {
13723 trimmed_selections.push(start..end);
13724 }
13725
13726 let is_multiline_trim = trimmed_selections.len() > 1;
13727 for trimmed_range in trimmed_selections {
13728 if is_first {
13729 is_first = false;
13730 } else if is_multiline_trim || !prev_selection_was_entire_line {
13731 text += "\n";
13732 }
13733 prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13734 let mut len = 0;
13735 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13736 text.push_str(chunk);
13737 len += chunk.len();
13738 }
13739 if add_trailing_newline {
13740 text.push('\n');
13741 len += 1;
13742 }
13743 clipboard_selections.push(ClipboardSelection::for_buffer(
13744 len,
13745 is_entire_line,
13746 trimmed_range,
13747 &buffer,
13748 self.project.as_ref(),
13749 cx,
13750 ));
13751 }
13752 }
13753 }
13754
13755 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13756 text,
13757 clipboard_selections,
13758 ));
13759 }
13760
13761 pub fn do_paste(
13762 &mut self,
13763 text: &String,
13764 clipboard_selections: Option<Vec<ClipboardSelection>>,
13765 handle_entire_lines: bool,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 if self.read_only(cx) {
13770 return;
13771 }
13772
13773 let clipboard_text = Cow::Borrowed(text.as_str());
13774
13775 self.transact(window, cx, |this, window, cx| {
13776 let had_active_edit_prediction = this.has_active_edit_prediction();
13777 let display_map = this.display_snapshot(cx);
13778 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13779 let cursor_offset = this
13780 .selections
13781 .last::<MultiBufferOffset>(&display_map)
13782 .head();
13783
13784 if let Some(mut clipboard_selections) = clipboard_selections {
13785 let all_selections_were_entire_line =
13786 clipboard_selections.iter().all(|s| s.is_entire_line);
13787 let first_selection_indent_column =
13788 clipboard_selections.first().map(|s| s.first_line_indent);
13789 if clipboard_selections.len() != old_selections.len() {
13790 clipboard_selections.drain(..);
13791 }
13792 let mut auto_indent_on_paste = true;
13793
13794 this.buffer.update(cx, |buffer, cx| {
13795 let snapshot = buffer.read(cx);
13796 auto_indent_on_paste = snapshot
13797 .language_settings_at(cursor_offset, cx)
13798 .auto_indent_on_paste;
13799
13800 let mut start_offset = 0;
13801 let mut edits = Vec::new();
13802 let mut original_indent_columns = Vec::new();
13803 for (ix, selection) in old_selections.iter().enumerate() {
13804 let to_insert;
13805 let entire_line;
13806 let original_indent_column;
13807 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13808 let end_offset = start_offset + clipboard_selection.len;
13809 to_insert = &clipboard_text[start_offset..end_offset];
13810 entire_line = clipboard_selection.is_entire_line;
13811 start_offset = if entire_line {
13812 end_offset
13813 } else {
13814 end_offset + 1
13815 };
13816 original_indent_column = Some(clipboard_selection.first_line_indent);
13817 } else {
13818 to_insert = &*clipboard_text;
13819 entire_line = all_selections_were_entire_line;
13820 original_indent_column = first_selection_indent_column
13821 }
13822
13823 let (range, to_insert) =
13824 if selection.is_empty() && handle_entire_lines && entire_line {
13825 // If the corresponding selection was empty when this slice of the
13826 // clipboard text was written, then the entire line containing the
13827 // selection was copied. If this selection is also currently empty,
13828 // then paste the line before the current line of the buffer.
13829 let column = selection.start.to_point(&snapshot).column as usize;
13830 let line_start = selection.start - column;
13831 (line_start..line_start, Cow::Borrowed(to_insert))
13832 } else {
13833 let language = snapshot.language_at(selection.head());
13834 let range = selection.range();
13835 if let Some(language) = language
13836 && language.name() == "Markdown".into()
13837 {
13838 edit_for_markdown_paste(
13839 &snapshot,
13840 range,
13841 to_insert,
13842 url::Url::parse(to_insert).ok(),
13843 )
13844 } else {
13845 (range, Cow::Borrowed(to_insert))
13846 }
13847 };
13848
13849 edits.push((range, to_insert));
13850 original_indent_columns.push(original_indent_column);
13851 }
13852 drop(snapshot);
13853
13854 buffer.edit(
13855 edits,
13856 if auto_indent_on_paste {
13857 Some(AutoindentMode::Block {
13858 original_indent_columns,
13859 })
13860 } else {
13861 None
13862 },
13863 cx,
13864 );
13865 });
13866
13867 let selections = this
13868 .selections
13869 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13870 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13871 } else {
13872 let url = url::Url::parse(&clipboard_text).ok();
13873
13874 let auto_indent_mode = if !clipboard_text.is_empty() {
13875 Some(AutoindentMode::Block {
13876 original_indent_columns: Vec::new(),
13877 })
13878 } else {
13879 None
13880 };
13881
13882 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13883 let snapshot = buffer.snapshot(cx);
13884
13885 let anchors = old_selections
13886 .iter()
13887 .map(|s| {
13888 let anchor = snapshot.anchor_after(s.head());
13889 s.map(|_| anchor)
13890 })
13891 .collect::<Vec<_>>();
13892
13893 let mut edits = Vec::new();
13894
13895 for selection in old_selections.iter() {
13896 let language = snapshot.language_at(selection.head());
13897 let range = selection.range();
13898
13899 let (edit_range, edit_text) = if let Some(language) = language
13900 && language.name() == "Markdown".into()
13901 {
13902 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13903 } else {
13904 (range, clipboard_text.clone())
13905 };
13906
13907 edits.push((edit_range, edit_text));
13908 }
13909
13910 drop(snapshot);
13911 buffer.edit(edits, auto_indent_mode, cx);
13912
13913 anchors
13914 });
13915
13916 this.change_selections(Default::default(), window, cx, |s| {
13917 s.select_anchors(selection_anchors);
13918 });
13919 }
13920
13921 // 🤔 | .. | show_in_menu |
13922 // | .. | true true
13923 // | had_edit_prediction | false true
13924
13925 let trigger_in_words =
13926 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13927
13928 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13929 });
13930 }
13931
13932 pub fn diff_clipboard_with_selection(
13933 &mut self,
13934 _: &DiffClipboardWithSelection,
13935 window: &mut Window,
13936 cx: &mut Context<Self>,
13937 ) {
13938 let selections = self
13939 .selections
13940 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13941
13942 if selections.is_empty() {
13943 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13944 return;
13945 };
13946
13947 let clipboard_text = match cx.read_from_clipboard() {
13948 Some(item) => match item.entries().first() {
13949 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13950 _ => None,
13951 },
13952 None => None,
13953 };
13954
13955 let Some(clipboard_text) = clipboard_text else {
13956 log::warn!("Clipboard doesn't contain text.");
13957 return;
13958 };
13959
13960 window.dispatch_action(
13961 Box::new(DiffClipboardWithSelectionData {
13962 clipboard_text,
13963 editor: cx.entity(),
13964 }),
13965 cx,
13966 );
13967 }
13968
13969 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13970 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13971 if let Some(item) = cx.read_from_clipboard() {
13972 let entries = item.entries();
13973
13974 match entries.first() {
13975 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13976 // of all the pasted entries.
13977 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13978 .do_paste(
13979 clipboard_string.text(),
13980 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13981 true,
13982 window,
13983 cx,
13984 ),
13985 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13986 }
13987 }
13988 }
13989
13990 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13991 if self.read_only(cx) {
13992 return;
13993 }
13994
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13996
13997 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13998 if let Some((selections, _)) =
13999 self.selection_history.transaction(transaction_id).cloned()
14000 {
14001 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14002 s.select_anchors(selections.to_vec());
14003 });
14004 } else {
14005 log::error!(
14006 "No entry in selection_history found for undo. \
14007 This may correspond to a bug where undo does not update the selection. \
14008 If this is occurring, please add details to \
14009 https://github.com/zed-industries/zed/issues/22692"
14010 );
14011 }
14012 self.request_autoscroll(Autoscroll::fit(), cx);
14013 self.unmark_text(window, cx);
14014 self.refresh_edit_prediction(true, false, window, cx);
14015 cx.emit(EditorEvent::Edited { transaction_id });
14016 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14017 }
14018 }
14019
14020 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14021 if self.read_only(cx) {
14022 return;
14023 }
14024
14025 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14026
14027 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14028 if let Some((_, Some(selections))) =
14029 self.selection_history.transaction(transaction_id).cloned()
14030 {
14031 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14032 s.select_anchors(selections.to_vec());
14033 });
14034 } else {
14035 log::error!(
14036 "No entry in selection_history found for redo. \
14037 This may correspond to a bug where undo does not update the selection. \
14038 If this is occurring, please add details to \
14039 https://github.com/zed-industries/zed/issues/22692"
14040 );
14041 }
14042 self.request_autoscroll(Autoscroll::fit(), cx);
14043 self.unmark_text(window, cx);
14044 self.refresh_edit_prediction(true, false, window, cx);
14045 cx.emit(EditorEvent::Edited { transaction_id });
14046 }
14047 }
14048
14049 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14050 self.buffer
14051 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14052 }
14053
14054 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14055 self.buffer
14056 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14057 }
14058
14059 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14061 self.change_selections(Default::default(), window, cx, |s| {
14062 s.move_with(&mut |map, selection| {
14063 let cursor = if selection.is_empty() {
14064 movement::left(map, selection.start)
14065 } else {
14066 selection.start
14067 };
14068 selection.collapse_to(cursor, SelectionGoal::None);
14069 });
14070 })
14071 }
14072
14073 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14075 self.change_selections(Default::default(), window, cx, |s| {
14076 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14077 })
14078 }
14079
14080 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14082 self.change_selections(Default::default(), window, cx, |s| {
14083 s.move_with(&mut |map, selection| {
14084 let cursor = if selection.is_empty() {
14085 movement::right(map, selection.end)
14086 } else {
14087 selection.end
14088 };
14089 selection.collapse_to(cursor, SelectionGoal::None)
14090 });
14091 })
14092 }
14093
14094 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14096 self.change_selections(Default::default(), window, cx, |s| {
14097 s.move_heads_with(&mut |map, head, _| {
14098 (movement::right(map, head), SelectionGoal::None)
14099 });
14100 });
14101 }
14102
14103 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14104 if self.take_rename(true, window, cx).is_some() {
14105 return;
14106 }
14107
14108 if self.mode.is_single_line() {
14109 cx.propagate();
14110 return;
14111 }
14112
14113 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14114
14115 let text_layout_details = &self.text_layout_details(window, cx);
14116 let selection_count = self.selections.count();
14117 let first_selection = self.selections.first_anchor();
14118
14119 self.change_selections(Default::default(), window, cx, |s| {
14120 s.move_with(&mut |map, selection| {
14121 if !selection.is_empty() {
14122 selection.goal = SelectionGoal::None;
14123 }
14124 let (cursor, goal) = movement::up(
14125 map,
14126 selection.start,
14127 selection.goal,
14128 false,
14129 text_layout_details,
14130 );
14131 selection.collapse_to(cursor, goal);
14132 });
14133 });
14134
14135 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14136 {
14137 cx.propagate();
14138 }
14139 }
14140
14141 pub fn move_up_by_lines(
14142 &mut self,
14143 action: &MoveUpByLines,
14144 window: &mut Window,
14145 cx: &mut Context<Self>,
14146 ) {
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
14160 self.change_selections(Default::default(), window, cx, |s| {
14161 s.move_with(&mut |map, selection| {
14162 if !selection.is_empty() {
14163 selection.goal = SelectionGoal::None;
14164 }
14165 let (cursor, goal) = movement::up_by_rows(
14166 map,
14167 selection.start,
14168 action.lines,
14169 selection.goal,
14170 false,
14171 text_layout_details,
14172 );
14173 selection.collapse_to(cursor, goal);
14174 });
14175 })
14176 }
14177
14178 pub fn move_down_by_lines(
14179 &mut self,
14180 action: &MoveDownByLines,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) {
14184 if self.take_rename(true, window, cx).is_some() {
14185 return;
14186 }
14187
14188 if self.mode.is_single_line() {
14189 cx.propagate();
14190 return;
14191 }
14192
14193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14194
14195 let text_layout_details = &self.text_layout_details(window, cx);
14196
14197 self.change_selections(Default::default(), window, cx, |s| {
14198 s.move_with(&mut |map, selection| {
14199 if !selection.is_empty() {
14200 selection.goal = SelectionGoal::None;
14201 }
14202 let (cursor, goal) = movement::down_by_rows(
14203 map,
14204 selection.start,
14205 action.lines,
14206 selection.goal,
14207 false,
14208 text_layout_details,
14209 );
14210 selection.collapse_to(cursor, goal);
14211 });
14212 })
14213 }
14214
14215 pub fn select_down_by_lines(
14216 &mut self,
14217 action: &SelectDownByLines,
14218 window: &mut Window,
14219 cx: &mut Context<Self>,
14220 ) {
14221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14222 let text_layout_details = &self.text_layout_details(window, cx);
14223 self.change_selections(Default::default(), window, cx, |s| {
14224 s.move_heads_with(&mut |map, head, goal| {
14225 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14226 })
14227 })
14228 }
14229
14230 pub fn select_up_by_lines(
14231 &mut self,
14232 action: &SelectUpByLines,
14233 window: &mut Window,
14234 cx: &mut Context<Self>,
14235 ) {
14236 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14237 let text_layout_details = &self.text_layout_details(window, cx);
14238 self.change_selections(Default::default(), window, cx, |s| {
14239 s.move_heads_with(&mut |map, head, goal| {
14240 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14241 })
14242 })
14243 }
14244
14245 pub fn select_page_up(
14246 &mut self,
14247 _: &SelectPageUp,
14248 window: &mut Window,
14249 cx: &mut Context<Self>,
14250 ) {
14251 let Some(row_count) = self.visible_row_count() else {
14252 return;
14253 };
14254
14255 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14256
14257 let text_layout_details = &self.text_layout_details(window, cx);
14258
14259 self.change_selections(Default::default(), window, cx, |s| {
14260 s.move_heads_with(&mut |map, head, goal| {
14261 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14262 })
14263 })
14264 }
14265
14266 pub fn move_page_up(
14267 &mut self,
14268 action: &MovePageUp,
14269 window: &mut Window,
14270 cx: &mut Context<Self>,
14271 ) {
14272 if self.take_rename(true, window, cx).is_some() {
14273 return;
14274 }
14275
14276 if self
14277 .context_menu
14278 .borrow_mut()
14279 .as_mut()
14280 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14281 .unwrap_or(false)
14282 {
14283 return;
14284 }
14285
14286 if matches!(self.mode, EditorMode::SingleLine) {
14287 cx.propagate();
14288 return;
14289 }
14290
14291 let Some(row_count) = self.visible_row_count() else {
14292 return;
14293 };
14294
14295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14296
14297 let effects = if action.center_cursor {
14298 SelectionEffects::scroll(Autoscroll::center())
14299 } else {
14300 SelectionEffects::default()
14301 };
14302
14303 let text_layout_details = &self.text_layout_details(window, cx);
14304
14305 self.change_selections(effects, window, cx, |s| {
14306 s.move_with(&mut |map, selection| {
14307 if !selection.is_empty() {
14308 selection.goal = SelectionGoal::None;
14309 }
14310 let (cursor, goal) = movement::up_by_rows(
14311 map,
14312 selection.end,
14313 row_count,
14314 selection.goal,
14315 false,
14316 text_layout_details,
14317 );
14318 selection.collapse_to(cursor, goal);
14319 });
14320 });
14321 }
14322
14323 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14325 let text_layout_details = &self.text_layout_details(window, cx);
14326 self.change_selections(Default::default(), window, cx, |s| {
14327 s.move_heads_with(&mut |map, head, goal| {
14328 movement::up(map, head, goal, false, text_layout_details)
14329 })
14330 })
14331 }
14332
14333 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14334 self.take_rename(true, window, cx);
14335
14336 if self.mode.is_single_line() {
14337 cx.propagate();
14338 return;
14339 }
14340
14341 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14342
14343 let text_layout_details = &self.text_layout_details(window, cx);
14344 let selection_count = self.selections.count();
14345 let first_selection = self.selections.first_anchor();
14346
14347 self.change_selections(Default::default(), window, cx, |s| {
14348 s.move_with(&mut |map, selection| {
14349 if !selection.is_empty() {
14350 selection.goal = SelectionGoal::None;
14351 }
14352 let (cursor, goal) = movement::down(
14353 map,
14354 selection.end,
14355 selection.goal,
14356 false,
14357 text_layout_details,
14358 );
14359 selection.collapse_to(cursor, goal);
14360 });
14361 });
14362
14363 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14364 {
14365 cx.propagate();
14366 }
14367 }
14368
14369 pub fn select_page_down(
14370 &mut self,
14371 _: &SelectPageDown,
14372 window: &mut Window,
14373 cx: &mut Context<Self>,
14374 ) {
14375 let Some(row_count) = self.visible_row_count() else {
14376 return;
14377 };
14378
14379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14380
14381 let text_layout_details = &self.text_layout_details(window, cx);
14382
14383 self.change_selections(Default::default(), window, cx, |s| {
14384 s.move_heads_with(&mut |map, head, goal| {
14385 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14386 })
14387 })
14388 }
14389
14390 pub fn move_page_down(
14391 &mut self,
14392 action: &MovePageDown,
14393 window: &mut Window,
14394 cx: &mut Context<Self>,
14395 ) {
14396 if self.take_rename(true, window, cx).is_some() {
14397 return;
14398 }
14399
14400 if self
14401 .context_menu
14402 .borrow_mut()
14403 .as_mut()
14404 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14405 .unwrap_or(false)
14406 {
14407 return;
14408 }
14409
14410 if matches!(self.mode, EditorMode::SingleLine) {
14411 cx.propagate();
14412 return;
14413 }
14414
14415 let Some(row_count) = self.visible_row_count() else {
14416 return;
14417 };
14418
14419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14420
14421 let effects = if action.center_cursor {
14422 SelectionEffects::scroll(Autoscroll::center())
14423 } else {
14424 SelectionEffects::default()
14425 };
14426
14427 let text_layout_details = &self.text_layout_details(window, cx);
14428 self.change_selections(effects, window, cx, |s| {
14429 s.move_with(&mut |map, selection| {
14430 if !selection.is_empty() {
14431 selection.goal = SelectionGoal::None;
14432 }
14433 let (cursor, goal) = movement::down_by_rows(
14434 map,
14435 selection.end,
14436 row_count,
14437 selection.goal,
14438 false,
14439 text_layout_details,
14440 );
14441 selection.collapse_to(cursor, goal);
14442 });
14443 });
14444 }
14445
14446 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14448 let text_layout_details = &self.text_layout_details(window, cx);
14449 self.change_selections(Default::default(), window, cx, |s| {
14450 s.move_heads_with(&mut |map, head, goal| {
14451 movement::down(map, head, goal, false, text_layout_details)
14452 })
14453 });
14454 }
14455
14456 pub fn context_menu_first(
14457 &mut self,
14458 _: &ContextMenuFirst,
14459 window: &mut Window,
14460 cx: &mut Context<Self>,
14461 ) {
14462 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14463 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14464 }
14465 }
14466
14467 pub fn context_menu_prev(
14468 &mut self,
14469 _: &ContextMenuPrevious,
14470 window: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) {
14473 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14474 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14475 }
14476 }
14477
14478 pub fn context_menu_next(
14479 &mut self,
14480 _: &ContextMenuNext,
14481 window: &mut Window,
14482 cx: &mut Context<Self>,
14483 ) {
14484 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14485 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14486 }
14487 }
14488
14489 pub fn context_menu_last(
14490 &mut self,
14491 _: &ContextMenuLast,
14492 window: &mut Window,
14493 cx: &mut Context<Self>,
14494 ) {
14495 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14496 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14497 }
14498 }
14499
14500 pub fn signature_help_prev(
14501 &mut self,
14502 _: &SignatureHelpPrevious,
14503 _: &mut Window,
14504 cx: &mut Context<Self>,
14505 ) {
14506 if let Some(popover) = self.signature_help_state.popover_mut() {
14507 if popover.current_signature == 0 {
14508 popover.current_signature = popover.signatures.len() - 1;
14509 } else {
14510 popover.current_signature -= 1;
14511 }
14512 cx.notify();
14513 }
14514 }
14515
14516 pub fn signature_help_next(
14517 &mut self,
14518 _: &SignatureHelpNext,
14519 _: &mut Window,
14520 cx: &mut Context<Self>,
14521 ) {
14522 if let Some(popover) = self.signature_help_state.popover_mut() {
14523 if popover.current_signature + 1 == popover.signatures.len() {
14524 popover.current_signature = 0;
14525 } else {
14526 popover.current_signature += 1;
14527 }
14528 cx.notify();
14529 }
14530 }
14531
14532 pub fn move_to_previous_word_start(
14533 &mut self,
14534 _: &MoveToPreviousWordStart,
14535 window: &mut Window,
14536 cx: &mut Context<Self>,
14537 ) {
14538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14539 self.change_selections(Default::default(), window, cx, |s| {
14540 s.move_cursors_with(&mut |map, head, _| {
14541 (
14542 movement::previous_word_start(map, head),
14543 SelectionGoal::None,
14544 )
14545 });
14546 })
14547 }
14548
14549 pub fn move_to_previous_subword_start(
14550 &mut self,
14551 _: &MoveToPreviousSubwordStart,
14552 window: &mut Window,
14553 cx: &mut Context<Self>,
14554 ) {
14555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14556 self.change_selections(Default::default(), window, cx, |s| {
14557 s.move_cursors_with(&mut |map, head, _| {
14558 (
14559 movement::previous_subword_start(map, head),
14560 SelectionGoal::None,
14561 )
14562 });
14563 })
14564 }
14565
14566 pub fn select_to_previous_word_start(
14567 &mut self,
14568 _: &SelectToPreviousWordStart,
14569 window: &mut Window,
14570 cx: &mut Context<Self>,
14571 ) {
14572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14573 self.change_selections(Default::default(), window, cx, |s| {
14574 s.move_heads_with(&mut |map, head, _| {
14575 (
14576 movement::previous_word_start(map, head),
14577 SelectionGoal::None,
14578 )
14579 });
14580 })
14581 }
14582
14583 pub fn select_to_previous_subword_start(
14584 &mut self,
14585 _: &SelectToPreviousSubwordStart,
14586 window: &mut Window,
14587 cx: &mut Context<Self>,
14588 ) {
14589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14590 self.change_selections(Default::default(), window, cx, |s| {
14591 s.move_heads_with(&mut |map, head, _| {
14592 (
14593 movement::previous_subword_start(map, head),
14594 SelectionGoal::None,
14595 )
14596 });
14597 })
14598 }
14599
14600 pub fn delete_to_previous_word_start(
14601 &mut self,
14602 action: &DeleteToPreviousWordStart,
14603 window: &mut Window,
14604 cx: &mut Context<Self>,
14605 ) {
14606 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14607 self.transact(window, cx, |this, window, cx| {
14608 this.select_autoclose_pair(window, cx);
14609 this.change_selections(Default::default(), window, cx, |s| {
14610 s.move_with(&mut |map, selection| {
14611 if selection.is_empty() {
14612 let mut cursor = if action.ignore_newlines {
14613 movement::previous_word_start(map, selection.head())
14614 } else {
14615 movement::previous_word_start_or_newline(map, selection.head())
14616 };
14617 cursor = movement::adjust_greedy_deletion(
14618 map,
14619 selection.head(),
14620 cursor,
14621 action.ignore_brackets,
14622 );
14623 selection.set_head(cursor, SelectionGoal::None);
14624 }
14625 });
14626 });
14627 this.insert("", window, cx);
14628 });
14629 }
14630
14631 pub fn delete_to_previous_subword_start(
14632 &mut self,
14633 action: &DeleteToPreviousSubwordStart,
14634 window: &mut Window,
14635 cx: &mut Context<Self>,
14636 ) {
14637 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14638 self.transact(window, cx, |this, window, cx| {
14639 this.select_autoclose_pair(window, cx);
14640 this.change_selections(Default::default(), window, cx, |s| {
14641 s.move_with(&mut |map, selection| {
14642 if selection.is_empty() {
14643 let mut cursor = if action.ignore_newlines {
14644 movement::previous_subword_start(map, selection.head())
14645 } else {
14646 movement::previous_subword_start_or_newline(map, selection.head())
14647 };
14648 cursor = movement::adjust_greedy_deletion(
14649 map,
14650 selection.head(),
14651 cursor,
14652 action.ignore_brackets,
14653 );
14654 selection.set_head(cursor, SelectionGoal::None);
14655 }
14656 });
14657 });
14658 this.insert("", window, cx);
14659 });
14660 }
14661
14662 pub fn move_to_next_word_end(
14663 &mut self,
14664 _: &MoveToNextWordEnd,
14665 window: &mut Window,
14666 cx: &mut Context<Self>,
14667 ) {
14668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14669 self.change_selections(Default::default(), window, cx, |s| {
14670 s.move_cursors_with(&mut |map, head, _| {
14671 (movement::next_word_end(map, head), SelectionGoal::None)
14672 });
14673 })
14674 }
14675
14676 pub fn move_to_next_subword_end(
14677 &mut self,
14678 _: &MoveToNextSubwordEnd,
14679 window: &mut Window,
14680 cx: &mut Context<Self>,
14681 ) {
14682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14683 self.change_selections(Default::default(), window, cx, |s| {
14684 s.move_cursors_with(&mut |map, head, _| {
14685 (movement::next_subword_end(map, head), SelectionGoal::None)
14686 });
14687 })
14688 }
14689
14690 pub fn select_to_next_word_end(
14691 &mut self,
14692 _: &SelectToNextWordEnd,
14693 window: &mut Window,
14694 cx: &mut Context<Self>,
14695 ) {
14696 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14697 self.change_selections(Default::default(), window, cx, |s| {
14698 s.move_heads_with(&mut |map, head, _| {
14699 (movement::next_word_end(map, head), SelectionGoal::None)
14700 });
14701 })
14702 }
14703
14704 pub fn select_to_next_subword_end(
14705 &mut self,
14706 _: &SelectToNextSubwordEnd,
14707 window: &mut Window,
14708 cx: &mut Context<Self>,
14709 ) {
14710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14711 self.change_selections(Default::default(), window, cx, |s| {
14712 s.move_heads_with(&mut |map, head, _| {
14713 (movement::next_subword_end(map, head), SelectionGoal::None)
14714 });
14715 })
14716 }
14717
14718 pub fn delete_to_next_word_end(
14719 &mut self,
14720 action: &DeleteToNextWordEnd,
14721 window: &mut Window,
14722 cx: &mut Context<Self>,
14723 ) {
14724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14725 self.transact(window, cx, |this, window, cx| {
14726 this.change_selections(Default::default(), window, cx, |s| {
14727 s.move_with(&mut |map, selection| {
14728 if selection.is_empty() {
14729 let mut cursor = if action.ignore_newlines {
14730 movement::next_word_end(map, selection.head())
14731 } else {
14732 movement::next_word_end_or_newline(map, selection.head())
14733 };
14734 cursor = movement::adjust_greedy_deletion(
14735 map,
14736 selection.head(),
14737 cursor,
14738 action.ignore_brackets,
14739 );
14740 selection.set_head(cursor, SelectionGoal::None);
14741 }
14742 });
14743 });
14744 this.insert("", window, cx);
14745 });
14746 }
14747
14748 pub fn delete_to_next_subword_end(
14749 &mut self,
14750 action: &DeleteToNextSubwordEnd,
14751 window: &mut Window,
14752 cx: &mut Context<Self>,
14753 ) {
14754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14755 self.transact(window, cx, |this, window, cx| {
14756 this.change_selections(Default::default(), window, cx, |s| {
14757 s.move_with(&mut |map, selection| {
14758 if selection.is_empty() {
14759 let mut cursor = if action.ignore_newlines {
14760 movement::next_subword_end(map, selection.head())
14761 } else {
14762 movement::next_subword_end_or_newline(map, selection.head())
14763 };
14764 cursor = movement::adjust_greedy_deletion(
14765 map,
14766 selection.head(),
14767 cursor,
14768 action.ignore_brackets,
14769 );
14770 selection.set_head(cursor, SelectionGoal::None);
14771 }
14772 });
14773 });
14774 this.insert("", window, cx);
14775 });
14776 }
14777
14778 pub fn move_to_beginning_of_line(
14779 &mut self,
14780 action: &MoveToBeginningOfLine,
14781 window: &mut Window,
14782 cx: &mut Context<Self>,
14783 ) {
14784 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14785 self.change_selections(Default::default(), window, cx, |s| {
14786 s.move_cursors_with(&mut |map, head, _| {
14787 (
14788 movement::indented_line_beginning(
14789 map,
14790 head,
14791 action.stop_at_soft_wraps,
14792 action.stop_at_indent,
14793 ),
14794 SelectionGoal::None,
14795 )
14796 });
14797 })
14798 }
14799
14800 pub fn select_to_beginning_of_line(
14801 &mut self,
14802 action: &SelectToBeginningOfLine,
14803 window: &mut Window,
14804 cx: &mut Context<Self>,
14805 ) {
14806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14807 self.change_selections(Default::default(), window, cx, |s| {
14808 s.move_heads_with(&mut |map, head, _| {
14809 (
14810 movement::indented_line_beginning(
14811 map,
14812 head,
14813 action.stop_at_soft_wraps,
14814 action.stop_at_indent,
14815 ),
14816 SelectionGoal::None,
14817 )
14818 });
14819 });
14820 }
14821
14822 pub fn delete_to_beginning_of_line(
14823 &mut self,
14824 action: &DeleteToBeginningOfLine,
14825 window: &mut Window,
14826 cx: &mut Context<Self>,
14827 ) {
14828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14829 self.transact(window, cx, |this, window, cx| {
14830 this.change_selections(Default::default(), window, cx, |s| {
14831 s.move_with(&mut |_, selection| {
14832 selection.reversed = true;
14833 });
14834 });
14835
14836 this.select_to_beginning_of_line(
14837 &SelectToBeginningOfLine {
14838 stop_at_soft_wraps: false,
14839 stop_at_indent: action.stop_at_indent,
14840 },
14841 window,
14842 cx,
14843 );
14844 this.backspace(&Backspace, window, cx);
14845 });
14846 }
14847
14848 pub fn move_to_end_of_line(
14849 &mut self,
14850 action: &MoveToEndOfLine,
14851 window: &mut Window,
14852 cx: &mut Context<Self>,
14853 ) {
14854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14855 self.change_selections(Default::default(), window, cx, |s| {
14856 s.move_cursors_with(&mut |map, head, _| {
14857 (
14858 movement::line_end(map, head, action.stop_at_soft_wraps),
14859 SelectionGoal::None,
14860 )
14861 });
14862 })
14863 }
14864
14865 pub fn select_to_end_of_line(
14866 &mut self,
14867 action: &SelectToEndOfLine,
14868 window: &mut Window,
14869 cx: &mut Context<Self>,
14870 ) {
14871 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14872 self.change_selections(Default::default(), window, cx, |s| {
14873 s.move_heads_with(&mut |map, head, _| {
14874 (
14875 movement::line_end(map, head, action.stop_at_soft_wraps),
14876 SelectionGoal::None,
14877 )
14878 });
14879 })
14880 }
14881
14882 pub fn delete_to_end_of_line(
14883 &mut self,
14884 _: &DeleteToEndOfLine,
14885 window: &mut Window,
14886 cx: &mut Context<Self>,
14887 ) {
14888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14889 self.transact(window, cx, |this, window, cx| {
14890 this.select_to_end_of_line(
14891 &SelectToEndOfLine {
14892 stop_at_soft_wraps: false,
14893 },
14894 window,
14895 cx,
14896 );
14897 this.delete(&Delete, window, cx);
14898 });
14899 }
14900
14901 pub fn cut_to_end_of_line(
14902 &mut self,
14903 action: &CutToEndOfLine,
14904 window: &mut Window,
14905 cx: &mut Context<Self>,
14906 ) {
14907 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14908 self.transact(window, cx, |this, window, cx| {
14909 this.select_to_end_of_line(
14910 &SelectToEndOfLine {
14911 stop_at_soft_wraps: false,
14912 },
14913 window,
14914 cx,
14915 );
14916 if !action.stop_at_newlines {
14917 this.change_selections(Default::default(), window, cx, |s| {
14918 s.move_with(&mut |_, sel| {
14919 if sel.is_empty() {
14920 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14921 }
14922 });
14923 });
14924 }
14925 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14926 let item = this.cut_common(false, window, cx);
14927 cx.write_to_clipboard(item);
14928 });
14929 }
14930
14931 pub fn move_to_start_of_paragraph(
14932 &mut self,
14933 _: &MoveToStartOfParagraph,
14934 window: &mut Window,
14935 cx: &mut Context<Self>,
14936 ) {
14937 if matches!(self.mode, EditorMode::SingleLine) {
14938 cx.propagate();
14939 return;
14940 }
14941 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14942 self.change_selections(Default::default(), window, cx, |s| {
14943 s.move_with(&mut |map, selection| {
14944 selection.collapse_to(
14945 movement::start_of_paragraph(map, selection.head(), 1),
14946 SelectionGoal::None,
14947 )
14948 });
14949 })
14950 }
14951
14952 pub fn move_to_end_of_paragraph(
14953 &mut self,
14954 _: &MoveToEndOfParagraph,
14955 window: &mut Window,
14956 cx: &mut Context<Self>,
14957 ) {
14958 if matches!(self.mode, EditorMode::SingleLine) {
14959 cx.propagate();
14960 return;
14961 }
14962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14963 self.change_selections(Default::default(), window, cx, |s| {
14964 s.move_with(&mut |map, selection| {
14965 selection.collapse_to(
14966 movement::end_of_paragraph(map, selection.head(), 1),
14967 SelectionGoal::None,
14968 )
14969 });
14970 })
14971 }
14972
14973 pub fn select_to_start_of_paragraph(
14974 &mut self,
14975 _: &SelectToStartOfParagraph,
14976 window: &mut Window,
14977 cx: &mut Context<Self>,
14978 ) {
14979 if matches!(self.mode, EditorMode::SingleLine) {
14980 cx.propagate();
14981 return;
14982 }
14983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14984 self.change_selections(Default::default(), window, cx, |s| {
14985 s.move_heads_with(&mut |map, head, _| {
14986 (
14987 movement::start_of_paragraph(map, head, 1),
14988 SelectionGoal::None,
14989 )
14990 });
14991 })
14992 }
14993
14994 pub fn select_to_end_of_paragraph(
14995 &mut self,
14996 _: &SelectToEndOfParagraph,
14997 window: &mut Window,
14998 cx: &mut Context<Self>,
14999 ) {
15000 if matches!(self.mode, EditorMode::SingleLine) {
15001 cx.propagate();
15002 return;
15003 }
15004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15005 self.change_selections(Default::default(), window, cx, |s| {
15006 s.move_heads_with(&mut |map, head, _| {
15007 (
15008 movement::end_of_paragraph(map, head, 1),
15009 SelectionGoal::None,
15010 )
15011 });
15012 })
15013 }
15014
15015 pub fn move_to_start_of_excerpt(
15016 &mut self,
15017 _: &MoveToStartOfExcerpt,
15018 window: &mut Window,
15019 cx: &mut Context<Self>,
15020 ) {
15021 if matches!(self.mode, EditorMode::SingleLine) {
15022 cx.propagate();
15023 return;
15024 }
15025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15026 self.change_selections(Default::default(), window, cx, |s| {
15027 s.move_with(&mut |map, selection| {
15028 selection.collapse_to(
15029 movement::start_of_excerpt(
15030 map,
15031 selection.head(),
15032 workspace::searchable::Direction::Prev,
15033 ),
15034 SelectionGoal::None,
15035 )
15036 });
15037 })
15038 }
15039
15040 pub fn move_to_start_of_next_excerpt(
15041 &mut self,
15042 _: &MoveToStartOfNextExcerpt,
15043 window: &mut Window,
15044 cx: &mut Context<Self>,
15045 ) {
15046 if matches!(self.mode, EditorMode::SingleLine) {
15047 cx.propagate();
15048 return;
15049 }
15050
15051 self.change_selections(Default::default(), window, cx, |s| {
15052 s.move_with(&mut |map, selection| {
15053 selection.collapse_to(
15054 movement::start_of_excerpt(
15055 map,
15056 selection.head(),
15057 workspace::searchable::Direction::Next,
15058 ),
15059 SelectionGoal::None,
15060 )
15061 });
15062 })
15063 }
15064
15065 pub fn move_to_end_of_excerpt(
15066 &mut self,
15067 _: &MoveToEndOfExcerpt,
15068 window: &mut Window,
15069 cx: &mut Context<Self>,
15070 ) {
15071 if matches!(self.mode, EditorMode::SingleLine) {
15072 cx.propagate();
15073 return;
15074 }
15075 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15076 self.change_selections(Default::default(), window, cx, |s| {
15077 s.move_with(&mut |map, selection| {
15078 selection.collapse_to(
15079 movement::end_of_excerpt(
15080 map,
15081 selection.head(),
15082 workspace::searchable::Direction::Next,
15083 ),
15084 SelectionGoal::None,
15085 )
15086 });
15087 })
15088 }
15089
15090 pub fn move_to_end_of_previous_excerpt(
15091 &mut self,
15092 _: &MoveToEndOfPreviousExcerpt,
15093 window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) {
15096 if matches!(self.mode, EditorMode::SingleLine) {
15097 cx.propagate();
15098 return;
15099 }
15100 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15101 self.change_selections(Default::default(), window, cx, |s| {
15102 s.move_with(&mut |map, selection| {
15103 selection.collapse_to(
15104 movement::end_of_excerpt(
15105 map,
15106 selection.head(),
15107 workspace::searchable::Direction::Prev,
15108 ),
15109 SelectionGoal::None,
15110 )
15111 });
15112 })
15113 }
15114
15115 pub fn select_to_start_of_excerpt(
15116 &mut self,
15117 _: &SelectToStartOfExcerpt,
15118 window: &mut Window,
15119 cx: &mut Context<Self>,
15120 ) {
15121 if matches!(self.mode, EditorMode::SingleLine) {
15122 cx.propagate();
15123 return;
15124 }
15125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15126 self.change_selections(Default::default(), window, cx, |s| {
15127 s.move_heads_with(&mut |map, head, _| {
15128 (
15129 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15130 SelectionGoal::None,
15131 )
15132 });
15133 })
15134 }
15135
15136 pub fn select_to_start_of_next_excerpt(
15137 &mut self,
15138 _: &SelectToStartOfNextExcerpt,
15139 window: &mut Window,
15140 cx: &mut Context<Self>,
15141 ) {
15142 if matches!(self.mode, EditorMode::SingleLine) {
15143 cx.propagate();
15144 return;
15145 }
15146 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15147 self.change_selections(Default::default(), window, cx, |s| {
15148 s.move_heads_with(&mut |map, head, _| {
15149 (
15150 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15151 SelectionGoal::None,
15152 )
15153 });
15154 })
15155 }
15156
15157 pub fn select_to_end_of_excerpt(
15158 &mut self,
15159 _: &SelectToEndOfExcerpt,
15160 window: &mut Window,
15161 cx: &mut Context<Self>,
15162 ) {
15163 if matches!(self.mode, EditorMode::SingleLine) {
15164 cx.propagate();
15165 return;
15166 }
15167 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15168 self.change_selections(Default::default(), window, cx, |s| {
15169 s.move_heads_with(&mut |map, head, _| {
15170 (
15171 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15172 SelectionGoal::None,
15173 )
15174 });
15175 })
15176 }
15177
15178 pub fn select_to_end_of_previous_excerpt(
15179 &mut self,
15180 _: &SelectToEndOfPreviousExcerpt,
15181 window: &mut Window,
15182 cx: &mut Context<Self>,
15183 ) {
15184 if matches!(self.mode, EditorMode::SingleLine) {
15185 cx.propagate();
15186 return;
15187 }
15188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15189 self.change_selections(Default::default(), window, cx, |s| {
15190 s.move_heads_with(&mut |map, head, _| {
15191 (
15192 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15193 SelectionGoal::None,
15194 )
15195 });
15196 })
15197 }
15198
15199 pub fn move_to_beginning(
15200 &mut self,
15201 _: &MoveToBeginning,
15202 window: &mut Window,
15203 cx: &mut Context<Self>,
15204 ) {
15205 if matches!(self.mode, EditorMode::SingleLine) {
15206 cx.propagate();
15207 return;
15208 }
15209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15210 self.change_selections(Default::default(), window, cx, |s| {
15211 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15212 });
15213 }
15214
15215 pub fn select_to_beginning(
15216 &mut self,
15217 _: &SelectToBeginning,
15218 window: &mut Window,
15219 cx: &mut Context<Self>,
15220 ) {
15221 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15222 selection.set_head(Point::zero(), SelectionGoal::None);
15223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15224 self.change_selections(Default::default(), window, cx, |s| {
15225 s.select(vec![selection]);
15226 });
15227 }
15228
15229 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15230 if matches!(self.mode, EditorMode::SingleLine) {
15231 cx.propagate();
15232 return;
15233 }
15234 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15235 let cursor = self.buffer.read(cx).read(cx).len();
15236 self.change_selections(Default::default(), window, cx, |s| {
15237 s.select_ranges(vec![cursor..cursor])
15238 });
15239 }
15240
15241 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15242 self.nav_history = nav_history;
15243 }
15244
15245 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15246 self.nav_history.as_ref()
15247 }
15248
15249 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15250 self.push_to_nav_history(
15251 self.selections.newest_anchor().head(),
15252 None,
15253 false,
15254 true,
15255 cx,
15256 );
15257 }
15258
15259 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15260 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15261 let buffer = self.buffer.read(cx).read(cx);
15262 let cursor_position = cursor_anchor.to_point(&buffer);
15263 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15264 let scroll_top_row = scroll_anchor.top_row(&buffer);
15265 drop(buffer);
15266
15267 NavigationData {
15268 cursor_anchor,
15269 cursor_position,
15270 scroll_anchor,
15271 scroll_top_row,
15272 }
15273 }
15274
15275 fn navigation_entry(
15276 &self,
15277 cursor_anchor: Anchor,
15278 cx: &mut Context<Self>,
15279 ) -> Option<NavigationEntry> {
15280 let Some(history) = self.nav_history.clone() else {
15281 return None;
15282 };
15283 let data = self.navigation_data(cursor_anchor, cx);
15284 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15285 }
15286
15287 fn push_to_nav_history(
15288 &mut self,
15289 cursor_anchor: Anchor,
15290 new_position: Option<Point>,
15291 is_deactivate: bool,
15292 always: bool,
15293 cx: &mut Context<Self>,
15294 ) {
15295 let data = self.navigation_data(cursor_anchor, cx);
15296 if let Some(nav_history) = self.nav_history.as_mut() {
15297 if let Some(new_position) = new_position {
15298 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15299 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15300 return;
15301 }
15302 }
15303
15304 nav_history.push(Some(data), cx);
15305 cx.emit(EditorEvent::PushedToNavHistory {
15306 anchor: cursor_anchor,
15307 is_deactivate,
15308 })
15309 }
15310 }
15311
15312 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15314 let buffer = self.buffer.read(cx).snapshot(cx);
15315 let mut selection = self
15316 .selections
15317 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15318 selection.set_head(buffer.len(), SelectionGoal::None);
15319 self.change_selections(Default::default(), window, cx, |s| {
15320 s.select(vec![selection]);
15321 });
15322 }
15323
15324 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15326 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15327 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
15328 });
15329 }
15330
15331 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15332 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15334 let mut selections = self.selections.all::<Point>(&display_map);
15335 let max_point = display_map.buffer_snapshot().max_point();
15336 for selection in &mut selections {
15337 let rows = selection.spanned_rows(true, &display_map);
15338 selection.start = Point::new(rows.start.0, 0);
15339 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15340 selection.reversed = false;
15341 }
15342 self.change_selections(Default::default(), window, cx, |s| {
15343 s.select(selections);
15344 });
15345 }
15346
15347 pub fn split_selection_into_lines(
15348 &mut self,
15349 action: &SplitSelectionIntoLines,
15350 window: &mut Window,
15351 cx: &mut Context<Self>,
15352 ) {
15353 let selections = self
15354 .selections
15355 .all::<Point>(&self.display_snapshot(cx))
15356 .into_iter()
15357 .map(|selection| selection.start..selection.end)
15358 .collect::<Vec<_>>();
15359 self.unfold_ranges(&selections, true, true, cx);
15360
15361 let mut new_selection_ranges = Vec::new();
15362 {
15363 let buffer = self.buffer.read(cx).read(cx);
15364 for selection in selections {
15365 for row in selection.start.row..selection.end.row {
15366 let line_start = Point::new(row, 0);
15367 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15368
15369 if action.keep_selections {
15370 // Keep the selection range for each line
15371 let selection_start = if row == selection.start.row {
15372 selection.start
15373 } else {
15374 line_start
15375 };
15376 new_selection_ranges.push(selection_start..line_end);
15377 } else {
15378 // Collapse to cursor at end of line
15379 new_selection_ranges.push(line_end..line_end);
15380 }
15381 }
15382
15383 let is_multiline_selection = selection.start.row != selection.end.row;
15384 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15385 // so this action feels more ergonomic when paired with other selection operations
15386 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15387 if !should_skip_last {
15388 if action.keep_selections {
15389 if is_multiline_selection {
15390 let line_start = Point::new(selection.end.row, 0);
15391 new_selection_ranges.push(line_start..selection.end);
15392 } else {
15393 new_selection_ranges.push(selection.start..selection.end);
15394 }
15395 } else {
15396 new_selection_ranges.push(selection.end..selection.end);
15397 }
15398 }
15399 }
15400 }
15401 self.change_selections(Default::default(), window, cx, |s| {
15402 s.select_ranges(new_selection_ranges);
15403 });
15404 }
15405
15406 pub fn add_selection_above(
15407 &mut self,
15408 action: &AddSelectionAbove,
15409 window: &mut Window,
15410 cx: &mut Context<Self>,
15411 ) {
15412 self.add_selection(true, action.skip_soft_wrap, window, cx);
15413 }
15414
15415 pub fn add_selection_below(
15416 &mut self,
15417 action: &AddSelectionBelow,
15418 window: &mut Window,
15419 cx: &mut Context<Self>,
15420 ) {
15421 self.add_selection(false, action.skip_soft_wrap, window, cx);
15422 }
15423
15424 fn add_selection(
15425 &mut self,
15426 above: bool,
15427 skip_soft_wrap: bool,
15428 window: &mut Window,
15429 cx: &mut Context<Self>,
15430 ) {
15431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15432
15433 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15434 let all_selections = self.selections.all::<Point>(&display_map);
15435 let text_layout_details = self.text_layout_details(window, cx);
15436
15437 let (mut columnar_selections, new_selections_to_columnarize) = {
15438 if let Some(state) = self.add_selections_state.as_ref() {
15439 let columnar_selection_ids: HashSet<_> = state
15440 .groups
15441 .iter()
15442 .flat_map(|group| group.stack.iter())
15443 .copied()
15444 .collect();
15445
15446 all_selections
15447 .into_iter()
15448 .partition(|s| columnar_selection_ids.contains(&s.id))
15449 } else {
15450 (Vec::new(), all_selections)
15451 }
15452 };
15453
15454 let mut state = self
15455 .add_selections_state
15456 .take()
15457 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15458
15459 for selection in new_selections_to_columnarize {
15460 let range = selection.display_range(&display_map).sorted();
15461 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15462 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15463 let positions = start_x.min(end_x)..start_x.max(end_x);
15464 let mut stack = Vec::new();
15465 for row in range.start.row().0..=range.end.row().0 {
15466 if let Some(selection) = self.selections.build_columnar_selection(
15467 &display_map,
15468 DisplayRow(row),
15469 &positions,
15470 selection.reversed,
15471 &text_layout_details,
15472 ) {
15473 stack.push(selection.id);
15474 columnar_selections.push(selection);
15475 }
15476 }
15477 if !stack.is_empty() {
15478 if above {
15479 stack.reverse();
15480 }
15481 state.groups.push(AddSelectionsGroup { above, stack });
15482 }
15483 }
15484
15485 let mut final_selections = Vec::new();
15486 let end_row = if above {
15487 DisplayRow(0)
15488 } else {
15489 display_map.max_point().row()
15490 };
15491
15492 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15493 // positions to place new selections, so we need to keep track of the
15494 // column range of the oldest selection in each group, because
15495 // intermediate selections may have been clamped to shorter lines.
15496 // selections may have been clamped to shorter lines.
15497 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15498 let mut map = HashMap::default();
15499 for group in state.groups.iter() {
15500 if let Some(oldest_id) = group.stack.first() {
15501 if let Some(oldest_selection) =
15502 columnar_selections.iter().find(|s| s.id == *oldest_id)
15503 {
15504 let start_col = oldest_selection.start.column;
15505 let end_col = oldest_selection.end.column;
15506 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15507 for id in &group.stack {
15508 map.insert(*id, goal_columns.clone());
15509 }
15510 }
15511 }
15512 }
15513 map
15514 } else {
15515 HashMap::default()
15516 };
15517
15518 let mut last_added_item_per_group = HashMap::default();
15519 for group in state.groups.iter_mut() {
15520 if let Some(last_id) = group.stack.last() {
15521 last_added_item_per_group.insert(*last_id, group);
15522 }
15523 }
15524
15525 for selection in columnar_selections {
15526 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15527 if above == group.above {
15528 let range = selection.display_range(&display_map).sorted();
15529 debug_assert_eq!(range.start.row(), range.end.row());
15530 let row = range.start.row();
15531 let positions =
15532 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15533 Pixels::from(start)..Pixels::from(end)
15534 } else {
15535 let start_x =
15536 display_map.x_for_display_point(range.start, &text_layout_details);
15537 let end_x =
15538 display_map.x_for_display_point(range.end, &text_layout_details);
15539 start_x.min(end_x)..start_x.max(end_x)
15540 };
15541
15542 let maybe_new_selection = if skip_soft_wrap {
15543 let goal_columns = goal_columns_by_selection_id
15544 .remove(&selection.id)
15545 .unwrap_or_else(|| {
15546 let start_col = selection.start.column;
15547 let end_col = selection.end.column;
15548 start_col.min(end_col)..start_col.max(end_col)
15549 });
15550 self.selections.find_next_columnar_selection_by_buffer_row(
15551 &display_map,
15552 row,
15553 end_row,
15554 above,
15555 &goal_columns,
15556 selection.reversed,
15557 &text_layout_details,
15558 )
15559 } else {
15560 self.selections.find_next_columnar_selection_by_display_row(
15561 &display_map,
15562 row,
15563 end_row,
15564 above,
15565 &positions,
15566 selection.reversed,
15567 &text_layout_details,
15568 )
15569 };
15570
15571 if let Some(new_selection) = maybe_new_selection {
15572 group.stack.push(new_selection.id);
15573 if above {
15574 final_selections.push(new_selection);
15575 final_selections.push(selection);
15576 } else {
15577 final_selections.push(selection);
15578 final_selections.push(new_selection);
15579 }
15580 } else {
15581 final_selections.push(selection);
15582 }
15583 } else {
15584 group.stack.pop();
15585 }
15586 } else {
15587 final_selections.push(selection);
15588 }
15589 }
15590
15591 self.change_selections(Default::default(), window, cx, |s| {
15592 s.select(final_selections);
15593 });
15594
15595 let final_selection_ids: HashSet<_> = self
15596 .selections
15597 .all::<Point>(&display_map)
15598 .iter()
15599 .map(|s| s.id)
15600 .collect();
15601 state.groups.retain_mut(|group| {
15602 // selections might get merged above so we remove invalid items from stacks
15603 group.stack.retain(|id| final_selection_ids.contains(id));
15604
15605 // single selection in stack can be treated as initial state
15606 group.stack.len() > 1
15607 });
15608
15609 if !state.groups.is_empty() {
15610 self.add_selections_state = Some(state);
15611 }
15612 }
15613
15614 pub fn insert_snippet_at_selections(
15615 &mut self,
15616 action: &InsertSnippet,
15617 window: &mut Window,
15618 cx: &mut Context<Self>,
15619 ) {
15620 self.try_insert_snippet_at_selections(action, window, cx)
15621 .log_err();
15622 }
15623
15624 fn try_insert_snippet_at_selections(
15625 &mut self,
15626 action: &InsertSnippet,
15627 window: &mut Window,
15628 cx: &mut Context<Self>,
15629 ) -> Result<()> {
15630 let insertion_ranges = self
15631 .selections
15632 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15633 .into_iter()
15634 .map(|selection| selection.range())
15635 .collect_vec();
15636
15637 let snippet = if let Some(snippet_body) = &action.snippet {
15638 if action.language.is_none() && action.name.is_none() {
15639 Snippet::parse(snippet_body)?
15640 } else {
15641 bail!("`snippet` is mutually exclusive with `language` and `name`")
15642 }
15643 } else if let Some(name) = &action.name {
15644 let project = self.project().context("no project")?;
15645 let snippet_store = project.read(cx).snippets().read(cx);
15646 let snippet = snippet_store
15647 .snippets_for(action.language.clone(), cx)
15648 .into_iter()
15649 .find(|snippet| snippet.name == *name)
15650 .context("snippet not found")?;
15651 Snippet::parse(&snippet.body)?
15652 } else {
15653 // todo(andrew): open modal to select snippet
15654 bail!("`name` or `snippet` is required")
15655 };
15656
15657 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15658 }
15659
15660 fn select_match_ranges(
15661 &mut self,
15662 range: Range<MultiBufferOffset>,
15663 reversed: bool,
15664 replace_newest: bool,
15665 auto_scroll: Option<Autoscroll>,
15666 window: &mut Window,
15667 cx: &mut Context<Editor>,
15668 ) {
15669 self.unfold_ranges(
15670 std::slice::from_ref(&range),
15671 false,
15672 auto_scroll.is_some(),
15673 cx,
15674 );
15675 let effects = if let Some(scroll) = auto_scroll {
15676 SelectionEffects::scroll(scroll)
15677 } else {
15678 SelectionEffects::no_scroll()
15679 };
15680 self.change_selections(effects, window, cx, |s| {
15681 if replace_newest {
15682 s.delete(s.newest_anchor().id);
15683 }
15684 if reversed {
15685 s.insert_range(range.end..range.start);
15686 } else {
15687 s.insert_range(range);
15688 }
15689 });
15690 }
15691
15692 pub fn select_next_match_internal(
15693 &mut self,
15694 display_map: &DisplaySnapshot,
15695 replace_newest: bool,
15696 autoscroll: Option<Autoscroll>,
15697 window: &mut Window,
15698 cx: &mut Context<Self>,
15699 ) -> Result<()> {
15700 let buffer = display_map.buffer_snapshot();
15701 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15702 if let Some(mut select_next_state) = self.select_next_state.take() {
15703 let query = &select_next_state.query;
15704 if !select_next_state.done {
15705 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15706 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15707 let mut next_selected_range = None;
15708
15709 let bytes_after_last_selection =
15710 buffer.bytes_in_range(last_selection.end..buffer.len());
15711 let bytes_before_first_selection =
15712 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15713 let query_matches = query
15714 .stream_find_iter(bytes_after_last_selection)
15715 .map(|result| (last_selection.end, result))
15716 .chain(
15717 query
15718 .stream_find_iter(bytes_before_first_selection)
15719 .map(|result| (MultiBufferOffset(0), result)),
15720 );
15721
15722 for (start_offset, query_match) in query_matches {
15723 let query_match = query_match.unwrap(); // can only fail due to I/O
15724 let offset_range =
15725 start_offset + query_match.start()..start_offset + query_match.end();
15726
15727 if !select_next_state.wordwise
15728 || (!buffer.is_inside_word(offset_range.start, None)
15729 && !buffer.is_inside_word(offset_range.end, None))
15730 {
15731 let idx = selections
15732 .partition_point(|selection| selection.end <= offset_range.start);
15733 let overlaps = selections
15734 .get(idx)
15735 .map_or(false, |selection| selection.start < offset_range.end);
15736
15737 if !overlaps {
15738 next_selected_range = Some(offset_range);
15739 break;
15740 }
15741 }
15742 }
15743
15744 if let Some(next_selected_range) = next_selected_range {
15745 self.select_match_ranges(
15746 next_selected_range,
15747 last_selection.reversed,
15748 replace_newest,
15749 autoscroll,
15750 window,
15751 cx,
15752 );
15753 } else {
15754 select_next_state.done = true;
15755 }
15756 }
15757
15758 self.select_next_state = Some(select_next_state);
15759 } else {
15760 let mut only_carets = true;
15761 let mut same_text_selected = true;
15762 let mut selected_text = None;
15763
15764 let mut selections_iter = selections.iter().peekable();
15765 while let Some(selection) = selections_iter.next() {
15766 if selection.start != selection.end {
15767 only_carets = false;
15768 }
15769
15770 if same_text_selected {
15771 if selected_text.is_none() {
15772 selected_text =
15773 Some(buffer.text_for_range(selection.range()).collect::<String>());
15774 }
15775
15776 if let Some(next_selection) = selections_iter.peek() {
15777 if next_selection.len() == selection.len() {
15778 let next_selected_text = buffer
15779 .text_for_range(next_selection.range())
15780 .collect::<String>();
15781 if Some(next_selected_text) != selected_text {
15782 same_text_selected = false;
15783 selected_text = None;
15784 }
15785 } else {
15786 same_text_selected = false;
15787 selected_text = None;
15788 }
15789 }
15790 }
15791 }
15792
15793 if only_carets {
15794 for selection in &mut selections {
15795 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15796 selection.start = word_range.start;
15797 selection.end = word_range.end;
15798 selection.goal = SelectionGoal::None;
15799 selection.reversed = false;
15800 self.select_match_ranges(
15801 selection.start..selection.end,
15802 selection.reversed,
15803 replace_newest,
15804 autoscroll,
15805 window,
15806 cx,
15807 );
15808 }
15809
15810 if selections.len() == 1 {
15811 let selection = selections
15812 .last()
15813 .expect("ensured that there's only one selection");
15814 let query = buffer
15815 .text_for_range(selection.start..selection.end)
15816 .collect::<String>();
15817 let is_empty = query.is_empty();
15818 let select_state = SelectNextState {
15819 query: self.build_query(&[query], cx)?,
15820 wordwise: true,
15821 done: is_empty,
15822 };
15823 self.select_next_state = Some(select_state);
15824 } else {
15825 self.select_next_state = None;
15826 }
15827 } else if let Some(selected_text) = selected_text {
15828 self.select_next_state = Some(SelectNextState {
15829 query: self.build_query(&[selected_text], cx)?,
15830 wordwise: false,
15831 done: false,
15832 });
15833 self.select_next_match_internal(
15834 display_map,
15835 replace_newest,
15836 autoscroll,
15837 window,
15838 cx,
15839 )?;
15840 }
15841 }
15842 Ok(())
15843 }
15844
15845 pub fn select_all_matches(
15846 &mut self,
15847 _action: &SelectAllMatches,
15848 window: &mut Window,
15849 cx: &mut Context<Self>,
15850 ) -> Result<()> {
15851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15852
15853 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15854
15855 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15856 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15857 else {
15858 return Ok(());
15859 };
15860
15861 let mut new_selections = Vec::new();
15862
15863 let reversed = self
15864 .selections
15865 .oldest::<MultiBufferOffset>(&display_map)
15866 .reversed;
15867 let buffer = display_map.buffer_snapshot();
15868 let query_matches = select_next_state
15869 .query
15870 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15871
15872 for query_match in query_matches.into_iter() {
15873 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15874 let offset_range = if reversed {
15875 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15876 } else {
15877 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15878 };
15879
15880 if !select_next_state.wordwise
15881 || (!buffer.is_inside_word(offset_range.start, None)
15882 && !buffer.is_inside_word(offset_range.end, None))
15883 {
15884 new_selections.push(offset_range.start..offset_range.end);
15885 }
15886 }
15887
15888 select_next_state.done = true;
15889
15890 if new_selections.is_empty() {
15891 log::error!("bug: new_selections is empty in select_all_matches");
15892 return Ok(());
15893 }
15894
15895 self.unfold_ranges(&new_selections, false, false, cx);
15896 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15897 selections.select_ranges(new_selections)
15898 });
15899
15900 Ok(())
15901 }
15902
15903 pub fn select_next(
15904 &mut self,
15905 action: &SelectNext,
15906 window: &mut Window,
15907 cx: &mut Context<Self>,
15908 ) -> Result<()> {
15909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15910 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15911 self.select_next_match_internal(
15912 &display_map,
15913 action.replace_newest,
15914 Some(Autoscroll::newest()),
15915 window,
15916 cx,
15917 )
15918 }
15919
15920 pub fn select_previous(
15921 &mut self,
15922 action: &SelectPrevious,
15923 window: &mut Window,
15924 cx: &mut Context<Self>,
15925 ) -> Result<()> {
15926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15928 let buffer = display_map.buffer_snapshot();
15929 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15930 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15931 let query = &select_prev_state.query;
15932 if !select_prev_state.done {
15933 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15934 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15935 let mut next_selected_range = None;
15936 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15937 let bytes_before_last_selection =
15938 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15939 let bytes_after_first_selection =
15940 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15941 let query_matches = query
15942 .stream_find_iter(bytes_before_last_selection)
15943 .map(|result| (last_selection.start, result))
15944 .chain(
15945 query
15946 .stream_find_iter(bytes_after_first_selection)
15947 .map(|result| (buffer.len(), result)),
15948 );
15949 for (end_offset, query_match) in query_matches {
15950 let query_match = query_match.unwrap(); // can only fail due to I/O
15951 let offset_range =
15952 end_offset - query_match.end()..end_offset - query_match.start();
15953
15954 if !select_prev_state.wordwise
15955 || (!buffer.is_inside_word(offset_range.start, None)
15956 && !buffer.is_inside_word(offset_range.end, None))
15957 {
15958 next_selected_range = Some(offset_range);
15959 break;
15960 }
15961 }
15962
15963 if let Some(next_selected_range) = next_selected_range {
15964 self.select_match_ranges(
15965 next_selected_range,
15966 last_selection.reversed,
15967 action.replace_newest,
15968 Some(Autoscroll::newest()),
15969 window,
15970 cx,
15971 );
15972 } else {
15973 select_prev_state.done = true;
15974 }
15975 }
15976
15977 self.select_prev_state = Some(select_prev_state);
15978 } else {
15979 let mut only_carets = true;
15980 let mut same_text_selected = true;
15981 let mut selected_text = None;
15982
15983 let mut selections_iter = selections.iter().peekable();
15984 while let Some(selection) = selections_iter.next() {
15985 if selection.start != selection.end {
15986 only_carets = false;
15987 }
15988
15989 if same_text_selected {
15990 if selected_text.is_none() {
15991 selected_text =
15992 Some(buffer.text_for_range(selection.range()).collect::<String>());
15993 }
15994
15995 if let Some(next_selection) = selections_iter.peek() {
15996 if next_selection.len() == selection.len() {
15997 let next_selected_text = buffer
15998 .text_for_range(next_selection.range())
15999 .collect::<String>();
16000 if Some(next_selected_text) != selected_text {
16001 same_text_selected = false;
16002 selected_text = None;
16003 }
16004 } else {
16005 same_text_selected = false;
16006 selected_text = None;
16007 }
16008 }
16009 }
16010 }
16011
16012 if only_carets {
16013 for selection in &mut selections {
16014 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16015 selection.start = word_range.start;
16016 selection.end = word_range.end;
16017 selection.goal = SelectionGoal::None;
16018 selection.reversed = false;
16019 self.select_match_ranges(
16020 selection.start..selection.end,
16021 selection.reversed,
16022 action.replace_newest,
16023 Some(Autoscroll::newest()),
16024 window,
16025 cx,
16026 );
16027 }
16028 if selections.len() == 1 {
16029 let selection = selections
16030 .last()
16031 .expect("ensured that there's only one selection");
16032 let query = buffer
16033 .text_for_range(selection.start..selection.end)
16034 .collect::<String>();
16035 let is_empty = query.is_empty();
16036 let select_state = SelectNextState {
16037 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16038 wordwise: true,
16039 done: is_empty,
16040 };
16041 self.select_prev_state = Some(select_state);
16042 } else {
16043 self.select_prev_state = None;
16044 }
16045 } else if let Some(selected_text) = selected_text {
16046 self.select_prev_state = Some(SelectNextState {
16047 query: self
16048 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16049 wordwise: false,
16050 done: false,
16051 });
16052 self.select_previous(action, window, cx)?;
16053 }
16054 }
16055 Ok(())
16056 }
16057
16058 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16059 /// setting the case sensitivity based on the global
16060 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16061 /// editor's settings.
16062 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16063 where
16064 I: IntoIterator<Item = P>,
16065 P: AsRef<[u8]>,
16066 {
16067 let case_sensitive = self
16068 .select_next_is_case_sensitive
16069 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16070
16071 let mut builder = AhoCorasickBuilder::new();
16072 builder.ascii_case_insensitive(!case_sensitive);
16073 builder.build(patterns)
16074 }
16075
16076 pub fn find_next_match(
16077 &mut self,
16078 _: &FindNextMatch,
16079 window: &mut Window,
16080 cx: &mut Context<Self>,
16081 ) -> Result<()> {
16082 let selections = self.selections.disjoint_anchors_arc();
16083 match selections.first() {
16084 Some(first) if selections.len() >= 2 => {
16085 self.change_selections(Default::default(), window, cx, |s| {
16086 s.select_ranges([first.range()]);
16087 });
16088 }
16089 _ => self.select_next(
16090 &SelectNext {
16091 replace_newest: true,
16092 },
16093 window,
16094 cx,
16095 )?,
16096 }
16097 Ok(())
16098 }
16099
16100 pub fn find_previous_match(
16101 &mut self,
16102 _: &FindPreviousMatch,
16103 window: &mut Window,
16104 cx: &mut Context<Self>,
16105 ) -> Result<()> {
16106 let selections = self.selections.disjoint_anchors_arc();
16107 match selections.last() {
16108 Some(last) if selections.len() >= 2 => {
16109 self.change_selections(Default::default(), window, cx, |s| {
16110 s.select_ranges([last.range()]);
16111 });
16112 }
16113 _ => self.select_previous(
16114 &SelectPrevious {
16115 replace_newest: true,
16116 },
16117 window,
16118 cx,
16119 )?,
16120 }
16121 Ok(())
16122 }
16123
16124 pub fn toggle_comments(
16125 &mut self,
16126 action: &ToggleComments,
16127 window: &mut Window,
16128 cx: &mut Context<Self>,
16129 ) {
16130 if self.read_only(cx) {
16131 return;
16132 }
16133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16134 let text_layout_details = &self.text_layout_details(window, cx);
16135 self.transact(window, cx, |this, window, cx| {
16136 let mut selections = this
16137 .selections
16138 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16139 let mut edits = Vec::new();
16140 let mut selection_edit_ranges = Vec::new();
16141 let mut last_toggled_row = None;
16142 let snapshot = this.buffer.read(cx).read(cx);
16143 let empty_str: Arc<str> = Arc::default();
16144 let mut suffixes_inserted = Vec::new();
16145 let ignore_indent = action.ignore_indent;
16146
16147 fn comment_prefix_range(
16148 snapshot: &MultiBufferSnapshot,
16149 row: MultiBufferRow,
16150 comment_prefix: &str,
16151 comment_prefix_whitespace: &str,
16152 ignore_indent: bool,
16153 ) -> Range<Point> {
16154 let indent_size = if ignore_indent {
16155 0
16156 } else {
16157 snapshot.indent_size_for_line(row).len
16158 };
16159
16160 let start = Point::new(row.0, indent_size);
16161
16162 let mut line_bytes = snapshot
16163 .bytes_in_range(start..snapshot.max_point())
16164 .flatten()
16165 .copied();
16166
16167 // If this line currently begins with the line comment prefix, then record
16168 // the range containing the prefix.
16169 if line_bytes
16170 .by_ref()
16171 .take(comment_prefix.len())
16172 .eq(comment_prefix.bytes())
16173 {
16174 // Include any whitespace that matches the comment prefix.
16175 let matching_whitespace_len = line_bytes
16176 .zip(comment_prefix_whitespace.bytes())
16177 .take_while(|(a, b)| a == b)
16178 .count() as u32;
16179 let end = Point::new(
16180 start.row,
16181 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16182 );
16183 start..end
16184 } else {
16185 start..start
16186 }
16187 }
16188
16189 fn comment_suffix_range(
16190 snapshot: &MultiBufferSnapshot,
16191 row: MultiBufferRow,
16192 comment_suffix: &str,
16193 comment_suffix_has_leading_space: bool,
16194 ) -> Range<Point> {
16195 let end = Point::new(row.0, snapshot.line_len(row));
16196 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16197
16198 let mut line_end_bytes = snapshot
16199 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16200 .flatten()
16201 .copied();
16202
16203 let leading_space_len = if suffix_start_column > 0
16204 && line_end_bytes.next() == Some(b' ')
16205 && comment_suffix_has_leading_space
16206 {
16207 1
16208 } else {
16209 0
16210 };
16211
16212 // If this line currently begins with the line comment prefix, then record
16213 // the range containing the prefix.
16214 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16215 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16216 start..end
16217 } else {
16218 end..end
16219 }
16220 }
16221
16222 // TODO: Handle selections that cross excerpts
16223 for selection in &mut selections {
16224 let start_column = snapshot
16225 .indent_size_for_line(MultiBufferRow(selection.start.row))
16226 .len;
16227 let language = if let Some(language) =
16228 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16229 {
16230 language
16231 } else {
16232 continue;
16233 };
16234
16235 selection_edit_ranges.clear();
16236
16237 // If multiple selections contain a given row, avoid processing that
16238 // row more than once.
16239 let mut start_row = MultiBufferRow(selection.start.row);
16240 if last_toggled_row == Some(start_row) {
16241 start_row = start_row.next_row();
16242 }
16243 let end_row =
16244 if selection.end.row > selection.start.row && selection.end.column == 0 {
16245 MultiBufferRow(selection.end.row - 1)
16246 } else {
16247 MultiBufferRow(selection.end.row)
16248 };
16249 last_toggled_row = Some(end_row);
16250
16251 if start_row > end_row {
16252 continue;
16253 }
16254
16255 // If the language has line comments, toggle those.
16256 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16257
16258 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16259 if ignore_indent {
16260 full_comment_prefixes = full_comment_prefixes
16261 .into_iter()
16262 .map(|s| Arc::from(s.trim_end()))
16263 .collect();
16264 }
16265
16266 if !full_comment_prefixes.is_empty() {
16267 let first_prefix = full_comment_prefixes
16268 .first()
16269 .expect("prefixes is non-empty");
16270 let prefix_trimmed_lengths = full_comment_prefixes
16271 .iter()
16272 .map(|p| p.trim_end_matches(' ').len())
16273 .collect::<SmallVec<[usize; 4]>>();
16274
16275 let mut all_selection_lines_are_comments = true;
16276
16277 for row in start_row.0..=end_row.0 {
16278 let row = MultiBufferRow(row);
16279 if start_row < end_row && snapshot.is_line_blank(row) {
16280 continue;
16281 }
16282
16283 let prefix_range = full_comment_prefixes
16284 .iter()
16285 .zip(prefix_trimmed_lengths.iter().copied())
16286 .map(|(prefix, trimmed_prefix_len)| {
16287 comment_prefix_range(
16288 snapshot.deref(),
16289 row,
16290 &prefix[..trimmed_prefix_len],
16291 &prefix[trimmed_prefix_len..],
16292 ignore_indent,
16293 )
16294 })
16295 .max_by_key(|range| range.end.column - range.start.column)
16296 .expect("prefixes is non-empty");
16297
16298 if prefix_range.is_empty() {
16299 all_selection_lines_are_comments = false;
16300 }
16301
16302 selection_edit_ranges.push(prefix_range);
16303 }
16304
16305 if all_selection_lines_are_comments {
16306 edits.extend(
16307 selection_edit_ranges
16308 .iter()
16309 .cloned()
16310 .map(|range| (range, empty_str.clone())),
16311 );
16312 } else {
16313 let min_column = selection_edit_ranges
16314 .iter()
16315 .map(|range| range.start.column)
16316 .min()
16317 .unwrap_or(0);
16318 edits.extend(selection_edit_ranges.iter().map(|range| {
16319 let position = Point::new(range.start.row, min_column);
16320 (position..position, first_prefix.clone())
16321 }));
16322 }
16323 } else if let Some(BlockCommentConfig {
16324 start: full_comment_prefix,
16325 end: comment_suffix,
16326 ..
16327 }) = language.block_comment()
16328 {
16329 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16330 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16331 let prefix_range = comment_prefix_range(
16332 snapshot.deref(),
16333 start_row,
16334 comment_prefix,
16335 comment_prefix_whitespace,
16336 ignore_indent,
16337 );
16338 let suffix_range = comment_suffix_range(
16339 snapshot.deref(),
16340 end_row,
16341 comment_suffix.trim_start_matches(' '),
16342 comment_suffix.starts_with(' '),
16343 );
16344
16345 if prefix_range.is_empty() || suffix_range.is_empty() {
16346 edits.push((
16347 prefix_range.start..prefix_range.start,
16348 full_comment_prefix.clone(),
16349 ));
16350 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16351 suffixes_inserted.push((end_row, comment_suffix.len()));
16352 } else {
16353 edits.push((prefix_range, empty_str.clone()));
16354 edits.push((suffix_range, empty_str.clone()));
16355 }
16356 } else {
16357 continue;
16358 }
16359 }
16360
16361 drop(snapshot);
16362 this.buffer.update(cx, |buffer, cx| {
16363 buffer.edit(edits, None, cx);
16364 });
16365
16366 // Adjust selections so that they end before any comment suffixes that
16367 // were inserted.
16368 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16369 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16370 let snapshot = this.buffer.read(cx).read(cx);
16371 for selection in &mut selections {
16372 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16373 match row.cmp(&MultiBufferRow(selection.end.row)) {
16374 Ordering::Less => {
16375 suffixes_inserted.next();
16376 continue;
16377 }
16378 Ordering::Greater => break,
16379 Ordering::Equal => {
16380 if selection.end.column == snapshot.line_len(row) {
16381 if selection.is_empty() {
16382 selection.start.column -= suffix_len as u32;
16383 }
16384 selection.end.column -= suffix_len as u32;
16385 }
16386 break;
16387 }
16388 }
16389 }
16390 }
16391
16392 drop(snapshot);
16393 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16394
16395 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16396 let selections_on_single_row = selections.windows(2).all(|selections| {
16397 selections[0].start.row == selections[1].start.row
16398 && selections[0].end.row == selections[1].end.row
16399 && selections[0].start.row == selections[0].end.row
16400 });
16401 let selections_selecting = selections
16402 .iter()
16403 .any(|selection| selection.start != selection.end);
16404 let advance_downwards = action.advance_downwards
16405 && selections_on_single_row
16406 && !selections_selecting
16407 && !matches!(this.mode, EditorMode::SingleLine);
16408
16409 if advance_downwards {
16410 let snapshot = this.buffer.read(cx).snapshot(cx);
16411
16412 this.change_selections(Default::default(), window, cx, |s| {
16413 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16414 let mut point = display_point.to_point(display_snapshot);
16415 point.row += 1;
16416 point = snapshot.clip_point(point, Bias::Left);
16417 let display_point = point.to_display_point(display_snapshot);
16418 let goal = SelectionGoal::HorizontalPosition(
16419 display_snapshot
16420 .x_for_display_point(display_point, text_layout_details)
16421 .into(),
16422 );
16423 (display_point, goal)
16424 })
16425 });
16426 }
16427 });
16428 }
16429
16430 pub fn select_enclosing_symbol(
16431 &mut self,
16432 _: &SelectEnclosingSymbol,
16433 window: &mut Window,
16434 cx: &mut Context<Self>,
16435 ) {
16436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16437
16438 let buffer = self.buffer.read(cx).snapshot(cx);
16439 let old_selections = self
16440 .selections
16441 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16442 .into_boxed_slice();
16443
16444 fn update_selection(
16445 selection: &Selection<MultiBufferOffset>,
16446 buffer_snap: &MultiBufferSnapshot,
16447 ) -> Option<Selection<MultiBufferOffset>> {
16448 let cursor = selection.head();
16449 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16450 for symbol in symbols.iter().rev() {
16451 let start = symbol.range.start.to_offset(buffer_snap);
16452 let end = symbol.range.end.to_offset(buffer_snap);
16453 let new_range = start..end;
16454 if start < selection.start || end > selection.end {
16455 return Some(Selection {
16456 id: selection.id,
16457 start: new_range.start,
16458 end: new_range.end,
16459 goal: SelectionGoal::None,
16460 reversed: selection.reversed,
16461 });
16462 }
16463 }
16464 None
16465 }
16466
16467 let mut selected_larger_symbol = false;
16468 let new_selections = old_selections
16469 .iter()
16470 .map(|selection| match update_selection(selection, &buffer) {
16471 Some(new_selection) => {
16472 if new_selection.range() != selection.range() {
16473 selected_larger_symbol = true;
16474 }
16475 new_selection
16476 }
16477 None => selection.clone(),
16478 })
16479 .collect::<Vec<_>>();
16480
16481 if selected_larger_symbol {
16482 self.change_selections(Default::default(), window, cx, |s| {
16483 s.select(new_selections);
16484 });
16485 }
16486 }
16487
16488 pub fn select_larger_syntax_node(
16489 &mut self,
16490 _: &SelectLargerSyntaxNode,
16491 window: &mut Window,
16492 cx: &mut Context<Self>,
16493 ) {
16494 let Some(visible_row_count) = self.visible_row_count() else {
16495 return;
16496 };
16497 let old_selections: Box<[_]> = self
16498 .selections
16499 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16500 .into();
16501 if old_selections.is_empty() {
16502 return;
16503 }
16504
16505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16506
16507 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16508 let buffer = self.buffer.read(cx).snapshot(cx);
16509
16510 let mut selected_larger_node = false;
16511 let mut new_selections = old_selections
16512 .iter()
16513 .map(|selection| {
16514 let old_range = selection.start..selection.end;
16515
16516 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16517 // manually select word at selection
16518 if ["string_content", "inline"].contains(&node.kind()) {
16519 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16520 // ignore if word is already selected
16521 if !word_range.is_empty() && old_range != word_range {
16522 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16523 // only select word if start and end point belongs to same word
16524 if word_range == last_word_range {
16525 selected_larger_node = true;
16526 return Selection {
16527 id: selection.id,
16528 start: word_range.start,
16529 end: word_range.end,
16530 goal: SelectionGoal::None,
16531 reversed: selection.reversed,
16532 };
16533 }
16534 }
16535 }
16536 }
16537
16538 let mut new_range = old_range.clone();
16539 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16540 new_range = range;
16541 if !node.is_named() {
16542 continue;
16543 }
16544 if !display_map.intersects_fold(new_range.start)
16545 && !display_map.intersects_fold(new_range.end)
16546 {
16547 break;
16548 }
16549 }
16550
16551 selected_larger_node |= new_range != old_range;
16552 Selection {
16553 id: selection.id,
16554 start: new_range.start,
16555 end: new_range.end,
16556 goal: SelectionGoal::None,
16557 reversed: selection.reversed,
16558 }
16559 })
16560 .collect::<Vec<_>>();
16561
16562 if !selected_larger_node {
16563 return; // don't put this call in the history
16564 }
16565
16566 // scroll based on transformation done to the last selection created by the user
16567 let (last_old, last_new) = old_selections
16568 .last()
16569 .zip(new_selections.last().cloned())
16570 .expect("old_selections isn't empty");
16571
16572 // revert selection
16573 let is_selection_reversed = {
16574 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16575 new_selections.last_mut().expect("checked above").reversed =
16576 should_newest_selection_be_reversed;
16577 should_newest_selection_be_reversed
16578 };
16579
16580 if selected_larger_node {
16581 self.select_syntax_node_history.disable_clearing = true;
16582 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16583 s.select(new_selections.clone());
16584 });
16585 self.select_syntax_node_history.disable_clearing = false;
16586 }
16587
16588 let start_row = last_new.start.to_display_point(&display_map).row().0;
16589 let end_row = last_new.end.to_display_point(&display_map).row().0;
16590 let selection_height = end_row - start_row + 1;
16591 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16592
16593 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16594 let scroll_behavior = if fits_on_the_screen {
16595 self.request_autoscroll(Autoscroll::fit(), cx);
16596 SelectSyntaxNodeScrollBehavior::FitSelection
16597 } else if is_selection_reversed {
16598 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16599 SelectSyntaxNodeScrollBehavior::CursorTop
16600 } else {
16601 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16602 SelectSyntaxNodeScrollBehavior::CursorBottom
16603 };
16604
16605 self.select_syntax_node_history.push((
16606 old_selections,
16607 scroll_behavior,
16608 is_selection_reversed,
16609 ));
16610 }
16611
16612 pub fn select_smaller_syntax_node(
16613 &mut self,
16614 _: &SelectSmallerSyntaxNode,
16615 window: &mut Window,
16616 cx: &mut Context<Self>,
16617 ) {
16618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16619
16620 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16621 self.select_syntax_node_history.pop()
16622 {
16623 if let Some(selection) = selections.last_mut() {
16624 selection.reversed = is_selection_reversed;
16625 }
16626
16627 self.select_syntax_node_history.disable_clearing = true;
16628 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16629 s.select(selections.to_vec());
16630 });
16631 self.select_syntax_node_history.disable_clearing = false;
16632
16633 match scroll_behavior {
16634 SelectSyntaxNodeScrollBehavior::CursorTop => {
16635 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16636 }
16637 SelectSyntaxNodeScrollBehavior::FitSelection => {
16638 self.request_autoscroll(Autoscroll::fit(), cx);
16639 }
16640 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16641 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16642 }
16643 }
16644 }
16645 }
16646
16647 pub fn unwrap_syntax_node(
16648 &mut self,
16649 _: &UnwrapSyntaxNode,
16650 window: &mut Window,
16651 cx: &mut Context<Self>,
16652 ) {
16653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16654
16655 let buffer = self.buffer.read(cx).snapshot(cx);
16656 let selections = self
16657 .selections
16658 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16659 .into_iter()
16660 // subtracting the offset requires sorting
16661 .sorted_by_key(|i| i.start);
16662
16663 let full_edits = selections
16664 .into_iter()
16665 .filter_map(|selection| {
16666 let child = if selection.is_empty()
16667 && let Some((_, ancestor_range)) =
16668 buffer.syntax_ancestor(selection.start..selection.end)
16669 {
16670 ancestor_range
16671 } else {
16672 selection.range()
16673 };
16674
16675 let mut parent = child.clone();
16676 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16677 parent = ancestor_range;
16678 if parent.start < child.start || parent.end > child.end {
16679 break;
16680 }
16681 }
16682
16683 if parent == child {
16684 return None;
16685 }
16686 let text = buffer.text_for_range(child).collect::<String>();
16687 Some((selection.id, parent, text))
16688 })
16689 .collect::<Vec<_>>();
16690 if full_edits.is_empty() {
16691 return;
16692 }
16693
16694 self.transact(window, cx, |this, window, cx| {
16695 this.buffer.update(cx, |buffer, cx| {
16696 buffer.edit(
16697 full_edits
16698 .iter()
16699 .map(|(_, p, t)| (p.clone(), t.clone()))
16700 .collect::<Vec<_>>(),
16701 None,
16702 cx,
16703 );
16704 });
16705 this.change_selections(Default::default(), window, cx, |s| {
16706 let mut offset = 0;
16707 let mut selections = vec![];
16708 for (id, parent, text) in full_edits {
16709 let start = parent.start - offset;
16710 offset += (parent.end - parent.start) - text.len();
16711 selections.push(Selection {
16712 id,
16713 start,
16714 end: start + text.len(),
16715 reversed: false,
16716 goal: Default::default(),
16717 });
16718 }
16719 s.select(selections);
16720 });
16721 });
16722 }
16723
16724 pub fn select_next_syntax_node(
16725 &mut self,
16726 _: &SelectNextSyntaxNode,
16727 window: &mut Window,
16728 cx: &mut Context<Self>,
16729 ) {
16730 let old_selections: Box<[_]> = self
16731 .selections
16732 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16733 .into();
16734 if old_selections.is_empty() {
16735 return;
16736 }
16737
16738 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16739
16740 let buffer = self.buffer.read(cx).snapshot(cx);
16741 let mut selected_sibling = false;
16742
16743 let new_selections = old_selections
16744 .iter()
16745 .map(|selection| {
16746 let old_range = selection.start..selection.end;
16747
16748 let old_range =
16749 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16750 let excerpt = buffer.excerpt_containing(old_range.clone());
16751
16752 if let Some(mut excerpt) = excerpt
16753 && let Some(node) = excerpt
16754 .buffer()
16755 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16756 {
16757 let new_range = excerpt.map_range_from_buffer(
16758 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16759 );
16760 selected_sibling = true;
16761 Selection {
16762 id: selection.id,
16763 start: new_range.start,
16764 end: new_range.end,
16765 goal: SelectionGoal::None,
16766 reversed: selection.reversed,
16767 }
16768 } else {
16769 selection.clone()
16770 }
16771 })
16772 .collect::<Vec<_>>();
16773
16774 if selected_sibling {
16775 self.change_selections(
16776 SelectionEffects::scroll(Autoscroll::fit()),
16777 window,
16778 cx,
16779 |s| {
16780 s.select(new_selections);
16781 },
16782 );
16783 }
16784 }
16785
16786 pub fn select_prev_syntax_node(
16787 &mut self,
16788 _: &SelectPreviousSyntaxNode,
16789 window: &mut Window,
16790 cx: &mut Context<Self>,
16791 ) {
16792 let old_selections: Box<[_]> = self
16793 .selections
16794 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16795 .into();
16796 if old_selections.is_empty() {
16797 return;
16798 }
16799
16800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16801
16802 let buffer = self.buffer.read(cx).snapshot(cx);
16803 let mut selected_sibling = false;
16804
16805 let new_selections = old_selections
16806 .iter()
16807 .map(|selection| {
16808 let old_range = selection.start..selection.end;
16809 let old_range =
16810 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16811 let excerpt = buffer.excerpt_containing(old_range.clone());
16812
16813 if let Some(mut excerpt) = excerpt
16814 && let Some(node) = excerpt
16815 .buffer()
16816 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16817 {
16818 let new_range = excerpt.map_range_from_buffer(
16819 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16820 );
16821 selected_sibling = true;
16822 Selection {
16823 id: selection.id,
16824 start: new_range.start,
16825 end: new_range.end,
16826 goal: SelectionGoal::None,
16827 reversed: selection.reversed,
16828 }
16829 } else {
16830 selection.clone()
16831 }
16832 })
16833 .collect::<Vec<_>>();
16834
16835 if selected_sibling {
16836 self.change_selections(
16837 SelectionEffects::scroll(Autoscroll::fit()),
16838 window,
16839 cx,
16840 |s| {
16841 s.select(new_selections);
16842 },
16843 );
16844 }
16845 }
16846
16847 pub fn move_to_start_of_larger_syntax_node(
16848 &mut self,
16849 _: &MoveToStartOfLargerSyntaxNode,
16850 window: &mut Window,
16851 cx: &mut Context<Self>,
16852 ) {
16853 self.move_cursors_to_syntax_nodes(window, cx, false);
16854 }
16855
16856 pub fn move_to_end_of_larger_syntax_node(
16857 &mut self,
16858 _: &MoveToEndOfLargerSyntaxNode,
16859 window: &mut Window,
16860 cx: &mut Context<Self>,
16861 ) {
16862 self.move_cursors_to_syntax_nodes(window, cx, true);
16863 }
16864
16865 fn find_syntax_node_boundary(
16866 &self,
16867 selection_pos: MultiBufferOffset,
16868 move_to_end: bool,
16869 display_map: &DisplaySnapshot,
16870 buffer: &MultiBufferSnapshot,
16871 ) -> MultiBufferOffset {
16872 let old_range = selection_pos..selection_pos;
16873 let mut new_pos = selection_pos;
16874 let mut search_range = old_range;
16875 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16876 search_range = range.clone();
16877 if !node.is_named()
16878 || display_map.intersects_fold(range.start)
16879 || display_map.intersects_fold(range.end)
16880 // If cursor is already at the end of the syntax node, continue searching
16881 || (move_to_end && range.end == selection_pos)
16882 // If cursor is already at the start of the syntax node, continue searching
16883 || (!move_to_end && range.start == selection_pos)
16884 {
16885 continue;
16886 }
16887
16888 // If we found a string_content node, find the largest parent that is still string_content
16889 // Enables us to skip to the end of strings without taking multiple steps inside the string
16890 let (_, final_range) = if node.kind() == "string_content" {
16891 let mut current_node = node;
16892 let mut current_range = range;
16893 while let Some((parent, parent_range)) =
16894 buffer.syntax_ancestor(current_range.clone())
16895 {
16896 if parent.kind() == "string_content" {
16897 current_node = parent;
16898 current_range = parent_range;
16899 } else {
16900 break;
16901 }
16902 }
16903
16904 (current_node, current_range)
16905 } else {
16906 (node, range)
16907 };
16908
16909 new_pos = if move_to_end {
16910 final_range.end
16911 } else {
16912 final_range.start
16913 };
16914
16915 break;
16916 }
16917
16918 new_pos
16919 }
16920
16921 fn move_cursors_to_syntax_nodes(
16922 &mut self,
16923 window: &mut Window,
16924 cx: &mut Context<Self>,
16925 move_to_end: bool,
16926 ) -> bool {
16927 let old_selections: Box<[_]> = self
16928 .selections
16929 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16930 .into();
16931 if old_selections.is_empty() {
16932 return false;
16933 }
16934
16935 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16936
16937 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16938 let buffer = self.buffer.read(cx).snapshot(cx);
16939
16940 let mut any_cursor_moved = false;
16941 let new_selections = old_selections
16942 .iter()
16943 .map(|selection| {
16944 if !selection.is_empty() {
16945 return selection.clone();
16946 }
16947
16948 let selection_pos = selection.head();
16949 let new_pos = self.find_syntax_node_boundary(
16950 selection_pos,
16951 move_to_end,
16952 &display_map,
16953 &buffer,
16954 );
16955
16956 any_cursor_moved |= new_pos != selection_pos;
16957
16958 Selection {
16959 id: selection.id,
16960 start: new_pos,
16961 end: new_pos,
16962 goal: SelectionGoal::None,
16963 reversed: false,
16964 }
16965 })
16966 .collect::<Vec<_>>();
16967
16968 self.change_selections(Default::default(), window, cx, |s| {
16969 s.select(new_selections);
16970 });
16971 self.request_autoscroll(Autoscroll::newest(), cx);
16972
16973 any_cursor_moved
16974 }
16975
16976 pub fn select_to_start_of_larger_syntax_node(
16977 &mut self,
16978 _: &SelectToStartOfLargerSyntaxNode,
16979 window: &mut Window,
16980 cx: &mut Context<Self>,
16981 ) {
16982 self.select_to_syntax_nodes(window, cx, false);
16983 }
16984
16985 pub fn select_to_end_of_larger_syntax_node(
16986 &mut self,
16987 _: &SelectToEndOfLargerSyntaxNode,
16988 window: &mut Window,
16989 cx: &mut Context<Self>,
16990 ) {
16991 self.select_to_syntax_nodes(window, cx, true);
16992 }
16993
16994 fn select_to_syntax_nodes(
16995 &mut self,
16996 window: &mut Window,
16997 cx: &mut Context<Self>,
16998 move_to_end: bool,
16999 ) {
17000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17001
17002 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17003 let buffer = self.buffer.read(cx).snapshot(cx);
17004 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17005
17006 let new_selections = old_selections
17007 .iter()
17008 .map(|selection| {
17009 let new_pos = self.find_syntax_node_boundary(
17010 selection.head(),
17011 move_to_end,
17012 &display_map,
17013 &buffer,
17014 );
17015
17016 let mut new_selection = selection.clone();
17017 new_selection.set_head(new_pos, SelectionGoal::None);
17018 new_selection
17019 })
17020 .collect::<Vec<_>>();
17021
17022 self.change_selections(Default::default(), window, cx, |s| {
17023 s.select(new_selections);
17024 });
17025 }
17026
17027 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
17028 if !EditorSettings::get_global(cx).gutter.runnables || !self.enable_runnables {
17029 self.clear_tasks();
17030 return Task::ready(());
17031 }
17032 let project = self.project().map(Entity::downgrade);
17033 let task_sources = self.lsp_task_sources(cx);
17034 let multi_buffer = self.buffer.downgrade();
17035 cx.spawn_in(window, async move |editor, cx| {
17036 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
17037 let Some(project) = project.and_then(|p| p.upgrade()) else {
17038 return;
17039 };
17040 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
17041 this.display_map.update(cx, |map, cx| map.snapshot(cx))
17042 }) else {
17043 return;
17044 };
17045
17046 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
17047 if hide_runnables {
17048 return;
17049 }
17050 let new_rows =
17051 cx.background_spawn({
17052 let snapshot = display_snapshot.clone();
17053 async move {
17054 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
17055 }
17056 })
17057 .await;
17058 let Ok(lsp_tasks) =
17059 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
17060 else {
17061 return;
17062 };
17063 let lsp_tasks = lsp_tasks.await;
17064
17065 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
17066 lsp_tasks
17067 .into_iter()
17068 .flat_map(|(kind, tasks)| {
17069 tasks.into_iter().filter_map(move |(location, task)| {
17070 Some((kind.clone(), location?, task))
17071 })
17072 })
17073 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
17074 let buffer = location.target.buffer;
17075 let buffer_snapshot = buffer.read(cx).snapshot();
17076 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
17077 |(excerpt_id, snapshot, _)| {
17078 if snapshot.remote_id() == buffer_snapshot.remote_id() {
17079 display_snapshot
17080 .buffer_snapshot()
17081 .anchor_in_excerpt(excerpt_id, location.target.range.start)
17082 } else {
17083 None
17084 }
17085 },
17086 );
17087 if let Some(offset) = offset {
17088 let task_buffer_range =
17089 location.target.range.to_point(&buffer_snapshot);
17090 let context_buffer_range =
17091 task_buffer_range.to_offset(&buffer_snapshot);
17092 let context_range = BufferOffset(context_buffer_range.start)
17093 ..BufferOffset(context_buffer_range.end);
17094
17095 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
17096 .or_insert_with(|| RunnableTasks {
17097 templates: Vec::new(),
17098 offset,
17099 column: task_buffer_range.start.column,
17100 extra_variables: HashMap::default(),
17101 context_range,
17102 })
17103 .templates
17104 .push((kind, task.original_task().clone()));
17105 }
17106
17107 acc
17108 })
17109 }) else {
17110 return;
17111 };
17112
17113 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
17114 buffer.language_settings(cx).tasks.prefer_lsp
17115 }) else {
17116 return;
17117 };
17118
17119 let rows = Self::runnable_rows(
17120 project,
17121 display_snapshot,
17122 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
17123 new_rows,
17124 cx.clone(),
17125 )
17126 .await;
17127 editor
17128 .update(cx, |editor, _| {
17129 editor.clear_tasks();
17130 for (key, mut value) in rows {
17131 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
17132 value.templates.extend(lsp_tasks.templates);
17133 }
17134
17135 editor.insert_tasks(key, value);
17136 }
17137 for (key, value) in lsp_tasks_by_rows {
17138 editor.insert_tasks(key, value);
17139 }
17140 })
17141 .ok();
17142 })
17143 }
17144 fn fetch_runnable_ranges(
17145 snapshot: &DisplaySnapshot,
17146 range: Range<Anchor>,
17147 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
17148 snapshot.buffer_snapshot().runnable_ranges(range).collect()
17149 }
17150
17151 fn runnable_rows(
17152 project: Entity<Project>,
17153 snapshot: DisplaySnapshot,
17154 prefer_lsp: bool,
17155 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
17156 cx: AsyncWindowContext,
17157 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
17158 cx.spawn(async move |cx| {
17159 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
17160 for (run_range, mut runnable) in runnable_ranges {
17161 let Some(tasks) = cx
17162 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
17163 .ok()
17164 else {
17165 continue;
17166 };
17167 let mut tasks = tasks.await;
17168
17169 if prefer_lsp {
17170 tasks.retain(|(task_kind, _)| {
17171 !matches!(task_kind, TaskSourceKind::Language { .. })
17172 });
17173 }
17174 if tasks.is_empty() {
17175 continue;
17176 }
17177
17178 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
17179 let Some(row) = snapshot
17180 .buffer_snapshot()
17181 .buffer_line_for_row(MultiBufferRow(point.row))
17182 .map(|(_, range)| range.start.row)
17183 else {
17184 continue;
17185 };
17186
17187 let context_range =
17188 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
17189 runnable_rows.push((
17190 (runnable.buffer_id, row),
17191 RunnableTasks {
17192 templates: tasks,
17193 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
17194 context_range,
17195 column: point.column,
17196 extra_variables: runnable.extra_captures,
17197 },
17198 ));
17199 }
17200 runnable_rows
17201 })
17202 }
17203
17204 fn templates_with_tags(
17205 project: &Entity<Project>,
17206 runnable: &mut Runnable,
17207 cx: &mut App,
17208 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
17209 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
17210 let (worktree_id, file) = project
17211 .buffer_for_id(runnable.buffer, cx)
17212 .and_then(|buffer| buffer.read(cx).file())
17213 .map(|file| (file.worktree_id(cx), file.clone()))
17214 .unzip();
17215
17216 (
17217 project.task_store().read(cx).task_inventory().cloned(),
17218 worktree_id,
17219 file,
17220 )
17221 });
17222
17223 let tags = mem::take(&mut runnable.tags);
17224 let language = runnable.language.clone();
17225 cx.spawn(async move |cx| {
17226 let mut templates_with_tags = Vec::new();
17227 if let Some(inventory) = inventory {
17228 for RunnableTag(tag) in tags {
17229 let new_tasks = inventory.update(cx, |inventory, cx| {
17230 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
17231 });
17232 templates_with_tags.extend(new_tasks.await.into_iter().filter(
17233 move |(_, template)| {
17234 template.tags.iter().any(|source_tag| source_tag == &tag)
17235 },
17236 ));
17237 }
17238 }
17239 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
17240
17241 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
17242 // Strongest source wins; if we have worktree tag binding, prefer that to
17243 // global and language bindings;
17244 // if we have a global binding, prefer that to language binding.
17245 let first_mismatch = templates_with_tags
17246 .iter()
17247 .position(|(tag_source, _)| tag_source != leading_tag_source);
17248 if let Some(index) = first_mismatch {
17249 templates_with_tags.truncate(index);
17250 }
17251 }
17252
17253 templates_with_tags
17254 })
17255 }
17256
17257 pub fn move_to_enclosing_bracket(
17258 &mut self,
17259 _: &MoveToEnclosingBracket,
17260 window: &mut Window,
17261 cx: &mut Context<Self>,
17262 ) {
17263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17264 self.change_selections(Default::default(), window, cx, |s| {
17265 s.move_offsets_with(&mut |snapshot, selection| {
17266 let Some(enclosing_bracket_ranges) =
17267 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17268 else {
17269 return;
17270 };
17271
17272 let mut best_length = usize::MAX;
17273 let mut best_inside = false;
17274 let mut best_in_bracket_range = false;
17275 let mut best_destination = None;
17276 for (open, close) in enclosing_bracket_ranges {
17277 let close = close.to_inclusive();
17278 let length = *close.end() - open.start;
17279 let inside = selection.start >= open.end && selection.end <= *close.start();
17280 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17281 || close.contains(&selection.head());
17282
17283 // If best is next to a bracket and current isn't, skip
17284 if !in_bracket_range && best_in_bracket_range {
17285 continue;
17286 }
17287
17288 // Prefer smaller lengths unless best is inside and current isn't
17289 if length > best_length && (best_inside || !inside) {
17290 continue;
17291 }
17292
17293 best_length = length;
17294 best_inside = inside;
17295 best_in_bracket_range = in_bracket_range;
17296 best_destination = Some(
17297 if close.contains(&selection.start) && close.contains(&selection.end) {
17298 if inside { open.end } else { open.start }
17299 } else if inside {
17300 *close.start()
17301 } else {
17302 *close.end()
17303 },
17304 );
17305 }
17306
17307 if let Some(destination) = best_destination {
17308 selection.collapse_to(destination, SelectionGoal::None);
17309 }
17310 })
17311 });
17312 }
17313
17314 pub fn undo_selection(
17315 &mut self,
17316 _: &UndoSelection,
17317 window: &mut Window,
17318 cx: &mut Context<Self>,
17319 ) {
17320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17321 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17322 self.selection_history.mode = SelectionHistoryMode::Undoing;
17323 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17324 this.end_selection(window, cx);
17325 this.change_selections(
17326 SelectionEffects::scroll(Autoscroll::newest()),
17327 window,
17328 cx,
17329 |s| s.select_anchors(entry.selections.to_vec()),
17330 );
17331 });
17332 self.selection_history.mode = SelectionHistoryMode::Normal;
17333
17334 self.select_next_state = entry.select_next_state;
17335 self.select_prev_state = entry.select_prev_state;
17336 self.add_selections_state = entry.add_selections_state;
17337 }
17338 }
17339
17340 pub fn redo_selection(
17341 &mut self,
17342 _: &RedoSelection,
17343 window: &mut Window,
17344 cx: &mut Context<Self>,
17345 ) {
17346 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17347 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17348 self.selection_history.mode = SelectionHistoryMode::Redoing;
17349 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17350 this.end_selection(window, cx);
17351 this.change_selections(
17352 SelectionEffects::scroll(Autoscroll::newest()),
17353 window,
17354 cx,
17355 |s| s.select_anchors(entry.selections.to_vec()),
17356 );
17357 });
17358 self.selection_history.mode = SelectionHistoryMode::Normal;
17359
17360 self.select_next_state = entry.select_next_state;
17361 self.select_prev_state = entry.select_prev_state;
17362 self.add_selections_state = entry.add_selections_state;
17363 }
17364 }
17365
17366 pub fn expand_excerpts(
17367 &mut self,
17368 action: &ExpandExcerpts,
17369 _: &mut Window,
17370 cx: &mut Context<Self>,
17371 ) {
17372 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17373 }
17374
17375 pub fn expand_excerpts_down(
17376 &mut self,
17377 action: &ExpandExcerptsDown,
17378 _: &mut Window,
17379 cx: &mut Context<Self>,
17380 ) {
17381 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17382 }
17383
17384 pub fn expand_excerpts_up(
17385 &mut self,
17386 action: &ExpandExcerptsUp,
17387 _: &mut Window,
17388 cx: &mut Context<Self>,
17389 ) {
17390 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17391 }
17392
17393 pub fn expand_excerpts_for_direction(
17394 &mut self,
17395 lines: u32,
17396 direction: ExpandExcerptDirection,
17397 cx: &mut Context<Self>,
17398 ) {
17399 let selections = self.selections.disjoint_anchors_arc();
17400
17401 let lines = if lines == 0 {
17402 EditorSettings::get_global(cx).expand_excerpt_lines
17403 } else {
17404 lines
17405 };
17406
17407 let snapshot = self.buffer.read(cx).snapshot(cx);
17408 let excerpt_ids = selections
17409 .iter()
17410 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17411 .unique()
17412 .sorted()
17413 .collect::<Vec<_>>();
17414
17415 if self.delegate_expand_excerpts {
17416 cx.emit(EditorEvent::ExpandExcerptsRequested {
17417 excerpt_ids,
17418 lines,
17419 direction,
17420 });
17421 return;
17422 }
17423
17424 self.buffer.update(cx, |buffer, cx| {
17425 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17426 })
17427 }
17428
17429 pub fn expand_excerpt(
17430 &mut self,
17431 excerpt: ExcerptId,
17432 direction: ExpandExcerptDirection,
17433 window: &mut Window,
17434 cx: &mut Context<Self>,
17435 ) {
17436 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17437
17438 if self.delegate_expand_excerpts {
17439 cx.emit(EditorEvent::ExpandExcerptsRequested {
17440 excerpt_ids: vec![excerpt],
17441 lines: lines_to_expand,
17442 direction,
17443 });
17444 return;
17445 }
17446
17447 let current_scroll_position = self.scroll_position(cx);
17448 let mut scroll = None;
17449
17450 if direction == ExpandExcerptDirection::Down {
17451 let multi_buffer = self.buffer.read(cx);
17452 let snapshot = multi_buffer.snapshot(cx);
17453 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17454 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17455 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17456 {
17457 let buffer_snapshot = buffer.read(cx).snapshot();
17458 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17459 let last_row = buffer_snapshot.max_point().row;
17460 let lines_below = last_row.saturating_sub(excerpt_end_row);
17461 if lines_below >= lines_to_expand {
17462 scroll = Some(
17463 current_scroll_position
17464 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17465 );
17466 }
17467 }
17468 }
17469 if direction == ExpandExcerptDirection::Up
17470 && self
17471 .buffer
17472 .read(cx)
17473 .snapshot(cx)
17474 .excerpt_before(excerpt)
17475 .is_none()
17476 {
17477 scroll = Some(current_scroll_position);
17478 }
17479
17480 self.buffer.update(cx, |buffer, cx| {
17481 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17482 });
17483
17484 if let Some(new_scroll_position) = scroll {
17485 self.set_scroll_position(new_scroll_position, window, cx);
17486 }
17487 }
17488
17489 pub fn go_to_singleton_buffer_point(
17490 &mut self,
17491 point: Point,
17492 window: &mut Window,
17493 cx: &mut Context<Self>,
17494 ) {
17495 self.go_to_singleton_buffer_range(point..point, window, cx);
17496 }
17497
17498 pub fn go_to_singleton_buffer_range(
17499 &mut self,
17500 range: Range<Point>,
17501 window: &mut Window,
17502 cx: &mut Context<Self>,
17503 ) {
17504 let multibuffer = self.buffer().read(cx);
17505 let Some(buffer) = multibuffer.as_singleton() else {
17506 return;
17507 };
17508 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17509 return;
17510 };
17511 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17512 return;
17513 };
17514 self.change_selections(
17515 SelectionEffects::default().nav_history(true),
17516 window,
17517 cx,
17518 |s| s.select_anchor_ranges([start..end]),
17519 );
17520 }
17521
17522 pub fn go_to_diagnostic(
17523 &mut self,
17524 action: &GoToDiagnostic,
17525 window: &mut Window,
17526 cx: &mut Context<Self>,
17527 ) {
17528 if !self.diagnostics_enabled() {
17529 return;
17530 }
17531 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17532 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17533 }
17534
17535 pub fn go_to_prev_diagnostic(
17536 &mut self,
17537 action: &GoToPreviousDiagnostic,
17538 window: &mut Window,
17539 cx: &mut Context<Self>,
17540 ) {
17541 if !self.diagnostics_enabled() {
17542 return;
17543 }
17544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17545 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17546 }
17547
17548 pub fn go_to_diagnostic_impl(
17549 &mut self,
17550 direction: Direction,
17551 severity: GoToDiagnosticSeverityFilter,
17552 window: &mut Window,
17553 cx: &mut Context<Self>,
17554 ) {
17555 let buffer = self.buffer.read(cx).snapshot(cx);
17556 let selection = self
17557 .selections
17558 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17559
17560 let mut active_group_id = None;
17561 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17562 && active_group.active_range.start.to_offset(&buffer) == selection.start
17563 {
17564 active_group_id = Some(active_group.group_id);
17565 }
17566
17567 fn filtered<'a>(
17568 severity: GoToDiagnosticSeverityFilter,
17569 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17570 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17571 diagnostics
17572 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17573 .filter(|entry| entry.range.start != entry.range.end)
17574 .filter(|entry| !entry.diagnostic.is_unnecessary)
17575 }
17576
17577 let before = filtered(
17578 severity,
17579 buffer
17580 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17581 .filter(|entry| entry.range.start <= selection.start),
17582 );
17583 let after = filtered(
17584 severity,
17585 buffer
17586 .diagnostics_in_range(selection.start..buffer.len())
17587 .filter(|entry| entry.range.start >= selection.start),
17588 );
17589
17590 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17591 if direction == Direction::Prev {
17592 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17593 {
17594 for diagnostic in prev_diagnostics.into_iter().rev() {
17595 if diagnostic.range.start != selection.start
17596 || active_group_id
17597 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17598 {
17599 found = Some(diagnostic);
17600 break 'outer;
17601 }
17602 }
17603 }
17604 } else {
17605 for diagnostic in after.chain(before) {
17606 if diagnostic.range.start != selection.start
17607 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17608 {
17609 found = Some(diagnostic);
17610 break;
17611 }
17612 }
17613 }
17614 let Some(next_diagnostic) = found else {
17615 return;
17616 };
17617
17618 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17619 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17620 return;
17621 };
17622 let snapshot = self.snapshot(window, cx);
17623 if snapshot.intersects_fold(next_diagnostic.range.start) {
17624 self.unfold_ranges(
17625 std::slice::from_ref(&next_diagnostic.range),
17626 true,
17627 false,
17628 cx,
17629 );
17630 }
17631 self.change_selections(Default::default(), window, cx, |s| {
17632 s.select_ranges(vec![
17633 next_diagnostic.range.start..next_diagnostic.range.start,
17634 ])
17635 });
17636 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17637 self.refresh_edit_prediction(false, true, window, cx);
17638 }
17639
17640 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17642 let snapshot = self.snapshot(window, cx);
17643 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17644 self.go_to_hunk_before_or_after_position(
17645 &snapshot,
17646 selection.head(),
17647 Direction::Next,
17648 window,
17649 cx,
17650 );
17651 }
17652
17653 pub fn go_to_hunk_before_or_after_position(
17654 &mut self,
17655 snapshot: &EditorSnapshot,
17656 position: Point,
17657 direction: Direction,
17658 window: &mut Window,
17659 cx: &mut Context<Editor>,
17660 ) {
17661 let row = if direction == Direction::Next {
17662 self.hunk_after_position(snapshot, position)
17663 .map(|hunk| hunk.row_range.start)
17664 } else {
17665 self.hunk_before_position(snapshot, position)
17666 };
17667
17668 if let Some(row) = row {
17669 let destination = Point::new(row.0, 0);
17670 let autoscroll = Autoscroll::center();
17671
17672 self.unfold_ranges(&[destination..destination], false, false, cx);
17673 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17674 s.select_ranges([destination..destination]);
17675 });
17676 }
17677 }
17678
17679 fn hunk_after_position(
17680 &mut self,
17681 snapshot: &EditorSnapshot,
17682 position: Point,
17683 ) -> Option<MultiBufferDiffHunk> {
17684 snapshot
17685 .buffer_snapshot()
17686 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17687 .find(|hunk| hunk.row_range.start.0 > position.row)
17688 .or_else(|| {
17689 snapshot
17690 .buffer_snapshot()
17691 .diff_hunks_in_range(Point::zero()..position)
17692 .find(|hunk| hunk.row_range.end.0 < position.row)
17693 })
17694 }
17695
17696 fn go_to_prev_hunk(
17697 &mut self,
17698 _: &GoToPreviousHunk,
17699 window: &mut Window,
17700 cx: &mut Context<Self>,
17701 ) {
17702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17703 let snapshot = self.snapshot(window, cx);
17704 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17705 self.go_to_hunk_before_or_after_position(
17706 &snapshot,
17707 selection.head(),
17708 Direction::Prev,
17709 window,
17710 cx,
17711 );
17712 }
17713
17714 fn hunk_before_position(
17715 &mut self,
17716 snapshot: &EditorSnapshot,
17717 position: Point,
17718 ) -> Option<MultiBufferRow> {
17719 snapshot
17720 .buffer_snapshot()
17721 .diff_hunk_before(position)
17722 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17723 }
17724
17725 fn go_to_next_change(
17726 &mut self,
17727 _: &GoToNextChange,
17728 window: &mut Window,
17729 cx: &mut Context<Self>,
17730 ) {
17731 if let Some(selections) = self
17732 .change_list
17733 .next_change(1, Direction::Next)
17734 .map(|s| s.to_vec())
17735 {
17736 self.change_selections(Default::default(), window, cx, |s| {
17737 let map = s.display_snapshot();
17738 s.select_display_ranges(selections.iter().map(|a| {
17739 let point = a.to_display_point(&map);
17740 point..point
17741 }))
17742 })
17743 }
17744 }
17745
17746 fn go_to_previous_change(
17747 &mut self,
17748 _: &GoToPreviousChange,
17749 window: &mut Window,
17750 cx: &mut Context<Self>,
17751 ) {
17752 if let Some(selections) = self
17753 .change_list
17754 .next_change(1, Direction::Prev)
17755 .map(|s| s.to_vec())
17756 {
17757 self.change_selections(Default::default(), window, cx, |s| {
17758 let map = s.display_snapshot();
17759 s.select_display_ranges(selections.iter().map(|a| {
17760 let point = a.to_display_point(&map);
17761 point..point
17762 }))
17763 })
17764 }
17765 }
17766
17767 pub fn go_to_next_document_highlight(
17768 &mut self,
17769 _: &GoToNextDocumentHighlight,
17770 window: &mut Window,
17771 cx: &mut Context<Self>,
17772 ) {
17773 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17774 }
17775
17776 pub fn go_to_prev_document_highlight(
17777 &mut self,
17778 _: &GoToPreviousDocumentHighlight,
17779 window: &mut Window,
17780 cx: &mut Context<Self>,
17781 ) {
17782 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17783 }
17784
17785 pub fn go_to_document_highlight_before_or_after_position(
17786 &mut self,
17787 direction: Direction,
17788 window: &mut Window,
17789 cx: &mut Context<Editor>,
17790 ) {
17791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17792 let snapshot = self.snapshot(window, cx);
17793 let buffer = &snapshot.buffer_snapshot();
17794 let position = self
17795 .selections
17796 .newest::<Point>(&snapshot.display_snapshot)
17797 .head();
17798 let anchor_position = buffer.anchor_after(position);
17799
17800 // Get all document highlights (both read and write)
17801 let mut all_highlights = Vec::new();
17802
17803 if let Some((_, read_highlights)) = self
17804 .background_highlights
17805 .get(&HighlightKey::DocumentHighlightRead)
17806 {
17807 all_highlights.extend(read_highlights.iter());
17808 }
17809
17810 if let Some((_, write_highlights)) = self
17811 .background_highlights
17812 .get(&HighlightKey::DocumentHighlightWrite)
17813 {
17814 all_highlights.extend(write_highlights.iter());
17815 }
17816
17817 if all_highlights.is_empty() {
17818 return;
17819 }
17820
17821 // Sort highlights by position
17822 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17823
17824 let target_highlight = match direction {
17825 Direction::Next => {
17826 // Find the first highlight after the current position
17827 all_highlights
17828 .iter()
17829 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17830 }
17831 Direction::Prev => {
17832 // Find the last highlight before the current position
17833 all_highlights
17834 .iter()
17835 .rev()
17836 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17837 }
17838 };
17839
17840 if let Some(highlight) = target_highlight {
17841 let destination = highlight.start.to_point(buffer);
17842 let autoscroll = Autoscroll::center();
17843
17844 self.unfold_ranges(&[destination..destination], false, false, cx);
17845 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17846 s.select_ranges([destination..destination]);
17847 });
17848 }
17849 }
17850
17851 fn go_to_line<T: 'static>(
17852 &mut self,
17853 position: Anchor,
17854 highlight_color: Option<Hsla>,
17855 window: &mut Window,
17856 cx: &mut Context<Self>,
17857 ) {
17858 let snapshot = self.snapshot(window, cx).display_snapshot;
17859 let position = position.to_point(&snapshot.buffer_snapshot());
17860 let start = snapshot
17861 .buffer_snapshot()
17862 .clip_point(Point::new(position.row, 0), Bias::Left);
17863 let end = start + Point::new(1, 0);
17864 let start = snapshot.buffer_snapshot().anchor_before(start);
17865 let end = snapshot.buffer_snapshot().anchor_before(end);
17866
17867 self.highlight_rows::<T>(
17868 start..end,
17869 highlight_color
17870 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17871 Default::default(),
17872 cx,
17873 );
17874
17875 if self.buffer.read(cx).is_singleton() {
17876 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17877 }
17878 }
17879
17880 pub fn go_to_definition(
17881 &mut self,
17882 _: &GoToDefinition,
17883 window: &mut Window,
17884 cx: &mut Context<Self>,
17885 ) -> Task<Result<Navigated>> {
17886 let definition =
17887 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17888 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17889 cx.spawn_in(window, async move |editor, cx| {
17890 if definition.await? == Navigated::Yes {
17891 return Ok(Navigated::Yes);
17892 }
17893 match fallback_strategy {
17894 GoToDefinitionFallback::None => Ok(Navigated::No),
17895 GoToDefinitionFallback::FindAllReferences => {
17896 match editor.update_in(cx, |editor, window, cx| {
17897 editor.find_all_references(&FindAllReferences::default(), window, cx)
17898 })? {
17899 Some(references) => references.await,
17900 None => Ok(Navigated::No),
17901 }
17902 }
17903 }
17904 })
17905 }
17906
17907 pub fn go_to_declaration(
17908 &mut self,
17909 _: &GoToDeclaration,
17910 window: &mut Window,
17911 cx: &mut Context<Self>,
17912 ) -> Task<Result<Navigated>> {
17913 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17914 }
17915
17916 pub fn go_to_declaration_split(
17917 &mut self,
17918 _: &GoToDeclaration,
17919 window: &mut Window,
17920 cx: &mut Context<Self>,
17921 ) -> Task<Result<Navigated>> {
17922 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17923 }
17924
17925 pub fn go_to_implementation(
17926 &mut self,
17927 _: &GoToImplementation,
17928 window: &mut Window,
17929 cx: &mut Context<Self>,
17930 ) -> Task<Result<Navigated>> {
17931 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17932 }
17933
17934 pub fn go_to_implementation_split(
17935 &mut self,
17936 _: &GoToImplementationSplit,
17937 window: &mut Window,
17938 cx: &mut Context<Self>,
17939 ) -> Task<Result<Navigated>> {
17940 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17941 }
17942
17943 pub fn go_to_type_definition(
17944 &mut self,
17945 _: &GoToTypeDefinition,
17946 window: &mut Window,
17947 cx: &mut Context<Self>,
17948 ) -> Task<Result<Navigated>> {
17949 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17950 }
17951
17952 pub fn go_to_definition_split(
17953 &mut self,
17954 _: &GoToDefinitionSplit,
17955 window: &mut Window,
17956 cx: &mut Context<Self>,
17957 ) -> Task<Result<Navigated>> {
17958 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17959 }
17960
17961 pub fn go_to_type_definition_split(
17962 &mut self,
17963 _: &GoToTypeDefinitionSplit,
17964 window: &mut Window,
17965 cx: &mut Context<Self>,
17966 ) -> Task<Result<Navigated>> {
17967 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17968 }
17969
17970 fn go_to_definition_of_kind(
17971 &mut self,
17972 kind: GotoDefinitionKind,
17973 split: bool,
17974 window: &mut Window,
17975 cx: &mut Context<Self>,
17976 ) -> Task<Result<Navigated>> {
17977 let Some(provider) = self.semantics_provider.clone() else {
17978 return Task::ready(Ok(Navigated::No));
17979 };
17980 let head = self
17981 .selections
17982 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17983 .head();
17984 let buffer = self.buffer.read(cx);
17985 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17986 return Task::ready(Ok(Navigated::No));
17987 };
17988 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17989 return Task::ready(Ok(Navigated::No));
17990 };
17991
17992 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17993
17994 cx.spawn_in(window, async move |editor, cx| {
17995 let Some(definitions) = definitions.await? else {
17996 return Ok(Navigated::No);
17997 };
17998 let navigated = editor
17999 .update_in(cx, |editor, window, cx| {
18000 editor.navigate_to_hover_links(
18001 Some(kind),
18002 definitions
18003 .into_iter()
18004 .filter(|location| {
18005 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18006 })
18007 .map(HoverLink::Text)
18008 .collect::<Vec<_>>(),
18009 nav_entry,
18010 split,
18011 window,
18012 cx,
18013 )
18014 })?
18015 .await?;
18016 anyhow::Ok(navigated)
18017 })
18018 }
18019
18020 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18021 let selection = self.selections.newest_anchor();
18022 let head = selection.head();
18023 let tail = selection.tail();
18024
18025 let Some((buffer, start_position)) =
18026 self.buffer.read(cx).text_anchor_for_position(head, cx)
18027 else {
18028 return;
18029 };
18030
18031 let end_position = if head != tail {
18032 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18033 return;
18034 };
18035 Some(pos)
18036 } else {
18037 None
18038 };
18039
18040 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18041 let url = if let Some(end_pos) = end_position {
18042 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18043 } else {
18044 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18045 };
18046
18047 if let Some(url) = url {
18048 cx.update(|window, cx| {
18049 if parse_zed_link(&url, cx).is_some() {
18050 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18051 } else {
18052 cx.open_url(&url);
18053 }
18054 })?;
18055 }
18056
18057 anyhow::Ok(())
18058 });
18059
18060 url_finder.detach();
18061 }
18062
18063 pub fn open_selected_filename(
18064 &mut self,
18065 _: &OpenSelectedFilename,
18066 window: &mut Window,
18067 cx: &mut Context<Self>,
18068 ) {
18069 let Some(workspace) = self.workspace() else {
18070 return;
18071 };
18072
18073 let position = self.selections.newest_anchor().head();
18074
18075 let Some((buffer, buffer_position)) =
18076 self.buffer.read(cx).text_anchor_for_position(position, cx)
18077 else {
18078 return;
18079 };
18080
18081 let project = self.project.clone();
18082
18083 cx.spawn_in(window, async move |_, cx| {
18084 let result = find_file(&buffer, project, buffer_position, cx).await;
18085
18086 if let Some((_, path)) = result {
18087 workspace
18088 .update_in(cx, |workspace, window, cx| {
18089 workspace.open_resolved_path(path, window, cx)
18090 })?
18091 .await?;
18092 }
18093 anyhow::Ok(())
18094 })
18095 .detach();
18096 }
18097
18098 pub(crate) fn navigate_to_hover_links(
18099 &mut self,
18100 kind: Option<GotoDefinitionKind>,
18101 definitions: Vec<HoverLink>,
18102 origin: Option<NavigationEntry>,
18103 split: bool,
18104 window: &mut Window,
18105 cx: &mut Context<Editor>,
18106 ) -> Task<Result<Navigated>> {
18107 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18108 let mut first_url_or_file = None;
18109 let definitions: Vec<_> = definitions
18110 .into_iter()
18111 .filter_map(|def| match def {
18112 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18113 HoverLink::InlayHint(lsp_location, server_id) => {
18114 let computation =
18115 self.compute_target_location(lsp_location, server_id, window, cx);
18116 Some(cx.background_spawn(computation))
18117 }
18118 HoverLink::Url(url) => {
18119 first_url_or_file = Some(Either::Left(url));
18120 None
18121 }
18122 HoverLink::File(path) => {
18123 first_url_or_file = Some(Either::Right(path));
18124 None
18125 }
18126 })
18127 .collect();
18128
18129 let workspace = self.workspace();
18130
18131 cx.spawn_in(window, async move |editor, cx| {
18132 let locations: Vec<Location> = future::join_all(definitions)
18133 .await
18134 .into_iter()
18135 .filter_map(|location| location.transpose())
18136 .collect::<Result<_>>()
18137 .context("location tasks")?;
18138 let mut locations = cx.update(|_, cx| {
18139 locations
18140 .into_iter()
18141 .map(|location| {
18142 let buffer = location.buffer.read(cx);
18143 (location.buffer, location.range.to_point(buffer))
18144 })
18145 .into_group_map()
18146 })?;
18147 let mut num_locations = 0;
18148 for ranges in locations.values_mut() {
18149 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18150 ranges.dedup();
18151 num_locations += ranges.len();
18152 }
18153
18154 if num_locations > 1 {
18155 let tab_kind = match kind {
18156 Some(GotoDefinitionKind::Implementation) => "Implementations",
18157 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18158 Some(GotoDefinitionKind::Declaration) => "Declarations",
18159 Some(GotoDefinitionKind::Type) => "Types",
18160 };
18161 let title = editor
18162 .update_in(cx, |_, _, cx| {
18163 let target = locations
18164 .iter()
18165 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18166 .map(|(buffer, location)| {
18167 buffer
18168 .read(cx)
18169 .text_for_range(location.clone())
18170 .collect::<String>()
18171 })
18172 .filter(|text| !text.contains('\n'))
18173 .unique()
18174 .take(3)
18175 .join(", ");
18176 if target.is_empty() {
18177 tab_kind.to_owned()
18178 } else {
18179 format!("{tab_kind} for {target}")
18180 }
18181 })
18182 .context("buffer title")?;
18183
18184 let Some(workspace) = workspace else {
18185 return Ok(Navigated::No);
18186 };
18187
18188 let opened = workspace
18189 .update_in(cx, |workspace, window, cx| {
18190 let allow_preview = PreviewTabsSettings::get_global(cx)
18191 .enable_preview_multibuffer_from_code_navigation;
18192 if let Some((target_editor, target_pane)) =
18193 Self::open_locations_in_multibuffer(
18194 workspace,
18195 locations,
18196 title,
18197 split,
18198 allow_preview,
18199 MultibufferSelectionMode::First,
18200 window,
18201 cx,
18202 )
18203 {
18204 // We create our own nav history instead of using
18205 // `target_editor.nav_history` because `nav_history`
18206 // seems to be populated asynchronously when an item
18207 // is added to a pane
18208 let mut nav_history = target_pane
18209 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18210 target_editor.update(cx, |editor, cx| {
18211 let nav_data = editor
18212 .navigation_data(editor.selections.newest_anchor().head(), cx);
18213 let target =
18214 Some(nav_history.navigation_entry(Some(
18215 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18216 )));
18217 nav_history.push_tag(origin, target);
18218 })
18219 }
18220 })
18221 .is_ok();
18222
18223 anyhow::Ok(Navigated::from_bool(opened))
18224 } else if num_locations == 0 {
18225 // If there is one url or file, open it directly
18226 match first_url_or_file {
18227 Some(Either::Left(url)) => {
18228 cx.update(|window, cx| {
18229 if parse_zed_link(&url, cx).is_some() {
18230 window
18231 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18232 } else {
18233 cx.open_url(&url);
18234 }
18235 })?;
18236 Ok(Navigated::Yes)
18237 }
18238 Some(Either::Right(path)) => {
18239 // TODO(andrew): respect preview tab settings
18240 // `enable_keep_preview_on_code_navigation` and
18241 // `enable_preview_file_from_code_navigation`
18242 let Some(workspace) = workspace else {
18243 return Ok(Navigated::No);
18244 };
18245 workspace
18246 .update_in(cx, |workspace, window, cx| {
18247 workspace.open_resolved_path(path, window, cx)
18248 })?
18249 .await?;
18250 Ok(Navigated::Yes)
18251 }
18252 None => Ok(Navigated::No),
18253 }
18254 } else {
18255 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18256 let target_range = target_ranges.first().unwrap().clone();
18257
18258 editor.update_in(cx, |editor, window, cx| {
18259 let range = editor.range_for_match(&target_range);
18260 let range = collapse_multiline_range(range);
18261
18262 if !split
18263 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18264 {
18265 editor.go_to_singleton_buffer_range(range, window, cx);
18266
18267 let target =
18268 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18269 if let Some(mut nav_history) = editor.nav_history.clone() {
18270 nav_history.push_tag(origin, target);
18271 }
18272 } else {
18273 let Some(workspace) = workspace else {
18274 return Navigated::No;
18275 };
18276 let pane = workspace.read(cx).active_pane().clone();
18277 window.defer(cx, move |window, cx| {
18278 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18279 workspace.update(cx, |workspace, cx| {
18280 let pane = if split {
18281 workspace.adjacent_pane(window, cx)
18282 } else {
18283 workspace.active_pane().clone()
18284 };
18285
18286 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18287 let keep_old_preview = preview_tabs_settings
18288 .enable_keep_preview_on_code_navigation;
18289 let allow_new_preview = preview_tabs_settings
18290 .enable_preview_file_from_code_navigation;
18291
18292 let editor = workspace.open_project_item(
18293 pane.clone(),
18294 target_buffer.clone(),
18295 true,
18296 true,
18297 keep_old_preview,
18298 allow_new_preview,
18299 window,
18300 cx,
18301 );
18302 (editor, pane)
18303 });
18304 // We create our own nav history instead of using
18305 // `target_editor.nav_history` because `nav_history`
18306 // seems to be populated asynchronously when an item
18307 // is added to a pane
18308 let mut nav_history = target_pane
18309 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18310 target_editor.update(cx, |target_editor, cx| {
18311 // When selecting a definition in a different buffer, disable the nav history
18312 // to avoid creating a history entry at the previous cursor location.
18313 pane.update(cx, |pane, _| pane.disable_history());
18314 target_editor.go_to_singleton_buffer_range(range, window, cx);
18315
18316 let nav_data = target_editor.navigation_data(
18317 target_editor.selections.newest_anchor().head(),
18318 cx,
18319 );
18320 let target =
18321 Some(nav_history.navigation_entry(Some(
18322 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18323 )));
18324 nav_history.push_tag(origin, target);
18325 pane.update(cx, |pane, _| pane.enable_history());
18326 });
18327 });
18328 }
18329 Navigated::Yes
18330 })
18331 }
18332 })
18333 }
18334
18335 fn compute_target_location(
18336 &self,
18337 lsp_location: lsp::Location,
18338 server_id: LanguageServerId,
18339 window: &mut Window,
18340 cx: &mut Context<Self>,
18341 ) -> Task<anyhow::Result<Option<Location>>> {
18342 let Some(project) = self.project.clone() else {
18343 return Task::ready(Ok(None));
18344 };
18345
18346 cx.spawn_in(window, async move |editor, cx| {
18347 let location_task = editor.update(cx, |_, cx| {
18348 project.update(cx, |project, cx| {
18349 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18350 })
18351 })?;
18352 let location = Some({
18353 let target_buffer_handle = location_task.await.context("open local buffer")?;
18354 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18355 let target_start = target_buffer
18356 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18357 let target_end = target_buffer
18358 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18359 target_buffer.anchor_after(target_start)
18360 ..target_buffer.anchor_before(target_end)
18361 });
18362 Location {
18363 buffer: target_buffer_handle,
18364 range,
18365 }
18366 });
18367 Ok(location)
18368 })
18369 }
18370
18371 fn go_to_next_reference(
18372 &mut self,
18373 _: &GoToNextReference,
18374 window: &mut Window,
18375 cx: &mut Context<Self>,
18376 ) {
18377 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18378 if let Some(task) = task {
18379 task.detach();
18380 };
18381 }
18382
18383 fn go_to_prev_reference(
18384 &mut self,
18385 _: &GoToPreviousReference,
18386 window: &mut Window,
18387 cx: &mut Context<Self>,
18388 ) {
18389 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18390 if let Some(task) = task {
18391 task.detach();
18392 };
18393 }
18394
18395 pub fn go_to_reference_before_or_after_position(
18396 &mut self,
18397 direction: Direction,
18398 count: usize,
18399 window: &mut Window,
18400 cx: &mut Context<Self>,
18401 ) -> Option<Task<Result<()>>> {
18402 let selection = self.selections.newest_anchor();
18403 let head = selection.head();
18404
18405 let multi_buffer = self.buffer.read(cx);
18406
18407 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18408 let workspace = self.workspace()?;
18409 let project = workspace.read(cx).project().clone();
18410 let references =
18411 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18412 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18413 let Some(locations) = references.await? else {
18414 return Ok(());
18415 };
18416
18417 if locations.is_empty() {
18418 // totally normal - the cursor may be on something which is not
18419 // a symbol (e.g. a keyword)
18420 log::info!("no references found under cursor");
18421 return Ok(());
18422 }
18423
18424 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18425
18426 let (locations, current_location_index) =
18427 multi_buffer.update(cx, |multi_buffer, cx| {
18428 let mut locations = locations
18429 .into_iter()
18430 .filter_map(|loc| {
18431 let start = multi_buffer.buffer_anchor_to_anchor(
18432 &loc.buffer,
18433 loc.range.start,
18434 cx,
18435 )?;
18436 let end = multi_buffer.buffer_anchor_to_anchor(
18437 &loc.buffer,
18438 loc.range.end,
18439 cx,
18440 )?;
18441 Some(start..end)
18442 })
18443 .collect::<Vec<_>>();
18444
18445 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18446 // There is an O(n) implementation, but given this list will be
18447 // small (usually <100 items), the extra O(log(n)) factor isn't
18448 // worth the (surprisingly large amount of) extra complexity.
18449 locations
18450 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18451
18452 let head_offset = head.to_offset(&multi_buffer_snapshot);
18453
18454 let current_location_index = locations.iter().position(|loc| {
18455 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18456 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18457 });
18458
18459 (locations, current_location_index)
18460 });
18461
18462 let Some(current_location_index) = current_location_index else {
18463 // This indicates something has gone wrong, because we already
18464 // handle the "no references" case above
18465 log::error!(
18466 "failed to find current reference under cursor. Total references: {}",
18467 locations.len()
18468 );
18469 return Ok(());
18470 };
18471
18472 let destination_location_index = match direction {
18473 Direction::Next => (current_location_index + count) % locations.len(),
18474 Direction::Prev => {
18475 (current_location_index + locations.len() - count % locations.len())
18476 % locations.len()
18477 }
18478 };
18479
18480 // TODO(cameron): is this needed?
18481 // the thinking is to avoid "jumping to the current location" (avoid
18482 // polluting "jumplist" in vim terms)
18483 if current_location_index == destination_location_index {
18484 return Ok(());
18485 }
18486
18487 let Range { start, end } = locations[destination_location_index];
18488
18489 editor.update_in(cx, |editor, window, cx| {
18490 let effects = SelectionEffects::default();
18491
18492 editor.unfold_ranges(&[start..end], false, false, cx);
18493 editor.change_selections(effects, window, cx, |s| {
18494 s.select_ranges([start..start]);
18495 });
18496 })?;
18497
18498 Ok(())
18499 }))
18500 }
18501
18502 pub fn find_all_references(
18503 &mut self,
18504 action: &FindAllReferences,
18505 window: &mut Window,
18506 cx: &mut Context<Self>,
18507 ) -> Option<Task<Result<Navigated>>> {
18508 let always_open_multibuffer = action.always_open_multibuffer;
18509 let selection = self.selections.newest_anchor();
18510 let multi_buffer = self.buffer.read(cx);
18511 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18512 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18513 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18514 let head = selection_offset.head();
18515
18516 let head_anchor = multi_buffer_snapshot.anchor_at(
18517 head,
18518 if head < selection_offset.tail() {
18519 Bias::Right
18520 } else {
18521 Bias::Left
18522 },
18523 );
18524
18525 match self
18526 .find_all_references_task_sources
18527 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18528 {
18529 Ok(_) => {
18530 log::info!(
18531 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18532 );
18533 return None;
18534 }
18535 Err(i) => {
18536 self.find_all_references_task_sources.insert(i, head_anchor);
18537 }
18538 }
18539
18540 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18541 let workspace = self.workspace()?;
18542 let project = workspace.read(cx).project().clone();
18543 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18544 Some(cx.spawn_in(window, async move |editor, cx| {
18545 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18546 if let Ok(i) = editor
18547 .find_all_references_task_sources
18548 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18549 {
18550 editor.find_all_references_task_sources.remove(i);
18551 }
18552 });
18553
18554 let Some(locations) = references.await? else {
18555 return anyhow::Ok(Navigated::No);
18556 };
18557 let mut locations = cx.update(|_, cx| {
18558 locations
18559 .into_iter()
18560 .map(|location| {
18561 let buffer = location.buffer.read(cx);
18562 (location.buffer, location.range.to_point(buffer))
18563 })
18564 // if special-casing the single-match case, remove ranges
18565 // that intersect current selection
18566 .filter(|(location_buffer, location)| {
18567 if always_open_multibuffer || &buffer != location_buffer {
18568 return true;
18569 }
18570
18571 !location.contains_inclusive(&selection_point.range())
18572 })
18573 .into_group_map()
18574 })?;
18575 if locations.is_empty() {
18576 return anyhow::Ok(Navigated::No);
18577 }
18578 for ranges in locations.values_mut() {
18579 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18580 ranges.dedup();
18581 }
18582 let mut num_locations = 0;
18583 for ranges in locations.values_mut() {
18584 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18585 ranges.dedup();
18586 num_locations += ranges.len();
18587 }
18588
18589 if num_locations == 1 && !always_open_multibuffer {
18590 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18591 let target_range = target_ranges.first().unwrap().clone();
18592
18593 return editor.update_in(cx, |editor, window, cx| {
18594 let range = target_range.to_point(target_buffer.read(cx));
18595 let range = editor.range_for_match(&range);
18596 let range = range.start..range.start;
18597
18598 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18599 editor.go_to_singleton_buffer_range(range, window, cx);
18600 } else {
18601 let pane = workspace.read(cx).active_pane().clone();
18602 window.defer(cx, move |window, cx| {
18603 let target_editor: Entity<Self> =
18604 workspace.update(cx, |workspace, cx| {
18605 let pane = workspace.active_pane().clone();
18606
18607 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18608 let keep_old_preview = preview_tabs_settings
18609 .enable_keep_preview_on_code_navigation;
18610 let allow_new_preview = preview_tabs_settings
18611 .enable_preview_file_from_code_navigation;
18612
18613 workspace.open_project_item(
18614 pane,
18615 target_buffer.clone(),
18616 true,
18617 true,
18618 keep_old_preview,
18619 allow_new_preview,
18620 window,
18621 cx,
18622 )
18623 });
18624 target_editor.update(cx, |target_editor, cx| {
18625 // When selecting a definition in a different buffer, disable the nav history
18626 // to avoid creating a history entry at the previous cursor location.
18627 pane.update(cx, |pane, _| pane.disable_history());
18628 target_editor.go_to_singleton_buffer_range(range, window, cx);
18629 pane.update(cx, |pane, _| pane.enable_history());
18630 });
18631 });
18632 }
18633 Navigated::No
18634 });
18635 }
18636
18637 workspace.update_in(cx, |workspace, window, cx| {
18638 let target = locations
18639 .iter()
18640 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18641 .map(|(buffer, location)| {
18642 buffer
18643 .read(cx)
18644 .text_for_range(location.clone())
18645 .collect::<String>()
18646 })
18647 .filter(|text| !text.contains('\n'))
18648 .unique()
18649 .take(3)
18650 .join(", ");
18651 let title = if target.is_empty() {
18652 "References".to_owned()
18653 } else {
18654 format!("References to {target}")
18655 };
18656 let allow_preview = PreviewTabsSettings::get_global(cx)
18657 .enable_preview_multibuffer_from_code_navigation;
18658 Self::open_locations_in_multibuffer(
18659 workspace,
18660 locations,
18661 title,
18662 false,
18663 allow_preview,
18664 MultibufferSelectionMode::First,
18665 window,
18666 cx,
18667 );
18668 Navigated::Yes
18669 })
18670 }))
18671 }
18672
18673 /// Opens a multibuffer with the given project locations in it.
18674 pub fn open_locations_in_multibuffer(
18675 workspace: &mut Workspace,
18676 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18677 title: String,
18678 split: bool,
18679 allow_preview: bool,
18680 multibuffer_selection_mode: MultibufferSelectionMode,
18681 window: &mut Window,
18682 cx: &mut Context<Workspace>,
18683 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18684 if locations.is_empty() {
18685 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18686 return None;
18687 }
18688
18689 let capability = workspace.project().read(cx).capability();
18690 let mut ranges = <Vec<Range<Anchor>>>::new();
18691
18692 // a key to find existing multibuffer editors with the same set of locations
18693 // to prevent us from opening more and more multibuffer tabs for searches and the like
18694 let mut key = (title.clone(), vec![]);
18695 let excerpt_buffer = cx.new(|cx| {
18696 let key = &mut key.1;
18697 let mut multibuffer = MultiBuffer::new(capability);
18698 for (buffer, mut ranges_for_buffer) in locations {
18699 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18700 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18701 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18702 PathKey::for_buffer(&buffer, cx),
18703 buffer.clone(),
18704 ranges_for_buffer,
18705 multibuffer_context_lines(cx),
18706 cx,
18707 );
18708 ranges.extend(new_ranges)
18709 }
18710
18711 multibuffer.with_title(title)
18712 });
18713 let existing = workspace.active_pane().update(cx, |pane, cx| {
18714 pane.items()
18715 .filter_map(|item| item.downcast::<Editor>())
18716 .find(|editor| {
18717 editor
18718 .read(cx)
18719 .lookup_key
18720 .as_ref()
18721 .and_then(|it| {
18722 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18723 })
18724 .is_some_and(|it| *it == key)
18725 })
18726 });
18727 let was_existing = existing.is_some();
18728 let editor = existing.unwrap_or_else(|| {
18729 cx.new(|cx| {
18730 let mut editor = Editor::for_multibuffer(
18731 excerpt_buffer,
18732 Some(workspace.project().clone()),
18733 window,
18734 cx,
18735 );
18736 editor.lookup_key = Some(Box::new(key));
18737 editor
18738 })
18739 });
18740 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18741 MultibufferSelectionMode::First => {
18742 if let Some(first_range) = ranges.first() {
18743 editor.change_selections(
18744 SelectionEffects::no_scroll(),
18745 window,
18746 cx,
18747 |selections| {
18748 selections.clear_disjoint();
18749 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18750 },
18751 );
18752 }
18753 editor.highlight_background(
18754 HighlightKey::Editor,
18755 &ranges,
18756 |_, theme| theme.colors().editor_highlighted_line_background,
18757 cx,
18758 );
18759 }
18760 MultibufferSelectionMode::All => {
18761 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18762 selections.clear_disjoint();
18763 selections.select_anchor_ranges(ranges);
18764 });
18765 }
18766 });
18767
18768 let item = Box::new(editor.clone());
18769
18770 let pane = if split {
18771 workspace.adjacent_pane(window, cx)
18772 } else {
18773 workspace.active_pane().clone()
18774 };
18775 let activate_pane = split;
18776
18777 let mut destination_index = None;
18778 pane.update(cx, |pane, cx| {
18779 if allow_preview && !was_existing {
18780 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18781 }
18782 if was_existing && !allow_preview {
18783 pane.unpreview_item_if_preview(item.item_id());
18784 }
18785 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18786 });
18787
18788 Some((editor, pane))
18789 }
18790
18791 pub fn rename(
18792 &mut self,
18793 _: &Rename,
18794 window: &mut Window,
18795 cx: &mut Context<Self>,
18796 ) -> Option<Task<Result<()>>> {
18797 use language::ToOffset as _;
18798
18799 let provider = self.semantics_provider.clone()?;
18800 let selection = self.selections.newest_anchor().clone();
18801 let (cursor_buffer, cursor_buffer_position) = self
18802 .buffer
18803 .read(cx)
18804 .text_anchor_for_position(selection.head(), cx)?;
18805 let (tail_buffer, cursor_buffer_position_end) = self
18806 .buffer
18807 .read(cx)
18808 .text_anchor_for_position(selection.tail(), cx)?;
18809 if tail_buffer != cursor_buffer {
18810 return None;
18811 }
18812
18813 let snapshot = cursor_buffer.read(cx).snapshot();
18814 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18815 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18816 let prepare_rename = provider
18817 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18818 .unwrap_or_else(|| Task::ready(Ok(None)));
18819 drop(snapshot);
18820
18821 Some(cx.spawn_in(window, async move |this, cx| {
18822 let rename_range = if let Some(range) = prepare_rename.await? {
18823 Some(range)
18824 } else {
18825 this.update(cx, |this, cx| {
18826 let buffer = this.buffer.read(cx).snapshot(cx);
18827 let mut buffer_highlights = this
18828 .document_highlights_for_position(selection.head(), &buffer)
18829 .filter(|highlight| {
18830 highlight.start.excerpt_id == selection.head().excerpt_id
18831 && highlight.end.excerpt_id == selection.head().excerpt_id
18832 });
18833 buffer_highlights
18834 .next()
18835 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18836 })?
18837 };
18838 if let Some(rename_range) = rename_range {
18839 this.update_in(cx, |this, window, cx| {
18840 let snapshot = cursor_buffer.read(cx).snapshot();
18841 let rename_buffer_range = rename_range.to_offset(&snapshot);
18842 let cursor_offset_in_rename_range =
18843 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18844 let cursor_offset_in_rename_range_end =
18845 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18846
18847 this.take_rename(false, window, cx);
18848 let buffer = this.buffer.read(cx).read(cx);
18849 let cursor_offset = selection.head().to_offset(&buffer);
18850 let rename_start =
18851 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18852 let rename_end = rename_start + rename_buffer_range.len();
18853 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18854 let mut old_highlight_id = None;
18855 let old_name: Arc<str> = buffer
18856 .chunks(rename_start..rename_end, true)
18857 .map(|chunk| {
18858 if old_highlight_id.is_none() {
18859 old_highlight_id = chunk.syntax_highlight_id;
18860 }
18861 chunk.text
18862 })
18863 .collect::<String>()
18864 .into();
18865
18866 drop(buffer);
18867
18868 // Position the selection in the rename editor so that it matches the current selection.
18869 this.show_local_selections = false;
18870 let rename_editor = cx.new(|cx| {
18871 let mut editor = Editor::single_line(window, cx);
18872 editor.buffer.update(cx, |buffer, cx| {
18873 buffer.edit(
18874 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18875 None,
18876 cx,
18877 )
18878 });
18879 let cursor_offset_in_rename_range =
18880 MultiBufferOffset(cursor_offset_in_rename_range);
18881 let cursor_offset_in_rename_range_end =
18882 MultiBufferOffset(cursor_offset_in_rename_range_end);
18883 let rename_selection_range = match cursor_offset_in_rename_range
18884 .cmp(&cursor_offset_in_rename_range_end)
18885 {
18886 Ordering::Equal => {
18887 editor.select_all(&SelectAll, window, cx);
18888 return editor;
18889 }
18890 Ordering::Less => {
18891 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18892 }
18893 Ordering::Greater => {
18894 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18895 }
18896 };
18897 if rename_selection_range.end.0 > old_name.len() {
18898 editor.select_all(&SelectAll, window, cx);
18899 } else {
18900 editor.change_selections(Default::default(), window, cx, |s| {
18901 s.select_ranges([rename_selection_range]);
18902 });
18903 }
18904 editor
18905 });
18906 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18907 if e == &EditorEvent::Focused {
18908 cx.emit(EditorEvent::FocusedIn)
18909 }
18910 })
18911 .detach();
18912
18913 let write_highlights =
18914 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
18915 let read_highlights =
18916 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
18917 let ranges = write_highlights
18918 .iter()
18919 .flat_map(|(_, ranges)| ranges.iter())
18920 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18921 .cloned()
18922 .collect();
18923
18924 this.highlight_text(
18925 HighlightKey::Rename,
18926 ranges,
18927 HighlightStyle {
18928 fade_out: Some(0.6),
18929 ..Default::default()
18930 },
18931 cx,
18932 );
18933 let rename_focus_handle = rename_editor.focus_handle(cx);
18934 window.focus(&rename_focus_handle, cx);
18935 let block_id = this.insert_blocks(
18936 [BlockProperties {
18937 style: BlockStyle::Flex,
18938 placement: BlockPlacement::Below(range.start),
18939 height: Some(1),
18940 render: Arc::new({
18941 let rename_editor = rename_editor.clone();
18942 move |cx: &mut BlockContext| {
18943 let mut text_style = cx.editor_style.text.clone();
18944 if let Some(highlight_style) = old_highlight_id
18945 .and_then(|h| h.style(&cx.editor_style.syntax))
18946 {
18947 text_style = text_style.highlight(highlight_style);
18948 }
18949 div()
18950 .block_mouse_except_scroll()
18951 .pl(cx.anchor_x)
18952 .child(EditorElement::new(
18953 &rename_editor,
18954 EditorStyle {
18955 background: cx.theme().system().transparent,
18956 local_player: cx.editor_style.local_player,
18957 text: text_style,
18958 scrollbar_width: cx.editor_style.scrollbar_width,
18959 syntax: cx.editor_style.syntax.clone(),
18960 status: cx.editor_style.status.clone(),
18961 inlay_hints_style: HighlightStyle {
18962 font_weight: Some(FontWeight::BOLD),
18963 ..make_inlay_hints_style(cx.app)
18964 },
18965 edit_prediction_styles: make_suggestion_styles(
18966 cx.app,
18967 ),
18968 ..EditorStyle::default()
18969 },
18970 ))
18971 .into_any_element()
18972 }
18973 }),
18974 priority: 0,
18975 }],
18976 Some(Autoscroll::fit()),
18977 cx,
18978 )[0];
18979 this.pending_rename = Some(RenameState {
18980 range,
18981 old_name,
18982 editor: rename_editor,
18983 block_id,
18984 });
18985 })?;
18986 }
18987
18988 Ok(())
18989 }))
18990 }
18991
18992 pub fn confirm_rename(
18993 &mut self,
18994 _: &ConfirmRename,
18995 window: &mut Window,
18996 cx: &mut Context<Self>,
18997 ) -> Option<Task<Result<()>>> {
18998 let rename = self.take_rename(false, window, cx)?;
18999 let workspace = self.workspace()?.downgrade();
19000 let (buffer, start) = self
19001 .buffer
19002 .read(cx)
19003 .text_anchor_for_position(rename.range.start, cx)?;
19004 let (end_buffer, _) = self
19005 .buffer
19006 .read(cx)
19007 .text_anchor_for_position(rename.range.end, cx)?;
19008 if buffer != end_buffer {
19009 return None;
19010 }
19011
19012 let old_name = rename.old_name;
19013 let new_name = rename.editor.read(cx).text(cx);
19014
19015 let rename = self.semantics_provider.as_ref()?.perform_rename(
19016 &buffer,
19017 start,
19018 new_name.clone(),
19019 cx,
19020 )?;
19021
19022 Some(cx.spawn_in(window, async move |editor, cx| {
19023 let project_transaction = rename.await?;
19024 Self::open_project_transaction(
19025 &editor,
19026 workspace,
19027 project_transaction,
19028 format!("Rename: {} → {}", old_name, new_name),
19029 cx,
19030 )
19031 .await?;
19032
19033 editor.update(cx, |editor, cx| {
19034 editor.refresh_document_highlights(cx);
19035 })?;
19036 Ok(())
19037 }))
19038 }
19039
19040 fn take_rename(
19041 &mut self,
19042 moving_cursor: bool,
19043 window: &mut Window,
19044 cx: &mut Context<Self>,
19045 ) -> Option<RenameState> {
19046 let rename = self.pending_rename.take()?;
19047 if rename.editor.focus_handle(cx).is_focused(window) {
19048 window.focus(&self.focus_handle, cx);
19049 }
19050
19051 self.remove_blocks(
19052 [rename.block_id].into_iter().collect(),
19053 Some(Autoscroll::fit()),
19054 cx,
19055 );
19056 self.clear_highlights(HighlightKey::Rename, cx);
19057 self.show_local_selections = true;
19058
19059 if moving_cursor {
19060 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19061 editor
19062 .selections
19063 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19064 .head()
19065 });
19066
19067 // Update the selection to match the position of the selection inside
19068 // the rename editor.
19069 let snapshot = self.buffer.read(cx).read(cx);
19070 let rename_range = rename.range.to_offset(&snapshot);
19071 let cursor_in_editor = snapshot
19072 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19073 .min(rename_range.end);
19074 drop(snapshot);
19075
19076 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19077 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19078 });
19079 } else {
19080 self.refresh_document_highlights(cx);
19081 }
19082
19083 Some(rename)
19084 }
19085
19086 pub fn pending_rename(&self) -> Option<&RenameState> {
19087 self.pending_rename.as_ref()
19088 }
19089
19090 fn format(
19091 &mut self,
19092 _: &Format,
19093 window: &mut Window,
19094 cx: &mut Context<Self>,
19095 ) -> Option<Task<Result<()>>> {
19096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19097
19098 let project = match &self.project {
19099 Some(project) => project.clone(),
19100 None => return None,
19101 };
19102
19103 Some(self.perform_format(
19104 project,
19105 FormatTrigger::Manual,
19106 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19107 window,
19108 cx,
19109 ))
19110 }
19111
19112 fn format_selections(
19113 &mut self,
19114 _: &FormatSelections,
19115 window: &mut Window,
19116 cx: &mut Context<Self>,
19117 ) -> Option<Task<Result<()>>> {
19118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19119
19120 let project = match &self.project {
19121 Some(project) => project.clone(),
19122 None => return None,
19123 };
19124
19125 let ranges = self
19126 .selections
19127 .all_adjusted(&self.display_snapshot(cx))
19128 .into_iter()
19129 .map(|selection| selection.range())
19130 .collect_vec();
19131
19132 Some(self.perform_format(
19133 project,
19134 FormatTrigger::Manual,
19135 FormatTarget::Ranges(ranges),
19136 window,
19137 cx,
19138 ))
19139 }
19140
19141 fn perform_format(
19142 &mut self,
19143 project: Entity<Project>,
19144 trigger: FormatTrigger,
19145 target: FormatTarget,
19146 window: &mut Window,
19147 cx: &mut Context<Self>,
19148 ) -> Task<Result<()>> {
19149 let buffer = self.buffer.clone();
19150 let (buffers, target) = match target {
19151 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19152 FormatTarget::Ranges(selection_ranges) => {
19153 let multi_buffer = buffer.read(cx);
19154 let snapshot = multi_buffer.read(cx);
19155 let mut buffers = HashSet::default();
19156 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19157 BTreeMap::new();
19158 for selection_range in selection_ranges {
19159 for (buffer, buffer_range, _) in
19160 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19161 {
19162 let buffer_id = buffer.remote_id();
19163 let start = buffer.anchor_before(buffer_range.start);
19164 let end = buffer.anchor_after(buffer_range.end);
19165 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19166 buffer_id_to_ranges
19167 .entry(buffer_id)
19168 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19169 .or_insert_with(|| vec![start..end]);
19170 }
19171 }
19172 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19173 }
19174 };
19175
19176 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19177 let selections_prev = transaction_id_prev
19178 .and_then(|transaction_id_prev| {
19179 // default to selections as they were after the last edit, if we have them,
19180 // instead of how they are now.
19181 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19182 // will take you back to where you made the last edit, instead of staying where you scrolled
19183 self.selection_history
19184 .transaction(transaction_id_prev)
19185 .map(|t| t.0.clone())
19186 })
19187 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19188
19189 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19190 let format = project.update(cx, |project, cx| {
19191 project.format(buffers, target, true, trigger, cx)
19192 });
19193
19194 cx.spawn_in(window, async move |editor, cx| {
19195 let transaction = futures::select_biased! {
19196 transaction = format.log_err().fuse() => transaction,
19197 () = timeout => {
19198 log::warn!("timed out waiting for formatting");
19199 None
19200 }
19201 };
19202
19203 buffer.update(cx, |buffer, cx| {
19204 if let Some(transaction) = transaction
19205 && !buffer.is_singleton()
19206 {
19207 buffer.push_transaction(&transaction.0, cx);
19208 }
19209 cx.notify();
19210 });
19211
19212 if let Some(transaction_id_now) =
19213 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19214 {
19215 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19216 if has_new_transaction {
19217 editor
19218 .update(cx, |editor, _| {
19219 editor
19220 .selection_history
19221 .insert_transaction(transaction_id_now, selections_prev);
19222 })
19223 .ok();
19224 }
19225 }
19226
19227 Ok(())
19228 })
19229 }
19230
19231 fn organize_imports(
19232 &mut self,
19233 _: &OrganizeImports,
19234 window: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) -> Option<Task<Result<()>>> {
19237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19238 let project = match &self.project {
19239 Some(project) => project.clone(),
19240 None => return None,
19241 };
19242 Some(self.perform_code_action_kind(
19243 project,
19244 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19245 window,
19246 cx,
19247 ))
19248 }
19249
19250 fn perform_code_action_kind(
19251 &mut self,
19252 project: Entity<Project>,
19253 kind: CodeActionKind,
19254 window: &mut Window,
19255 cx: &mut Context<Self>,
19256 ) -> Task<Result<()>> {
19257 let buffer = self.buffer.clone();
19258 let buffers = buffer.read(cx).all_buffers();
19259 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19260 let apply_action = project.update(cx, |project, cx| {
19261 project.apply_code_action_kind(buffers, kind, true, cx)
19262 });
19263 cx.spawn_in(window, async move |_, cx| {
19264 let transaction = futures::select_biased! {
19265 () = timeout => {
19266 log::warn!("timed out waiting for executing code action");
19267 None
19268 }
19269 transaction = apply_action.log_err().fuse() => transaction,
19270 };
19271 buffer.update(cx, |buffer, cx| {
19272 // check if we need this
19273 if let Some(transaction) = transaction
19274 && !buffer.is_singleton()
19275 {
19276 buffer.push_transaction(&transaction.0, cx);
19277 }
19278 cx.notify();
19279 });
19280 Ok(())
19281 })
19282 }
19283
19284 pub fn restart_language_server(
19285 &mut self,
19286 _: &RestartLanguageServer,
19287 _: &mut Window,
19288 cx: &mut Context<Self>,
19289 ) {
19290 if let Some(project) = self.project.clone() {
19291 self.buffer.update(cx, |multi_buffer, cx| {
19292 project.update(cx, |project, cx| {
19293 project.restart_language_servers_for_buffers(
19294 multi_buffer.all_buffers().into_iter().collect(),
19295 HashSet::default(),
19296 cx,
19297 );
19298 });
19299 })
19300 }
19301 }
19302
19303 pub fn stop_language_server(
19304 &mut self,
19305 _: &StopLanguageServer,
19306 _: &mut Window,
19307 cx: &mut Context<Self>,
19308 ) {
19309 if let Some(project) = self.project.clone() {
19310 self.buffer.update(cx, |multi_buffer, cx| {
19311 project.update(cx, |project, cx| {
19312 project.stop_language_servers_for_buffers(
19313 multi_buffer.all_buffers().into_iter().collect(),
19314 HashSet::default(),
19315 cx,
19316 );
19317 });
19318 });
19319 }
19320 }
19321
19322 fn cancel_language_server_work(
19323 workspace: &mut Workspace,
19324 _: &actions::CancelLanguageServerWork,
19325 _: &mut Window,
19326 cx: &mut Context<Workspace>,
19327 ) {
19328 let project = workspace.project();
19329 let buffers = workspace
19330 .active_item(cx)
19331 .and_then(|item| item.act_as::<Editor>(cx))
19332 .map_or(HashSet::default(), |editor| {
19333 editor.read(cx).buffer.read(cx).all_buffers()
19334 });
19335 project.update(cx, |project, cx| {
19336 project.cancel_language_server_work_for_buffers(buffers, cx);
19337 });
19338 }
19339
19340 fn show_character_palette(
19341 &mut self,
19342 _: &ShowCharacterPalette,
19343 window: &mut Window,
19344 _: &mut Context<Self>,
19345 ) {
19346 window.show_character_palette();
19347 }
19348
19349 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19350 if !self.diagnostics_enabled() {
19351 return;
19352 }
19353
19354 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19355 let buffer = self.buffer.read(cx).snapshot(cx);
19356 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19357 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19358 let is_valid = buffer
19359 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19360 .any(|entry| {
19361 entry.diagnostic.is_primary
19362 && !entry.range.is_empty()
19363 && entry.range.start == primary_range_start
19364 && entry.diagnostic.message == active_diagnostics.active_message
19365 });
19366
19367 if !is_valid {
19368 self.dismiss_diagnostics(cx);
19369 }
19370 }
19371 }
19372
19373 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19374 match &self.active_diagnostics {
19375 ActiveDiagnostic::Group(group) => Some(group),
19376 _ => None,
19377 }
19378 }
19379
19380 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19381 if !self.diagnostics_enabled() {
19382 return;
19383 }
19384 self.dismiss_diagnostics(cx);
19385 self.active_diagnostics = ActiveDiagnostic::All;
19386 }
19387
19388 fn activate_diagnostics(
19389 &mut self,
19390 buffer_id: BufferId,
19391 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19392 window: &mut Window,
19393 cx: &mut Context<Self>,
19394 ) {
19395 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19396 return;
19397 }
19398 self.dismiss_diagnostics(cx);
19399 let snapshot = self.snapshot(window, cx);
19400 let buffer = self.buffer.read(cx).snapshot(cx);
19401 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19402 return;
19403 };
19404
19405 let diagnostic_group = buffer
19406 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19407 .collect::<Vec<_>>();
19408
19409 let language_registry = self
19410 .project()
19411 .map(|project| project.read(cx).languages().clone());
19412
19413 let blocks = renderer.render_group(
19414 diagnostic_group,
19415 buffer_id,
19416 snapshot,
19417 cx.weak_entity(),
19418 language_registry,
19419 cx,
19420 );
19421
19422 let blocks = self.display_map.update(cx, |display_map, cx| {
19423 display_map.insert_blocks(blocks, cx).into_iter().collect()
19424 });
19425 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19426 active_range: buffer.anchor_before(diagnostic.range.start)
19427 ..buffer.anchor_after(diagnostic.range.end),
19428 active_message: diagnostic.diagnostic.message.clone(),
19429 group_id: diagnostic.diagnostic.group_id,
19430 blocks,
19431 });
19432 cx.notify();
19433 }
19434
19435 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19436 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19437 return;
19438 };
19439
19440 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19441 if let ActiveDiagnostic::Group(group) = prev {
19442 self.display_map.update(cx, |display_map, cx| {
19443 display_map.remove_blocks(group.blocks, cx);
19444 });
19445 cx.notify();
19446 }
19447 }
19448
19449 /// Disable inline diagnostics rendering for this editor.
19450 pub fn disable_inline_diagnostics(&mut self) {
19451 self.inline_diagnostics_enabled = false;
19452 self.inline_diagnostics_update = Task::ready(());
19453 self.inline_diagnostics.clear();
19454 }
19455
19456 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19457 self.diagnostics_enabled = false;
19458 self.dismiss_diagnostics(cx);
19459 self.inline_diagnostics_update = Task::ready(());
19460 self.inline_diagnostics.clear();
19461 }
19462
19463 pub fn disable_word_completions(&mut self) {
19464 self.word_completions_enabled = false;
19465 }
19466
19467 pub fn diagnostics_enabled(&self) -> bool {
19468 self.diagnostics_enabled && self.mode.is_full()
19469 }
19470
19471 pub fn inline_diagnostics_enabled(&self) -> bool {
19472 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19473 }
19474
19475 pub fn show_inline_diagnostics(&self) -> bool {
19476 self.show_inline_diagnostics
19477 }
19478
19479 pub fn toggle_inline_diagnostics(
19480 &mut self,
19481 _: &ToggleInlineDiagnostics,
19482 window: &mut Window,
19483 cx: &mut Context<Editor>,
19484 ) {
19485 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19486 self.refresh_inline_diagnostics(false, window, cx);
19487 }
19488
19489 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19490 self.diagnostics_max_severity = severity;
19491 self.display_map.update(cx, |display_map, _| {
19492 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19493 });
19494 }
19495
19496 pub fn toggle_diagnostics(
19497 &mut self,
19498 _: &ToggleDiagnostics,
19499 window: &mut Window,
19500 cx: &mut Context<Editor>,
19501 ) {
19502 if !self.diagnostics_enabled() {
19503 return;
19504 }
19505
19506 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19507 EditorSettings::get_global(cx)
19508 .diagnostics_max_severity
19509 .filter(|severity| severity != &DiagnosticSeverity::Off)
19510 .unwrap_or(DiagnosticSeverity::Hint)
19511 } else {
19512 DiagnosticSeverity::Off
19513 };
19514 self.set_max_diagnostics_severity(new_severity, cx);
19515 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19516 self.active_diagnostics = ActiveDiagnostic::None;
19517 self.inline_diagnostics_update = Task::ready(());
19518 self.inline_diagnostics.clear();
19519 } else {
19520 self.refresh_inline_diagnostics(false, window, cx);
19521 }
19522
19523 cx.notify();
19524 }
19525
19526 pub fn toggle_minimap(
19527 &mut self,
19528 _: &ToggleMinimap,
19529 window: &mut Window,
19530 cx: &mut Context<Editor>,
19531 ) {
19532 if self.supports_minimap(cx) {
19533 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19534 }
19535 }
19536
19537 fn refresh_inline_diagnostics(
19538 &mut self,
19539 debounce: bool,
19540 window: &mut Window,
19541 cx: &mut Context<Self>,
19542 ) {
19543 let max_severity = ProjectSettings::get_global(cx)
19544 .diagnostics
19545 .inline
19546 .max_severity
19547 .unwrap_or(self.diagnostics_max_severity);
19548
19549 if !self.inline_diagnostics_enabled()
19550 || !self.diagnostics_enabled()
19551 || !self.show_inline_diagnostics
19552 || max_severity == DiagnosticSeverity::Off
19553 {
19554 self.inline_diagnostics_update = Task::ready(());
19555 self.inline_diagnostics.clear();
19556 return;
19557 }
19558
19559 let debounce_ms = ProjectSettings::get_global(cx)
19560 .diagnostics
19561 .inline
19562 .update_debounce_ms;
19563 let debounce = if debounce && debounce_ms > 0 {
19564 Some(Duration::from_millis(debounce_ms))
19565 } else {
19566 None
19567 };
19568 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19569 if let Some(debounce) = debounce {
19570 cx.background_executor().timer(debounce).await;
19571 }
19572 let Some(snapshot) = editor.upgrade().map(|editor| {
19573 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19574 }) else {
19575 return;
19576 };
19577
19578 let new_inline_diagnostics = cx
19579 .background_spawn(async move {
19580 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19581 for diagnostic_entry in
19582 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19583 {
19584 let message = diagnostic_entry
19585 .diagnostic
19586 .message
19587 .split_once('\n')
19588 .map(|(line, _)| line)
19589 .map(SharedString::new)
19590 .unwrap_or_else(|| {
19591 SharedString::new(&*diagnostic_entry.diagnostic.message)
19592 });
19593 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19594 let (Ok(i) | Err(i)) = inline_diagnostics
19595 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19596 inline_diagnostics.insert(
19597 i,
19598 (
19599 start_anchor,
19600 InlineDiagnostic {
19601 message,
19602 group_id: diagnostic_entry.diagnostic.group_id,
19603 start: diagnostic_entry.range.start.to_point(&snapshot),
19604 is_primary: diagnostic_entry.diagnostic.is_primary,
19605 severity: diagnostic_entry.diagnostic.severity,
19606 },
19607 ),
19608 );
19609 }
19610 inline_diagnostics
19611 })
19612 .await;
19613
19614 editor
19615 .update(cx, |editor, cx| {
19616 editor.inline_diagnostics = new_inline_diagnostics;
19617 cx.notify();
19618 })
19619 .ok();
19620 });
19621 }
19622
19623 fn pull_diagnostics(
19624 &mut self,
19625 buffer_id: BufferId,
19626 _window: &Window,
19627 cx: &mut Context<Self>,
19628 ) -> Option<()> {
19629 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19630 // skip any LSP updates for it.
19631
19632 if self.active_diagnostics == ActiveDiagnostic::All
19633 || !self.mode().is_full()
19634 || !self.diagnostics_enabled()
19635 {
19636 return None;
19637 }
19638 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19639 .diagnostics
19640 .lsp_pull_diagnostics;
19641 if !pull_diagnostics_settings.enabled {
19642 return None;
19643 }
19644 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19645 let project = self.project()?.downgrade();
19646 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19647
19648 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19649 cx.background_executor().timer(debounce).await;
19650 if let Ok(task) = project.update(cx, |project, cx| {
19651 project.lsp_store().update(cx, |lsp_store, cx| {
19652 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19653 })
19654 }) {
19655 task.await.log_err();
19656 }
19657 project
19658 .update(cx, |project, cx| {
19659 project.lsp_store().update(cx, |lsp_store, cx| {
19660 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19661 })
19662 })
19663 .log_err();
19664 });
19665
19666 Some(())
19667 }
19668
19669 pub fn set_selections_from_remote(
19670 &mut self,
19671 selections: Vec<Selection<Anchor>>,
19672 pending_selection: Option<Selection<Anchor>>,
19673 window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 let old_cursor_position = self.selections.newest_anchor().head();
19677 self.selections
19678 .change_with(&self.display_snapshot(cx), |s| {
19679 s.select_anchors(selections);
19680 if let Some(pending_selection) = pending_selection {
19681 s.set_pending(pending_selection, SelectMode::Character);
19682 } else {
19683 s.clear_pending();
19684 }
19685 });
19686 self.selections_did_change(
19687 false,
19688 &old_cursor_position,
19689 SelectionEffects::default(),
19690 window,
19691 cx,
19692 );
19693 }
19694
19695 pub fn transact(
19696 &mut self,
19697 window: &mut Window,
19698 cx: &mut Context<Self>,
19699 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19700 ) -> Option<TransactionId> {
19701 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19702 this.start_transaction_at(Instant::now(), window, cx);
19703 update(this, window, cx);
19704 this.end_transaction_at(Instant::now(), cx)
19705 })
19706 }
19707
19708 pub fn start_transaction_at(
19709 &mut self,
19710 now: Instant,
19711 window: &mut Window,
19712 cx: &mut Context<Self>,
19713 ) -> Option<TransactionId> {
19714 self.end_selection(window, cx);
19715 if let Some(tx_id) = self
19716 .buffer
19717 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19718 {
19719 self.selection_history
19720 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19721 cx.emit(EditorEvent::TransactionBegun {
19722 transaction_id: tx_id,
19723 });
19724 Some(tx_id)
19725 } else {
19726 None
19727 }
19728 }
19729
19730 pub fn end_transaction_at(
19731 &mut self,
19732 now: Instant,
19733 cx: &mut Context<Self>,
19734 ) -> Option<TransactionId> {
19735 if let Some(transaction_id) = self
19736 .buffer
19737 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19738 {
19739 if let Some((_, end_selections)) =
19740 self.selection_history.transaction_mut(transaction_id)
19741 {
19742 *end_selections = Some(self.selections.disjoint_anchors_arc());
19743 } else {
19744 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19745 }
19746
19747 cx.emit(EditorEvent::Edited { transaction_id });
19748 Some(transaction_id)
19749 } else {
19750 None
19751 }
19752 }
19753
19754 pub fn modify_transaction_selection_history(
19755 &mut self,
19756 transaction_id: TransactionId,
19757 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19758 ) -> bool {
19759 self.selection_history
19760 .transaction_mut(transaction_id)
19761 .map(modify)
19762 .is_some()
19763 }
19764
19765 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19766 if self.selection_mark_mode {
19767 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19768 s.move_with(&mut |_, sel| {
19769 sel.collapse_to(sel.head(), SelectionGoal::None);
19770 });
19771 })
19772 }
19773 self.selection_mark_mode = true;
19774 cx.notify();
19775 }
19776
19777 pub fn swap_selection_ends(
19778 &mut self,
19779 _: &actions::SwapSelectionEnds,
19780 window: &mut Window,
19781 cx: &mut Context<Self>,
19782 ) {
19783 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19784 s.move_with(&mut |_, sel| {
19785 if sel.start != sel.end {
19786 sel.reversed = !sel.reversed
19787 }
19788 });
19789 });
19790 self.request_autoscroll(Autoscroll::newest(), cx);
19791 cx.notify();
19792 }
19793
19794 pub fn toggle_focus(
19795 workspace: &mut Workspace,
19796 _: &actions::ToggleFocus,
19797 window: &mut Window,
19798 cx: &mut Context<Workspace>,
19799 ) {
19800 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19801 return;
19802 };
19803 workspace.activate_item(&item, true, true, window, cx);
19804 }
19805
19806 pub fn toggle_fold(
19807 &mut self,
19808 _: &actions::ToggleFold,
19809 window: &mut Window,
19810 cx: &mut Context<Self>,
19811 ) {
19812 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19813 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19814 let selection = self.selections.newest::<Point>(&display_map);
19815
19816 let range = if selection.is_empty() {
19817 let point = selection.head().to_display_point(&display_map);
19818 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19819 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19820 .to_point(&display_map);
19821 start..end
19822 } else {
19823 selection.range()
19824 };
19825 if display_map.folds_in_range(range).next().is_some() {
19826 self.unfold_lines(&Default::default(), window, cx)
19827 } else {
19828 self.fold(&Default::default(), window, cx)
19829 }
19830 } else {
19831 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19832 let buffer_ids: HashSet<_> = self
19833 .selections
19834 .disjoint_anchor_ranges()
19835 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19836 .collect();
19837
19838 let should_unfold = buffer_ids
19839 .iter()
19840 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19841
19842 for buffer_id in buffer_ids {
19843 if should_unfold {
19844 self.unfold_buffer(buffer_id, cx);
19845 } else {
19846 self.fold_buffer(buffer_id, cx);
19847 }
19848 }
19849 }
19850 }
19851
19852 pub fn toggle_fold_recursive(
19853 &mut self,
19854 _: &actions::ToggleFoldRecursive,
19855 window: &mut Window,
19856 cx: &mut Context<Self>,
19857 ) {
19858 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19859
19860 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19861 let range = if selection.is_empty() {
19862 let point = selection.head().to_display_point(&display_map);
19863 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19864 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19865 .to_point(&display_map);
19866 start..end
19867 } else {
19868 selection.range()
19869 };
19870 if display_map.folds_in_range(range).next().is_some() {
19871 self.unfold_recursive(&Default::default(), window, cx)
19872 } else {
19873 self.fold_recursive(&Default::default(), window, cx)
19874 }
19875 }
19876
19877 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19878 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19879 let mut to_fold = Vec::new();
19880 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19881 let selections = self.selections.all_adjusted(&display_map);
19882
19883 for selection in selections {
19884 let range = selection.range().sorted();
19885 let buffer_start_row = range.start.row;
19886
19887 if range.start.row != range.end.row {
19888 let mut found = false;
19889 let mut row = range.start.row;
19890 while row <= range.end.row {
19891 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19892 {
19893 found = true;
19894 row = crease.range().end.row + 1;
19895 to_fold.push(crease);
19896 } else {
19897 row += 1
19898 }
19899 }
19900 if found {
19901 continue;
19902 }
19903 }
19904
19905 for row in (0..=range.start.row).rev() {
19906 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19907 && crease.range().end.row >= buffer_start_row
19908 {
19909 to_fold.push(crease);
19910 if row <= range.start.row {
19911 break;
19912 }
19913 }
19914 }
19915 }
19916
19917 self.fold_creases(to_fold, true, window, cx);
19918 } else {
19919 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19920 let buffer_ids = self
19921 .selections
19922 .disjoint_anchor_ranges()
19923 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19924 .collect::<HashSet<_>>();
19925 for buffer_id in buffer_ids {
19926 self.fold_buffer(buffer_id, cx);
19927 }
19928 }
19929 }
19930
19931 pub fn toggle_fold_all(
19932 &mut self,
19933 _: &actions::ToggleFoldAll,
19934 window: &mut Window,
19935 cx: &mut Context<Self>,
19936 ) {
19937 let has_folds = if self.buffer.read(cx).is_singleton() {
19938 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19939 let has_folds = display_map
19940 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19941 .next()
19942 .is_some();
19943 has_folds
19944 } else {
19945 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19946 let has_folds = buffer_ids
19947 .iter()
19948 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19949 has_folds
19950 };
19951
19952 if has_folds {
19953 self.unfold_all(&actions::UnfoldAll, window, cx);
19954 } else {
19955 self.fold_all(&actions::FoldAll, window, cx);
19956 }
19957 }
19958
19959 fn fold_at_level(
19960 &mut self,
19961 fold_at: &FoldAtLevel,
19962 window: &mut Window,
19963 cx: &mut Context<Self>,
19964 ) {
19965 if !self.buffer.read(cx).is_singleton() {
19966 return;
19967 }
19968
19969 let fold_at_level = fold_at.0;
19970 let snapshot = self.buffer.read(cx).snapshot(cx);
19971 let mut to_fold = Vec::new();
19972 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19973
19974 let row_ranges_to_keep: Vec<Range<u32>> = self
19975 .selections
19976 .all::<Point>(&self.display_snapshot(cx))
19977 .into_iter()
19978 .map(|sel| sel.start.row..sel.end.row)
19979 .collect();
19980
19981 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19982 while start_row < end_row {
19983 match self
19984 .snapshot(window, cx)
19985 .crease_for_buffer_row(MultiBufferRow(start_row))
19986 {
19987 Some(crease) => {
19988 let nested_start_row = crease.range().start.row + 1;
19989 let nested_end_row = crease.range().end.row;
19990
19991 if current_level < fold_at_level {
19992 stack.push((nested_start_row, nested_end_row, current_level + 1));
19993 } else if current_level == fold_at_level {
19994 // Fold iff there is no selection completely contained within the fold region
19995 if !row_ranges_to_keep.iter().any(|selection| {
19996 selection.end >= nested_start_row
19997 && selection.start <= nested_end_row
19998 }) {
19999 to_fold.push(crease);
20000 }
20001 }
20002
20003 start_row = nested_end_row + 1;
20004 }
20005 None => start_row += 1,
20006 }
20007 }
20008 }
20009
20010 self.fold_creases(to_fold, true, window, cx);
20011 }
20012
20013 pub fn fold_at_level_1(
20014 &mut self,
20015 _: &actions::FoldAtLevel1,
20016 window: &mut Window,
20017 cx: &mut Context<Self>,
20018 ) {
20019 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20020 }
20021
20022 pub fn fold_at_level_2(
20023 &mut self,
20024 _: &actions::FoldAtLevel2,
20025 window: &mut Window,
20026 cx: &mut Context<Self>,
20027 ) {
20028 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20029 }
20030
20031 pub fn fold_at_level_3(
20032 &mut self,
20033 _: &actions::FoldAtLevel3,
20034 window: &mut Window,
20035 cx: &mut Context<Self>,
20036 ) {
20037 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20038 }
20039
20040 pub fn fold_at_level_4(
20041 &mut self,
20042 _: &actions::FoldAtLevel4,
20043 window: &mut Window,
20044 cx: &mut Context<Self>,
20045 ) {
20046 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20047 }
20048
20049 pub fn fold_at_level_5(
20050 &mut self,
20051 _: &actions::FoldAtLevel5,
20052 window: &mut Window,
20053 cx: &mut Context<Self>,
20054 ) {
20055 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20056 }
20057
20058 pub fn fold_at_level_6(
20059 &mut self,
20060 _: &actions::FoldAtLevel6,
20061 window: &mut Window,
20062 cx: &mut Context<Self>,
20063 ) {
20064 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20065 }
20066
20067 pub fn fold_at_level_7(
20068 &mut self,
20069 _: &actions::FoldAtLevel7,
20070 window: &mut Window,
20071 cx: &mut Context<Self>,
20072 ) {
20073 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20074 }
20075
20076 pub fn fold_at_level_8(
20077 &mut self,
20078 _: &actions::FoldAtLevel8,
20079 window: &mut Window,
20080 cx: &mut Context<Self>,
20081 ) {
20082 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20083 }
20084
20085 pub fn fold_at_level_9(
20086 &mut self,
20087 _: &actions::FoldAtLevel9,
20088 window: &mut Window,
20089 cx: &mut Context<Self>,
20090 ) {
20091 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20092 }
20093
20094 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20095 if self.buffer.read(cx).is_singleton() {
20096 let mut fold_ranges = Vec::new();
20097 let snapshot = self.buffer.read(cx).snapshot(cx);
20098
20099 for row in 0..snapshot.max_row().0 {
20100 if let Some(foldable_range) = self
20101 .snapshot(window, cx)
20102 .crease_for_buffer_row(MultiBufferRow(row))
20103 {
20104 fold_ranges.push(foldable_range);
20105 }
20106 }
20107
20108 self.fold_creases(fold_ranges, true, window, cx);
20109 } else {
20110 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20111 editor
20112 .update_in(cx, |editor, _, cx| {
20113 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20114 editor.fold_buffer(buffer_id, cx);
20115 }
20116 })
20117 .ok();
20118 });
20119 }
20120 }
20121
20122 pub fn fold_function_bodies(
20123 &mut self,
20124 _: &actions::FoldFunctionBodies,
20125 window: &mut Window,
20126 cx: &mut Context<Self>,
20127 ) {
20128 let snapshot = self.buffer.read(cx).snapshot(cx);
20129
20130 let ranges = snapshot
20131 .text_object_ranges(
20132 MultiBufferOffset(0)..snapshot.len(),
20133 TreeSitterOptions::default(),
20134 )
20135 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20136 .collect::<Vec<_>>();
20137
20138 let creases = ranges
20139 .into_iter()
20140 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20141 .collect();
20142
20143 self.fold_creases(creases, true, window, cx);
20144 }
20145
20146 pub fn fold_recursive(
20147 &mut self,
20148 _: &actions::FoldRecursive,
20149 window: &mut Window,
20150 cx: &mut Context<Self>,
20151 ) {
20152 let mut to_fold = Vec::new();
20153 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20154 let selections = self.selections.all_adjusted(&display_map);
20155
20156 for selection in selections {
20157 let range = selection.range().sorted();
20158 let buffer_start_row = range.start.row;
20159
20160 if range.start.row != range.end.row {
20161 let mut found = false;
20162 for row in range.start.row..=range.end.row {
20163 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20164 found = true;
20165 to_fold.push(crease);
20166 }
20167 }
20168 if found {
20169 continue;
20170 }
20171 }
20172
20173 for row in (0..=range.start.row).rev() {
20174 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20175 if crease.range().end.row >= buffer_start_row {
20176 to_fold.push(crease);
20177 } else {
20178 break;
20179 }
20180 }
20181 }
20182 }
20183
20184 self.fold_creases(to_fold, true, window, cx);
20185 }
20186
20187 pub fn fold_at(
20188 &mut self,
20189 buffer_row: MultiBufferRow,
20190 window: &mut Window,
20191 cx: &mut Context<Self>,
20192 ) {
20193 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20194
20195 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20196 let autoscroll = self
20197 .selections
20198 .all::<Point>(&display_map)
20199 .iter()
20200 .any(|selection| crease.range().overlaps(&selection.range()));
20201
20202 self.fold_creases(vec![crease], autoscroll, window, cx);
20203 }
20204 }
20205
20206 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20207 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20208 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20209 let buffer = display_map.buffer_snapshot();
20210 let selections = self.selections.all::<Point>(&display_map);
20211 let ranges = selections
20212 .iter()
20213 .map(|s| {
20214 let range = s.display_range(&display_map).sorted();
20215 let mut start = range.start.to_point(&display_map);
20216 let mut end = range.end.to_point(&display_map);
20217 start.column = 0;
20218 end.column = buffer.line_len(MultiBufferRow(end.row));
20219 start..end
20220 })
20221 .collect::<Vec<_>>();
20222
20223 self.unfold_ranges(&ranges, true, true, cx);
20224 } else {
20225 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20226 let buffer_ids = self
20227 .selections
20228 .disjoint_anchor_ranges()
20229 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20230 .collect::<HashSet<_>>();
20231 for buffer_id in buffer_ids {
20232 self.unfold_buffer(buffer_id, cx);
20233 }
20234 }
20235 }
20236
20237 pub fn unfold_recursive(
20238 &mut self,
20239 _: &UnfoldRecursive,
20240 _window: &mut Window,
20241 cx: &mut Context<Self>,
20242 ) {
20243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20244 let selections = self.selections.all::<Point>(&display_map);
20245 let ranges = selections
20246 .iter()
20247 .map(|s| {
20248 let mut range = s.display_range(&display_map).sorted();
20249 *range.start.column_mut() = 0;
20250 *range.end.column_mut() = display_map.line_len(range.end.row());
20251 let start = range.start.to_point(&display_map);
20252 let end = range.end.to_point(&display_map);
20253 start..end
20254 })
20255 .collect::<Vec<_>>();
20256
20257 self.unfold_ranges(&ranges, true, true, cx);
20258 }
20259
20260 pub fn unfold_at(
20261 &mut self,
20262 buffer_row: MultiBufferRow,
20263 _window: &mut Window,
20264 cx: &mut Context<Self>,
20265 ) {
20266 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20267
20268 let intersection_range = Point::new(buffer_row.0, 0)
20269 ..Point::new(
20270 buffer_row.0,
20271 display_map.buffer_snapshot().line_len(buffer_row),
20272 );
20273
20274 let autoscroll = self
20275 .selections
20276 .all::<Point>(&display_map)
20277 .iter()
20278 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20279
20280 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20281 }
20282
20283 pub fn unfold_all(
20284 &mut self,
20285 _: &actions::UnfoldAll,
20286 _window: &mut Window,
20287 cx: &mut Context<Self>,
20288 ) {
20289 if self.buffer.read(cx).is_singleton() {
20290 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20291 self.unfold_ranges(
20292 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20293 true,
20294 true,
20295 cx,
20296 );
20297 } else {
20298 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20299 editor
20300 .update(cx, |editor, cx| {
20301 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20302 editor.unfold_buffer(buffer_id, cx);
20303 }
20304 })
20305 .ok();
20306 });
20307 }
20308 }
20309
20310 pub fn fold_selected_ranges(
20311 &mut self,
20312 _: &FoldSelectedRanges,
20313 window: &mut Window,
20314 cx: &mut Context<Self>,
20315 ) {
20316 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20317 let selections = self.selections.all_adjusted(&display_map);
20318 let ranges = selections
20319 .into_iter()
20320 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20321 .collect::<Vec<_>>();
20322 self.fold_creases(ranges, true, window, cx);
20323 }
20324
20325 pub fn fold_ranges<T: ToOffset + Clone>(
20326 &mut self,
20327 ranges: Vec<Range<T>>,
20328 auto_scroll: bool,
20329 window: &mut Window,
20330 cx: &mut Context<Self>,
20331 ) {
20332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20333 let ranges = ranges
20334 .into_iter()
20335 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20336 .collect::<Vec<_>>();
20337 self.fold_creases(ranges, auto_scroll, window, cx);
20338 }
20339
20340 pub fn fold_creases<T: ToOffset + Clone>(
20341 &mut self,
20342 creases: Vec<Crease<T>>,
20343 auto_scroll: bool,
20344 _window: &mut Window,
20345 cx: &mut Context<Self>,
20346 ) {
20347 if creases.is_empty() {
20348 return;
20349 }
20350
20351 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20352
20353 if auto_scroll {
20354 self.request_autoscroll(Autoscroll::fit(), cx);
20355 }
20356
20357 cx.notify();
20358
20359 self.scrollbar_marker_state.dirty = true;
20360 self.folds_did_change(cx);
20361 }
20362
20363 /// Removes any folds whose ranges intersect any of the given ranges.
20364 pub fn unfold_ranges<T: ToOffset + Clone>(
20365 &mut self,
20366 ranges: &[Range<T>],
20367 inclusive: bool,
20368 auto_scroll: bool,
20369 cx: &mut Context<Self>,
20370 ) {
20371 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20372 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20373 });
20374 self.folds_did_change(cx);
20375 }
20376
20377 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20378 self.fold_buffers([buffer_id], cx);
20379 }
20380
20381 pub fn fold_buffers(
20382 &mut self,
20383 buffer_ids: impl IntoIterator<Item = BufferId>,
20384 cx: &mut Context<Self>,
20385 ) {
20386 if self.buffer().read(cx).is_singleton() {
20387 return;
20388 }
20389
20390 let ids_to_fold: Vec<BufferId> = buffer_ids
20391 .into_iter()
20392 .filter(|id| !self.is_buffer_folded(*id, cx))
20393 .collect();
20394
20395 if ids_to_fold.is_empty() {
20396 return;
20397 }
20398
20399 let mut all_folded_excerpt_ids = Vec::new();
20400 for buffer_id in &ids_to_fold {
20401 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20402 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _)| id));
20403 }
20404
20405 self.display_map.update(cx, |display_map, cx| {
20406 display_map.fold_buffers(ids_to_fold.clone(), cx)
20407 });
20408
20409 let snapshot = self.display_snapshot(cx);
20410 self.selections.change_with(&snapshot, |selections| {
20411 for buffer_id in ids_to_fold {
20412 selections.remove_selections_from_buffer(buffer_id);
20413 }
20414 });
20415
20416 cx.emit(EditorEvent::BufferFoldToggled {
20417 ids: all_folded_excerpt_ids,
20418 folded: true,
20419 });
20420 cx.notify();
20421 }
20422
20423 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20424 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20425 return;
20426 }
20427 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20428 self.display_map.update(cx, |display_map, cx| {
20429 display_map.unfold_buffers([buffer_id], cx);
20430 });
20431 cx.emit(EditorEvent::BufferFoldToggled {
20432 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20433 folded: false,
20434 });
20435 cx.notify();
20436 }
20437
20438 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20439 self.display_map.read(cx).is_buffer_folded(buffer)
20440 }
20441
20442 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20443 if self.buffer().read(cx).is_singleton() {
20444 return false;
20445 }
20446 !self.folded_buffers(cx).is_empty()
20447 }
20448
20449 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20450 self.display_map.read(cx).folded_buffers()
20451 }
20452
20453 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20454 self.display_map.update(cx, |display_map, cx| {
20455 display_map.disable_header_for_buffer(buffer_id, cx);
20456 });
20457 cx.notify();
20458 }
20459
20460 /// Removes any folds with the given ranges.
20461 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20462 &mut self,
20463 ranges: &[Range<T>],
20464 type_id: TypeId,
20465 auto_scroll: bool,
20466 cx: &mut Context<Self>,
20467 ) {
20468 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20469 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20470 });
20471 self.folds_did_change(cx);
20472 }
20473
20474 fn remove_folds_with<T: ToOffset + Clone>(
20475 &mut self,
20476 ranges: &[Range<T>],
20477 auto_scroll: bool,
20478 cx: &mut Context<Self>,
20479 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20480 ) {
20481 if ranges.is_empty() {
20482 return;
20483 }
20484
20485 let mut buffers_affected = HashSet::default();
20486 let multi_buffer = self.buffer().read(cx);
20487 for range in ranges {
20488 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20489 buffers_affected.insert(buffer.read(cx).remote_id());
20490 };
20491 }
20492
20493 self.display_map.update(cx, update);
20494
20495 if auto_scroll {
20496 self.request_autoscroll(Autoscroll::fit(), cx);
20497 }
20498
20499 cx.notify();
20500 self.scrollbar_marker_state.dirty = true;
20501 self.active_indent_guides_state.dirty = true;
20502 }
20503
20504 pub fn update_renderer_widths(
20505 &mut self,
20506 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20507 cx: &mut Context<Self>,
20508 ) -> bool {
20509 self.display_map
20510 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20511 }
20512
20513 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20514 self.display_map.read(cx).fold_placeholder.clone()
20515 }
20516
20517 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20518 self.buffer.update(cx, |buffer, cx| {
20519 buffer.set_all_diff_hunks_expanded(cx);
20520 });
20521 }
20522
20523 pub fn expand_all_diff_hunks(
20524 &mut self,
20525 _: &ExpandAllDiffHunks,
20526 _window: &mut Window,
20527 cx: &mut Context<Self>,
20528 ) {
20529 self.buffer.update(cx, |buffer, cx| {
20530 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20531 });
20532 }
20533
20534 pub fn collapse_all_diff_hunks(
20535 &mut self,
20536 _: &CollapseAllDiffHunks,
20537 _window: &mut Window,
20538 cx: &mut Context<Self>,
20539 ) {
20540 self.buffer.update(cx, |buffer, cx| {
20541 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20542 });
20543 }
20544
20545 pub fn toggle_selected_diff_hunks(
20546 &mut self,
20547 _: &ToggleSelectedDiffHunks,
20548 _window: &mut Window,
20549 cx: &mut Context<Self>,
20550 ) {
20551 let ranges: Vec<_> = self
20552 .selections
20553 .disjoint_anchors()
20554 .iter()
20555 .map(|s| s.range())
20556 .collect();
20557 self.toggle_diff_hunks_in_ranges(ranges, cx);
20558 }
20559
20560 pub fn diff_hunks_in_ranges<'a>(
20561 &'a self,
20562 ranges: &'a [Range<Anchor>],
20563 buffer: &'a MultiBufferSnapshot,
20564 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20565 ranges.iter().flat_map(move |range| {
20566 let end_excerpt_id = range.end.excerpt_id;
20567 let range = range.to_point(buffer);
20568 let mut peek_end = range.end;
20569 if range.end.row < buffer.max_row().0 {
20570 peek_end = Point::new(range.end.row + 1, 0);
20571 }
20572 buffer
20573 .diff_hunks_in_range(range.start..peek_end)
20574 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20575 })
20576 }
20577
20578 pub fn has_stageable_diff_hunks_in_ranges(
20579 &self,
20580 ranges: &[Range<Anchor>],
20581 snapshot: &MultiBufferSnapshot,
20582 ) -> bool {
20583 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20584 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20585 }
20586
20587 pub fn toggle_staged_selected_diff_hunks(
20588 &mut self,
20589 _: &::git::ToggleStaged,
20590 _: &mut Window,
20591 cx: &mut Context<Self>,
20592 ) {
20593 let snapshot = self.buffer.read(cx).snapshot(cx);
20594 let ranges: Vec<_> = self
20595 .selections
20596 .disjoint_anchors()
20597 .iter()
20598 .map(|s| s.range())
20599 .collect();
20600 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20601 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20602 }
20603
20604 pub fn set_render_diff_hunk_controls(
20605 &mut self,
20606 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20607 cx: &mut Context<Self>,
20608 ) {
20609 self.render_diff_hunk_controls = render_diff_hunk_controls;
20610 cx.notify();
20611 }
20612
20613 pub fn stage_and_next(
20614 &mut self,
20615 _: &::git::StageAndNext,
20616 window: &mut Window,
20617 cx: &mut Context<Self>,
20618 ) {
20619 self.do_stage_or_unstage_and_next(true, window, cx);
20620 }
20621
20622 pub fn unstage_and_next(
20623 &mut self,
20624 _: &::git::UnstageAndNext,
20625 window: &mut Window,
20626 cx: &mut Context<Self>,
20627 ) {
20628 self.do_stage_or_unstage_and_next(false, window, cx);
20629 }
20630
20631 pub fn stage_or_unstage_diff_hunks(
20632 &mut self,
20633 stage: bool,
20634 ranges: Vec<Range<Anchor>>,
20635 cx: &mut Context<Self>,
20636 ) {
20637 if self.delegate_stage_and_restore {
20638 let snapshot = self.buffer.read(cx).snapshot(cx);
20639 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20640 if !hunks.is_empty() {
20641 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20642 }
20643 return;
20644 }
20645 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20646 cx.spawn(async move |this, cx| {
20647 task.await?;
20648 this.update(cx, |this, cx| {
20649 let snapshot = this.buffer.read(cx).snapshot(cx);
20650 let chunk_by = this
20651 .diff_hunks_in_ranges(&ranges, &snapshot)
20652 .chunk_by(|hunk| hunk.buffer_id);
20653 for (buffer_id, hunks) in &chunk_by {
20654 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20655 }
20656 })
20657 })
20658 .detach_and_log_err(cx);
20659 }
20660
20661 fn save_buffers_for_ranges_if_needed(
20662 &mut self,
20663 ranges: &[Range<Anchor>],
20664 cx: &mut Context<Editor>,
20665 ) -> Task<Result<()>> {
20666 let multibuffer = self.buffer.read(cx);
20667 let snapshot = multibuffer.read(cx);
20668 let buffer_ids: HashSet<_> = ranges
20669 .iter()
20670 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20671 .collect();
20672 drop(snapshot);
20673
20674 let mut buffers = HashSet::default();
20675 for buffer_id in buffer_ids {
20676 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20677 let buffer = buffer_entity.read(cx);
20678 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20679 {
20680 buffers.insert(buffer_entity);
20681 }
20682 }
20683 }
20684
20685 if let Some(project) = &self.project {
20686 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20687 } else {
20688 Task::ready(Ok(()))
20689 }
20690 }
20691
20692 fn do_stage_or_unstage_and_next(
20693 &mut self,
20694 stage: bool,
20695 window: &mut Window,
20696 cx: &mut Context<Self>,
20697 ) {
20698 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20699
20700 if ranges.iter().any(|range| range.start != range.end) {
20701 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20702 return;
20703 }
20704
20705 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20706 let snapshot = self.snapshot(window, cx);
20707 let position = self
20708 .selections
20709 .newest::<Point>(&snapshot.display_snapshot)
20710 .head();
20711 let mut row = snapshot
20712 .buffer_snapshot()
20713 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20714 .find(|hunk| hunk.row_range.start.0 > position.row)
20715 .map(|hunk| hunk.row_range.start);
20716
20717 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20718 // Outside of the project diff editor, wrap around to the beginning.
20719 if !all_diff_hunks_expanded {
20720 row = row.or_else(|| {
20721 snapshot
20722 .buffer_snapshot()
20723 .diff_hunks_in_range(Point::zero()..position)
20724 .find(|hunk| hunk.row_range.end.0 < position.row)
20725 .map(|hunk| hunk.row_range.start)
20726 });
20727 }
20728
20729 if let Some(row) = row {
20730 let destination = Point::new(row.0, 0);
20731 let autoscroll = Autoscroll::center();
20732
20733 self.unfold_ranges(&[destination..destination], false, false, cx);
20734 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20735 s.select_ranges([destination..destination]);
20736 });
20737 }
20738 }
20739
20740 pub(crate) fn do_stage_or_unstage(
20741 &self,
20742 stage: bool,
20743 buffer_id: BufferId,
20744 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20745 cx: &mut App,
20746 ) -> Option<()> {
20747 let project = self.project()?;
20748 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20749 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20750 let buffer_snapshot = buffer.read(cx).snapshot();
20751 let file_exists = buffer_snapshot
20752 .file()
20753 .is_some_and(|file| file.disk_state().exists());
20754 diff.update(cx, |diff, cx| {
20755 diff.stage_or_unstage_hunks(
20756 stage,
20757 &hunks
20758 .map(|hunk| buffer_diff::DiffHunk {
20759 buffer_range: hunk.buffer_range,
20760 // We don't need to pass in word diffs here because they're only used for rendering and
20761 // this function changes internal state
20762 base_word_diffs: Vec::default(),
20763 buffer_word_diffs: Vec::default(),
20764 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20765 ..hunk.diff_base_byte_range.end.0,
20766 secondary_status: hunk.status.secondary,
20767 range: Point::zero()..Point::zero(), // unused
20768 })
20769 .collect::<Vec<_>>(),
20770 &buffer_snapshot,
20771 file_exists,
20772 cx,
20773 )
20774 });
20775 None
20776 }
20777
20778 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20779 let ranges: Vec<_> = self
20780 .selections
20781 .disjoint_anchors()
20782 .iter()
20783 .map(|s| s.range())
20784 .collect();
20785 self.buffer
20786 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20787 }
20788
20789 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20790 self.buffer.update(cx, |buffer, cx| {
20791 let ranges = vec![Anchor::min()..Anchor::max()];
20792 if !buffer.all_diff_hunks_expanded()
20793 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20794 {
20795 buffer.collapse_diff_hunks(ranges, cx);
20796 true
20797 } else {
20798 false
20799 }
20800 })
20801 }
20802
20803 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20804 if self.buffer.read(cx).all_diff_hunks_expanded() {
20805 return true;
20806 }
20807 let ranges = vec![Anchor::min()..Anchor::max()];
20808 self.buffer
20809 .read(cx)
20810 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20811 }
20812
20813 fn toggle_diff_hunks_in_ranges(
20814 &mut self,
20815 ranges: Vec<Range<Anchor>>,
20816 cx: &mut Context<Editor>,
20817 ) {
20818 self.buffer.update(cx, |buffer, cx| {
20819 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20820 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20821 })
20822 }
20823
20824 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20825 self.buffer.update(cx, |buffer, cx| {
20826 buffer.toggle_single_diff_hunk(range, cx);
20827 })
20828 }
20829
20830 pub(crate) fn apply_all_diff_hunks(
20831 &mut self,
20832 _: &ApplyAllDiffHunks,
20833 window: &mut Window,
20834 cx: &mut Context<Self>,
20835 ) {
20836 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20837
20838 let buffers = self.buffer.read(cx).all_buffers();
20839 for branch_buffer in buffers {
20840 branch_buffer.update(cx, |branch_buffer, cx| {
20841 branch_buffer.merge_into_base(Vec::new(), cx);
20842 });
20843 }
20844
20845 if let Some(project) = self.project.clone() {
20846 self.save(
20847 SaveOptions {
20848 format: true,
20849 autosave: false,
20850 },
20851 project,
20852 window,
20853 cx,
20854 )
20855 .detach_and_log_err(cx);
20856 }
20857 }
20858
20859 pub(crate) fn apply_selected_diff_hunks(
20860 &mut self,
20861 _: &ApplyDiffHunk,
20862 window: &mut Window,
20863 cx: &mut Context<Self>,
20864 ) {
20865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20866 let snapshot = self.snapshot(window, cx);
20867 let hunks = snapshot.hunks_for_ranges(
20868 self.selections
20869 .all(&snapshot.display_snapshot)
20870 .into_iter()
20871 .map(|selection| selection.range()),
20872 );
20873 let mut ranges_by_buffer = HashMap::default();
20874 self.transact(window, cx, |editor, _window, cx| {
20875 for hunk in hunks {
20876 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20877 ranges_by_buffer
20878 .entry(buffer.clone())
20879 .or_insert_with(Vec::new)
20880 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20881 }
20882 }
20883
20884 for (buffer, ranges) in ranges_by_buffer {
20885 buffer.update(cx, |buffer, cx| {
20886 buffer.merge_into_base(ranges, cx);
20887 });
20888 }
20889 });
20890
20891 if let Some(project) = self.project.clone() {
20892 self.save(
20893 SaveOptions {
20894 format: true,
20895 autosave: false,
20896 },
20897 project,
20898 window,
20899 cx,
20900 )
20901 .detach_and_log_err(cx);
20902 }
20903 }
20904
20905 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20906 if hovered != self.gutter_hovered {
20907 self.gutter_hovered = hovered;
20908 cx.notify();
20909 }
20910 }
20911
20912 pub fn insert_blocks(
20913 &mut self,
20914 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20915 autoscroll: Option<Autoscroll>,
20916 cx: &mut Context<Self>,
20917 ) -> Vec<CustomBlockId> {
20918 let blocks = self
20919 .display_map
20920 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20921 if let Some(autoscroll) = autoscroll {
20922 self.request_autoscroll(autoscroll, cx);
20923 }
20924 cx.notify();
20925 blocks
20926 }
20927
20928 pub fn resize_blocks(
20929 &mut self,
20930 heights: HashMap<CustomBlockId, u32>,
20931 autoscroll: Option<Autoscroll>,
20932 cx: &mut Context<Self>,
20933 ) {
20934 self.display_map
20935 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20936 if let Some(autoscroll) = autoscroll {
20937 self.request_autoscroll(autoscroll, cx);
20938 }
20939 cx.notify();
20940 }
20941
20942 pub fn replace_blocks(
20943 &mut self,
20944 renderers: HashMap<CustomBlockId, RenderBlock>,
20945 autoscroll: Option<Autoscroll>,
20946 cx: &mut Context<Self>,
20947 ) {
20948 self.display_map
20949 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20950 if let Some(autoscroll) = autoscroll {
20951 self.request_autoscroll(autoscroll, cx);
20952 }
20953 cx.notify();
20954 }
20955
20956 pub fn remove_blocks(
20957 &mut self,
20958 block_ids: HashSet<CustomBlockId>,
20959 autoscroll: Option<Autoscroll>,
20960 cx: &mut Context<Self>,
20961 ) {
20962 self.display_map.update(cx, |display_map, cx| {
20963 display_map.remove_blocks(block_ids, cx)
20964 });
20965 if let Some(autoscroll) = autoscroll {
20966 self.request_autoscroll(autoscroll, cx);
20967 }
20968 cx.notify();
20969 }
20970
20971 pub fn row_for_block(
20972 &self,
20973 block_id: CustomBlockId,
20974 cx: &mut Context<Self>,
20975 ) -> Option<DisplayRow> {
20976 self.display_map
20977 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20978 }
20979
20980 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20981 self.focused_block = Some(focused_block);
20982 }
20983
20984 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20985 self.focused_block.take()
20986 }
20987
20988 pub fn insert_creases(
20989 &mut self,
20990 creases: impl IntoIterator<Item = Crease<Anchor>>,
20991 cx: &mut Context<Self>,
20992 ) -> Vec<CreaseId> {
20993 self.display_map
20994 .update(cx, |map, cx| map.insert_creases(creases, cx))
20995 }
20996
20997 pub fn remove_creases(
20998 &mut self,
20999 ids: impl IntoIterator<Item = CreaseId>,
21000 cx: &mut Context<Self>,
21001 ) -> Vec<(CreaseId, Range<Anchor>)> {
21002 self.display_map
21003 .update(cx, |map, cx| map.remove_creases(ids, cx))
21004 }
21005
21006 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21007 self.display_map
21008 .update(cx, |map, cx| map.snapshot(cx))
21009 .longest_row()
21010 }
21011
21012 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21013 self.display_map
21014 .update(cx, |map, cx| map.snapshot(cx))
21015 .max_point()
21016 }
21017
21018 pub fn text(&self, cx: &App) -> String {
21019 self.buffer.read(cx).read(cx).text()
21020 }
21021
21022 pub fn is_empty(&self, cx: &App) -> bool {
21023 self.buffer.read(cx).read(cx).is_empty()
21024 }
21025
21026 pub fn text_option(&self, cx: &App) -> Option<String> {
21027 let text = self.text(cx);
21028 let text = text.trim();
21029
21030 if text.is_empty() {
21031 return None;
21032 }
21033
21034 Some(text.to_string())
21035 }
21036
21037 pub fn set_text(
21038 &mut self,
21039 text: impl Into<Arc<str>>,
21040 window: &mut Window,
21041 cx: &mut Context<Self>,
21042 ) {
21043 self.transact(window, cx, |this, _, cx| {
21044 this.buffer
21045 .read(cx)
21046 .as_singleton()
21047 .expect("you can only call set_text on editors for singleton buffers")
21048 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21049 });
21050 }
21051
21052 pub fn display_text(&self, cx: &mut App) -> String {
21053 self.display_map
21054 .update(cx, |map, cx| map.snapshot(cx))
21055 .text()
21056 }
21057
21058 fn create_minimap(
21059 &self,
21060 minimap_settings: MinimapSettings,
21061 window: &mut Window,
21062 cx: &mut Context<Self>,
21063 ) -> Option<Entity<Self>> {
21064 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21065 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21066 }
21067
21068 fn initialize_new_minimap(
21069 &self,
21070 minimap_settings: MinimapSettings,
21071 window: &mut Window,
21072 cx: &mut Context<Self>,
21073 ) -> Entity<Self> {
21074 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21075 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21076
21077 let mut minimap = Editor::new_internal(
21078 EditorMode::Minimap {
21079 parent: cx.weak_entity(),
21080 },
21081 self.buffer.clone(),
21082 None,
21083 Some(self.display_map.clone()),
21084 window,
21085 cx,
21086 );
21087 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21088 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21089 minimap.scroll_manager.clone_state(
21090 &self.scroll_manager,
21091 &my_snapshot,
21092 &minimap_snapshot,
21093 cx,
21094 );
21095 minimap.set_text_style_refinement(TextStyleRefinement {
21096 font_size: Some(MINIMAP_FONT_SIZE),
21097 font_weight: Some(MINIMAP_FONT_WEIGHT),
21098 font_family: Some(MINIMAP_FONT_FAMILY),
21099 ..Default::default()
21100 });
21101 minimap.update_minimap_configuration(minimap_settings, cx);
21102 cx.new(|_| minimap)
21103 }
21104
21105 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21106 let current_line_highlight = minimap_settings
21107 .current_line_highlight
21108 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21109 self.set_current_line_highlight(Some(current_line_highlight));
21110 }
21111
21112 pub fn minimap(&self) -> Option<&Entity<Self>> {
21113 self.minimap
21114 .as_ref()
21115 .filter(|_| self.minimap_visibility.visible())
21116 }
21117
21118 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21119 let mut wrap_guides = smallvec![];
21120
21121 if self.show_wrap_guides == Some(false) {
21122 return wrap_guides;
21123 }
21124
21125 let settings = self.buffer.read(cx).language_settings(cx);
21126 if settings.show_wrap_guides {
21127 match self.soft_wrap_mode(cx) {
21128 SoftWrap::Column(soft_wrap) => {
21129 wrap_guides.push((soft_wrap as usize, true));
21130 }
21131 SoftWrap::Bounded(soft_wrap) => {
21132 wrap_guides.push((soft_wrap as usize, true));
21133 }
21134 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21135 }
21136 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21137 }
21138
21139 wrap_guides
21140 }
21141
21142 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21143 let settings = self.buffer.read(cx).language_settings(cx);
21144 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21145 match mode {
21146 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21147 SoftWrap::None
21148 }
21149 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21150 language_settings::SoftWrap::PreferredLineLength => {
21151 SoftWrap::Column(settings.preferred_line_length)
21152 }
21153 language_settings::SoftWrap::Bounded => {
21154 SoftWrap::Bounded(settings.preferred_line_length)
21155 }
21156 }
21157 }
21158
21159 pub fn set_soft_wrap_mode(
21160 &mut self,
21161 mode: language_settings::SoftWrap,
21162 cx: &mut Context<Self>,
21163 ) {
21164 self.soft_wrap_mode_override = Some(mode);
21165 cx.notify();
21166 }
21167
21168 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21169 self.hard_wrap = hard_wrap;
21170 cx.notify();
21171 }
21172
21173 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21174 self.text_style_refinement = Some(style);
21175 }
21176
21177 /// called by the Element so we know what style we were most recently rendered with.
21178 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21179 // We intentionally do not inform the display map about the minimap style
21180 // so that wrapping is not recalculated and stays consistent for the editor
21181 // and its linked minimap.
21182 if !self.mode.is_minimap() {
21183 let font = style.text.font();
21184 let font_size = style.text.font_size.to_pixels(window.rem_size());
21185 let display_map = self
21186 .placeholder_display_map
21187 .as_ref()
21188 .filter(|_| self.is_empty(cx))
21189 .unwrap_or(&self.display_map);
21190
21191 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21192 }
21193 self.style = Some(style);
21194 }
21195
21196 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21197 if self.style.is_none() {
21198 self.style = Some(self.create_style(cx));
21199 }
21200 self.style.as_ref().unwrap()
21201 }
21202
21203 // Called by the element. This method is not designed to be called outside of the editor
21204 // element's layout code because it does not notify when rewrapping is computed synchronously.
21205 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21206 if self.is_empty(cx) {
21207 self.placeholder_display_map
21208 .as_ref()
21209 .map_or(false, |display_map| {
21210 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21211 })
21212 } else {
21213 self.display_map
21214 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21215 }
21216 }
21217
21218 pub fn set_soft_wrap(&mut self) {
21219 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21220 }
21221
21222 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21223 if self.soft_wrap_mode_override.is_some() {
21224 self.soft_wrap_mode_override.take();
21225 } else {
21226 let soft_wrap = match self.soft_wrap_mode(cx) {
21227 SoftWrap::GitDiff => return,
21228 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21229 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21230 language_settings::SoftWrap::None
21231 }
21232 };
21233 self.soft_wrap_mode_override = Some(soft_wrap);
21234 }
21235 cx.notify();
21236 }
21237
21238 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21239 let Some(workspace) = self.workspace() else {
21240 return;
21241 };
21242 let fs = workspace.read(cx).app_state().fs.clone();
21243 let current_show = TabBarSettings::get_global(cx).show;
21244 update_settings_file(fs, cx, move |setting, _| {
21245 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21246 });
21247 }
21248
21249 pub fn toggle_indent_guides(
21250 &mut self,
21251 _: &ToggleIndentGuides,
21252 _: &mut Window,
21253 cx: &mut Context<Self>,
21254 ) {
21255 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21256 self.buffer
21257 .read(cx)
21258 .language_settings(cx)
21259 .indent_guides
21260 .enabled
21261 });
21262 self.show_indent_guides = Some(!currently_enabled);
21263 cx.notify();
21264 }
21265
21266 fn should_show_indent_guides(&self) -> Option<bool> {
21267 self.show_indent_guides
21268 }
21269
21270 pub fn disable_indent_guides_for_buffer(
21271 &mut self,
21272 buffer_id: BufferId,
21273 cx: &mut Context<Self>,
21274 ) {
21275 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21276 cx.notify();
21277 }
21278
21279 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21280 self.buffers_with_disabled_indent_guides
21281 .contains(&buffer_id)
21282 }
21283
21284 pub fn toggle_line_numbers(
21285 &mut self,
21286 _: &ToggleLineNumbers,
21287 _: &mut Window,
21288 cx: &mut Context<Self>,
21289 ) {
21290 let mut editor_settings = EditorSettings::get_global(cx).clone();
21291 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21292 EditorSettings::override_global(editor_settings, cx);
21293 }
21294
21295 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21296 if let Some(show_line_numbers) = self.show_line_numbers {
21297 return show_line_numbers;
21298 }
21299 EditorSettings::get_global(cx).gutter.line_numbers
21300 }
21301
21302 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21303 match (
21304 self.use_relative_line_numbers,
21305 EditorSettings::get_global(cx).relative_line_numbers,
21306 ) {
21307 (None, setting) => setting,
21308 (Some(false), _) => RelativeLineNumbers::Disabled,
21309 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21310 (Some(true), _) => RelativeLineNumbers::Enabled,
21311 }
21312 }
21313
21314 pub fn toggle_relative_line_numbers(
21315 &mut self,
21316 _: &ToggleRelativeLineNumbers,
21317 _: &mut Window,
21318 cx: &mut Context<Self>,
21319 ) {
21320 let is_relative = self.relative_line_numbers(cx);
21321 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21322 }
21323
21324 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21325 self.use_relative_line_numbers = is_relative;
21326 cx.notify();
21327 }
21328
21329 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21330 self.show_gutter = show_gutter;
21331 cx.notify();
21332 }
21333
21334 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21335 self.show_scrollbars = ScrollbarAxes {
21336 horizontal: show,
21337 vertical: show,
21338 };
21339 cx.notify();
21340 }
21341
21342 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21343 self.show_scrollbars.vertical = show;
21344 cx.notify();
21345 }
21346
21347 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21348 self.show_scrollbars.horizontal = show;
21349 cx.notify();
21350 }
21351
21352 pub fn set_minimap_visibility(
21353 &mut self,
21354 minimap_visibility: MinimapVisibility,
21355 window: &mut Window,
21356 cx: &mut Context<Self>,
21357 ) {
21358 if self.minimap_visibility != minimap_visibility {
21359 if minimap_visibility.visible() && self.minimap.is_none() {
21360 let minimap_settings = EditorSettings::get_global(cx).minimap;
21361 self.minimap =
21362 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21363 }
21364 self.minimap_visibility = minimap_visibility;
21365 cx.notify();
21366 }
21367 }
21368
21369 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21370 self.set_show_scrollbars(false, cx);
21371 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21372 }
21373
21374 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21375 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21376 }
21377
21378 /// Normally the text in full mode and auto height editors is padded on the
21379 /// left side by roughly half a character width for improved hit testing.
21380 ///
21381 /// Use this method to disable this for cases where this is not wanted (e.g.
21382 /// if you want to align the editor text with some other text above or below)
21383 /// or if you want to add this padding to single-line editors.
21384 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21385 self.offset_content = offset_content;
21386 cx.notify();
21387 }
21388
21389 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21390 self.show_line_numbers = Some(show_line_numbers);
21391 cx.notify();
21392 }
21393
21394 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21395 self.disable_expand_excerpt_buttons = true;
21396 cx.notify();
21397 }
21398
21399 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21400 self.number_deleted_lines = number;
21401 cx.notify();
21402 }
21403
21404 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21405 self.delegate_expand_excerpts = delegate;
21406 }
21407
21408 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21409 self.delegate_stage_and_restore = delegate;
21410 }
21411
21412 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21413 self.delegate_open_excerpts = delegate;
21414 }
21415
21416 pub fn set_on_local_selections_changed(
21417 &mut self,
21418 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21419 ) {
21420 self.on_local_selections_changed = callback;
21421 }
21422
21423 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21424 self.suppress_selection_callback = suppress;
21425 }
21426
21427 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21428 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21429 cx.notify();
21430 }
21431
21432 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21433 self.show_code_actions = Some(show_code_actions);
21434 cx.notify();
21435 }
21436
21437 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21438 self.show_runnables = Some(show_runnables);
21439 cx.notify();
21440 }
21441
21442 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21443 self.show_breakpoints = Some(show_breakpoints);
21444 cx.notify();
21445 }
21446
21447 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21448 self.show_diff_review_button = show;
21449 cx.notify();
21450 }
21451
21452 pub fn show_diff_review_button(&self) -> bool {
21453 self.show_diff_review_button
21454 }
21455
21456 pub fn render_diff_review_button(
21457 &self,
21458 display_row: DisplayRow,
21459 width: Pixels,
21460 cx: &mut Context<Self>,
21461 ) -> impl IntoElement {
21462 let text_color = cx.theme().colors().text;
21463 let icon_color = cx.theme().colors().icon_accent;
21464
21465 h_flex()
21466 .id("diff_review_button")
21467 .cursor_pointer()
21468 .w(width - px(1.))
21469 .h(relative(0.9))
21470 .justify_center()
21471 .rounded_sm()
21472 .border_1()
21473 .border_color(text_color.opacity(0.1))
21474 .bg(text_color.opacity(0.15))
21475 .hover(|s| {
21476 s.bg(icon_color.opacity(0.4))
21477 .border_color(icon_color.opacity(0.5))
21478 })
21479 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21480 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21481 .on_mouse_down(
21482 gpui::MouseButton::Left,
21483 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21484 editor.start_diff_review_drag(display_row, window, cx);
21485 }),
21486 )
21487 }
21488
21489 pub fn start_diff_review_drag(
21490 &mut self,
21491 display_row: DisplayRow,
21492 window: &mut Window,
21493 cx: &mut Context<Self>,
21494 ) {
21495 let snapshot = self.snapshot(window, cx);
21496 let point = snapshot
21497 .display_snapshot
21498 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21499 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21500 self.diff_review_drag_state = Some(DiffReviewDragState {
21501 start_anchor: anchor,
21502 current_anchor: anchor,
21503 });
21504 cx.notify();
21505 }
21506
21507 pub fn update_diff_review_drag(
21508 &mut self,
21509 display_row: DisplayRow,
21510 window: &mut Window,
21511 cx: &mut Context<Self>,
21512 ) {
21513 if self.diff_review_drag_state.is_none() {
21514 return;
21515 }
21516 let snapshot = self.snapshot(window, cx);
21517 let point = snapshot
21518 .display_snapshot
21519 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21520 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21521 if let Some(drag_state) = &mut self.diff_review_drag_state {
21522 drag_state.current_anchor = anchor;
21523 cx.notify();
21524 }
21525 }
21526
21527 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21528 if let Some(drag_state) = self.diff_review_drag_state.take() {
21529 let snapshot = self.snapshot(window, cx);
21530 let range = drag_state.row_range(&snapshot.display_snapshot);
21531 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21532 }
21533 cx.notify();
21534 }
21535
21536 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21537 self.diff_review_drag_state = None;
21538 cx.notify();
21539 }
21540
21541 /// Calculates the appropriate block height for the diff review overlay.
21542 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21543 /// and 2 lines per comment when expanded.
21544 fn calculate_overlay_height(
21545 &self,
21546 hunk_key: &DiffHunkKey,
21547 comments_expanded: bool,
21548 snapshot: &MultiBufferSnapshot,
21549 ) -> u32 {
21550 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21551 let base_height: u32 = 2; // Input row with avatar and buttons
21552
21553 if comment_count == 0 {
21554 base_height
21555 } else if comments_expanded {
21556 // Header (1 line) + 2 lines per comment
21557 base_height + 1 + (comment_count as u32 * 2)
21558 } else {
21559 // Just header when collapsed
21560 base_height + 1
21561 }
21562 }
21563
21564 pub fn show_diff_review_overlay(
21565 &mut self,
21566 display_range: Range<DisplayRow>,
21567 window: &mut Window,
21568 cx: &mut Context<Self>,
21569 ) {
21570 let Range { start, end } = display_range.sorted();
21571
21572 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21573 let editor_snapshot = self.snapshot(window, cx);
21574
21575 // Convert display rows to multibuffer points
21576 let start_point = editor_snapshot
21577 .display_snapshot
21578 .display_point_to_point(start.as_display_point(), Bias::Left);
21579 let end_point = editor_snapshot
21580 .display_snapshot
21581 .display_point_to_point(end.as_display_point(), Bias::Left);
21582 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21583
21584 // Create anchor range for the selected lines (start of first line to end of last line)
21585 let line_end = Point::new(
21586 end_point.row,
21587 buffer_snapshot.line_len(end_multi_buffer_row),
21588 );
21589 let anchor_range =
21590 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21591
21592 // Compute the hunk key for this display row
21593 let file_path = buffer_snapshot
21594 .file_at(start_point)
21595 .map(|file: &Arc<dyn language::File>| file.path().clone())
21596 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21597 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21598 let new_hunk_key = DiffHunkKey {
21599 file_path,
21600 hunk_start_anchor,
21601 };
21602
21603 // Check if we already have an overlay for this hunk
21604 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21605 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21606 }) {
21607 // Just focus the existing overlay's prompt editor
21608 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21609 window.focus(&focus_handle, cx);
21610 return;
21611 }
21612
21613 // Dismiss overlays that have no comments for their hunks
21614 self.dismiss_overlays_without_comments(cx);
21615
21616 // Get the current user's avatar URI from the project's user_store
21617 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21618 let user_store = project.read(cx).user_store();
21619 user_store
21620 .read(cx)
21621 .current_user()
21622 .map(|user| user.avatar_uri.clone())
21623 });
21624
21625 // Create anchor at the end of the last row so the block appears immediately below it
21626 // Use multibuffer coordinates for anchor creation
21627 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21628 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21629
21630 // Use the hunk key we already computed
21631 let hunk_key = new_hunk_key;
21632
21633 // Create the prompt editor for the review input
21634 let prompt_editor = cx.new(|cx| {
21635 let mut editor = Editor::single_line(window, cx);
21636 editor.set_placeholder_text("Add a review comment...", window, cx);
21637 editor
21638 });
21639
21640 // Register the Newline action on the prompt editor to submit the review
21641 let parent_editor = cx.entity().downgrade();
21642 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21643 prompt_editor.register_action({
21644 let parent_editor = parent_editor.clone();
21645 move |_: &crate::actions::Newline, window, cx| {
21646 if let Some(editor) = parent_editor.upgrade() {
21647 editor.update(cx, |editor, cx| {
21648 editor.submit_diff_review_comment(window, cx);
21649 });
21650 }
21651 }
21652 })
21653 });
21654
21655 // Calculate initial height based on existing comments for this hunk
21656 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21657
21658 // Create the overlay block
21659 let prompt_editor_for_render = prompt_editor.clone();
21660 let hunk_key_for_render = hunk_key.clone();
21661 let editor_handle = cx.entity().downgrade();
21662 let block = BlockProperties {
21663 style: BlockStyle::Sticky,
21664 placement: BlockPlacement::Below(anchor),
21665 height: Some(initial_height),
21666 render: Arc::new(move |cx| {
21667 Self::render_diff_review_overlay(
21668 &prompt_editor_for_render,
21669 &hunk_key_for_render,
21670 &editor_handle,
21671 cx,
21672 )
21673 }),
21674 priority: 0,
21675 };
21676
21677 let block_ids = self.insert_blocks([block], None, cx);
21678 let Some(block_id) = block_ids.into_iter().next() else {
21679 log::error!("Failed to insert diff review overlay block");
21680 return;
21681 };
21682
21683 self.diff_review_overlays.push(DiffReviewOverlay {
21684 anchor_range,
21685 block_id,
21686 prompt_editor: prompt_editor.clone(),
21687 hunk_key,
21688 comments_expanded: true,
21689 inline_edit_editors: HashMap::default(),
21690 inline_edit_subscriptions: HashMap::default(),
21691 user_avatar_uri,
21692 _subscription: subscription,
21693 });
21694
21695 // Focus the prompt editor
21696 let focus_handle = prompt_editor.focus_handle(cx);
21697 window.focus(&focus_handle, cx);
21698
21699 cx.notify();
21700 }
21701
21702 /// Dismisses all diff review overlays.
21703 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21704 if self.diff_review_overlays.is_empty() {
21705 return;
21706 }
21707 let block_ids: HashSet<_> = self
21708 .diff_review_overlays
21709 .drain(..)
21710 .map(|overlay| overlay.block_id)
21711 .collect();
21712 self.remove_blocks(block_ids, None, cx);
21713 cx.notify();
21714 }
21715
21716 /// Dismisses overlays that have no comments stored for their hunks.
21717 /// Keeps overlays that have at least one comment.
21718 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21719 let snapshot = self.buffer.read(cx).snapshot(cx);
21720
21721 // First, compute which overlays have comments (to avoid borrow issues with retain)
21722 let overlays_with_comments: Vec<bool> = self
21723 .diff_review_overlays
21724 .iter()
21725 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21726 .collect();
21727
21728 // Now collect block IDs to remove and retain overlays
21729 let mut block_ids_to_remove = HashSet::default();
21730 let mut index = 0;
21731 self.diff_review_overlays.retain(|overlay| {
21732 let has_comments = overlays_with_comments[index];
21733 index += 1;
21734 if !has_comments {
21735 block_ids_to_remove.insert(overlay.block_id);
21736 }
21737 has_comments
21738 });
21739
21740 if !block_ids_to_remove.is_empty() {
21741 self.remove_blocks(block_ids_to_remove, None, cx);
21742 cx.notify();
21743 }
21744 }
21745
21746 /// Refreshes the diff review overlay block to update its height and render function.
21747 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21748 fn refresh_diff_review_overlay_height(
21749 &mut self,
21750 hunk_key: &DiffHunkKey,
21751 _window: &mut Window,
21752 cx: &mut Context<Self>,
21753 ) {
21754 // Extract all needed data from overlay first to avoid borrow conflicts
21755 let snapshot = self.buffer.read(cx).snapshot(cx);
21756 let (comments_expanded, block_id, prompt_editor) = {
21757 let Some(overlay) = self
21758 .diff_review_overlays
21759 .iter()
21760 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21761 else {
21762 return;
21763 };
21764
21765 (
21766 overlay.comments_expanded,
21767 overlay.block_id,
21768 overlay.prompt_editor.clone(),
21769 )
21770 };
21771
21772 // Calculate new height
21773 let snapshot = self.buffer.read(cx).snapshot(cx);
21774 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21775
21776 // Update the block height using resize_blocks (avoids flicker)
21777 let mut heights = HashMap::default();
21778 heights.insert(block_id, new_height);
21779 self.resize_blocks(heights, None, cx);
21780
21781 // Update the render function using replace_blocks (avoids flicker)
21782 let hunk_key_for_render = hunk_key.clone();
21783 let editor_handle = cx.entity().downgrade();
21784 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21785 Arc::new(move |cx| {
21786 Self::render_diff_review_overlay(
21787 &prompt_editor,
21788 &hunk_key_for_render,
21789 &editor_handle,
21790 cx,
21791 )
21792 });
21793
21794 let mut renderers = HashMap::default();
21795 renderers.insert(block_id, render);
21796 self.replace_blocks(renderers, None, cx);
21797 }
21798
21799 /// Action handler for SubmitDiffReviewComment.
21800 pub fn submit_diff_review_comment_action(
21801 &mut self,
21802 _: &SubmitDiffReviewComment,
21803 window: &mut Window,
21804 cx: &mut Context<Self>,
21805 ) {
21806 self.submit_diff_review_comment(window, cx);
21807 }
21808
21809 /// Stores the diff review comment locally.
21810 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21811 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21812 // Find the overlay that currently has focus
21813 let overlay_index = self
21814 .diff_review_overlays
21815 .iter()
21816 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21817 let Some(overlay_index) = overlay_index else {
21818 return;
21819 };
21820 let overlay = &self.diff_review_overlays[overlay_index];
21821
21822 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21823 if comment_text.is_empty() {
21824 return;
21825 }
21826
21827 let anchor_range = overlay.anchor_range.clone();
21828 let hunk_key = overlay.hunk_key.clone();
21829
21830 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21831
21832 // Clear the prompt editor but keep the overlay open
21833 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21834 overlay.prompt_editor.update(cx, |editor, cx| {
21835 editor.clear(window, cx);
21836 });
21837 }
21838
21839 // Refresh the overlay to update the block height for the new comment
21840 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21841
21842 cx.notify();
21843 }
21844
21845 /// Returns the prompt editor for the diff review overlay, if one is active.
21846 /// This is primarily used for testing.
21847 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21848 self.diff_review_overlays
21849 .first()
21850 .map(|overlay| &overlay.prompt_editor)
21851 }
21852
21853 /// Returns the line range for the first diff review overlay, if one is active.
21854 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21855 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21856 let overlay = self.diff_review_overlays.first()?;
21857 let snapshot = self.buffer.read(cx).snapshot(cx);
21858 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21859 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21860 let start_row = snapshot
21861 .point_to_buffer_point(start_point)
21862 .map(|(_, p, _)| p.row)
21863 .unwrap_or(start_point.row);
21864 let end_row = snapshot
21865 .point_to_buffer_point(end_point)
21866 .map(|(_, p, _)| p.row)
21867 .unwrap_or(end_point.row);
21868 Some((start_row, end_row))
21869 }
21870
21871 /// Sets whether the comments section is expanded in the diff review overlay.
21872 /// This is primarily used for testing.
21873 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21874 for overlay in &mut self.diff_review_overlays {
21875 overlay.comments_expanded = expanded;
21876 }
21877 cx.notify();
21878 }
21879
21880 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21881 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21882 a.file_path == b.file_path
21883 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21884 }
21885
21886 /// Returns comments for a specific hunk, ordered by creation time.
21887 pub fn comments_for_hunk<'a>(
21888 &'a self,
21889 key: &DiffHunkKey,
21890 snapshot: &MultiBufferSnapshot,
21891 ) -> &'a [StoredReviewComment] {
21892 let key_point = key.hunk_start_anchor.to_point(snapshot);
21893 self.stored_review_comments
21894 .iter()
21895 .find(|(k, _)| {
21896 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21897 })
21898 .map(|(_, comments)| comments.as_slice())
21899 .unwrap_or(&[])
21900 }
21901
21902 /// Returns the total count of stored review comments across all hunks.
21903 pub fn total_review_comment_count(&self) -> usize {
21904 self.stored_review_comments
21905 .iter()
21906 .map(|(_, v)| v.len())
21907 .sum()
21908 }
21909
21910 /// Returns the count of comments for a specific hunk.
21911 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21912 let key_point = key.hunk_start_anchor.to_point(snapshot);
21913 self.stored_review_comments
21914 .iter()
21915 .find(|(k, _)| {
21916 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21917 })
21918 .map(|(_, v)| v.len())
21919 .unwrap_or(0)
21920 }
21921
21922 /// Adds a new review comment to a specific hunk.
21923 pub fn add_review_comment(
21924 &mut self,
21925 hunk_key: DiffHunkKey,
21926 comment: String,
21927 anchor_range: Range<Anchor>,
21928 cx: &mut Context<Self>,
21929 ) -> usize {
21930 let id = self.next_review_comment_id;
21931 self.next_review_comment_id += 1;
21932
21933 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21934
21935 let snapshot = self.buffer.read(cx).snapshot(cx);
21936 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21937
21938 // Find existing entry for this hunk or add a new one
21939 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21940 k.file_path == hunk_key.file_path
21941 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21942 }) {
21943 comments.push(stored_comment);
21944 } else {
21945 self.stored_review_comments
21946 .push((hunk_key, vec![stored_comment]));
21947 }
21948
21949 cx.emit(EditorEvent::ReviewCommentsChanged {
21950 total_count: self.total_review_comment_count(),
21951 });
21952 cx.notify();
21953 id
21954 }
21955
21956 /// Removes a review comment by ID from any hunk.
21957 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21958 for (_, comments) in self.stored_review_comments.iter_mut() {
21959 if let Some(index) = comments.iter().position(|c| c.id == id) {
21960 comments.remove(index);
21961 cx.emit(EditorEvent::ReviewCommentsChanged {
21962 total_count: self.total_review_comment_count(),
21963 });
21964 cx.notify();
21965 return true;
21966 }
21967 }
21968 false
21969 }
21970
21971 /// Updates a review comment's text by ID.
21972 pub fn update_review_comment(
21973 &mut self,
21974 id: usize,
21975 new_comment: String,
21976 cx: &mut Context<Self>,
21977 ) -> bool {
21978 for (_, comments) in self.stored_review_comments.iter_mut() {
21979 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21980 comment.comment = new_comment;
21981 comment.is_editing = false;
21982 cx.emit(EditorEvent::ReviewCommentsChanged {
21983 total_count: self.total_review_comment_count(),
21984 });
21985 cx.notify();
21986 return true;
21987 }
21988 }
21989 false
21990 }
21991
21992 /// Sets a comment's editing state.
21993 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21994 for (_, comments) in self.stored_review_comments.iter_mut() {
21995 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21996 comment.is_editing = is_editing;
21997 cx.notify();
21998 return;
21999 }
22000 }
22001 }
22002
22003 /// Takes all stored comments from all hunks, clearing the storage.
22004 /// Returns a Vec of (hunk_key, comments) pairs.
22005 pub fn take_all_review_comments(
22006 &mut self,
22007 cx: &mut Context<Self>,
22008 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22009 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22010 self.dismiss_all_diff_review_overlays(cx);
22011 let comments = std::mem::take(&mut self.stored_review_comments);
22012 // Reset the ID counter since all comments have been taken
22013 self.next_review_comment_id = 0;
22014 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22015 cx.notify();
22016 comments
22017 }
22018
22019 /// Removes review comments whose anchors are no longer valid or whose
22020 /// associated diff hunks no longer exist.
22021 ///
22022 /// This should be called when the buffer changes to prevent orphaned comments
22023 /// from accumulating.
22024 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22025 let snapshot = self.buffer.read(cx).snapshot(cx);
22026 let original_count = self.total_review_comment_count();
22027
22028 // Remove comments with invalid hunk anchors
22029 self.stored_review_comments
22030 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22031
22032 // Also clean up individual comments with invalid anchor ranges
22033 for (_, comments) in &mut self.stored_review_comments {
22034 comments.retain(|comment| {
22035 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22036 });
22037 }
22038
22039 // Remove empty hunk entries
22040 self.stored_review_comments
22041 .retain(|(_, comments)| !comments.is_empty());
22042
22043 let new_count = self.total_review_comment_count();
22044 if new_count != original_count {
22045 cx.emit(EditorEvent::ReviewCommentsChanged {
22046 total_count: new_count,
22047 });
22048 cx.notify();
22049 }
22050 }
22051
22052 /// Toggles the expanded state of the comments section in the overlay.
22053 pub fn toggle_review_comments_expanded(
22054 &mut self,
22055 _: &ToggleReviewCommentsExpanded,
22056 window: &mut Window,
22057 cx: &mut Context<Self>,
22058 ) {
22059 // Find the overlay that currently has focus, or use the first one
22060 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22061 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22062 overlay.comments_expanded = !overlay.comments_expanded;
22063 Some(overlay.hunk_key.clone())
22064 } else {
22065 None
22066 }
22067 });
22068
22069 // If no focused overlay found, toggle the first one
22070 let hunk_key = overlay_info.or_else(|| {
22071 self.diff_review_overlays.first_mut().map(|overlay| {
22072 overlay.comments_expanded = !overlay.comments_expanded;
22073 overlay.hunk_key.clone()
22074 })
22075 });
22076
22077 if let Some(hunk_key) = hunk_key {
22078 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22079 cx.notify();
22080 }
22081 }
22082
22083 /// Handles the EditReviewComment action - sets a comment into editing mode.
22084 pub fn edit_review_comment(
22085 &mut self,
22086 action: &EditReviewComment,
22087 window: &mut Window,
22088 cx: &mut Context<Self>,
22089 ) {
22090 let comment_id = action.id;
22091
22092 // Set the comment to editing mode
22093 self.set_comment_editing(comment_id, true, cx);
22094
22095 // Find the overlay that contains this comment and create an inline editor if needed
22096 // First, find which hunk this comment belongs to
22097 let hunk_key = self
22098 .stored_review_comments
22099 .iter()
22100 .find_map(|(key, comments)| {
22101 if comments.iter().any(|c| c.id == comment_id) {
22102 Some(key.clone())
22103 } else {
22104 None
22105 }
22106 });
22107
22108 let snapshot = self.buffer.read(cx).snapshot(cx);
22109 if let Some(hunk_key) = hunk_key {
22110 if let Some(overlay) = self
22111 .diff_review_overlays
22112 .iter_mut()
22113 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22114 {
22115 if let std::collections::hash_map::Entry::Vacant(entry) =
22116 overlay.inline_edit_editors.entry(comment_id)
22117 {
22118 // Find the comment text
22119 let comment_text = self
22120 .stored_review_comments
22121 .iter()
22122 .flat_map(|(_, comments)| comments)
22123 .find(|c| c.id == comment_id)
22124 .map(|c| c.comment.clone())
22125 .unwrap_or_default();
22126
22127 // Create inline editor
22128 let parent_editor = cx.entity().downgrade();
22129 let inline_editor = cx.new(|cx| {
22130 let mut editor = Editor::single_line(window, cx);
22131 editor.set_text(&*comment_text, window, cx);
22132 // Select all text for easy replacement
22133 editor.select_all(&crate::actions::SelectAll, window, cx);
22134 editor
22135 });
22136
22137 // Register the Newline action to confirm the edit
22138 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22139 inline_editor.register_action({
22140 let parent_editor = parent_editor.clone();
22141 move |_: &crate::actions::Newline, window, cx| {
22142 if let Some(editor) = parent_editor.upgrade() {
22143 editor.update(cx, |editor, cx| {
22144 editor.confirm_edit_review_comment(comment_id, window, cx);
22145 });
22146 }
22147 }
22148 })
22149 });
22150
22151 // Store the subscription to keep the action handler alive
22152 overlay
22153 .inline_edit_subscriptions
22154 .insert(comment_id, subscription);
22155
22156 // Focus the inline editor
22157 let focus_handle = inline_editor.focus_handle(cx);
22158 window.focus(&focus_handle, cx);
22159
22160 entry.insert(inline_editor);
22161 }
22162 }
22163 }
22164
22165 cx.notify();
22166 }
22167
22168 /// Confirms an inline edit of a review comment.
22169 pub fn confirm_edit_review_comment(
22170 &mut self,
22171 comment_id: usize,
22172 _window: &mut Window,
22173 cx: &mut Context<Self>,
22174 ) {
22175 // Get the new text from the inline editor
22176 // Find the overlay containing this comment's inline editor
22177 let snapshot = self.buffer.read(cx).snapshot(cx);
22178 let hunk_key = self
22179 .stored_review_comments
22180 .iter()
22181 .find_map(|(key, comments)| {
22182 if comments.iter().any(|c| c.id == comment_id) {
22183 Some(key.clone())
22184 } else {
22185 None
22186 }
22187 });
22188
22189 let new_text = hunk_key
22190 .as_ref()
22191 .and_then(|hunk_key| {
22192 self.diff_review_overlays
22193 .iter()
22194 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22195 })
22196 .as_ref()
22197 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22198 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22199
22200 if let Some(new_text) = new_text {
22201 if !new_text.is_empty() {
22202 self.update_review_comment(comment_id, new_text, cx);
22203 }
22204 }
22205
22206 // Remove the inline editor and its subscription
22207 if let Some(hunk_key) = hunk_key {
22208 if let Some(overlay) = self
22209 .diff_review_overlays
22210 .iter_mut()
22211 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22212 {
22213 overlay.inline_edit_editors.remove(&comment_id);
22214 overlay.inline_edit_subscriptions.remove(&comment_id);
22215 }
22216 }
22217
22218 // Clear editing state
22219 self.set_comment_editing(comment_id, false, cx);
22220 }
22221
22222 /// Cancels an inline edit of a review comment.
22223 pub fn cancel_edit_review_comment(
22224 &mut self,
22225 comment_id: usize,
22226 _window: &mut Window,
22227 cx: &mut Context<Self>,
22228 ) {
22229 // Find which hunk this comment belongs to
22230 let hunk_key = self
22231 .stored_review_comments
22232 .iter()
22233 .find_map(|(key, comments)| {
22234 if comments.iter().any(|c| c.id == comment_id) {
22235 Some(key.clone())
22236 } else {
22237 None
22238 }
22239 });
22240
22241 // Remove the inline editor and its subscription
22242 if let Some(hunk_key) = hunk_key {
22243 let snapshot = self.buffer.read(cx).snapshot(cx);
22244 if let Some(overlay) = self
22245 .diff_review_overlays
22246 .iter_mut()
22247 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22248 {
22249 overlay.inline_edit_editors.remove(&comment_id);
22250 overlay.inline_edit_subscriptions.remove(&comment_id);
22251 }
22252 }
22253
22254 // Clear editing state
22255 self.set_comment_editing(comment_id, false, cx);
22256 }
22257
22258 /// Action handler for ConfirmEditReviewComment.
22259 pub fn confirm_edit_review_comment_action(
22260 &mut self,
22261 action: &ConfirmEditReviewComment,
22262 window: &mut Window,
22263 cx: &mut Context<Self>,
22264 ) {
22265 self.confirm_edit_review_comment(action.id, window, cx);
22266 }
22267
22268 /// Action handler for CancelEditReviewComment.
22269 pub fn cancel_edit_review_comment_action(
22270 &mut self,
22271 action: &CancelEditReviewComment,
22272 window: &mut Window,
22273 cx: &mut Context<Self>,
22274 ) {
22275 self.cancel_edit_review_comment(action.id, window, cx);
22276 }
22277
22278 /// Handles the DeleteReviewComment action - removes a comment.
22279 pub fn delete_review_comment(
22280 &mut self,
22281 action: &DeleteReviewComment,
22282 window: &mut Window,
22283 cx: &mut Context<Self>,
22284 ) {
22285 // Get the hunk key before removing the comment
22286 // Find the hunk key from the comment itself
22287 let comment_id = action.id;
22288 let hunk_key = self
22289 .stored_review_comments
22290 .iter()
22291 .find_map(|(key, comments)| {
22292 if comments.iter().any(|c| c.id == comment_id) {
22293 Some(key.clone())
22294 } else {
22295 None
22296 }
22297 });
22298
22299 // Also get it from the overlay for refresh purposes
22300 let overlay_hunk_key = self
22301 .diff_review_overlays
22302 .first()
22303 .map(|o| o.hunk_key.clone());
22304
22305 self.remove_review_comment(action.id, cx);
22306
22307 // Refresh the overlay height after removing a comment
22308 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22309 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22310 }
22311 }
22312
22313 fn render_diff_review_overlay(
22314 prompt_editor: &Entity<Editor>,
22315 hunk_key: &DiffHunkKey,
22316 editor_handle: &WeakEntity<Editor>,
22317 cx: &mut BlockContext,
22318 ) -> AnyElement {
22319 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22320 if ranges.is_empty() {
22321 return None;
22322 }
22323 let formatted: Vec<String> = ranges
22324 .iter()
22325 .map(|(start, end)| {
22326 let start_line = start + 1;
22327 let end_line = end + 1;
22328 if start_line == end_line {
22329 format!("Line {start_line}")
22330 } else {
22331 format!("Lines {start_line}-{end_line}")
22332 }
22333 })
22334 .collect();
22335 // Don't show label for single line in single excerpt
22336 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22337 return None;
22338 }
22339 Some(formatted.join(" ⋯ "))
22340 }
22341
22342 let theme = cx.theme();
22343 let colors = theme.colors();
22344
22345 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22346 editor_handle
22347 .upgrade()
22348 .map(|editor| {
22349 let editor = editor.read(cx);
22350 let snapshot = editor.buffer().read(cx).snapshot(cx);
22351 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22352 let (expanded, editors, avatar_uri, line_ranges) = editor
22353 .diff_review_overlays
22354 .iter()
22355 .find(|overlay| {
22356 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22357 })
22358 .map(|o| {
22359 let start_point = o.anchor_range.start.to_point(&snapshot);
22360 let end_point = o.anchor_range.end.to_point(&snapshot);
22361 // Get line ranges per excerpt to detect discontinuities
22362 let buffer_ranges =
22363 snapshot.range_to_buffer_ranges(start_point..end_point);
22364 let ranges: Vec<(u32, u32)> = buffer_ranges
22365 .iter()
22366 .map(|(buffer, range, _)| {
22367 let start = buffer.offset_to_point(range.start.0).row;
22368 let end = buffer.offset_to_point(range.end.0).row;
22369 (start, end)
22370 })
22371 .collect();
22372 (
22373 o.comments_expanded,
22374 o.inline_edit_editors.clone(),
22375 o.user_avatar_uri.clone(),
22376 if ranges.is_empty() {
22377 None
22378 } else {
22379 Some(ranges)
22380 },
22381 )
22382 })
22383 .unwrap_or((true, HashMap::default(), None, None));
22384 (comments, expanded, editors, avatar_uri, line_ranges)
22385 })
22386 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22387
22388 let comment_count = comments.len();
22389 let avatar_size = px(20.);
22390 let action_icon_size = IconSize::XSmall;
22391
22392 v_flex()
22393 .w_full()
22394 .bg(colors.editor_background)
22395 .border_b_1()
22396 .border_color(colors.border)
22397 .px_2()
22398 .pb_2()
22399 .gap_2()
22400 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22401 .when_some(line_ranges, |el, ranges| {
22402 let label = format_line_ranges(&ranges);
22403 if let Some(label) = label {
22404 el.child(
22405 h_flex()
22406 .w_full()
22407 .px_2()
22408 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22409 )
22410 } else {
22411 el
22412 }
22413 })
22414 // Top row: editable input with user's avatar
22415 .child(
22416 h_flex()
22417 .w_full()
22418 .items_center()
22419 .gap_2()
22420 .px_2()
22421 .py_1p5()
22422 .rounded_md()
22423 .bg(colors.surface_background)
22424 .child(
22425 div()
22426 .size(avatar_size)
22427 .flex_shrink_0()
22428 .rounded_full()
22429 .overflow_hidden()
22430 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22431 Avatar::new(avatar_uri.clone())
22432 .size(avatar_size)
22433 .into_any_element()
22434 } else {
22435 Icon::new(IconName::Person)
22436 .size(IconSize::Small)
22437 .color(ui::Color::Muted)
22438 .into_any_element()
22439 }),
22440 )
22441 .child(
22442 div()
22443 .flex_1()
22444 .border_1()
22445 .border_color(colors.border)
22446 .rounded_md()
22447 .bg(colors.editor_background)
22448 .px_2()
22449 .py_1()
22450 .child(prompt_editor.clone()),
22451 )
22452 .child(
22453 h_flex()
22454 .flex_shrink_0()
22455 .gap_1()
22456 .child(
22457 IconButton::new("diff-review-close", IconName::Close)
22458 .icon_color(ui::Color::Muted)
22459 .icon_size(action_icon_size)
22460 .tooltip(Tooltip::text("Close"))
22461 .on_click(|_, window, cx| {
22462 window
22463 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22464 }),
22465 )
22466 .child(
22467 IconButton::new("diff-review-add", IconName::Return)
22468 .icon_color(ui::Color::Muted)
22469 .icon_size(action_icon_size)
22470 .tooltip(Tooltip::text("Add comment"))
22471 .on_click(|_, window, cx| {
22472 window.dispatch_action(
22473 Box::new(crate::actions::SubmitDiffReviewComment),
22474 cx,
22475 );
22476 }),
22477 ),
22478 ),
22479 )
22480 // Expandable comments section (only shown when there are comments)
22481 .when(comment_count > 0, |el| {
22482 el.child(Self::render_comments_section(
22483 comments,
22484 comments_expanded,
22485 inline_editors,
22486 user_avatar_uri,
22487 avatar_size,
22488 action_icon_size,
22489 colors,
22490 ))
22491 })
22492 .into_any_element()
22493 }
22494
22495 fn render_comments_section(
22496 comments: Vec<StoredReviewComment>,
22497 expanded: bool,
22498 inline_editors: HashMap<usize, Entity<Editor>>,
22499 user_avatar_uri: Option<SharedUri>,
22500 avatar_size: Pixels,
22501 action_icon_size: IconSize,
22502 colors: &theme::ThemeColors,
22503 ) -> impl IntoElement {
22504 let comment_count = comments.len();
22505
22506 v_flex()
22507 .w_full()
22508 .gap_1()
22509 // Header with expand/collapse toggle
22510 .child(
22511 h_flex()
22512 .id("review-comments-header")
22513 .w_full()
22514 .items_center()
22515 .gap_1()
22516 .px_2()
22517 .py_1()
22518 .cursor_pointer()
22519 .rounded_md()
22520 .hover(|style| style.bg(colors.ghost_element_hover))
22521 .on_click(|_, window: &mut Window, cx| {
22522 window.dispatch_action(
22523 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22524 cx,
22525 );
22526 })
22527 .child(
22528 Icon::new(if expanded {
22529 IconName::ChevronDown
22530 } else {
22531 IconName::ChevronRight
22532 })
22533 .size(IconSize::Small)
22534 .color(ui::Color::Muted),
22535 )
22536 .child(
22537 Label::new(format!(
22538 "{} Comment{}",
22539 comment_count,
22540 if comment_count == 1 { "" } else { "s" }
22541 ))
22542 .size(LabelSize::Small)
22543 .color(Color::Muted),
22544 ),
22545 )
22546 // Comments list (when expanded)
22547 .when(expanded, |el| {
22548 el.children(comments.into_iter().map(|comment| {
22549 let inline_editor = inline_editors.get(&comment.id).cloned();
22550 Self::render_comment_row(
22551 comment,
22552 inline_editor,
22553 user_avatar_uri.clone(),
22554 avatar_size,
22555 action_icon_size,
22556 colors,
22557 )
22558 }))
22559 })
22560 }
22561
22562 fn render_comment_row(
22563 comment: StoredReviewComment,
22564 inline_editor: Option<Entity<Editor>>,
22565 user_avatar_uri: Option<SharedUri>,
22566 avatar_size: Pixels,
22567 action_icon_size: IconSize,
22568 colors: &theme::ThemeColors,
22569 ) -> impl IntoElement {
22570 let comment_id = comment.id;
22571 let is_editing = inline_editor.is_some();
22572
22573 h_flex()
22574 .w_full()
22575 .items_center()
22576 .gap_2()
22577 .px_2()
22578 .py_1p5()
22579 .rounded_md()
22580 .bg(colors.surface_background)
22581 .child(
22582 div()
22583 .size(avatar_size)
22584 .flex_shrink_0()
22585 .rounded_full()
22586 .overflow_hidden()
22587 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22588 Avatar::new(avatar_uri.clone())
22589 .size(avatar_size)
22590 .into_any_element()
22591 } else {
22592 Icon::new(IconName::Person)
22593 .size(IconSize::Small)
22594 .color(ui::Color::Muted)
22595 .into_any_element()
22596 }),
22597 )
22598 .child(if let Some(editor) = inline_editor {
22599 // Inline edit mode: show an editable text field
22600 div()
22601 .flex_1()
22602 .border_1()
22603 .border_color(colors.border)
22604 .rounded_md()
22605 .bg(colors.editor_background)
22606 .px_2()
22607 .py_1()
22608 .child(editor)
22609 .into_any_element()
22610 } else {
22611 // Display mode: show the comment text
22612 div()
22613 .flex_1()
22614 .text_sm()
22615 .text_color(colors.text)
22616 .child(comment.comment)
22617 .into_any_element()
22618 })
22619 .child(if is_editing {
22620 // Editing mode: show close and confirm buttons
22621 h_flex()
22622 .gap_1()
22623 .child(
22624 IconButton::new(
22625 format!("diff-review-cancel-edit-{comment_id}"),
22626 IconName::Close,
22627 )
22628 .icon_color(ui::Color::Muted)
22629 .icon_size(action_icon_size)
22630 .tooltip(Tooltip::text("Cancel"))
22631 .on_click(move |_, window, cx| {
22632 window.dispatch_action(
22633 Box::new(crate::actions::CancelEditReviewComment {
22634 id: comment_id,
22635 }),
22636 cx,
22637 );
22638 }),
22639 )
22640 .child(
22641 IconButton::new(
22642 format!("diff-review-confirm-edit-{comment_id}"),
22643 IconName::Return,
22644 )
22645 .icon_color(ui::Color::Muted)
22646 .icon_size(action_icon_size)
22647 .tooltip(Tooltip::text("Confirm"))
22648 .on_click(move |_, window, cx| {
22649 window.dispatch_action(
22650 Box::new(crate::actions::ConfirmEditReviewComment {
22651 id: comment_id,
22652 }),
22653 cx,
22654 );
22655 }),
22656 )
22657 .into_any_element()
22658 } else {
22659 // Display mode: no action buttons for now (edit/delete not yet implemented)
22660 gpui::Empty.into_any_element()
22661 })
22662 }
22663
22664 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22665 if self.display_map.read(cx).masked != masked {
22666 self.display_map.update(cx, |map, _| map.masked = masked);
22667 }
22668 cx.notify()
22669 }
22670
22671 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22672 self.show_wrap_guides = Some(show_wrap_guides);
22673 cx.notify();
22674 }
22675
22676 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22677 self.show_indent_guides = Some(show_indent_guides);
22678 cx.notify();
22679 }
22680
22681 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22682 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22683 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22684 && let Some(dir) = file.abs_path(cx).parent()
22685 {
22686 return Some(dir.to_owned());
22687 }
22688 }
22689
22690 None
22691 }
22692
22693 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22694 self.active_excerpt(cx)?
22695 .1
22696 .read(cx)
22697 .file()
22698 .and_then(|f| f.as_local())
22699 }
22700
22701 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22702 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22703 let buffer = buffer.read(cx);
22704 if let Some(project_path) = buffer.project_path(cx) {
22705 let project = self.project()?.read(cx);
22706 project.absolute_path(&project_path, cx)
22707 } else {
22708 buffer
22709 .file()
22710 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22711 }
22712 })
22713 }
22714
22715 pub fn reveal_in_finder(
22716 &mut self,
22717 _: &RevealInFileManager,
22718 _window: &mut Window,
22719 cx: &mut Context<Self>,
22720 ) {
22721 if let Some(path) = self.target_file_abs_path(cx) {
22722 if let Some(project) = self.project() {
22723 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22724 } else {
22725 cx.reveal_path(&path);
22726 }
22727 }
22728 }
22729
22730 pub fn copy_path(
22731 &mut self,
22732 _: &zed_actions::workspace::CopyPath,
22733 _window: &mut Window,
22734 cx: &mut Context<Self>,
22735 ) {
22736 if let Some(path) = self.target_file_abs_path(cx)
22737 && let Some(path) = path.to_str()
22738 {
22739 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22740 } else {
22741 cx.propagate();
22742 }
22743 }
22744
22745 pub fn copy_relative_path(
22746 &mut self,
22747 _: &zed_actions::workspace::CopyRelativePath,
22748 _window: &mut Window,
22749 cx: &mut Context<Self>,
22750 ) {
22751 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22752 let project = self.project()?.read(cx);
22753 let path = buffer.read(cx).file()?.path();
22754 let path = path.display(project.path_style(cx));
22755 Some(path)
22756 }) {
22757 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22758 } else {
22759 cx.propagate();
22760 }
22761 }
22762
22763 /// Returns the project path for the editor's buffer, if any buffer is
22764 /// opened in the editor.
22765 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22766 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22767 buffer.read(cx).project_path(cx)
22768 } else {
22769 None
22770 }
22771 }
22772
22773 // Returns true if the editor handled a go-to-line request
22774 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22775 maybe!({
22776 let breakpoint_store = self.breakpoint_store.as_ref()?;
22777
22778 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22779 else {
22780 self.clear_row_highlights::<ActiveDebugLine>();
22781 return None;
22782 };
22783
22784 let position = active_stack_frame.position;
22785 let buffer_id = position.buffer_id?;
22786 let snapshot = self
22787 .project
22788 .as_ref()?
22789 .read(cx)
22790 .buffer_for_id(buffer_id, cx)?
22791 .read(cx)
22792 .snapshot();
22793
22794 let mut handled = false;
22795 for (id, ExcerptRange { context, .. }) in
22796 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22797 {
22798 if context.start.cmp(&position, &snapshot).is_ge()
22799 || context.end.cmp(&position, &snapshot).is_lt()
22800 {
22801 continue;
22802 }
22803 let snapshot = self.buffer.read(cx).snapshot(cx);
22804 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22805
22806 handled = true;
22807 self.clear_row_highlights::<ActiveDebugLine>();
22808
22809 self.go_to_line::<ActiveDebugLine>(
22810 multibuffer_anchor,
22811 Some(cx.theme().colors().editor_debugger_active_line_background),
22812 window,
22813 cx,
22814 );
22815
22816 cx.notify();
22817 }
22818
22819 handled.then_some(())
22820 })
22821 .is_some()
22822 }
22823
22824 pub fn copy_file_name_without_extension(
22825 &mut self,
22826 _: &CopyFileNameWithoutExtension,
22827 _: &mut Window,
22828 cx: &mut Context<Self>,
22829 ) {
22830 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22831 let file = buffer.read(cx).file()?;
22832 file.path().file_stem()
22833 }) {
22834 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22835 }
22836 }
22837
22838 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22839 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22840 let file = buffer.read(cx).file()?;
22841 Some(file.file_name(cx))
22842 }) {
22843 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22844 }
22845 }
22846
22847 pub fn toggle_git_blame(
22848 &mut self,
22849 _: &::git::Blame,
22850 window: &mut Window,
22851 cx: &mut Context<Self>,
22852 ) {
22853 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22854
22855 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22856 self.start_git_blame(true, window, cx);
22857 }
22858
22859 cx.notify();
22860 }
22861
22862 pub fn toggle_git_blame_inline(
22863 &mut self,
22864 _: &ToggleGitBlameInline,
22865 window: &mut Window,
22866 cx: &mut Context<Self>,
22867 ) {
22868 self.toggle_git_blame_inline_internal(true, window, cx);
22869 cx.notify();
22870 }
22871
22872 pub fn open_git_blame_commit(
22873 &mut self,
22874 _: &OpenGitBlameCommit,
22875 window: &mut Window,
22876 cx: &mut Context<Self>,
22877 ) {
22878 self.open_git_blame_commit_internal(window, cx);
22879 }
22880
22881 fn open_git_blame_commit_internal(
22882 &mut self,
22883 window: &mut Window,
22884 cx: &mut Context<Self>,
22885 ) -> Option<()> {
22886 let blame = self.blame.as_ref()?;
22887 let snapshot = self.snapshot(window, cx);
22888 let cursor = self
22889 .selections
22890 .newest::<Point>(&snapshot.display_snapshot)
22891 .head();
22892 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22893 let (_, blame_entry) = blame
22894 .update(cx, |blame, cx| {
22895 blame
22896 .blame_for_rows(
22897 &[RowInfo {
22898 buffer_id: Some(buffer.remote_id()),
22899 buffer_row: Some(point.row),
22900 ..Default::default()
22901 }],
22902 cx,
22903 )
22904 .next()
22905 })
22906 .flatten()?;
22907 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22908 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22909 let workspace = self.workspace()?.downgrade();
22910 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22911 None
22912 }
22913
22914 pub fn git_blame_inline_enabled(&self) -> bool {
22915 self.git_blame_inline_enabled
22916 }
22917
22918 pub fn toggle_selection_menu(
22919 &mut self,
22920 _: &ToggleSelectionMenu,
22921 _: &mut Window,
22922 cx: &mut Context<Self>,
22923 ) {
22924 self.show_selection_menu = self
22925 .show_selection_menu
22926 .map(|show_selections_menu| !show_selections_menu)
22927 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22928
22929 cx.notify();
22930 }
22931
22932 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22933 self.show_selection_menu
22934 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22935 }
22936
22937 fn start_git_blame(
22938 &mut self,
22939 user_triggered: bool,
22940 window: &mut Window,
22941 cx: &mut Context<Self>,
22942 ) {
22943 if let Some(project) = self.project() {
22944 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22945 && buffer.read(cx).file().is_none()
22946 {
22947 return;
22948 }
22949
22950 let focused = self.focus_handle(cx).contains_focused(window, cx);
22951
22952 let project = project.clone();
22953 let blame = cx
22954 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22955 self.blame_subscription =
22956 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22957 self.blame = Some(blame);
22958 }
22959 }
22960
22961 fn toggle_git_blame_inline_internal(
22962 &mut self,
22963 user_triggered: bool,
22964 window: &mut Window,
22965 cx: &mut Context<Self>,
22966 ) {
22967 if self.git_blame_inline_enabled {
22968 self.git_blame_inline_enabled = false;
22969 self.show_git_blame_inline = false;
22970 self.show_git_blame_inline_delay_task.take();
22971 } else {
22972 self.git_blame_inline_enabled = true;
22973 self.start_git_blame_inline(user_triggered, window, cx);
22974 }
22975
22976 cx.notify();
22977 }
22978
22979 fn start_git_blame_inline(
22980 &mut self,
22981 user_triggered: bool,
22982 window: &mut Window,
22983 cx: &mut Context<Self>,
22984 ) {
22985 self.start_git_blame(user_triggered, window, cx);
22986
22987 if ProjectSettings::get_global(cx)
22988 .git
22989 .inline_blame_delay()
22990 .is_some()
22991 {
22992 self.start_inline_blame_timer(window, cx);
22993 } else {
22994 self.show_git_blame_inline = true
22995 }
22996 }
22997
22998 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22999 self.blame.as_ref()
23000 }
23001
23002 pub fn show_git_blame_gutter(&self) -> bool {
23003 self.show_git_blame_gutter
23004 }
23005
23006 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23007 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23008 }
23009
23010 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23011 self.show_git_blame_inline
23012 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23013 && !self.newest_selection_head_on_empty_line(cx)
23014 && self.has_blame_entries(cx)
23015 }
23016
23017 fn has_blame_entries(&self, cx: &App) -> bool {
23018 self.blame()
23019 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23020 }
23021
23022 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23023 let cursor_anchor = self.selections.newest_anchor().head();
23024
23025 let snapshot = self.buffer.read(cx).snapshot(cx);
23026 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23027
23028 snapshot.line_len(buffer_row) == 0
23029 }
23030
23031 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23032 let buffer_and_selection = maybe!({
23033 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23034 let selection_range = selection.range();
23035
23036 let multi_buffer = self.buffer().read(cx);
23037 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23038 let buffer_ranges = multi_buffer_snapshot
23039 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23040
23041 let (buffer, range, _) = if selection.reversed {
23042 buffer_ranges.first()
23043 } else {
23044 buffer_ranges.last()
23045 }?;
23046
23047 let buffer_range = range.to_point(buffer);
23048
23049 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23050 return Some((
23051 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23052 buffer_range.start.row..buffer_range.end.row,
23053 ));
23054 };
23055
23056 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23057 let start =
23058 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23059 let end =
23060 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23061
23062 Some((
23063 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23064 start.row..end.row,
23065 ))
23066 });
23067
23068 let Some((buffer, selection)) = buffer_and_selection else {
23069 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23070 };
23071
23072 let Some(project) = self.project() else {
23073 return Task::ready(Err(anyhow!("editor does not have project")));
23074 };
23075
23076 project.update(cx, |project, cx| {
23077 project.get_permalink_to_line(&buffer, selection, cx)
23078 })
23079 }
23080
23081 pub fn copy_permalink_to_line(
23082 &mut self,
23083 _: &CopyPermalinkToLine,
23084 window: &mut Window,
23085 cx: &mut Context<Self>,
23086 ) {
23087 let permalink_task = self.get_permalink_to_line(cx);
23088 let workspace = self.workspace();
23089
23090 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23091 Ok(permalink) => {
23092 cx.update(|_, cx| {
23093 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23094 })
23095 .ok();
23096 }
23097 Err(err) => {
23098 let message = format!("Failed to copy permalink: {err}");
23099
23100 anyhow::Result::<()>::Err(err).log_err();
23101
23102 if let Some(workspace) = workspace {
23103 workspace
23104 .update_in(cx, |workspace, _, cx| {
23105 struct CopyPermalinkToLine;
23106
23107 workspace.show_toast(
23108 Toast::new(
23109 NotificationId::unique::<CopyPermalinkToLine>(),
23110 message,
23111 ),
23112 cx,
23113 )
23114 })
23115 .ok();
23116 }
23117 }
23118 })
23119 .detach();
23120 }
23121
23122 pub fn copy_file_location(
23123 &mut self,
23124 _: &CopyFileLocation,
23125 _: &mut Window,
23126 cx: &mut Context<Self>,
23127 ) {
23128 let selection = self
23129 .selections
23130 .newest::<Point>(&self.display_snapshot(cx))
23131 .start
23132 .row
23133 + 1;
23134 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23135 let project = self.project()?.read(cx);
23136 let file = buffer.read(cx).file()?;
23137 let path = file.path().display(project.path_style(cx));
23138
23139 Some(format!("{path}:{selection}"))
23140 }) {
23141 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23142 }
23143 }
23144
23145 pub fn open_permalink_to_line(
23146 &mut self,
23147 _: &OpenPermalinkToLine,
23148 window: &mut Window,
23149 cx: &mut Context<Self>,
23150 ) {
23151 let permalink_task = self.get_permalink_to_line(cx);
23152 let workspace = self.workspace();
23153
23154 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23155 Ok(permalink) => {
23156 cx.update(|_, cx| {
23157 cx.open_url(permalink.as_ref());
23158 })
23159 .ok();
23160 }
23161 Err(err) => {
23162 let message = format!("Failed to open permalink: {err}");
23163
23164 anyhow::Result::<()>::Err(err).log_err();
23165
23166 if let Some(workspace) = workspace {
23167 workspace.update(cx, |workspace, cx| {
23168 struct OpenPermalinkToLine;
23169
23170 workspace.show_toast(
23171 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23172 cx,
23173 )
23174 });
23175 }
23176 }
23177 })
23178 .detach();
23179 }
23180
23181 pub fn insert_uuid_v4(
23182 &mut self,
23183 _: &InsertUuidV4,
23184 window: &mut Window,
23185 cx: &mut Context<Self>,
23186 ) {
23187 self.insert_uuid(UuidVersion::V4, window, cx);
23188 }
23189
23190 pub fn insert_uuid_v7(
23191 &mut self,
23192 _: &InsertUuidV7,
23193 window: &mut Window,
23194 cx: &mut Context<Self>,
23195 ) {
23196 self.insert_uuid(UuidVersion::V7, window, cx);
23197 }
23198
23199 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23200 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23201 self.transact(window, cx, |this, window, cx| {
23202 let edits = this
23203 .selections
23204 .all::<Point>(&this.display_snapshot(cx))
23205 .into_iter()
23206 .map(|selection| {
23207 let uuid = match version {
23208 UuidVersion::V4 => uuid::Uuid::new_v4(),
23209 UuidVersion::V7 => uuid::Uuid::now_v7(),
23210 };
23211
23212 (selection.range(), uuid.to_string())
23213 });
23214 this.edit(edits, cx);
23215 this.refresh_edit_prediction(true, false, window, cx);
23216 });
23217 }
23218
23219 pub fn open_selections_in_multibuffer(
23220 &mut self,
23221 _: &OpenSelectionsInMultibuffer,
23222 window: &mut Window,
23223 cx: &mut Context<Self>,
23224 ) {
23225 let multibuffer = self.buffer.read(cx);
23226
23227 let Some(buffer) = multibuffer.as_singleton() else {
23228 return;
23229 };
23230
23231 let Some(workspace) = self.workspace() else {
23232 return;
23233 };
23234
23235 let title = multibuffer.title(cx).to_string();
23236
23237 let locations = self
23238 .selections
23239 .all_anchors(&self.display_snapshot(cx))
23240 .iter()
23241 .map(|selection| {
23242 (
23243 buffer.clone(),
23244 (selection.start.text_anchor..selection.end.text_anchor)
23245 .to_point(buffer.read(cx)),
23246 )
23247 })
23248 .into_group_map();
23249
23250 cx.spawn_in(window, async move |_, cx| {
23251 workspace.update_in(cx, |workspace, window, cx| {
23252 Self::open_locations_in_multibuffer(
23253 workspace,
23254 locations,
23255 format!("Selections for '{title}'"),
23256 false,
23257 false,
23258 MultibufferSelectionMode::All,
23259 window,
23260 cx,
23261 );
23262 })
23263 })
23264 .detach();
23265 }
23266
23267 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23268 /// last highlight added will be used.
23269 ///
23270 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23271 pub fn highlight_rows<T: 'static>(
23272 &mut self,
23273 range: Range<Anchor>,
23274 color: Hsla,
23275 options: RowHighlightOptions,
23276 cx: &mut Context<Self>,
23277 ) {
23278 let snapshot = self.buffer().read(cx).snapshot(cx);
23279 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23280 let ix = row_highlights.binary_search_by(|highlight| {
23281 Ordering::Equal
23282 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23283 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23284 });
23285
23286 if let Err(mut ix) = ix {
23287 let index = post_inc(&mut self.highlight_order);
23288
23289 // If this range intersects with the preceding highlight, then merge it with
23290 // the preceding highlight. Otherwise insert a new highlight.
23291 let mut merged = false;
23292 if ix > 0 {
23293 let prev_highlight = &mut row_highlights[ix - 1];
23294 if prev_highlight
23295 .range
23296 .end
23297 .cmp(&range.start, &snapshot)
23298 .is_ge()
23299 {
23300 ix -= 1;
23301 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23302 prev_highlight.range.end = range.end;
23303 }
23304 merged = true;
23305 prev_highlight.index = index;
23306 prev_highlight.color = color;
23307 prev_highlight.options = options;
23308 }
23309 }
23310
23311 if !merged {
23312 row_highlights.insert(
23313 ix,
23314 RowHighlight {
23315 range,
23316 index,
23317 color,
23318 options,
23319 type_id: TypeId::of::<T>(),
23320 },
23321 );
23322 }
23323
23324 // If any of the following highlights intersect with this one, merge them.
23325 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23326 let highlight = &row_highlights[ix];
23327 if next_highlight
23328 .range
23329 .start
23330 .cmp(&highlight.range.end, &snapshot)
23331 .is_le()
23332 {
23333 if next_highlight
23334 .range
23335 .end
23336 .cmp(&highlight.range.end, &snapshot)
23337 .is_gt()
23338 {
23339 row_highlights[ix].range.end = next_highlight.range.end;
23340 }
23341 row_highlights.remove(ix + 1);
23342 } else {
23343 break;
23344 }
23345 }
23346 }
23347 }
23348
23349 /// Remove any highlighted row ranges of the given type that intersect the
23350 /// given ranges.
23351 pub fn remove_highlighted_rows<T: 'static>(
23352 &mut self,
23353 ranges_to_remove: Vec<Range<Anchor>>,
23354 cx: &mut Context<Self>,
23355 ) {
23356 let snapshot = self.buffer().read(cx).snapshot(cx);
23357 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23358 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23359 row_highlights.retain(|highlight| {
23360 while let Some(range_to_remove) = ranges_to_remove.peek() {
23361 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23362 Ordering::Less | Ordering::Equal => {
23363 ranges_to_remove.next();
23364 }
23365 Ordering::Greater => {
23366 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23367 Ordering::Less | Ordering::Equal => {
23368 return false;
23369 }
23370 Ordering::Greater => break,
23371 }
23372 }
23373 }
23374 }
23375
23376 true
23377 })
23378 }
23379
23380 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23381 pub fn clear_row_highlights<T: 'static>(&mut self) {
23382 self.highlighted_rows.remove(&TypeId::of::<T>());
23383 }
23384
23385 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23386 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23387 self.highlighted_rows
23388 .get(&TypeId::of::<T>())
23389 .map_or(&[] as &[_], |vec| vec.as_slice())
23390 .iter()
23391 .map(|highlight| (highlight.range.clone(), highlight.color))
23392 }
23393
23394 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23395 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23396 /// Allows to ignore certain kinds of highlights.
23397 pub fn highlighted_display_rows(
23398 &self,
23399 window: &mut Window,
23400 cx: &mut App,
23401 ) -> BTreeMap<DisplayRow, LineHighlight> {
23402 let snapshot = self.snapshot(window, cx);
23403 let mut used_highlight_orders = HashMap::default();
23404 self.highlighted_rows
23405 .iter()
23406 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23407 .fold(
23408 BTreeMap::<DisplayRow, LineHighlight>::new(),
23409 |mut unique_rows, highlight| {
23410 let start = highlight.range.start.to_display_point(&snapshot);
23411 let end = highlight.range.end.to_display_point(&snapshot);
23412 let start_row = start.row().0;
23413 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23414 {
23415 end.row().0.saturating_sub(1)
23416 } else {
23417 end.row().0
23418 };
23419 for row in start_row..=end_row {
23420 let used_index =
23421 used_highlight_orders.entry(row).or_insert(highlight.index);
23422 if highlight.index >= *used_index {
23423 *used_index = highlight.index;
23424 unique_rows.insert(
23425 DisplayRow(row),
23426 LineHighlight {
23427 include_gutter: highlight.options.include_gutter,
23428 border: None,
23429 background: highlight.color.into(),
23430 type_id: Some(highlight.type_id),
23431 },
23432 );
23433 }
23434 }
23435 unique_rows
23436 },
23437 )
23438 }
23439
23440 pub fn highlighted_display_row_for_autoscroll(
23441 &self,
23442 snapshot: &DisplaySnapshot,
23443 ) -> Option<DisplayRow> {
23444 self.highlighted_rows
23445 .values()
23446 .flat_map(|highlighted_rows| highlighted_rows.iter())
23447 .filter_map(|highlight| {
23448 if highlight.options.autoscroll {
23449 Some(highlight.range.start.to_display_point(snapshot).row())
23450 } else {
23451 None
23452 }
23453 })
23454 .min()
23455 }
23456
23457 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23458 self.highlight_background(
23459 HighlightKey::SearchWithinRange,
23460 ranges,
23461 |_, colors| colors.colors().editor_document_highlight_read_background,
23462 cx,
23463 )
23464 }
23465
23466 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23467 self.breadcrumb_header = Some(new_header);
23468 }
23469
23470 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23471 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23472 }
23473
23474 pub fn highlight_background(
23475 &mut self,
23476 key: HighlightKey,
23477 ranges: &[Range<Anchor>],
23478 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23479 cx: &mut Context<Self>,
23480 ) {
23481 self.background_highlights
23482 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23483 self.scrollbar_marker_state.dirty = true;
23484 cx.notify();
23485 }
23486
23487 pub fn highlight_background_key(
23488 &mut self,
23489 key: HighlightKey,
23490 ranges: &[Range<Anchor>],
23491 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23492 cx: &mut Context<Self>,
23493 ) {
23494 self.background_highlights
23495 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23496 self.scrollbar_marker_state.dirty = true;
23497 cx.notify();
23498 }
23499
23500 pub fn clear_background_highlights(
23501 &mut self,
23502 key: HighlightKey,
23503 cx: &mut Context<Self>,
23504 ) -> Option<BackgroundHighlight> {
23505 let text_highlights = self.background_highlights.remove(&key)?;
23506 if !text_highlights.1.is_empty() {
23507 self.scrollbar_marker_state.dirty = true;
23508 cx.notify();
23509 }
23510 Some(text_highlights)
23511 }
23512
23513 pub fn highlight_gutter<T: 'static>(
23514 &mut self,
23515 ranges: impl Into<Vec<Range<Anchor>>>,
23516 color_fetcher: fn(&App) -> Hsla,
23517 cx: &mut Context<Self>,
23518 ) {
23519 self.gutter_highlights
23520 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23521 cx.notify();
23522 }
23523
23524 pub fn clear_gutter_highlights<T: 'static>(
23525 &mut self,
23526 cx: &mut Context<Self>,
23527 ) -> Option<GutterHighlight> {
23528 cx.notify();
23529 self.gutter_highlights.remove(&TypeId::of::<T>())
23530 }
23531
23532 pub fn insert_gutter_highlight<T: 'static>(
23533 &mut self,
23534 range: Range<Anchor>,
23535 color_fetcher: fn(&App) -> Hsla,
23536 cx: &mut Context<Self>,
23537 ) {
23538 let snapshot = self.buffer().read(cx).snapshot(cx);
23539 let mut highlights = self
23540 .gutter_highlights
23541 .remove(&TypeId::of::<T>())
23542 .map(|(_, highlights)| highlights)
23543 .unwrap_or_default();
23544 let ix = highlights.binary_search_by(|highlight| {
23545 Ordering::Equal
23546 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23547 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23548 });
23549 if let Err(ix) = ix {
23550 highlights.insert(ix, range);
23551 }
23552 self.gutter_highlights
23553 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23554 }
23555
23556 pub fn remove_gutter_highlights<T: 'static>(
23557 &mut self,
23558 ranges_to_remove: Vec<Range<Anchor>>,
23559 cx: &mut Context<Self>,
23560 ) {
23561 let snapshot = self.buffer().read(cx).snapshot(cx);
23562 let Some((color_fetcher, mut gutter_highlights)) =
23563 self.gutter_highlights.remove(&TypeId::of::<T>())
23564 else {
23565 return;
23566 };
23567 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23568 gutter_highlights.retain(|highlight| {
23569 while let Some(range_to_remove) = ranges_to_remove.peek() {
23570 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23571 Ordering::Less | Ordering::Equal => {
23572 ranges_to_remove.next();
23573 }
23574 Ordering::Greater => {
23575 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23576 Ordering::Less | Ordering::Equal => {
23577 return false;
23578 }
23579 Ordering::Greater => break,
23580 }
23581 }
23582 }
23583 }
23584
23585 true
23586 });
23587 self.gutter_highlights
23588 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23589 }
23590
23591 #[cfg(feature = "test-support")]
23592 pub fn all_text_highlights(
23593 &self,
23594 window: &mut Window,
23595 cx: &mut Context<Self>,
23596 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23597 let snapshot = self.snapshot(window, cx);
23598 self.display_map.update(cx, |display_map, _| {
23599 display_map
23600 .all_text_highlights()
23601 .map(|(_, highlight)| {
23602 let (style, ranges) = highlight.as_ref();
23603 (
23604 *style,
23605 ranges
23606 .iter()
23607 .map(|range| range.clone().to_display_points(&snapshot))
23608 .collect(),
23609 )
23610 })
23611 .collect()
23612 })
23613 }
23614
23615 #[cfg(feature = "test-support")]
23616 pub fn all_text_background_highlights(
23617 &self,
23618 window: &mut Window,
23619 cx: &mut Context<Self>,
23620 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23621 let snapshot = self.snapshot(window, cx);
23622 let buffer = &snapshot.buffer_snapshot();
23623 let start = buffer.anchor_before(MultiBufferOffset(0));
23624 let end = buffer.anchor_after(buffer.len());
23625 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23626 }
23627
23628 #[cfg(any(test, feature = "test-support"))]
23629 pub fn sorted_background_highlights_in_range(
23630 &self,
23631 search_range: Range<Anchor>,
23632 display_snapshot: &DisplaySnapshot,
23633 theme: &Theme,
23634 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23635 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23636 res.sort_by(|a, b| {
23637 a.0.start
23638 .cmp(&b.0.start)
23639 .then_with(|| a.0.end.cmp(&b.0.end))
23640 .then_with(|| a.1.cmp(&b.1))
23641 });
23642 res
23643 }
23644
23645 #[cfg(feature = "test-support")]
23646 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23647 let snapshot = self.buffer().read(cx).snapshot(cx);
23648
23649 let highlights = self
23650 .background_highlights
23651 .get(&HighlightKey::BufferSearchHighlights);
23652
23653 if let Some((_color, ranges)) = highlights {
23654 ranges
23655 .iter()
23656 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23657 .collect_vec()
23658 } else {
23659 vec![]
23660 }
23661 }
23662
23663 fn document_highlights_for_position<'a>(
23664 &'a self,
23665 position: Anchor,
23666 buffer: &'a MultiBufferSnapshot,
23667 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23668 let read_highlights = self
23669 .background_highlights
23670 .get(&HighlightKey::DocumentHighlightRead)
23671 .map(|h| &h.1);
23672 let write_highlights = self
23673 .background_highlights
23674 .get(&HighlightKey::DocumentHighlightWrite)
23675 .map(|h| &h.1);
23676 let left_position = position.bias_left(buffer);
23677 let right_position = position.bias_right(buffer);
23678 read_highlights
23679 .into_iter()
23680 .chain(write_highlights)
23681 .flat_map(move |ranges| {
23682 let start_ix = match ranges.binary_search_by(|probe| {
23683 let cmp = probe.end.cmp(&left_position, buffer);
23684 if cmp.is_ge() {
23685 Ordering::Greater
23686 } else {
23687 Ordering::Less
23688 }
23689 }) {
23690 Ok(i) | Err(i) => i,
23691 };
23692
23693 ranges[start_ix..]
23694 .iter()
23695 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23696 })
23697 }
23698
23699 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23700 self.background_highlights
23701 .get(&key)
23702 .is_some_and(|(_, highlights)| !highlights.is_empty())
23703 }
23704
23705 /// Returns all background highlights for a given range.
23706 ///
23707 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23708 pub fn background_highlights_in_range(
23709 &self,
23710 search_range: Range<Anchor>,
23711 display_snapshot: &DisplaySnapshot,
23712 theme: &Theme,
23713 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23714 let mut results = Vec::new();
23715 for (color_fetcher, ranges) in self.background_highlights.values() {
23716 let start_ix = match ranges.binary_search_by(|probe| {
23717 let cmp = probe
23718 .end
23719 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23720 if cmp.is_gt() {
23721 Ordering::Greater
23722 } else {
23723 Ordering::Less
23724 }
23725 }) {
23726 Ok(i) | Err(i) => i,
23727 };
23728 for (index, range) in ranges[start_ix..].iter().enumerate() {
23729 if range
23730 .start
23731 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23732 .is_ge()
23733 {
23734 break;
23735 }
23736
23737 let color = color_fetcher(&(start_ix + index), theme);
23738 let start = range.start.to_display_point(display_snapshot);
23739 let end = range.end.to_display_point(display_snapshot);
23740 results.push((start..end, color))
23741 }
23742 }
23743 results
23744 }
23745
23746 pub fn gutter_highlights_in_range(
23747 &self,
23748 search_range: Range<Anchor>,
23749 display_snapshot: &DisplaySnapshot,
23750 cx: &App,
23751 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23752 let mut results = Vec::new();
23753 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23754 let color = color_fetcher(cx);
23755 let start_ix = match ranges.binary_search_by(|probe| {
23756 let cmp = probe
23757 .end
23758 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23759 if cmp.is_gt() {
23760 Ordering::Greater
23761 } else {
23762 Ordering::Less
23763 }
23764 }) {
23765 Ok(i) | Err(i) => i,
23766 };
23767 for range in &ranges[start_ix..] {
23768 if range
23769 .start
23770 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23771 .is_ge()
23772 {
23773 break;
23774 }
23775
23776 let start = range.start.to_display_point(display_snapshot);
23777 let end = range.end.to_display_point(display_snapshot);
23778 results.push((start..end, color))
23779 }
23780 }
23781 results
23782 }
23783
23784 /// Get the text ranges corresponding to the redaction query
23785 pub fn redacted_ranges(
23786 &self,
23787 search_range: Range<Anchor>,
23788 display_snapshot: &DisplaySnapshot,
23789 cx: &App,
23790 ) -> Vec<Range<DisplayPoint>> {
23791 display_snapshot
23792 .buffer_snapshot()
23793 .redacted_ranges(search_range, |file| {
23794 if let Some(file) = file {
23795 file.is_private()
23796 && EditorSettings::get(
23797 Some(SettingsLocation {
23798 worktree_id: file.worktree_id(cx),
23799 path: file.path().as_ref(),
23800 }),
23801 cx,
23802 )
23803 .redact_private_values
23804 } else {
23805 false
23806 }
23807 })
23808 .map(|range| {
23809 range.start.to_display_point(display_snapshot)
23810 ..range.end.to_display_point(display_snapshot)
23811 })
23812 .collect()
23813 }
23814
23815 pub fn highlight_text_key(
23816 &mut self,
23817 key: HighlightKey,
23818 ranges: Vec<Range<Anchor>>,
23819 style: HighlightStyle,
23820 merge: bool,
23821 cx: &mut Context<Self>,
23822 ) {
23823 self.display_map.update(cx, |map, cx| {
23824 map.highlight_text(key, ranges, style, merge, cx);
23825 });
23826 cx.notify();
23827 }
23828
23829 pub fn highlight_text(
23830 &mut self,
23831 key: HighlightKey,
23832 ranges: Vec<Range<Anchor>>,
23833 style: HighlightStyle,
23834 cx: &mut Context<Self>,
23835 ) {
23836 self.display_map.update(cx, |map, cx| {
23837 map.highlight_text(key, ranges, style, false, cx)
23838 });
23839 cx.notify();
23840 }
23841
23842 pub fn text_highlights<'a>(
23843 &'a self,
23844 key: HighlightKey,
23845 cx: &'a App,
23846 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23847 self.display_map.read(cx).text_highlights(key)
23848 }
23849
23850 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23851 let cleared = self
23852 .display_map
23853 .update(cx, |map, _| map.clear_highlights(key));
23854 if cleared {
23855 cx.notify();
23856 }
23857 }
23858
23859 pub fn clear_highlights_with(
23860 &mut self,
23861 f: &mut dyn FnMut(&HighlightKey) -> bool,
23862 cx: &mut Context<Self>,
23863 ) {
23864 let cleared = self
23865 .display_map
23866 .update(cx, |map, _| map.clear_highlights_with(f));
23867 if cleared {
23868 cx.notify();
23869 }
23870 }
23871
23872 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23873 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23874 && self.focus_handle.is_focused(window)
23875 }
23876
23877 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23878 self.show_cursor_when_unfocused = is_enabled;
23879 cx.notify();
23880 }
23881
23882 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23883 cx.notify();
23884 }
23885
23886 fn on_debug_session_event(
23887 &mut self,
23888 _session: Entity<Session>,
23889 event: &SessionEvent,
23890 cx: &mut Context<Self>,
23891 ) {
23892 if let SessionEvent::InvalidateInlineValue = event {
23893 self.refresh_inline_values(cx);
23894 }
23895 }
23896
23897 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23898 let Some(project) = self.project.clone() else {
23899 return;
23900 };
23901
23902 if !self.inline_value_cache.enabled {
23903 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23904 self.splice_inlays(&inlays, Vec::new(), cx);
23905 return;
23906 }
23907
23908 let current_execution_position = self
23909 .highlighted_rows
23910 .get(&TypeId::of::<ActiveDebugLine>())
23911 .and_then(|lines| lines.last().map(|line| line.range.end));
23912
23913 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23914 let inline_values = editor
23915 .update(cx, |editor, cx| {
23916 let Some(current_execution_position) = current_execution_position else {
23917 return Some(Task::ready(Ok(Vec::new())));
23918 };
23919
23920 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23921 let snapshot = buffer.snapshot(cx);
23922
23923 let excerpt = snapshot.excerpt_containing(
23924 current_execution_position..current_execution_position,
23925 )?;
23926
23927 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23928 })?;
23929
23930 let range =
23931 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23932
23933 project.inline_values(buffer, range, cx)
23934 })
23935 .ok()
23936 .flatten()?
23937 .await
23938 .context("refreshing debugger inlays")
23939 .log_err()?;
23940
23941 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23942
23943 for (buffer_id, inline_value) in inline_values
23944 .into_iter()
23945 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23946 {
23947 buffer_inline_values
23948 .entry(buffer_id)
23949 .or_default()
23950 .push(inline_value);
23951 }
23952
23953 editor
23954 .update(cx, |editor, cx| {
23955 let snapshot = editor.buffer.read(cx).snapshot(cx);
23956 let mut new_inlays = Vec::default();
23957
23958 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23959 let buffer_id = buffer_snapshot.remote_id();
23960 buffer_inline_values
23961 .get(&buffer_id)
23962 .into_iter()
23963 .flatten()
23964 .for_each(|hint| {
23965 let inlay = Inlay::debugger(
23966 post_inc(&mut editor.next_inlay_id),
23967 Anchor::in_buffer(excerpt_id, hint.position),
23968 hint.text(),
23969 );
23970 if !inlay.text().chars().contains(&'\n') {
23971 new_inlays.push(inlay);
23972 }
23973 });
23974 }
23975
23976 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23977 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23978
23979 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23980 })
23981 .ok()?;
23982 Some(())
23983 });
23984 }
23985
23986 fn on_buffer_event(
23987 &mut self,
23988 multibuffer: &Entity<MultiBuffer>,
23989 event: &multi_buffer::Event,
23990 window: &mut Window,
23991 cx: &mut Context<Self>,
23992 ) {
23993 match event {
23994 multi_buffer::Event::Edited { edited_buffer } => {
23995 self.scrollbar_marker_state.dirty = true;
23996 self.active_indent_guides_state.dirty = true;
23997 self.refresh_active_diagnostics(cx);
23998 self.refresh_code_actions(window, cx);
23999 self.refresh_single_line_folds(window, cx);
24000 let snapshot = self.snapshot(window, cx);
24001 self.refresh_matching_bracket_highlights(&snapshot, cx);
24002 self.refresh_outline_symbols_at_cursor(cx);
24003 self.refresh_sticky_headers(&snapshot, cx);
24004 if self.has_active_edit_prediction() {
24005 self.update_visible_edit_prediction(window, cx);
24006 }
24007
24008 // Clean up orphaned review comments after edits
24009 self.cleanup_orphaned_review_comments(cx);
24010
24011 if let Some(buffer) = edited_buffer {
24012 if buffer.read(cx).file().is_none() {
24013 cx.emit(EditorEvent::TitleChanged);
24014 }
24015
24016 if self.project.is_some() {
24017 let buffer_id = buffer.read(cx).remote_id();
24018 self.register_buffer(buffer_id, cx);
24019 self.update_lsp_data(Some(buffer_id), window, cx);
24020 self.refresh_inlay_hints(
24021 InlayHintRefreshReason::BufferEdited(buffer_id),
24022 cx,
24023 );
24024 }
24025 }
24026
24027 cx.emit(EditorEvent::BufferEdited);
24028 cx.emit(SearchEvent::MatchesInvalidated);
24029
24030 let Some(project) = &self.project else { return };
24031 let (telemetry, is_via_ssh) = {
24032 let project = project.read(cx);
24033 let telemetry = project.client().telemetry().clone();
24034 let is_via_ssh = project.is_via_remote_server();
24035 (telemetry, is_via_ssh)
24036 };
24037 telemetry.log_edit_event("editor", is_via_ssh);
24038 }
24039 multi_buffer::Event::ExcerptsAdded {
24040 buffer,
24041 predecessor,
24042 excerpts,
24043 } => {
24044 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24045 let buffer_id = buffer.read(cx).remote_id();
24046 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24047 && let Some(project) = &self.project
24048 {
24049 update_uncommitted_diff_for_buffer(
24050 cx.entity(),
24051 project,
24052 [buffer.clone()],
24053 self.buffer.clone(),
24054 cx,
24055 )
24056 .detach();
24057 }
24058 self.semantic_token_state
24059 .invalidate_buffer(&buffer.read(cx).remote_id());
24060 self.update_lsp_data(Some(buffer_id), window, cx);
24061 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24062 self.colorize_brackets(false, cx);
24063 self.refresh_selected_text_highlights(true, window, cx);
24064 cx.emit(EditorEvent::ExcerptsAdded {
24065 buffer: buffer.clone(),
24066 predecessor: *predecessor,
24067 excerpts: excerpts.clone(),
24068 });
24069 }
24070 multi_buffer::Event::ExcerptsRemoved {
24071 ids,
24072 removed_buffer_ids,
24073 } => {
24074 if let Some(inlay_hints) = &mut self.inlay_hints {
24075 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24076 }
24077 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24078 for buffer_id in removed_buffer_ids {
24079 self.registered_buffers.remove(buffer_id);
24080 self.tasks
24081 .retain(|(task_buffer_id, _), _| task_buffer_id != buffer_id);
24082 self.semantic_token_state.invalidate_buffer(buffer_id);
24083 self.display_map.update(cx, |display_map, cx| {
24084 display_map.invalidate_semantic_highlights(*buffer_id);
24085 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24086 });
24087 }
24088 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24089 cx.emit(EditorEvent::ExcerptsRemoved {
24090 ids: ids.clone(),
24091 removed_buffer_ids: removed_buffer_ids.clone(),
24092 });
24093 }
24094 multi_buffer::Event::ExcerptsEdited {
24095 excerpt_ids,
24096 buffer_ids,
24097 } => {
24098 self.display_map.update(cx, |map, cx| {
24099 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24100 });
24101 cx.emit(EditorEvent::ExcerptsEdited {
24102 ids: excerpt_ids.clone(),
24103 });
24104 }
24105 multi_buffer::Event::ExcerptsExpanded { ids } => {
24106 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24107 self.refresh_document_highlights(cx);
24108 let snapshot = multibuffer.read(cx).snapshot(cx);
24109 for id in ids {
24110 self.fetched_tree_sitter_chunks.remove(id);
24111 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24112 self.semantic_token_state
24113 .invalidate_buffer(&buffer.remote_id());
24114 }
24115 }
24116 self.colorize_brackets(false, cx);
24117 self.update_lsp_data(None, window, cx);
24118 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24119 }
24120 multi_buffer::Event::Reparsed(buffer_id) => {
24121 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24122 self.refresh_selected_text_highlights(true, window, cx);
24123 self.colorize_brackets(true, cx);
24124 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24125
24126 cx.emit(EditorEvent::Reparsed(*buffer_id));
24127 }
24128 multi_buffer::Event::DiffHunksToggled => {
24129 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24130 }
24131 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24132 if !is_fresh_language {
24133 self.registered_buffers.remove(&buffer_id);
24134 }
24135 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24136 cx.emit(EditorEvent::Reparsed(*buffer_id));
24137 cx.notify();
24138 }
24139 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24140 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24141 multi_buffer::Event::FileHandleChanged
24142 | multi_buffer::Event::Reloaded
24143 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24144 multi_buffer::Event::DiagnosticsUpdated => {
24145 self.update_diagnostics_state(window, cx);
24146 }
24147 _ => {}
24148 };
24149 }
24150
24151 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24152 if !self.diagnostics_enabled() {
24153 return;
24154 }
24155 self.refresh_active_diagnostics(cx);
24156 self.refresh_inline_diagnostics(true, window, cx);
24157 self.scrollbar_marker_state.dirty = true;
24158 cx.notify();
24159 }
24160
24161 pub fn start_temporary_diff_override(&mut self) {
24162 self.load_diff_task.take();
24163 self.temporary_diff_override = true;
24164 }
24165
24166 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24167 self.temporary_diff_override = false;
24168 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24169 self.buffer.update(cx, |buffer, cx| {
24170 buffer.set_all_diff_hunks_collapsed(cx);
24171 });
24172
24173 if let Some(project) = self.project.clone() {
24174 self.load_diff_task = Some(
24175 update_uncommitted_diff_for_buffer(
24176 cx.entity(),
24177 &project,
24178 self.buffer.read(cx).all_buffers(),
24179 self.buffer.clone(),
24180 cx,
24181 )
24182 .shared(),
24183 );
24184 }
24185 }
24186
24187 fn on_display_map_changed(
24188 &mut self,
24189 _: Entity<DisplayMap>,
24190 _: &mut Window,
24191 cx: &mut Context<Self>,
24192 ) {
24193 cx.notify();
24194 }
24195
24196 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24197 if !self.mode.is_full() {
24198 return None;
24199 }
24200
24201 let theme_settings = theme::ThemeSettings::get_global(cx);
24202 let theme = cx.theme();
24203 let accent_colors = theme.accents().clone();
24204
24205 let accent_overrides = theme_settings
24206 .theme_overrides
24207 .get(theme.name.as_ref())
24208 .map(|theme_style| &theme_style.accents)
24209 .into_iter()
24210 .flatten()
24211 .chain(
24212 theme_settings
24213 .experimental_theme_overrides
24214 .as_ref()
24215 .map(|overrides| &overrides.accents)
24216 .into_iter()
24217 .flatten(),
24218 )
24219 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24220 .collect();
24221
24222 Some(AccentData {
24223 colors: accent_colors,
24224 overrides: accent_overrides,
24225 })
24226 }
24227
24228 fn fetch_applicable_language_settings(
24229 &self,
24230 cx: &App,
24231 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24232 if !self.mode.is_full() {
24233 return HashMap::default();
24234 }
24235
24236 self.buffer().read(cx).all_buffers().into_iter().fold(
24237 HashMap::default(),
24238 |mut acc, buffer| {
24239 let buffer = buffer.read(cx);
24240 let language = buffer.language().map(|language| language.name());
24241 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24242 let file = buffer.file();
24243 v.insert(language_settings(language, file, cx).into_owned());
24244 }
24245 acc
24246 },
24247 )
24248 }
24249
24250 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24251 let new_language_settings = self.fetch_applicable_language_settings(cx);
24252 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24253 self.applicable_language_settings = new_language_settings;
24254
24255 let new_accents = self.fetch_accent_data(cx);
24256 let accents_changed = new_accents != self.accent_data;
24257 self.accent_data = new_accents;
24258
24259 if self.diagnostics_enabled() {
24260 let new_severity = EditorSettings::get_global(cx)
24261 .diagnostics_max_severity
24262 .unwrap_or(DiagnosticSeverity::Hint);
24263 self.set_max_diagnostics_severity(new_severity, cx);
24264 }
24265 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24266 self.update_edit_prediction_settings(cx);
24267 self.refresh_edit_prediction(true, false, window, cx);
24268 self.refresh_inline_values(cx);
24269
24270 let old_cursor_shape = self.cursor_shape;
24271 let old_show_breadcrumbs = self.show_breadcrumbs;
24272
24273 {
24274 let editor_settings = EditorSettings::get_global(cx);
24275 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24276 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24277 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24278 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24279 }
24280
24281 if old_cursor_shape != self.cursor_shape {
24282 cx.emit(EditorEvent::CursorShapeChanged);
24283 }
24284
24285 if old_show_breadcrumbs != self.show_breadcrumbs {
24286 cx.emit(EditorEvent::BreadcrumbsChanged);
24287 }
24288
24289 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24290 let project_settings = ProjectSettings::get_global(cx);
24291 (
24292 project_settings.session.restore_unsaved_buffers,
24293 project_settings.diagnostics.inline.enabled,
24294 project_settings.git.inline_blame.enabled,
24295 )
24296 };
24297 self.buffer_serialization = self
24298 .should_serialize_buffer()
24299 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24300
24301 if self.mode.is_full() {
24302 if self.show_inline_diagnostics != show_inline_diagnostics {
24303 self.show_inline_diagnostics = show_inline_diagnostics;
24304 self.refresh_inline_diagnostics(false, window, cx);
24305 }
24306
24307 if self.git_blame_inline_enabled != inline_blame_enabled {
24308 self.toggle_git_blame_inline_internal(false, window, cx);
24309 }
24310
24311 let minimap_settings = EditorSettings::get_global(cx).minimap;
24312 if self.minimap_visibility != MinimapVisibility::Disabled {
24313 if self.minimap_visibility.settings_visibility()
24314 != minimap_settings.minimap_enabled()
24315 {
24316 self.set_minimap_visibility(
24317 MinimapVisibility::for_mode(self.mode(), cx),
24318 window,
24319 cx,
24320 );
24321 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24322 minimap_entity.update(cx, |minimap_editor, cx| {
24323 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24324 })
24325 }
24326 }
24327
24328 if language_settings_changed || accents_changed {
24329 self.colorize_brackets(true, cx);
24330 }
24331
24332 if language_settings_changed {
24333 self.clear_disabled_lsp_folding_ranges(window, cx);
24334 self.refresh_document_symbols(None, cx);
24335 }
24336
24337 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24338 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24339 }) {
24340 if !inlay_splice.is_empty() {
24341 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24342 }
24343 self.refresh_document_colors(None, window, cx);
24344 }
24345
24346 self.refresh_inlay_hints(
24347 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24348 self.selections.newest_anchor().head(),
24349 &self.buffer.read(cx).snapshot(cx),
24350 cx,
24351 )),
24352 cx,
24353 );
24354
24355 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24356 .global_lsp_settings
24357 .semantic_token_rules
24358 .clone();
24359 let semantic_token_rules_changed = self
24360 .semantic_token_state
24361 .update_rules(new_semantic_token_rules);
24362 if language_settings_changed || semantic_token_rules_changed {
24363 self.invalidate_semantic_tokens(None);
24364 self.refresh_semantic_tokens(None, None, cx);
24365 }
24366 }
24367
24368 cx.notify();
24369 }
24370
24371 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24372 if !self.mode.is_full() {
24373 return;
24374 }
24375
24376 let new_accents = self.fetch_accent_data(cx);
24377 if new_accents != self.accent_data {
24378 self.accent_data = new_accents;
24379 self.colorize_brackets(true, cx);
24380 }
24381
24382 self.invalidate_semantic_tokens(None);
24383 self.refresh_semantic_tokens(None, None, cx);
24384 }
24385
24386 pub fn set_searchable(&mut self, searchable: bool) {
24387 self.searchable = searchable;
24388 }
24389
24390 pub fn searchable(&self) -> bool {
24391 self.searchable
24392 }
24393
24394 pub fn open_excerpts_in_split(
24395 &mut self,
24396 _: &OpenExcerptsSplit,
24397 window: &mut Window,
24398 cx: &mut Context<Self>,
24399 ) {
24400 self.open_excerpts_common(None, true, window, cx)
24401 }
24402
24403 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24404 self.open_excerpts_common(None, false, window, cx)
24405 }
24406
24407 pub(crate) fn open_excerpts_common(
24408 &mut self,
24409 jump_data: Option<JumpData>,
24410 split: bool,
24411 window: &mut Window,
24412 cx: &mut Context<Self>,
24413 ) {
24414 if self.buffer.read(cx).is_singleton() {
24415 cx.propagate();
24416 return;
24417 }
24418
24419 let mut new_selections_by_buffer = HashMap::default();
24420 match &jump_data {
24421 Some(JumpData::MultiBufferPoint {
24422 excerpt_id,
24423 position,
24424 anchor,
24425 line_offset_from_top,
24426 }) => {
24427 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24428 if let Some(buffer) = multi_buffer_snapshot
24429 .buffer_id_for_excerpt(*excerpt_id)
24430 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24431 {
24432 let buffer_snapshot = buffer.read(cx).snapshot();
24433 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24434 language::ToPoint::to_point(anchor, &buffer_snapshot)
24435 } else {
24436 buffer_snapshot.clip_point(*position, Bias::Left)
24437 };
24438 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24439 new_selections_by_buffer.insert(
24440 buffer,
24441 (
24442 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24443 Some(*line_offset_from_top),
24444 ),
24445 );
24446 }
24447 }
24448 Some(JumpData::MultiBufferRow {
24449 row,
24450 line_offset_from_top,
24451 }) => {
24452 let point = MultiBufferPoint::new(row.0, 0);
24453 if let Some((buffer, buffer_point, _)) =
24454 self.buffer.read(cx).point_to_buffer_point(point, cx)
24455 {
24456 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24457 new_selections_by_buffer
24458 .entry(buffer)
24459 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24460 .0
24461 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24462 }
24463 }
24464 None => {
24465 let selections = self
24466 .selections
24467 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24468 let multi_buffer = self.buffer.read(cx);
24469 for selection in selections {
24470 for (snapshot, range, _, anchor) in multi_buffer
24471 .snapshot(cx)
24472 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24473 {
24474 if let Some(anchor) = anchor {
24475 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24476 else {
24477 continue;
24478 };
24479 let offset = text::ToOffset::to_offset(
24480 &anchor.text_anchor,
24481 &buffer_handle.read(cx).snapshot(),
24482 );
24483 let range = BufferOffset(offset)..BufferOffset(offset);
24484 new_selections_by_buffer
24485 .entry(buffer_handle)
24486 .or_insert((Vec::new(), None))
24487 .0
24488 .push(range)
24489 } else {
24490 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24491 else {
24492 continue;
24493 };
24494 new_selections_by_buffer
24495 .entry(buffer_handle)
24496 .or_insert((Vec::new(), None))
24497 .0
24498 .push(range)
24499 }
24500 }
24501 }
24502 }
24503 }
24504
24505 if self.delegate_open_excerpts {
24506 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24507 .into_iter()
24508 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24509 .collect();
24510 if !selections_by_buffer.is_empty() {
24511 cx.emit(EditorEvent::OpenExcerptsRequested {
24512 selections_by_buffer,
24513 split,
24514 });
24515 }
24516 return;
24517 }
24518
24519 let Some(workspace) = self.workspace() else {
24520 cx.propagate();
24521 return;
24522 };
24523
24524 new_selections_by_buffer
24525 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24526
24527 if new_selections_by_buffer.is_empty() {
24528 return;
24529 }
24530
24531 Self::open_buffers_in_workspace(
24532 workspace.downgrade(),
24533 new_selections_by_buffer,
24534 split,
24535 window,
24536 cx,
24537 );
24538 }
24539
24540 pub(crate) fn open_buffers_in_workspace(
24541 workspace: WeakEntity<Workspace>,
24542 new_selections_by_buffer: HashMap<
24543 Entity<language::Buffer>,
24544 (Vec<Range<BufferOffset>>, Option<u32>),
24545 >,
24546 split: bool,
24547 window: &mut Window,
24548 cx: &mut App,
24549 ) {
24550 // We defer the pane interaction because we ourselves are a workspace item
24551 // and activating a new item causes the pane to call a method on us reentrantly,
24552 // which panics if we're on the stack.
24553 window.defer(cx, move |window, cx| {
24554 workspace
24555 .update(cx, |workspace, cx| {
24556 let pane = if split {
24557 workspace.adjacent_pane(window, cx)
24558 } else {
24559 workspace.active_pane().clone()
24560 };
24561
24562 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24563 let buffer_read = buffer.read(cx);
24564 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24565 (true, project::File::from_dyn(Some(file)).is_some())
24566 } else {
24567 (false, false)
24568 };
24569
24570 // If project file is none workspace.open_project_item will fail to open the excerpt
24571 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24572 // so we check if there's a tab match in that case first
24573 let editor = (!has_file || !is_project_file)
24574 .then(|| {
24575 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24576 // so `workspace.open_project_item` will never find them, always opening a new editor.
24577 // Instead, we try to activate the existing editor in the pane first.
24578 let (editor, pane_item_index, pane_item_id) =
24579 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24580 let editor = item.downcast::<Editor>()?;
24581 let singleton_buffer =
24582 editor.read(cx).buffer().read(cx).as_singleton()?;
24583 if singleton_buffer == buffer {
24584 Some((editor, i, item.item_id()))
24585 } else {
24586 None
24587 }
24588 })?;
24589 pane.update(cx, |pane, cx| {
24590 pane.activate_item(pane_item_index, true, true, window, cx);
24591 if !PreviewTabsSettings::get_global(cx)
24592 .enable_preview_from_multibuffer
24593 {
24594 pane.unpreview_item_if_preview(pane_item_id);
24595 }
24596 });
24597 Some(editor)
24598 })
24599 .flatten()
24600 .unwrap_or_else(|| {
24601 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24602 .enable_keep_preview_on_code_navigation;
24603 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24604 .enable_preview_from_multibuffer;
24605 workspace.open_project_item::<Self>(
24606 pane.clone(),
24607 buffer,
24608 true,
24609 true,
24610 keep_old_preview,
24611 allow_new_preview,
24612 window,
24613 cx,
24614 )
24615 });
24616
24617 editor.update(cx, |editor, cx| {
24618 if has_file && !is_project_file {
24619 editor.set_read_only(true);
24620 }
24621 let autoscroll = match scroll_offset {
24622 Some(scroll_offset) => {
24623 Autoscroll::top_relative(scroll_offset as usize)
24624 }
24625 None => Autoscroll::newest(),
24626 };
24627 let nav_history = editor.nav_history.take();
24628 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24629 let Some((excerpt_id, _, buffer_snapshot)) =
24630 multibuffer_snapshot.as_singleton()
24631 else {
24632 return;
24633 };
24634 editor.change_selections(
24635 SelectionEffects::scroll(autoscroll),
24636 window,
24637 cx,
24638 |s| {
24639 s.select_ranges(ranges.into_iter().map(|range| {
24640 let range = buffer_snapshot.anchor_before(range.start)
24641 ..buffer_snapshot.anchor_after(range.end);
24642 multibuffer_snapshot
24643 .anchor_range_in_excerpt(excerpt_id, range)
24644 .unwrap()
24645 }));
24646 },
24647 );
24648 editor.nav_history = nav_history;
24649 });
24650 }
24651 })
24652 .ok();
24653 });
24654 }
24655
24656 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24657 let snapshot = self.buffer.read(cx).read(cx);
24658 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24659 Some(
24660 ranges
24661 .iter()
24662 .map(move |range| {
24663 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24664 })
24665 .collect(),
24666 )
24667 }
24668
24669 fn selection_replacement_ranges(
24670 &self,
24671 range: Range<MultiBufferOffsetUtf16>,
24672 cx: &mut App,
24673 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24674 let selections = self
24675 .selections
24676 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24677 let newest_selection = selections
24678 .iter()
24679 .max_by_key(|selection| selection.id)
24680 .unwrap();
24681 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24682 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24683 let snapshot = self.buffer.read(cx).read(cx);
24684 selections
24685 .into_iter()
24686 .map(|mut selection| {
24687 selection.start.0.0 =
24688 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24689 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24690 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24691 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24692 })
24693 .collect()
24694 }
24695
24696 fn report_editor_event(
24697 &self,
24698 reported_event: ReportEditorEvent,
24699 file_extension: Option<String>,
24700 cx: &App,
24701 ) {
24702 if cfg!(any(test, feature = "test-support")) {
24703 return;
24704 }
24705
24706 let Some(project) = &self.project else { return };
24707
24708 // If None, we are in a file without an extension
24709 let file = self
24710 .buffer
24711 .read(cx)
24712 .as_singleton()
24713 .and_then(|b| b.read(cx).file());
24714 let file_extension = file_extension.or(file
24715 .as_ref()
24716 .and_then(|file| Path::new(file.file_name(cx)).extension())
24717 .and_then(|e| e.to_str())
24718 .map(|a| a.to_string()));
24719
24720 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24721 .map(|vim_mode| vim_mode.0)
24722 .unwrap_or(false);
24723
24724 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24725 let copilot_enabled = edit_predictions_provider
24726 == language::language_settings::EditPredictionProvider::Copilot;
24727 let copilot_enabled_for_language = self
24728 .buffer
24729 .read(cx)
24730 .language_settings(cx)
24731 .show_edit_predictions;
24732
24733 let project = project.read(cx);
24734 let event_type = reported_event.event_type();
24735
24736 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24737 telemetry::event!(
24738 event_type,
24739 type = if auto_saved {"autosave"} else {"manual"},
24740 file_extension,
24741 vim_mode,
24742 copilot_enabled,
24743 copilot_enabled_for_language,
24744 edit_predictions_provider,
24745 is_via_ssh = project.is_via_remote_server(),
24746 );
24747 } else {
24748 telemetry::event!(
24749 event_type,
24750 file_extension,
24751 vim_mode,
24752 copilot_enabled,
24753 copilot_enabled_for_language,
24754 edit_predictions_provider,
24755 is_via_ssh = project.is_via_remote_server(),
24756 );
24757 };
24758 }
24759
24760 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24761 /// with each line being an array of {text, highlight} objects.
24762 fn copy_highlight_json(
24763 &mut self,
24764 _: &CopyHighlightJson,
24765 window: &mut Window,
24766 cx: &mut Context<Self>,
24767 ) {
24768 #[derive(Serialize)]
24769 struct Chunk<'a> {
24770 text: String,
24771 highlight: Option<&'a str>,
24772 }
24773
24774 let snapshot = self.buffer.read(cx).snapshot(cx);
24775 let range = self
24776 .selected_text_range(false, window, cx)
24777 .and_then(|selection| {
24778 if selection.range.is_empty() {
24779 None
24780 } else {
24781 Some(
24782 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24783 selection.range.start,
24784 )))
24785 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24786 selection.range.end,
24787 ))),
24788 )
24789 }
24790 })
24791 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24792
24793 let chunks = snapshot.chunks(range, true);
24794 let mut lines = Vec::new();
24795 let mut line: VecDeque<Chunk> = VecDeque::new();
24796
24797 let Some(style) = self.style.as_ref() else {
24798 return;
24799 };
24800
24801 for chunk in chunks {
24802 let highlight = chunk
24803 .syntax_highlight_id
24804 .and_then(|id| id.name(&style.syntax));
24805 let mut chunk_lines = chunk.text.split('\n').peekable();
24806 while let Some(text) = chunk_lines.next() {
24807 let mut merged_with_last_token = false;
24808 if let Some(last_token) = line.back_mut()
24809 && last_token.highlight == highlight
24810 {
24811 last_token.text.push_str(text);
24812 merged_with_last_token = true;
24813 }
24814
24815 if !merged_with_last_token {
24816 line.push_back(Chunk {
24817 text: text.into(),
24818 highlight,
24819 });
24820 }
24821
24822 if chunk_lines.peek().is_some() {
24823 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24824 line.pop_front();
24825 }
24826 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24827 line.pop_back();
24828 }
24829
24830 lines.push(mem::take(&mut line));
24831 }
24832 }
24833 }
24834
24835 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24836 return;
24837 };
24838 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24839 }
24840
24841 pub fn open_context_menu(
24842 &mut self,
24843 _: &OpenContextMenu,
24844 window: &mut Window,
24845 cx: &mut Context<Self>,
24846 ) {
24847 self.request_autoscroll(Autoscroll::newest(), cx);
24848 let position = self
24849 .selections
24850 .newest_display(&self.display_snapshot(cx))
24851 .start;
24852 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24853 }
24854
24855 pub fn replay_insert_event(
24856 &mut self,
24857 text: &str,
24858 relative_utf16_range: Option<Range<isize>>,
24859 window: &mut Window,
24860 cx: &mut Context<Self>,
24861 ) {
24862 if !self.input_enabled {
24863 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24864 return;
24865 }
24866 if let Some(relative_utf16_range) = relative_utf16_range {
24867 let selections = self
24868 .selections
24869 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24870 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24871 let new_ranges = selections.into_iter().map(|range| {
24872 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24873 range
24874 .head()
24875 .0
24876 .0
24877 .saturating_add_signed(relative_utf16_range.start),
24878 ));
24879 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24880 range
24881 .head()
24882 .0
24883 .0
24884 .saturating_add_signed(relative_utf16_range.end),
24885 ));
24886 start..end
24887 });
24888 s.select_ranges(new_ranges);
24889 });
24890 }
24891
24892 self.handle_input(text, window, cx);
24893 }
24894
24895 pub fn is_focused(&self, window: &Window) -> bool {
24896 self.focus_handle.is_focused(window)
24897 }
24898
24899 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24900 cx.emit(EditorEvent::Focused);
24901
24902 if let Some(descendant) = self
24903 .last_focused_descendant
24904 .take()
24905 .and_then(|descendant| descendant.upgrade())
24906 {
24907 window.focus(&descendant, cx);
24908 } else {
24909 if let Some(blame) = self.blame.as_ref() {
24910 blame.update(cx, GitBlame::focus)
24911 }
24912
24913 self.blink_manager.update(cx, BlinkManager::enable);
24914 self.show_cursor_names(window, cx);
24915 self.buffer.update(cx, |buffer, cx| {
24916 buffer.finalize_last_transaction(cx);
24917 if self.leader_id.is_none() {
24918 buffer.set_active_selections(
24919 &self.selections.disjoint_anchors_arc(),
24920 self.selections.line_mode(),
24921 self.cursor_shape,
24922 cx,
24923 );
24924 }
24925 });
24926
24927 if let Some(position_map) = self.last_position_map.clone() {
24928 EditorElement::mouse_moved(
24929 self,
24930 &MouseMoveEvent {
24931 position: window.mouse_position(),
24932 pressed_button: None,
24933 modifiers: window.modifiers(),
24934 },
24935 &position_map,
24936 None,
24937 window,
24938 cx,
24939 );
24940 }
24941 }
24942 }
24943
24944 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24945 cx.emit(EditorEvent::FocusedIn)
24946 }
24947
24948 fn handle_focus_out(
24949 &mut self,
24950 event: FocusOutEvent,
24951 _window: &mut Window,
24952 cx: &mut Context<Self>,
24953 ) {
24954 if event.blurred != self.focus_handle {
24955 self.last_focused_descendant = Some(event.blurred);
24956 }
24957 self.selection_drag_state = SelectionDragState::None;
24958 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24959 }
24960
24961 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24962 self.blink_manager.update(cx, BlinkManager::disable);
24963 self.buffer
24964 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24965
24966 if let Some(blame) = self.blame.as_ref() {
24967 blame.update(cx, GitBlame::blur)
24968 }
24969 if !self.hover_state.focused(window, cx) {
24970 hide_hover(self, cx);
24971 }
24972 if !self
24973 .context_menu
24974 .borrow()
24975 .as_ref()
24976 .is_some_and(|context_menu| context_menu.focused(window, cx))
24977 {
24978 self.hide_context_menu(window, cx);
24979 }
24980 self.take_active_edit_prediction(cx);
24981 cx.emit(EditorEvent::Blurred);
24982 cx.notify();
24983 }
24984
24985 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24986 let mut pending: String = window
24987 .pending_input_keystrokes()
24988 .into_iter()
24989 .flatten()
24990 .filter_map(|keystroke| keystroke.key_char.clone())
24991 .collect();
24992
24993 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24994 pending = "".to_string();
24995 }
24996
24997 let existing_pending = self
24998 .text_highlights(HighlightKey::PendingInput, cx)
24999 .map(|(_, ranges)| ranges.to_vec());
25000 if existing_pending.is_none() && pending.is_empty() {
25001 return;
25002 }
25003 let transaction =
25004 self.transact(window, cx, |this, window, cx| {
25005 let selections = this
25006 .selections
25007 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25008 let edits = selections
25009 .iter()
25010 .map(|selection| (selection.end..selection.end, pending.clone()));
25011 this.edit(edits, cx);
25012 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25013 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25014 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25015 }));
25016 });
25017 if let Some(existing_ranges) = existing_pending {
25018 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25019 this.edit(edits, cx);
25020 }
25021 });
25022
25023 let snapshot = self.snapshot(window, cx);
25024 let ranges = self
25025 .selections
25026 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25027 .into_iter()
25028 .map(|selection| {
25029 snapshot.buffer_snapshot().anchor_after(selection.end)
25030 ..snapshot
25031 .buffer_snapshot()
25032 .anchor_before(selection.end + pending.len())
25033 })
25034 .collect();
25035
25036 if pending.is_empty() {
25037 self.clear_highlights(HighlightKey::PendingInput, cx);
25038 } else {
25039 self.highlight_text(
25040 HighlightKey::PendingInput,
25041 ranges,
25042 HighlightStyle {
25043 underline: Some(UnderlineStyle {
25044 thickness: px(1.),
25045 color: None,
25046 wavy: false,
25047 }),
25048 ..Default::default()
25049 },
25050 cx,
25051 );
25052 }
25053
25054 self.ime_transaction = self.ime_transaction.or(transaction);
25055 if let Some(transaction) = self.ime_transaction {
25056 self.buffer.update(cx, |buffer, cx| {
25057 buffer.group_until_transaction(transaction, cx);
25058 });
25059 }
25060
25061 if self
25062 .text_highlights(HighlightKey::PendingInput, cx)
25063 .is_none()
25064 {
25065 self.ime_transaction.take();
25066 }
25067 }
25068
25069 pub fn register_action_renderer(
25070 &mut self,
25071 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25072 ) -> Subscription {
25073 let id = self.next_editor_action_id.post_inc();
25074 self.editor_actions
25075 .borrow_mut()
25076 .insert(id, Box::new(listener));
25077
25078 let editor_actions = self.editor_actions.clone();
25079 Subscription::new(move || {
25080 editor_actions.borrow_mut().remove(&id);
25081 })
25082 }
25083
25084 pub fn register_action<A: Action>(
25085 &mut self,
25086 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25087 ) -> Subscription {
25088 let id = self.next_editor_action_id.post_inc();
25089 let listener = Arc::new(listener);
25090 self.editor_actions.borrow_mut().insert(
25091 id,
25092 Box::new(move |_, window, _| {
25093 let listener = listener.clone();
25094 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25095 let action = action.downcast_ref().unwrap();
25096 if phase == DispatchPhase::Bubble {
25097 listener(action, window, cx)
25098 }
25099 })
25100 }),
25101 );
25102
25103 let editor_actions = self.editor_actions.clone();
25104 Subscription::new(move || {
25105 editor_actions.borrow_mut().remove(&id);
25106 })
25107 }
25108
25109 pub fn file_header_size(&self) -> u32 {
25110 FILE_HEADER_HEIGHT
25111 }
25112
25113 pub fn restore(
25114 &mut self,
25115 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25116 window: &mut Window,
25117 cx: &mut Context<Self>,
25118 ) {
25119 self.buffer().update(cx, |multi_buffer, cx| {
25120 for (buffer_id, changes) in revert_changes {
25121 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25122 buffer.update(cx, |buffer, cx| {
25123 buffer.edit(
25124 changes
25125 .into_iter()
25126 .map(|(range, text)| (range, text.to_string())),
25127 None,
25128 cx,
25129 );
25130 });
25131 }
25132 }
25133 });
25134 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25135 selections.refresh()
25136 });
25137 }
25138
25139 pub fn to_pixel_point(
25140 &mut self,
25141 source: Anchor,
25142 editor_snapshot: &EditorSnapshot,
25143 window: &mut Window,
25144 cx: &mut App,
25145 ) -> Option<gpui::Point<Pixels>> {
25146 let source_point = source.to_display_point(editor_snapshot);
25147 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25148 }
25149
25150 pub fn display_to_pixel_point(
25151 &mut self,
25152 source: DisplayPoint,
25153 editor_snapshot: &EditorSnapshot,
25154 window: &mut Window,
25155 cx: &mut App,
25156 ) -> Option<gpui::Point<Pixels>> {
25157 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25158 let text_layout_details = self.text_layout_details(window, cx);
25159 let scroll_top = text_layout_details
25160 .scroll_anchor
25161 .scroll_position(editor_snapshot)
25162 .y;
25163
25164 if source.row().as_f64() < scroll_top.floor() {
25165 return None;
25166 }
25167 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25168 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25169 Some(gpui::Point::new(source_x, source_y))
25170 }
25171
25172 pub fn has_visible_completions_menu(&self) -> bool {
25173 !self.edit_prediction_preview_is_active()
25174 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25175 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25176 })
25177 }
25178
25179 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25180 if self.mode.is_minimap() {
25181 return;
25182 }
25183 self.addons
25184 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25185 }
25186
25187 pub fn unregister_addon<T: Addon>(&mut self) {
25188 self.addons.remove(&std::any::TypeId::of::<T>());
25189 }
25190
25191 pub fn addon<T: Addon>(&self) -> Option<&T> {
25192 let type_id = std::any::TypeId::of::<T>();
25193 self.addons
25194 .get(&type_id)
25195 .and_then(|item| item.to_any().downcast_ref::<T>())
25196 }
25197
25198 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25199 let type_id = std::any::TypeId::of::<T>();
25200 self.addons
25201 .get_mut(&type_id)
25202 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25203 }
25204
25205 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25206 let text_layout_details = self.text_layout_details(window, cx);
25207 let style = &text_layout_details.editor_style;
25208 let font_id = window.text_system().resolve_font(&style.text.font());
25209 let font_size = style.text.font_size.to_pixels(window.rem_size());
25210 let line_height = style.text.line_height_in_pixels(window.rem_size());
25211 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25212 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25213
25214 CharacterDimensions {
25215 em_width,
25216 em_advance,
25217 line_height,
25218 }
25219 }
25220
25221 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25222 self.load_diff_task.clone()
25223 }
25224
25225 fn read_metadata_from_db(
25226 &mut self,
25227 item_id: u64,
25228 workspace_id: WorkspaceId,
25229 window: &mut Window,
25230 cx: &mut Context<Editor>,
25231 ) {
25232 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25233 && !self.mode.is_minimap()
25234 && WorkspaceSettings::get(None, cx).restore_on_startup
25235 != RestoreOnStartupBehavior::EmptyTab
25236 {
25237 let buffer_snapshot = OnceCell::new();
25238
25239 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
25240 && !folds.is_empty()
25241 {
25242 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25243 let snapshot_len = snapshot.len().0;
25244
25245 // Helper: search for fingerprint in buffer, return offset if found
25246 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25247 // Ensure we start at a character boundary (defensive)
25248 let search_start = snapshot
25249 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25250 .0;
25251 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25252
25253 let mut byte_offset = search_start;
25254 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25255 if byte_offset > search_end {
25256 break;
25257 }
25258 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25259 return Some(byte_offset);
25260 }
25261 byte_offset += ch.len_utf8();
25262 }
25263 None
25264 };
25265
25266 // Track search position to handle duplicate fingerprints correctly.
25267 // Folds are stored in document order, so we advance after each match.
25268 let mut search_start = 0usize;
25269
25270 let valid_folds: Vec<_> = folds
25271 .into_iter()
25272 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25273 // Skip folds without fingerprints (old data before migration)
25274 let sfp = start_fp?;
25275 let efp = end_fp?;
25276 let efp_len = efp.len();
25277
25278 // Fast path: check if fingerprints match at stored offsets
25279 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25280 let start_matches = stored_start < snapshot_len
25281 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25282 let efp_check_pos = stored_end.saturating_sub(efp_len);
25283 let end_matches = efp_check_pos >= stored_start
25284 && stored_end <= snapshot_len
25285 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25286
25287 let (new_start, new_end) = if start_matches && end_matches {
25288 // Offsets unchanged, use stored values
25289 (stored_start, stored_end)
25290 } else if sfp == efp {
25291 // Short fold: identical fingerprints can only match once per search
25292 // Use stored fold length to compute new_end
25293 let new_start = find_fingerprint(&sfp, search_start)?;
25294 let fold_len = stored_end - stored_start;
25295 let new_end = new_start + fold_len;
25296 (new_start, new_end)
25297 } else {
25298 // Slow path: search for fingerprints in buffer
25299 let new_start = find_fingerprint(&sfp, search_start)?;
25300 // Search for end_fp after start, then add efp_len to get actual fold end
25301 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25302 let new_end = efp_pos + efp_len;
25303 (new_start, new_end)
25304 };
25305
25306 // Advance search position for next fold
25307 search_start = new_end;
25308
25309 // Validate fold makes sense (end must be after start)
25310 if new_end <= new_start {
25311 return None;
25312 }
25313
25314 Some(
25315 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25316 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25317 )
25318 })
25319 .collect();
25320
25321 if !valid_folds.is_empty() {
25322 self.fold_ranges(valid_folds, false, window, cx);
25323
25324 // Migrate folds to current entity_id before workspace cleanup runs.
25325 // Entity IDs change between sessions, but workspace cleanup deletes
25326 // old editor rows (cascading to folds) based on current entity IDs.
25327 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
25328 if new_editor_id != item_id {
25329 cx.spawn(async move |_, _| {
25330 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
25331 .await
25332 .log_err();
25333 })
25334 .detach();
25335 }
25336 }
25337 }
25338
25339 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25340 && !selections.is_empty()
25341 {
25342 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25343 // skip adding the initial selection to selection history
25344 self.selection_history.mode = SelectionHistoryMode::Skipping;
25345 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25346 s.select_ranges(selections.into_iter().map(|(start, end)| {
25347 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25348 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25349 }));
25350 });
25351 self.selection_history.mode = SelectionHistoryMode::Normal;
25352 };
25353 }
25354
25355 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25356 }
25357
25358 fn update_lsp_data(
25359 &mut self,
25360 for_buffer: Option<BufferId>,
25361 window: &mut Window,
25362 cx: &mut Context<'_, Self>,
25363 ) {
25364 if !self.enable_lsp_data {
25365 return;
25366 }
25367
25368 if let Some(buffer_id) = for_buffer {
25369 self.pull_diagnostics(buffer_id, window, cx);
25370 }
25371 self.refresh_semantic_tokens(for_buffer, None, cx);
25372 self.refresh_document_colors(for_buffer, window, cx);
25373 self.refresh_folding_ranges(for_buffer, window, cx);
25374 self.refresh_document_symbols(for_buffer, cx);
25375 }
25376
25377 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25378 if !self.mode().is_full() {
25379 return;
25380 }
25381 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25382 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25383 }
25384 }
25385
25386 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25387 if !self.mode().is_full() {
25388 return;
25389 }
25390
25391 if !self.registered_buffers.contains_key(&buffer_id)
25392 && let Some(project) = self.project.as_ref()
25393 {
25394 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25395 project.update(cx, |project, cx| {
25396 self.registered_buffers.insert(
25397 buffer_id,
25398 project.register_buffer_with_language_servers(&buffer, cx),
25399 );
25400 });
25401 } else {
25402 self.registered_buffers.remove(&buffer_id);
25403 }
25404 }
25405 }
25406
25407 fn create_style(&self, cx: &App) -> EditorStyle {
25408 let settings = ThemeSettings::get_global(cx);
25409
25410 let mut text_style = match self.mode {
25411 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25412 color: cx.theme().colors().editor_foreground,
25413 font_family: settings.ui_font.family.clone(),
25414 font_features: settings.ui_font.features.clone(),
25415 font_fallbacks: settings.ui_font.fallbacks.clone(),
25416 font_size: rems(0.875).into(),
25417 font_weight: settings.ui_font.weight,
25418 line_height: relative(settings.buffer_line_height.value()),
25419 ..Default::default()
25420 },
25421 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25422 color: cx.theme().colors().editor_foreground,
25423 font_family: settings.buffer_font.family.clone(),
25424 font_features: settings.buffer_font.features.clone(),
25425 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25426 font_size: settings.buffer_font_size(cx).into(),
25427 font_weight: settings.buffer_font.weight,
25428 line_height: relative(settings.buffer_line_height.value()),
25429 ..Default::default()
25430 },
25431 };
25432 if let Some(text_style_refinement) = &self.text_style_refinement {
25433 text_style.refine(text_style_refinement)
25434 }
25435
25436 let background = match self.mode {
25437 EditorMode::SingleLine => cx.theme().system().transparent,
25438 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25439 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25440 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25441 };
25442
25443 EditorStyle {
25444 background,
25445 border: cx.theme().colors().border,
25446 local_player: cx.theme().players().local(),
25447 text: text_style,
25448 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25449 syntax: cx.theme().syntax().clone(),
25450 status: cx.theme().status().clone(),
25451 inlay_hints_style: make_inlay_hints_style(cx),
25452 edit_prediction_styles: make_suggestion_styles(cx),
25453 unnecessary_code_fade: settings.unnecessary_code_fade,
25454 show_underlines: self.diagnostics_enabled(),
25455 }
25456 }
25457
25458 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
25459 let multibuffer = self.buffer().read(cx);
25460 let is_singleton = multibuffer.is_singleton();
25461 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25462 let buffer = multibuffer.buffer(*buffer_id)?;
25463
25464 let buffer = buffer.read(cx);
25465 let settings = ThemeSettings::get_global(cx);
25466 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25467 let mut breadcrumbs = if is_singleton {
25468 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25469 buffer
25470 .snapshot()
25471 .resolve_file_path(
25472 self.project
25473 .as_ref()
25474 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25475 .unwrap_or_default(),
25476 cx,
25477 )
25478 .unwrap_or_else(|| {
25479 if multibuffer.is_singleton() {
25480 multibuffer.title(cx).to_string()
25481 } else {
25482 "untitled".to_string()
25483 }
25484 })
25485 });
25486 vec![BreadcrumbText {
25487 text,
25488 highlights: None,
25489 font: Some(settings.buffer_font.clone()),
25490 }]
25491 } else {
25492 vec![]
25493 };
25494
25495 breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
25496 text: symbol.text.clone(),
25497 highlights: Some(symbol.highlight_ranges.clone()),
25498 font: Some(settings.buffer_font.clone()),
25499 }));
25500 Some(breadcrumbs)
25501 }
25502
25503 fn disable_lsp_data(&mut self) {
25504 self.enable_lsp_data = false;
25505 }
25506
25507 fn disable_runnables(&mut self) {
25508 self.enable_runnables = false;
25509 }
25510}
25511
25512fn edit_for_markdown_paste<'a>(
25513 buffer: &MultiBufferSnapshot,
25514 range: Range<MultiBufferOffset>,
25515 to_insert: &'a str,
25516 url: Option<url::Url>,
25517) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25518 if url.is_none() {
25519 return (range, Cow::Borrowed(to_insert));
25520 };
25521
25522 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25523
25524 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25525 Cow::Borrowed(to_insert)
25526 } else {
25527 Cow::Owned(format!("[{old_text}]({to_insert})"))
25528 };
25529 (range, new_text)
25530}
25531
25532fn process_completion_for_edit(
25533 completion: &Completion,
25534 intent: CompletionIntent,
25535 buffer: &Entity<Buffer>,
25536 cursor_position: &text::Anchor,
25537 cx: &mut Context<Editor>,
25538) -> CompletionEdit {
25539 let buffer = buffer.read(cx);
25540 let buffer_snapshot = buffer.snapshot();
25541 let (snippet, new_text) = if completion.is_snippet() {
25542 let mut snippet_source = completion.new_text.clone();
25543 // Workaround for typescript language server issues so that methods don't expand within
25544 // strings and functions with type expressions. The previous point is used because the query
25545 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25546 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25547 let previous_point = if previous_point.column > 0 {
25548 cursor_position.to_previous_offset(&buffer_snapshot)
25549 } else {
25550 cursor_position.to_offset(&buffer_snapshot)
25551 };
25552 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25553 && scope.prefers_label_for_snippet_in_completion()
25554 && let Some(label) = completion.label()
25555 && matches!(
25556 completion.kind(),
25557 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25558 )
25559 {
25560 snippet_source = label;
25561 }
25562 match Snippet::parse(&snippet_source).log_err() {
25563 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25564 None => (None, completion.new_text.clone()),
25565 }
25566 } else {
25567 (None, completion.new_text.clone())
25568 };
25569
25570 let mut range_to_replace = {
25571 let replace_range = &completion.replace_range;
25572 if let CompletionSource::Lsp {
25573 insert_range: Some(insert_range),
25574 ..
25575 } = &completion.source
25576 {
25577 debug_assert_eq!(
25578 insert_range.start, replace_range.start,
25579 "insert_range and replace_range should start at the same position"
25580 );
25581 debug_assert!(
25582 insert_range
25583 .start
25584 .cmp(cursor_position, &buffer_snapshot)
25585 .is_le(),
25586 "insert_range should start before or at cursor position"
25587 );
25588 debug_assert!(
25589 replace_range
25590 .start
25591 .cmp(cursor_position, &buffer_snapshot)
25592 .is_le(),
25593 "replace_range should start before or at cursor position"
25594 );
25595
25596 let should_replace = match intent {
25597 CompletionIntent::CompleteWithInsert => false,
25598 CompletionIntent::CompleteWithReplace => true,
25599 CompletionIntent::Complete | CompletionIntent::Compose => {
25600 let insert_mode =
25601 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25602 .completions
25603 .lsp_insert_mode;
25604 match insert_mode {
25605 LspInsertMode::Insert => false,
25606 LspInsertMode::Replace => true,
25607 LspInsertMode::ReplaceSubsequence => {
25608 let mut text_to_replace = buffer.chars_for_range(
25609 buffer.anchor_before(replace_range.start)
25610 ..buffer.anchor_after(replace_range.end),
25611 );
25612 let mut current_needle = text_to_replace.next();
25613 for haystack_ch in completion.label.text.chars() {
25614 if let Some(needle_ch) = current_needle
25615 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25616 {
25617 current_needle = text_to_replace.next();
25618 }
25619 }
25620 current_needle.is_none()
25621 }
25622 LspInsertMode::ReplaceSuffix => {
25623 if replace_range
25624 .end
25625 .cmp(cursor_position, &buffer_snapshot)
25626 .is_gt()
25627 {
25628 let range_after_cursor = *cursor_position..replace_range.end;
25629 let text_after_cursor = buffer
25630 .text_for_range(
25631 buffer.anchor_before(range_after_cursor.start)
25632 ..buffer.anchor_after(range_after_cursor.end),
25633 )
25634 .collect::<String>()
25635 .to_ascii_lowercase();
25636 completion
25637 .label
25638 .text
25639 .to_ascii_lowercase()
25640 .ends_with(&text_after_cursor)
25641 } else {
25642 true
25643 }
25644 }
25645 }
25646 }
25647 };
25648
25649 if should_replace {
25650 replace_range.clone()
25651 } else {
25652 insert_range.clone()
25653 }
25654 } else {
25655 replace_range.clone()
25656 }
25657 };
25658
25659 if range_to_replace
25660 .end
25661 .cmp(cursor_position, &buffer_snapshot)
25662 .is_lt()
25663 {
25664 range_to_replace.end = *cursor_position;
25665 }
25666
25667 let replace_range = range_to_replace.to_offset(buffer);
25668 CompletionEdit {
25669 new_text,
25670 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25671 snippet,
25672 }
25673}
25674
25675struct CompletionEdit {
25676 new_text: String,
25677 replace_range: Range<BufferOffset>,
25678 snippet: Option<Snippet>,
25679}
25680
25681fn comment_delimiter_for_newline(
25682 start_point: &Point,
25683 buffer: &MultiBufferSnapshot,
25684 language: &LanguageScope,
25685) -> Option<Arc<str>> {
25686 let delimiters = language.line_comment_prefixes();
25687 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25688 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25689
25690 let num_of_whitespaces = snapshot
25691 .chars_for_range(range.clone())
25692 .take_while(|c| c.is_whitespace())
25693 .count();
25694 let comment_candidate = snapshot
25695 .chars_for_range(range.clone())
25696 .skip(num_of_whitespaces)
25697 .take(max_len_of_delimiter)
25698 .collect::<String>();
25699 let (delimiter, trimmed_len) = delimiters
25700 .iter()
25701 .filter_map(|delimiter| {
25702 let prefix = delimiter.trim_end();
25703 if comment_candidate.starts_with(prefix) {
25704 Some((delimiter, prefix.len()))
25705 } else {
25706 None
25707 }
25708 })
25709 .max_by_key(|(_, len)| *len)?;
25710
25711 if let Some(BlockCommentConfig {
25712 start: block_start, ..
25713 }) = language.block_comment()
25714 {
25715 let block_start_trimmed = block_start.trim_end();
25716 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25717 let line_content = snapshot
25718 .chars_for_range(range)
25719 .skip(num_of_whitespaces)
25720 .take(block_start_trimmed.len())
25721 .collect::<String>();
25722
25723 if line_content.starts_with(block_start_trimmed) {
25724 return None;
25725 }
25726 }
25727 }
25728
25729 let cursor_is_placed_after_comment_marker =
25730 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25731 if cursor_is_placed_after_comment_marker {
25732 Some(delimiter.clone())
25733 } else {
25734 None
25735 }
25736}
25737
25738fn documentation_delimiter_for_newline(
25739 start_point: &Point,
25740 buffer: &MultiBufferSnapshot,
25741 language: &LanguageScope,
25742 newline_config: &mut NewlineConfig,
25743) -> Option<Arc<str>> {
25744 let BlockCommentConfig {
25745 start: start_tag,
25746 end: end_tag,
25747 prefix: delimiter,
25748 tab_size: len,
25749 } = language.documentation_comment()?;
25750 let is_within_block_comment = buffer
25751 .language_scope_at(*start_point)
25752 .is_some_and(|scope| scope.override_name() == Some("comment"));
25753 if !is_within_block_comment {
25754 return None;
25755 }
25756
25757 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25758
25759 let num_of_whitespaces = snapshot
25760 .chars_for_range(range.clone())
25761 .take_while(|c| c.is_whitespace())
25762 .count();
25763
25764 // 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.
25765 let column = start_point.column;
25766 let cursor_is_after_start_tag = {
25767 let start_tag_len = start_tag.len();
25768 let start_tag_line = snapshot
25769 .chars_for_range(range.clone())
25770 .skip(num_of_whitespaces)
25771 .take(start_tag_len)
25772 .collect::<String>();
25773 if start_tag_line.starts_with(start_tag.as_ref()) {
25774 num_of_whitespaces + start_tag_len <= column as usize
25775 } else {
25776 false
25777 }
25778 };
25779
25780 let cursor_is_after_delimiter = {
25781 let delimiter_trim = delimiter.trim_end();
25782 let delimiter_line = snapshot
25783 .chars_for_range(range.clone())
25784 .skip(num_of_whitespaces)
25785 .take(delimiter_trim.len())
25786 .collect::<String>();
25787 if delimiter_line.starts_with(delimiter_trim) {
25788 num_of_whitespaces + delimiter_trim.len() <= column as usize
25789 } else {
25790 false
25791 }
25792 };
25793
25794 let mut needs_extra_line = false;
25795 let mut extra_line_additional_indent = IndentSize::spaces(0);
25796
25797 let cursor_is_before_end_tag_if_exists = {
25798 let mut char_position = 0u32;
25799 let mut end_tag_offset = None;
25800
25801 'outer: for chunk in snapshot.text_for_range(range) {
25802 if let Some(byte_pos) = chunk.find(&**end_tag) {
25803 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25804 end_tag_offset = Some(char_position + chars_before_match);
25805 break 'outer;
25806 }
25807 char_position += chunk.chars().count() as u32;
25808 }
25809
25810 if let Some(end_tag_offset) = end_tag_offset {
25811 let cursor_is_before_end_tag = column <= end_tag_offset;
25812 if cursor_is_after_start_tag {
25813 if cursor_is_before_end_tag {
25814 needs_extra_line = true;
25815 }
25816 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25817 if cursor_is_at_start_of_end_tag {
25818 extra_line_additional_indent.len = *len;
25819 }
25820 }
25821 cursor_is_before_end_tag
25822 } else {
25823 true
25824 }
25825 };
25826
25827 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25828 && cursor_is_before_end_tag_if_exists
25829 {
25830 let additional_indent = if cursor_is_after_start_tag {
25831 IndentSize::spaces(*len)
25832 } else {
25833 IndentSize::spaces(0)
25834 };
25835
25836 *newline_config = NewlineConfig::Newline {
25837 additional_indent,
25838 extra_line_additional_indent: if needs_extra_line {
25839 Some(extra_line_additional_indent)
25840 } else {
25841 None
25842 },
25843 prevent_auto_indent: true,
25844 };
25845 Some(delimiter.clone())
25846 } else {
25847 None
25848 }
25849}
25850
25851const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25852
25853fn list_delimiter_for_newline(
25854 start_point: &Point,
25855 buffer: &MultiBufferSnapshot,
25856 language: &LanguageScope,
25857 newline_config: &mut NewlineConfig,
25858) -> Option<Arc<str>> {
25859 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25860
25861 let num_of_whitespaces = snapshot
25862 .chars_for_range(range.clone())
25863 .take_while(|c| c.is_whitespace())
25864 .count();
25865
25866 let task_list_entries: Vec<_> = language
25867 .task_list()
25868 .into_iter()
25869 .flat_map(|config| {
25870 config
25871 .prefixes
25872 .iter()
25873 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25874 })
25875 .collect();
25876 let unordered_list_entries: Vec<_> = language
25877 .unordered_list()
25878 .iter()
25879 .map(|marker| (marker.as_ref(), marker.as_ref()))
25880 .collect();
25881
25882 let all_entries: Vec<_> = task_list_entries
25883 .into_iter()
25884 .chain(unordered_list_entries)
25885 .collect();
25886
25887 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25888 let candidate: String = snapshot
25889 .chars_for_range(range.clone())
25890 .skip(num_of_whitespaces)
25891 .take(max_prefix_len)
25892 .collect();
25893
25894 if let Some((prefix, continuation)) = all_entries
25895 .iter()
25896 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25897 .max_by_key(|(prefix, _)| prefix.len())
25898 {
25899 let end_of_prefix = num_of_whitespaces + prefix.len();
25900 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25901 let has_content_after_marker = snapshot
25902 .chars_for_range(range)
25903 .skip(end_of_prefix)
25904 .any(|c| !c.is_whitespace());
25905
25906 if has_content_after_marker && cursor_is_after_prefix {
25907 return Some((*continuation).into());
25908 }
25909
25910 if start_point.column as usize == end_of_prefix {
25911 if num_of_whitespaces == 0 {
25912 *newline_config = NewlineConfig::ClearCurrentLine;
25913 } else {
25914 *newline_config = NewlineConfig::UnindentCurrentLine {
25915 continuation: (*continuation).into(),
25916 };
25917 }
25918 }
25919
25920 return None;
25921 }
25922 }
25923
25924 let candidate: String = snapshot
25925 .chars_for_range(range.clone())
25926 .skip(num_of_whitespaces)
25927 .take(ORDERED_LIST_MAX_MARKER_LEN)
25928 .collect();
25929
25930 for ordered_config in language.ordered_list() {
25931 let regex = match Regex::new(&ordered_config.pattern) {
25932 Ok(r) => r,
25933 Err(_) => continue,
25934 };
25935
25936 if let Some(captures) = regex.captures(&candidate) {
25937 let full_match = captures.get(0)?;
25938 let marker_len = full_match.len();
25939 let end_of_prefix = num_of_whitespaces + marker_len;
25940 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25941
25942 let has_content_after_marker = snapshot
25943 .chars_for_range(range)
25944 .skip(end_of_prefix)
25945 .any(|c| !c.is_whitespace());
25946
25947 if has_content_after_marker && cursor_is_after_prefix {
25948 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25949 let continuation = ordered_config
25950 .format
25951 .replace("{1}", &(number + 1).to_string());
25952 return Some(continuation.into());
25953 }
25954
25955 if start_point.column as usize == end_of_prefix {
25956 let continuation = ordered_config.format.replace("{1}", "1");
25957 if num_of_whitespaces == 0 {
25958 *newline_config = NewlineConfig::ClearCurrentLine;
25959 } else {
25960 *newline_config = NewlineConfig::UnindentCurrentLine {
25961 continuation: continuation.into(),
25962 };
25963 }
25964 }
25965
25966 return None;
25967 }
25968 }
25969
25970 None
25971}
25972
25973fn is_list_prefix_row(
25974 row: MultiBufferRow,
25975 buffer: &MultiBufferSnapshot,
25976 language: &LanguageScope,
25977) -> bool {
25978 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25979 return false;
25980 };
25981
25982 let num_of_whitespaces = snapshot
25983 .chars_for_range(range.clone())
25984 .take_while(|c| c.is_whitespace())
25985 .count();
25986
25987 let task_list_prefixes: Vec<_> = language
25988 .task_list()
25989 .into_iter()
25990 .flat_map(|config| {
25991 config
25992 .prefixes
25993 .iter()
25994 .map(|p| p.as_ref())
25995 .collect::<Vec<_>>()
25996 })
25997 .collect();
25998 let unordered_list_markers: Vec<_> = language
25999 .unordered_list()
26000 .iter()
26001 .map(|marker| marker.as_ref())
26002 .collect();
26003 let all_prefixes: Vec<_> = task_list_prefixes
26004 .into_iter()
26005 .chain(unordered_list_markers)
26006 .collect();
26007 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26008 let candidate: String = snapshot
26009 .chars_for_range(range.clone())
26010 .skip(num_of_whitespaces)
26011 .take(max_prefix_len)
26012 .collect();
26013 if all_prefixes
26014 .iter()
26015 .any(|prefix| candidate.starts_with(*prefix))
26016 {
26017 return true;
26018 }
26019 }
26020
26021 let ordered_list_candidate: String = snapshot
26022 .chars_for_range(range)
26023 .skip(num_of_whitespaces)
26024 .take(ORDERED_LIST_MAX_MARKER_LEN)
26025 .collect();
26026 for ordered_config in language.ordered_list() {
26027 let regex = match Regex::new(&ordered_config.pattern) {
26028 Ok(r) => r,
26029 Err(_) => continue,
26030 };
26031 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26032 return captures.get(0).is_some();
26033 }
26034 }
26035
26036 false
26037}
26038
26039#[derive(Debug)]
26040enum NewlineConfig {
26041 /// Insert newline with optional additional indent and optional extra blank line
26042 Newline {
26043 additional_indent: IndentSize,
26044 extra_line_additional_indent: Option<IndentSize>,
26045 prevent_auto_indent: bool,
26046 },
26047 /// Clear the current line
26048 ClearCurrentLine,
26049 /// Unindent the current line and add continuation
26050 UnindentCurrentLine { continuation: Arc<str> },
26051}
26052
26053impl NewlineConfig {
26054 fn has_extra_line(&self) -> bool {
26055 matches!(
26056 self,
26057 Self::Newline {
26058 extra_line_additional_indent: Some(_),
26059 ..
26060 }
26061 )
26062 }
26063
26064 fn insert_extra_newline_brackets(
26065 buffer: &MultiBufferSnapshot,
26066 range: Range<MultiBufferOffset>,
26067 language: &language::LanguageScope,
26068 ) -> bool {
26069 let leading_whitespace_len = buffer
26070 .reversed_chars_at(range.start)
26071 .take_while(|c| c.is_whitespace() && *c != '\n')
26072 .map(|c| c.len_utf8())
26073 .sum::<usize>();
26074 let trailing_whitespace_len = buffer
26075 .chars_at(range.end)
26076 .take_while(|c| c.is_whitespace() && *c != '\n')
26077 .map(|c| c.len_utf8())
26078 .sum::<usize>();
26079 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26080
26081 language.brackets().any(|(pair, enabled)| {
26082 let pair_start = pair.start.trim_end();
26083 let pair_end = pair.end.trim_start();
26084
26085 enabled
26086 && pair.newline
26087 && buffer.contains_str_at(range.end, pair_end)
26088 && buffer.contains_str_at(
26089 range.start.saturating_sub_usize(pair_start.len()),
26090 pair_start,
26091 )
26092 })
26093 }
26094
26095 fn insert_extra_newline_tree_sitter(
26096 buffer: &MultiBufferSnapshot,
26097 range: Range<MultiBufferOffset>,
26098 ) -> bool {
26099 let (buffer, range) = match buffer
26100 .range_to_buffer_ranges(range.start..=range.end)
26101 .as_slice()
26102 {
26103 [(buffer, range, _)] => (*buffer, range.clone()),
26104 _ => return false,
26105 };
26106 let pair = {
26107 let mut result: Option<BracketMatch<usize>> = None;
26108
26109 for pair in buffer
26110 .all_bracket_ranges(range.start.0..range.end.0)
26111 .filter(move |pair| {
26112 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26113 })
26114 {
26115 let len = pair.close_range.end - pair.open_range.start;
26116
26117 if let Some(existing) = &result {
26118 let existing_len = existing.close_range.end - existing.open_range.start;
26119 if len > existing_len {
26120 continue;
26121 }
26122 }
26123
26124 result = Some(pair);
26125 }
26126
26127 result
26128 };
26129 let Some(pair) = pair else {
26130 return false;
26131 };
26132 pair.newline_only
26133 && buffer
26134 .chars_for_range(pair.open_range.end..range.start.0)
26135 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26136 .all(|c| c.is_whitespace() && c != '\n')
26137 }
26138}
26139
26140fn update_uncommitted_diff_for_buffer(
26141 editor: Entity<Editor>,
26142 project: &Entity<Project>,
26143 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26144 buffer: Entity<MultiBuffer>,
26145 cx: &mut App,
26146) -> Task<()> {
26147 let mut tasks = Vec::new();
26148 project.update(cx, |project, cx| {
26149 for buffer in buffers {
26150 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26151 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26152 }
26153 }
26154 });
26155 cx.spawn(async move |cx| {
26156 let diffs = future::join_all(tasks).await;
26157 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26158 return;
26159 }
26160
26161 buffer.update(cx, |buffer, cx| {
26162 for diff in diffs.into_iter().flatten() {
26163 buffer.add_diff(diff, cx);
26164 }
26165 });
26166 })
26167}
26168
26169fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26170 let tab_size = tab_size.get() as usize;
26171 let mut width = offset;
26172
26173 for ch in text.chars() {
26174 width += if ch == '\t' {
26175 tab_size - (width % tab_size)
26176 } else {
26177 1
26178 };
26179 }
26180
26181 width - offset
26182}
26183
26184#[cfg(test)]
26185mod tests {
26186 use super::*;
26187
26188 #[test]
26189 fn test_string_size_with_expanded_tabs() {
26190 let nz = |val| NonZeroU32::new(val).unwrap();
26191 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26192 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26193 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26194 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26195 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26196 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26197 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26198 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26199 }
26200}
26201
26202/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26203struct WordBreakingTokenizer<'a> {
26204 input: &'a str,
26205}
26206
26207impl<'a> WordBreakingTokenizer<'a> {
26208 fn new(input: &'a str) -> Self {
26209 Self { input }
26210 }
26211}
26212
26213fn is_char_ideographic(ch: char) -> bool {
26214 use unicode_script::Script::*;
26215 use unicode_script::UnicodeScript;
26216 matches!(ch.script(), Han | Tangut | Yi)
26217}
26218
26219fn is_grapheme_ideographic(text: &str) -> bool {
26220 text.chars().any(is_char_ideographic)
26221}
26222
26223fn is_grapheme_whitespace(text: &str) -> bool {
26224 text.chars().any(|x| x.is_whitespace())
26225}
26226
26227fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26228 text.chars()
26229 .next()
26230 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26231}
26232
26233#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26234enum WordBreakToken<'a> {
26235 Word { token: &'a str, grapheme_len: usize },
26236 InlineWhitespace { token: &'a str, grapheme_len: usize },
26237 Newline,
26238}
26239
26240impl<'a> Iterator for WordBreakingTokenizer<'a> {
26241 /// Yields a span, the count of graphemes in the token, and whether it was
26242 /// whitespace. Note that it also breaks at word boundaries.
26243 type Item = WordBreakToken<'a>;
26244
26245 fn next(&mut self) -> Option<Self::Item> {
26246 use unicode_segmentation::UnicodeSegmentation;
26247 if self.input.is_empty() {
26248 return None;
26249 }
26250
26251 let mut iter = self.input.graphemes(true).peekable();
26252 let mut offset = 0;
26253 let mut grapheme_len = 0;
26254 if let Some(first_grapheme) = iter.next() {
26255 let is_newline = first_grapheme == "\n";
26256 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26257 offset += first_grapheme.len();
26258 grapheme_len += 1;
26259 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26260 if let Some(grapheme) = iter.peek().copied()
26261 && should_stay_with_preceding_ideograph(grapheme)
26262 {
26263 offset += grapheme.len();
26264 grapheme_len += 1;
26265 }
26266 } else {
26267 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26268 let mut next_word_bound = words.peek().copied();
26269 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26270 next_word_bound = words.next();
26271 }
26272 while let Some(grapheme) = iter.peek().copied() {
26273 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26274 break;
26275 };
26276 if is_grapheme_whitespace(grapheme) != is_whitespace
26277 || (grapheme == "\n") != is_newline
26278 {
26279 break;
26280 };
26281 offset += grapheme.len();
26282 grapheme_len += 1;
26283 iter.next();
26284 }
26285 }
26286 let token = &self.input[..offset];
26287 self.input = &self.input[offset..];
26288 if token == "\n" {
26289 Some(WordBreakToken::Newline)
26290 } else if is_whitespace {
26291 Some(WordBreakToken::InlineWhitespace {
26292 token,
26293 grapheme_len,
26294 })
26295 } else {
26296 Some(WordBreakToken::Word {
26297 token,
26298 grapheme_len,
26299 })
26300 }
26301 } else {
26302 None
26303 }
26304 }
26305}
26306
26307#[test]
26308fn test_word_breaking_tokenizer() {
26309 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26310 ("", &[]),
26311 (" ", &[whitespace(" ", 2)]),
26312 ("Ʒ", &[word("Ʒ", 1)]),
26313 ("Ǽ", &[word("Ǽ", 1)]),
26314 ("⋑", &[word("⋑", 1)]),
26315 ("⋑⋑", &[word("⋑⋑", 2)]),
26316 (
26317 "原理,进而",
26318 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26319 ),
26320 (
26321 "hello world",
26322 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26323 ),
26324 (
26325 "hello, world",
26326 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26327 ),
26328 (
26329 " hello world",
26330 &[
26331 whitespace(" ", 2),
26332 word("hello", 5),
26333 whitespace(" ", 1),
26334 word("world", 5),
26335 ],
26336 ),
26337 (
26338 "这是什么 \n 钢笔",
26339 &[
26340 word("这", 1),
26341 word("是", 1),
26342 word("什", 1),
26343 word("么", 1),
26344 whitespace(" ", 1),
26345 newline(),
26346 whitespace(" ", 1),
26347 word("钢", 1),
26348 word("笔", 1),
26349 ],
26350 ),
26351 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26352 ];
26353
26354 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26355 WordBreakToken::Word {
26356 token,
26357 grapheme_len,
26358 }
26359 }
26360
26361 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26362 WordBreakToken::InlineWhitespace {
26363 token,
26364 grapheme_len,
26365 }
26366 }
26367
26368 fn newline() -> WordBreakToken<'static> {
26369 WordBreakToken::Newline
26370 }
26371
26372 for (input, result) in tests {
26373 assert_eq!(
26374 WordBreakingTokenizer::new(input)
26375 .collect::<Vec<_>>()
26376 .as_slice(),
26377 *result,
26378 );
26379 }
26380}
26381
26382fn wrap_with_prefix(
26383 first_line_prefix: String,
26384 subsequent_lines_prefix: String,
26385 unwrapped_text: String,
26386 wrap_column: usize,
26387 tab_size: NonZeroU32,
26388 preserve_existing_whitespace: bool,
26389) -> String {
26390 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26391 let subsequent_lines_prefix_len =
26392 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26393 let mut wrapped_text = String::new();
26394 let mut current_line = first_line_prefix;
26395 let mut is_first_line = true;
26396
26397 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26398 let mut current_line_len = first_line_prefix_len;
26399 let mut in_whitespace = false;
26400 for token in tokenizer {
26401 let have_preceding_whitespace = in_whitespace;
26402 match token {
26403 WordBreakToken::Word {
26404 token,
26405 grapheme_len,
26406 } => {
26407 in_whitespace = false;
26408 let current_prefix_len = if is_first_line {
26409 first_line_prefix_len
26410 } else {
26411 subsequent_lines_prefix_len
26412 };
26413 if current_line_len + grapheme_len > wrap_column
26414 && current_line_len != current_prefix_len
26415 {
26416 wrapped_text.push_str(current_line.trim_end());
26417 wrapped_text.push('\n');
26418 is_first_line = false;
26419 current_line = subsequent_lines_prefix.clone();
26420 current_line_len = subsequent_lines_prefix_len;
26421 }
26422 current_line.push_str(token);
26423 current_line_len += grapheme_len;
26424 }
26425 WordBreakToken::InlineWhitespace {
26426 mut token,
26427 mut grapheme_len,
26428 } => {
26429 in_whitespace = true;
26430 if have_preceding_whitespace && !preserve_existing_whitespace {
26431 continue;
26432 }
26433 if !preserve_existing_whitespace {
26434 // Keep a single whitespace grapheme as-is
26435 if let Some(first) =
26436 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26437 {
26438 token = first;
26439 } else {
26440 token = " ";
26441 }
26442 grapheme_len = 1;
26443 }
26444 let current_prefix_len = if is_first_line {
26445 first_line_prefix_len
26446 } else {
26447 subsequent_lines_prefix_len
26448 };
26449 if current_line_len + grapheme_len > wrap_column {
26450 wrapped_text.push_str(current_line.trim_end());
26451 wrapped_text.push('\n');
26452 is_first_line = false;
26453 current_line = subsequent_lines_prefix.clone();
26454 current_line_len = subsequent_lines_prefix_len;
26455 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26456 current_line.push_str(token);
26457 current_line_len += grapheme_len;
26458 }
26459 }
26460 WordBreakToken::Newline => {
26461 in_whitespace = true;
26462 let current_prefix_len = if is_first_line {
26463 first_line_prefix_len
26464 } else {
26465 subsequent_lines_prefix_len
26466 };
26467 if preserve_existing_whitespace {
26468 wrapped_text.push_str(current_line.trim_end());
26469 wrapped_text.push('\n');
26470 is_first_line = false;
26471 current_line = subsequent_lines_prefix.clone();
26472 current_line_len = subsequent_lines_prefix_len;
26473 } else if have_preceding_whitespace {
26474 continue;
26475 } else if current_line_len + 1 > wrap_column
26476 && current_line_len != current_prefix_len
26477 {
26478 wrapped_text.push_str(current_line.trim_end());
26479 wrapped_text.push('\n');
26480 is_first_line = false;
26481 current_line = subsequent_lines_prefix.clone();
26482 current_line_len = subsequent_lines_prefix_len;
26483 } else if current_line_len != current_prefix_len {
26484 current_line.push(' ');
26485 current_line_len += 1;
26486 }
26487 }
26488 }
26489 }
26490
26491 if !current_line.is_empty() {
26492 wrapped_text.push_str(¤t_line);
26493 }
26494 wrapped_text
26495}
26496
26497#[test]
26498fn test_wrap_with_prefix() {
26499 assert_eq!(
26500 wrap_with_prefix(
26501 "# ".to_string(),
26502 "# ".to_string(),
26503 "abcdefg".to_string(),
26504 4,
26505 NonZeroU32::new(4).unwrap(),
26506 false,
26507 ),
26508 "# abcdefg"
26509 );
26510 assert_eq!(
26511 wrap_with_prefix(
26512 "".to_string(),
26513 "".to_string(),
26514 "\thello world".to_string(),
26515 8,
26516 NonZeroU32::new(4).unwrap(),
26517 false,
26518 ),
26519 "hello\nworld"
26520 );
26521 assert_eq!(
26522 wrap_with_prefix(
26523 "// ".to_string(),
26524 "// ".to_string(),
26525 "xx \nyy zz aa bb cc".to_string(),
26526 12,
26527 NonZeroU32::new(4).unwrap(),
26528 false,
26529 ),
26530 "// xx yy zz\n// aa bb cc"
26531 );
26532 assert_eq!(
26533 wrap_with_prefix(
26534 String::new(),
26535 String::new(),
26536 "这是什么 \n 钢笔".to_string(),
26537 3,
26538 NonZeroU32::new(4).unwrap(),
26539 false,
26540 ),
26541 "这是什\n么 钢\n笔"
26542 );
26543 assert_eq!(
26544 wrap_with_prefix(
26545 String::new(),
26546 String::new(),
26547 format!("foo{}bar", '\u{2009}'), // thin space
26548 80,
26549 NonZeroU32::new(4).unwrap(),
26550 false,
26551 ),
26552 format!("foo{}bar", '\u{2009}')
26553 );
26554}
26555
26556pub trait CollaborationHub {
26557 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26558 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26559 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26560}
26561
26562impl CollaborationHub for Entity<Project> {
26563 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26564 self.read(cx).collaborators()
26565 }
26566
26567 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26568 self.read(cx).user_store().read(cx).participant_indices()
26569 }
26570
26571 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26572 let this = self.read(cx);
26573 let user_ids = this.collaborators().values().map(|c| c.user_id);
26574 this.user_store().read(cx).participant_names(user_ids, cx)
26575 }
26576}
26577
26578pub trait SemanticsProvider {
26579 fn hover(
26580 &self,
26581 buffer: &Entity<Buffer>,
26582 position: text::Anchor,
26583 cx: &mut App,
26584 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26585
26586 fn inline_values(
26587 &self,
26588 buffer_handle: Entity<Buffer>,
26589 range: Range<text::Anchor>,
26590 cx: &mut App,
26591 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26592
26593 fn applicable_inlay_chunks(
26594 &self,
26595 buffer: &Entity<Buffer>,
26596 ranges: &[Range<text::Anchor>],
26597 cx: &mut App,
26598 ) -> Vec<Range<BufferRow>>;
26599
26600 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26601
26602 fn inlay_hints(
26603 &self,
26604 invalidate: InvalidationStrategy,
26605 buffer: Entity<Buffer>,
26606 ranges: Vec<Range<text::Anchor>>,
26607 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26608 cx: &mut App,
26609 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26610
26611 fn semantic_tokens(
26612 &self,
26613 buffer: Entity<Buffer>,
26614 refresh: Option<RefreshForServer>,
26615 cx: &mut App,
26616 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>;
26617
26618 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26619
26620 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26621
26622 fn document_highlights(
26623 &self,
26624 buffer: &Entity<Buffer>,
26625 position: text::Anchor,
26626 cx: &mut App,
26627 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26628
26629 fn definitions(
26630 &self,
26631 buffer: &Entity<Buffer>,
26632 position: text::Anchor,
26633 kind: GotoDefinitionKind,
26634 cx: &mut App,
26635 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26636
26637 fn range_for_rename(
26638 &self,
26639 buffer: &Entity<Buffer>,
26640 position: text::Anchor,
26641 cx: &mut App,
26642 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26643
26644 fn perform_rename(
26645 &self,
26646 buffer: &Entity<Buffer>,
26647 position: text::Anchor,
26648 new_name: String,
26649 cx: &mut App,
26650 ) -> Option<Task<Result<ProjectTransaction>>>;
26651}
26652
26653pub trait CompletionProvider {
26654 fn completions(
26655 &self,
26656 excerpt_id: ExcerptId,
26657 buffer: &Entity<Buffer>,
26658 buffer_position: text::Anchor,
26659 trigger: CompletionContext,
26660 window: &mut Window,
26661 cx: &mut Context<Editor>,
26662 ) -> Task<Result<Vec<CompletionResponse>>>;
26663
26664 fn resolve_completions(
26665 &self,
26666 _buffer: Entity<Buffer>,
26667 _completion_indices: Vec<usize>,
26668 _completions: Rc<RefCell<Box<[Completion]>>>,
26669 _cx: &mut Context<Editor>,
26670 ) -> Task<Result<bool>> {
26671 Task::ready(Ok(false))
26672 }
26673
26674 fn apply_additional_edits_for_completion(
26675 &self,
26676 _buffer: Entity<Buffer>,
26677 _completions: Rc<RefCell<Box<[Completion]>>>,
26678 _completion_index: usize,
26679 _push_to_history: bool,
26680 _cx: &mut Context<Editor>,
26681 ) -> Task<Result<Option<language::Transaction>>> {
26682 Task::ready(Ok(None))
26683 }
26684
26685 fn is_completion_trigger(
26686 &self,
26687 buffer: &Entity<Buffer>,
26688 position: language::Anchor,
26689 text: &str,
26690 trigger_in_words: bool,
26691 cx: &mut Context<Editor>,
26692 ) -> bool;
26693
26694 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26695
26696 fn sort_completions(&self) -> bool {
26697 true
26698 }
26699
26700 fn filter_completions(&self) -> bool {
26701 true
26702 }
26703
26704 fn show_snippets(&self) -> bool {
26705 false
26706 }
26707}
26708
26709pub trait CodeActionProvider {
26710 fn id(&self) -> Arc<str>;
26711
26712 fn code_actions(
26713 &self,
26714 buffer: &Entity<Buffer>,
26715 range: Range<text::Anchor>,
26716 window: &mut Window,
26717 cx: &mut App,
26718 ) -> Task<Result<Vec<CodeAction>>>;
26719
26720 fn apply_code_action(
26721 &self,
26722 buffer_handle: Entity<Buffer>,
26723 action: CodeAction,
26724 excerpt_id: ExcerptId,
26725 push_to_history: bool,
26726 window: &mut Window,
26727 cx: &mut App,
26728 ) -> Task<Result<ProjectTransaction>>;
26729}
26730
26731impl CodeActionProvider for Entity<Project> {
26732 fn id(&self) -> Arc<str> {
26733 "project".into()
26734 }
26735
26736 fn code_actions(
26737 &self,
26738 buffer: &Entity<Buffer>,
26739 range: Range<text::Anchor>,
26740 _window: &mut Window,
26741 cx: &mut App,
26742 ) -> Task<Result<Vec<CodeAction>>> {
26743 self.update(cx, |project, cx| {
26744 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26745 let code_actions = project.code_actions(buffer, range, None, cx);
26746 cx.background_spawn(async move {
26747 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26748 Ok(code_lens_actions
26749 .context("code lens fetch")?
26750 .into_iter()
26751 .flatten()
26752 .chain(
26753 code_actions
26754 .context("code action fetch")?
26755 .into_iter()
26756 .flatten(),
26757 )
26758 .collect())
26759 })
26760 })
26761 }
26762
26763 fn apply_code_action(
26764 &self,
26765 buffer_handle: Entity<Buffer>,
26766 action: CodeAction,
26767 _excerpt_id: ExcerptId,
26768 push_to_history: bool,
26769 _window: &mut Window,
26770 cx: &mut App,
26771 ) -> Task<Result<ProjectTransaction>> {
26772 self.update(cx, |project, cx| {
26773 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26774 })
26775 }
26776}
26777
26778fn snippet_completions(
26779 project: &Project,
26780 buffer: &Entity<Buffer>,
26781 buffer_anchor: text::Anchor,
26782 classifier: CharClassifier,
26783 cx: &mut App,
26784) -> Task<Result<CompletionResponse>> {
26785 let languages = buffer.read(cx).languages_at(buffer_anchor);
26786 let snippet_store = project.snippets().read(cx);
26787
26788 let scopes: Vec<_> = languages
26789 .iter()
26790 .filter_map(|language| {
26791 let language_name = language.lsp_id();
26792 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26793
26794 if snippets.is_empty() {
26795 None
26796 } else {
26797 Some((language.default_scope(), snippets))
26798 }
26799 })
26800 .collect();
26801
26802 if scopes.is_empty() {
26803 return Task::ready(Ok(CompletionResponse {
26804 completions: vec![],
26805 display_options: CompletionDisplayOptions::default(),
26806 is_incomplete: false,
26807 }));
26808 }
26809
26810 let snapshot = buffer.read(cx).text_snapshot();
26811 let executor = cx.background_executor().clone();
26812
26813 cx.background_spawn(async move {
26814 let is_word_char = |c| classifier.is_word(c);
26815
26816 let mut is_incomplete = false;
26817 let mut completions: Vec<Completion> = Vec::new();
26818
26819 const MAX_PREFIX_LEN: usize = 128;
26820 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26821 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26822 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26823
26824 let max_buffer_window: String = snapshot
26825 .text_for_range(window_start..buffer_offset)
26826 .collect();
26827
26828 if max_buffer_window.is_empty() {
26829 return Ok(CompletionResponse {
26830 completions: vec![],
26831 display_options: CompletionDisplayOptions::default(),
26832 is_incomplete: true,
26833 });
26834 }
26835
26836 for (_scope, snippets) in scopes.into_iter() {
26837 // Sort snippets by word count to match longer snippet prefixes first.
26838 let mut sorted_snippet_candidates = snippets
26839 .iter()
26840 .enumerate()
26841 .flat_map(|(snippet_ix, snippet)| {
26842 snippet
26843 .prefix
26844 .iter()
26845 .enumerate()
26846 .map(move |(prefix_ix, prefix)| {
26847 let word_count =
26848 snippet_candidate_suffixes(prefix, is_word_char).count();
26849 ((snippet_ix, prefix_ix), prefix, word_count)
26850 })
26851 })
26852 .collect_vec();
26853 sorted_snippet_candidates
26854 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26855
26856 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26857
26858 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26859 .take(
26860 sorted_snippet_candidates
26861 .first()
26862 .map(|(_, _, word_count)| *word_count)
26863 .unwrap_or_default(),
26864 )
26865 .collect_vec();
26866
26867 const MAX_RESULTS: usize = 100;
26868 // Each match also remembers how many characters from the buffer it consumed
26869 let mut matches: Vec<(StringMatch, usize)> = vec![];
26870
26871 let mut snippet_list_cutoff_index = 0;
26872 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26873 let word_count = buffer_index + 1;
26874 // Increase `snippet_list_cutoff_index` until we have all of the
26875 // snippets with sufficiently many words.
26876 while sorted_snippet_candidates
26877 .get(snippet_list_cutoff_index)
26878 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26879 *snippet_word_count >= word_count
26880 })
26881 {
26882 snippet_list_cutoff_index += 1;
26883 }
26884
26885 // Take only the candidates with at least `word_count` many words
26886 let snippet_candidates_at_word_len =
26887 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26888
26889 let candidates = snippet_candidates_at_word_len
26890 .iter()
26891 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26892 .enumerate() // index in `sorted_snippet_candidates`
26893 // First char must match
26894 .filter(|(_ix, prefix)| {
26895 itertools::equal(
26896 prefix
26897 .chars()
26898 .next()
26899 .into_iter()
26900 .flat_map(|c| c.to_lowercase()),
26901 buffer_window
26902 .chars()
26903 .next()
26904 .into_iter()
26905 .flat_map(|c| c.to_lowercase()),
26906 )
26907 })
26908 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26909 .collect::<Vec<StringMatchCandidate>>();
26910
26911 matches.extend(
26912 fuzzy::match_strings(
26913 &candidates,
26914 &buffer_window,
26915 buffer_window.chars().any(|c| c.is_uppercase()),
26916 true,
26917 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26918 &Default::default(),
26919 executor.clone(),
26920 )
26921 .await
26922 .into_iter()
26923 .map(|string_match| (string_match, buffer_window.len())),
26924 );
26925
26926 if matches.len() >= MAX_RESULTS {
26927 break;
26928 }
26929 }
26930
26931 let to_lsp = |point: &text::Anchor| {
26932 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26933 point_to_lsp(end)
26934 };
26935 let lsp_end = to_lsp(&buffer_anchor);
26936
26937 if matches.len() >= MAX_RESULTS {
26938 is_incomplete = true;
26939 }
26940
26941 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26942 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26943 sorted_snippet_candidates[string_match.candidate_id];
26944 let snippet = &snippets[snippet_index];
26945 let start = buffer_offset - buffer_window_len;
26946 let start = snapshot.anchor_before(start);
26947 let range = start..buffer_anchor;
26948 let lsp_start = to_lsp(&start);
26949 let lsp_range = lsp::Range {
26950 start: lsp_start,
26951 end: lsp_end,
26952 };
26953 Completion {
26954 replace_range: range,
26955 new_text: snippet.body.clone(),
26956 source: CompletionSource::Lsp {
26957 insert_range: None,
26958 server_id: LanguageServerId(usize::MAX),
26959 resolved: true,
26960 lsp_completion: Box::new(lsp::CompletionItem {
26961 label: snippet.prefix.first().unwrap().clone(),
26962 kind: Some(CompletionItemKind::SNIPPET),
26963 label_details: snippet.description.as_ref().map(|description| {
26964 lsp::CompletionItemLabelDetails {
26965 detail: Some(description.clone()),
26966 description: None,
26967 }
26968 }),
26969 insert_text_format: Some(InsertTextFormat::SNIPPET),
26970 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26971 lsp::InsertReplaceEdit {
26972 new_text: snippet.body.clone(),
26973 insert: lsp_range,
26974 replace: lsp_range,
26975 },
26976 )),
26977 filter_text: Some(snippet.body.clone()),
26978 sort_text: Some(char::MAX.to_string()),
26979 ..lsp::CompletionItem::default()
26980 }),
26981 lsp_defaults: None,
26982 },
26983 label: CodeLabel {
26984 text: matching_prefix.clone(),
26985 runs: Vec::new(),
26986 filter_range: 0..matching_prefix.len(),
26987 },
26988 icon_path: None,
26989 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26990 single_line: snippet.name.clone().into(),
26991 plain_text: snippet
26992 .description
26993 .clone()
26994 .map(|description| description.into()),
26995 }),
26996 insert_text_mode: None,
26997 confirm: None,
26998 match_start: Some(start),
26999 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27000 }
27001 }));
27002 }
27003
27004 Ok(CompletionResponse {
27005 completions,
27006 display_options: CompletionDisplayOptions::default(),
27007 is_incomplete,
27008 })
27009 })
27010}
27011
27012impl CompletionProvider for Entity<Project> {
27013 fn completions(
27014 &self,
27015 _excerpt_id: ExcerptId,
27016 buffer: &Entity<Buffer>,
27017 buffer_position: text::Anchor,
27018 options: CompletionContext,
27019 _window: &mut Window,
27020 cx: &mut Context<Editor>,
27021 ) -> Task<Result<Vec<CompletionResponse>>> {
27022 self.update(cx, |project, cx| {
27023 let task = project.completions(buffer, buffer_position, options, cx);
27024 cx.background_spawn(task)
27025 })
27026 }
27027
27028 fn resolve_completions(
27029 &self,
27030 buffer: Entity<Buffer>,
27031 completion_indices: Vec<usize>,
27032 completions: Rc<RefCell<Box<[Completion]>>>,
27033 cx: &mut Context<Editor>,
27034 ) -> Task<Result<bool>> {
27035 self.update(cx, |project, cx| {
27036 project.lsp_store().update(cx, |lsp_store, cx| {
27037 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27038 })
27039 })
27040 }
27041
27042 fn apply_additional_edits_for_completion(
27043 &self,
27044 buffer: Entity<Buffer>,
27045 completions: Rc<RefCell<Box<[Completion]>>>,
27046 completion_index: usize,
27047 push_to_history: bool,
27048 cx: &mut Context<Editor>,
27049 ) -> Task<Result<Option<language::Transaction>>> {
27050 self.update(cx, |project, cx| {
27051 project.lsp_store().update(cx, |lsp_store, cx| {
27052 lsp_store.apply_additional_edits_for_completion(
27053 buffer,
27054 completions,
27055 completion_index,
27056 push_to_history,
27057 cx,
27058 )
27059 })
27060 })
27061 }
27062
27063 fn is_completion_trigger(
27064 &self,
27065 buffer: &Entity<Buffer>,
27066 position: language::Anchor,
27067 text: &str,
27068 trigger_in_words: bool,
27069 cx: &mut Context<Editor>,
27070 ) -> bool {
27071 let mut chars = text.chars();
27072 let char = if let Some(char) = chars.next() {
27073 char
27074 } else {
27075 return false;
27076 };
27077 if chars.next().is_some() {
27078 return false;
27079 }
27080
27081 let buffer = buffer.read(cx);
27082 let snapshot = buffer.snapshot();
27083 let classifier = snapshot
27084 .char_classifier_at(position)
27085 .scope_context(Some(CharScopeContext::Completion));
27086 if trigger_in_words && classifier.is_word(char) {
27087 return true;
27088 }
27089
27090 buffer.completion_triggers().contains(text)
27091 }
27092
27093 fn show_snippets(&self) -> bool {
27094 true
27095 }
27096}
27097
27098impl SemanticsProvider for Entity<Project> {
27099 fn hover(
27100 &self,
27101 buffer: &Entity<Buffer>,
27102 position: text::Anchor,
27103 cx: &mut App,
27104 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27105 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
27106 }
27107
27108 fn document_highlights(
27109 &self,
27110 buffer: &Entity<Buffer>,
27111 position: text::Anchor,
27112 cx: &mut App,
27113 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27114 Some(self.update(cx, |project, cx| {
27115 project.document_highlights(buffer, position, cx)
27116 }))
27117 }
27118
27119 fn definitions(
27120 &self,
27121 buffer: &Entity<Buffer>,
27122 position: text::Anchor,
27123 kind: GotoDefinitionKind,
27124 cx: &mut App,
27125 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27126 Some(self.update(cx, |project, cx| match kind {
27127 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27128 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27129 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27130 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27131 }))
27132 }
27133
27134 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27135 self.update(cx, |project, cx| {
27136 if project
27137 .active_debug_session(cx)
27138 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27139 {
27140 return true;
27141 }
27142
27143 buffer.update(cx, |buffer, cx| {
27144 project.any_language_server_supports_inlay_hints(buffer, cx)
27145 })
27146 })
27147 }
27148
27149 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27150 self.update(cx, |project, cx| {
27151 buffer.update(cx, |buffer, cx| {
27152 project.any_language_server_supports_semantic_tokens(buffer, cx)
27153 })
27154 })
27155 }
27156
27157 fn inline_values(
27158 &self,
27159 buffer_handle: Entity<Buffer>,
27160 range: Range<text::Anchor>,
27161 cx: &mut App,
27162 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27163 self.update(cx, |project, cx| {
27164 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27165
27166 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27167 })
27168 }
27169
27170 fn applicable_inlay_chunks(
27171 &self,
27172 buffer: &Entity<Buffer>,
27173 ranges: &[Range<text::Anchor>],
27174 cx: &mut App,
27175 ) -> Vec<Range<BufferRow>> {
27176 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27177 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27178 })
27179 }
27180
27181 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27182 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
27183 lsp_store.invalidate_inlay_hints(for_buffers)
27184 });
27185 }
27186
27187 fn inlay_hints(
27188 &self,
27189 invalidate: InvalidationStrategy,
27190 buffer: Entity<Buffer>,
27191 ranges: Vec<Range<text::Anchor>>,
27192 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27193 cx: &mut App,
27194 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27195 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27196 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27197 }))
27198 }
27199
27200 fn semantic_tokens(
27201 &self,
27202 buffer: Entity<Buffer>,
27203 refresh: Option<RefreshForServer>,
27204 cx: &mut App,
27205 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>> {
27206 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27207 lsp_store.semantic_tokens(buffer, refresh, cx)
27208 })
27209 }
27210
27211 fn range_for_rename(
27212 &self,
27213 buffer: &Entity<Buffer>,
27214 position: text::Anchor,
27215 cx: &mut App,
27216 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27217 Some(self.update(cx, |project, cx| {
27218 let buffer = buffer.clone();
27219 let task = project.prepare_rename(buffer.clone(), position, cx);
27220 cx.spawn(async move |_, cx| {
27221 Ok(match task.await? {
27222 PrepareRenameResponse::Success(range) => Some(range),
27223 PrepareRenameResponse::InvalidPosition => None,
27224 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27225 // Fallback on using TreeSitter info to determine identifier range
27226 buffer.read_with(cx, |buffer, _| {
27227 let snapshot = buffer.snapshot();
27228 let (range, kind) = snapshot.surrounding_word(position, None);
27229 if kind != Some(CharKind::Word) {
27230 return None;
27231 }
27232 Some(
27233 snapshot.anchor_before(range.start)
27234 ..snapshot.anchor_after(range.end),
27235 )
27236 })
27237 }
27238 })
27239 })
27240 }))
27241 }
27242
27243 fn perform_rename(
27244 &self,
27245 buffer: &Entity<Buffer>,
27246 position: text::Anchor,
27247 new_name: String,
27248 cx: &mut App,
27249 ) -> Option<Task<Result<ProjectTransaction>>> {
27250 Some(self.update(cx, |project, cx| {
27251 project.perform_rename(buffer.clone(), position, new_name, cx)
27252 }))
27253 }
27254}
27255
27256fn consume_contiguous_rows(
27257 contiguous_row_selections: &mut Vec<Selection<Point>>,
27258 selection: &Selection<Point>,
27259 display_map: &DisplaySnapshot,
27260 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27261) -> (MultiBufferRow, MultiBufferRow) {
27262 contiguous_row_selections.push(selection.clone());
27263 let start_row = starting_row(selection, display_map);
27264 let mut end_row = ending_row(selection, display_map);
27265
27266 while let Some(next_selection) = selections.peek() {
27267 if next_selection.start.row <= end_row.0 {
27268 end_row = ending_row(next_selection, display_map);
27269 contiguous_row_selections.push(selections.next().unwrap().clone());
27270 } else {
27271 break;
27272 }
27273 }
27274 (start_row, end_row)
27275}
27276
27277fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27278 if selection.start.column > 0 {
27279 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27280 } else {
27281 MultiBufferRow(selection.start.row)
27282 }
27283}
27284
27285fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27286 if next_selection.end.column > 0 || next_selection.is_empty() {
27287 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27288 } else {
27289 MultiBufferRow(next_selection.end.row)
27290 }
27291}
27292
27293impl EditorSnapshot {
27294 pub fn remote_selections_in_range<'a>(
27295 &'a self,
27296 range: &'a Range<Anchor>,
27297 collaboration_hub: &dyn CollaborationHub,
27298 cx: &'a App,
27299 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27300 let participant_names = collaboration_hub.user_names(cx);
27301 let participant_indices = collaboration_hub.user_participant_indices(cx);
27302 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27303 let collaborators_by_replica_id = collaborators_by_peer_id
27304 .values()
27305 .map(|collaborator| (collaborator.replica_id, collaborator))
27306 .collect::<HashMap<_, _>>();
27307 self.buffer_snapshot()
27308 .selections_in_range(range, false)
27309 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27310 if replica_id == ReplicaId::AGENT {
27311 Some(RemoteSelection {
27312 replica_id,
27313 selection,
27314 cursor_shape,
27315 line_mode,
27316 collaborator_id: CollaboratorId::Agent,
27317 user_name: Some("Agent".into()),
27318 color: cx.theme().players().agent(),
27319 })
27320 } else {
27321 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27322 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27323 let user_name = participant_names.get(&collaborator.user_id).cloned();
27324 Some(RemoteSelection {
27325 replica_id,
27326 selection,
27327 cursor_shape,
27328 line_mode,
27329 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27330 user_name,
27331 color: if let Some(index) = participant_index {
27332 cx.theme().players().color_for_participant(index.0)
27333 } else {
27334 cx.theme().players().absent()
27335 },
27336 })
27337 }
27338 })
27339 }
27340
27341 pub fn hunks_for_ranges(
27342 &self,
27343 ranges: impl IntoIterator<Item = Range<Point>>,
27344 ) -> Vec<MultiBufferDiffHunk> {
27345 let mut hunks = Vec::new();
27346 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27347 HashMap::default();
27348 for query_range in ranges {
27349 let query_rows =
27350 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27351 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27352 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27353 ) {
27354 // Include deleted hunks that are adjacent to the query range, because
27355 // otherwise they would be missed.
27356 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27357 if hunk.status().is_deleted() {
27358 intersects_range |= hunk.row_range.start == query_rows.end;
27359 intersects_range |= hunk.row_range.end == query_rows.start;
27360 }
27361 if intersects_range {
27362 if !processed_buffer_rows
27363 .entry(hunk.buffer_id)
27364 .or_default()
27365 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27366 {
27367 continue;
27368 }
27369 hunks.push(hunk);
27370 }
27371 }
27372 }
27373
27374 hunks
27375 }
27376
27377 fn display_diff_hunks_for_rows<'a>(
27378 &'a self,
27379 display_rows: Range<DisplayRow>,
27380 folded_buffers: &'a HashSet<BufferId>,
27381 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27382 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27383 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27384
27385 self.buffer_snapshot()
27386 .diff_hunks_in_range(buffer_start..buffer_end)
27387 .filter_map(|hunk| {
27388 if folded_buffers.contains(&hunk.buffer_id)
27389 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27390 {
27391 return None;
27392 }
27393
27394 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27395 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27396 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27397 let line_len = self.buffer_snapshot().line_len(last_row);
27398 Point::new(last_row.0, line_len)
27399 } else {
27400 Point::new(hunk.row_range.end.0, 0)
27401 };
27402
27403 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27404 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27405
27406 let display_hunk = if hunk_display_start.column() != 0 {
27407 DisplayDiffHunk::Folded {
27408 display_row: hunk_display_start.row(),
27409 }
27410 } else {
27411 let mut end_row = hunk_display_end.row();
27412 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27413 end_row.0 += 1;
27414 }
27415 let is_created_file = hunk.is_created_file();
27416
27417 DisplayDiffHunk::Unfolded {
27418 status: hunk.status(),
27419 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27420 ..hunk.diff_base_byte_range.end.0,
27421 word_diffs: hunk.word_diffs,
27422 display_row_range: hunk_display_start.row()..end_row,
27423 multi_buffer_range: Anchor::range_in_buffer(
27424 hunk.excerpt_id,
27425 hunk.buffer_range,
27426 ),
27427 is_created_file,
27428 }
27429 };
27430
27431 Some(display_hunk)
27432 })
27433 }
27434
27435 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27436 self.display_snapshot
27437 .buffer_snapshot()
27438 .language_at(position)
27439 }
27440
27441 pub fn is_focused(&self) -> bool {
27442 self.is_focused
27443 }
27444
27445 pub fn placeholder_text(&self) -> Option<String> {
27446 self.placeholder_display_snapshot
27447 .as_ref()
27448 .map(|display_map| display_map.text())
27449 }
27450
27451 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27452 self.scroll_anchor.scroll_position(&self.display_snapshot)
27453 }
27454
27455 pub fn gutter_dimensions(
27456 &self,
27457 font_id: FontId,
27458 font_size: Pixels,
27459 style: &EditorStyle,
27460 window: &mut Window,
27461 cx: &App,
27462 ) -> GutterDimensions {
27463 if self.show_gutter
27464 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27465 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27466 {
27467 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27468 matches!(
27469 ProjectSettings::get_global(cx).git.git_gutter,
27470 GitGutterSetting::TrackedFiles
27471 )
27472 });
27473 let gutter_settings = EditorSettings::get_global(cx).gutter;
27474 let show_line_numbers = self
27475 .show_line_numbers
27476 .unwrap_or(gutter_settings.line_numbers);
27477 let line_gutter_width = if show_line_numbers {
27478 // Avoid flicker-like gutter resizes when the line number gains another digit by
27479 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27480 let min_width_for_number_on_gutter =
27481 ch_advance * gutter_settings.min_line_number_digits as f32;
27482 self.max_line_number_width(style, window)
27483 .max(min_width_for_number_on_gutter)
27484 } else {
27485 0.0.into()
27486 };
27487
27488 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27489 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27490
27491 let git_blame_entries_width =
27492 self.git_blame_gutter_max_author_length
27493 .map(|max_author_length| {
27494 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27495 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27496
27497 /// The number of characters to dedicate to gaps and margins.
27498 const SPACING_WIDTH: usize = 4;
27499
27500 let max_char_count = max_author_length.min(renderer.max_author_length())
27501 + ::git::SHORT_SHA_LENGTH
27502 + MAX_RELATIVE_TIMESTAMP.len()
27503 + SPACING_WIDTH;
27504
27505 ch_advance * max_char_count
27506 });
27507
27508 let is_singleton = self.buffer_snapshot().is_singleton();
27509
27510 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27511 left_padding += if !is_singleton {
27512 ch_width * 4.0
27513 } else if show_runnables || show_breakpoints {
27514 ch_width * 3.0
27515 } else if show_git_gutter && show_line_numbers {
27516 ch_width * 2.0
27517 } else if show_git_gutter || show_line_numbers {
27518 ch_width
27519 } else {
27520 px(0.)
27521 };
27522
27523 let shows_folds = is_singleton && gutter_settings.folds;
27524
27525 let right_padding = if shows_folds && show_line_numbers {
27526 ch_width * 4.0
27527 } else if shows_folds || (!is_singleton && show_line_numbers) {
27528 ch_width * 3.0
27529 } else if show_line_numbers {
27530 ch_width
27531 } else {
27532 px(0.)
27533 };
27534
27535 GutterDimensions {
27536 left_padding,
27537 right_padding,
27538 width: line_gutter_width + left_padding + right_padding,
27539 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27540 git_blame_entries_width,
27541 }
27542 } else if self.offset_content {
27543 GutterDimensions::default_with_margin(font_id, font_size, cx)
27544 } else {
27545 GutterDimensions::default()
27546 }
27547 }
27548
27549 pub fn render_crease_toggle(
27550 &self,
27551 buffer_row: MultiBufferRow,
27552 row_contains_cursor: bool,
27553 editor: Entity<Editor>,
27554 window: &mut Window,
27555 cx: &mut App,
27556 ) -> Option<AnyElement> {
27557 let folded = self.is_line_folded(buffer_row);
27558 let mut is_foldable = false;
27559
27560 if let Some(crease) = self
27561 .crease_snapshot
27562 .query_row(buffer_row, self.buffer_snapshot())
27563 {
27564 is_foldable = true;
27565 match crease {
27566 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27567 if let Some(render_toggle) = render_toggle {
27568 let toggle_callback =
27569 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27570 if folded {
27571 editor.update(cx, |editor, cx| {
27572 editor.fold_at(buffer_row, window, cx)
27573 });
27574 } else {
27575 editor.update(cx, |editor, cx| {
27576 editor.unfold_at(buffer_row, window, cx)
27577 });
27578 }
27579 });
27580 return Some((render_toggle)(
27581 buffer_row,
27582 folded,
27583 toggle_callback,
27584 window,
27585 cx,
27586 ));
27587 }
27588 }
27589 }
27590 }
27591
27592 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27593
27594 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27595 Some(
27596 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27597 .toggle_state(folded)
27598 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27599 if folded {
27600 this.unfold_at(buffer_row, window, cx);
27601 } else {
27602 this.fold_at(buffer_row, window, cx);
27603 }
27604 }))
27605 .into_any_element(),
27606 )
27607 } else {
27608 None
27609 }
27610 }
27611
27612 pub fn render_crease_trailer(
27613 &self,
27614 buffer_row: MultiBufferRow,
27615 window: &mut Window,
27616 cx: &mut App,
27617 ) -> Option<AnyElement> {
27618 let folded = self.is_line_folded(buffer_row);
27619 if let Crease::Inline { render_trailer, .. } = self
27620 .crease_snapshot
27621 .query_row(buffer_row, self.buffer_snapshot())?
27622 {
27623 let render_trailer = render_trailer.as_ref()?;
27624 Some(render_trailer(buffer_row, folded, window, cx))
27625 } else {
27626 None
27627 }
27628 }
27629
27630 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27631 let digit_count = self.widest_line_number().ilog10() + 1;
27632 column_pixels(style, digit_count as usize, window)
27633 }
27634
27635 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27636 ///
27637 /// This is positive if `base` is before `line`.
27638 fn relative_line_delta(
27639 &self,
27640 current_selection_head: DisplayRow,
27641 first_visible_row: DisplayRow,
27642 consider_wrapped_lines: bool,
27643 ) -> i64 {
27644 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27645 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27646
27647 if consider_wrapped_lines {
27648 let wrap_snapshot = self.wrap_snapshot();
27649 let base_wrap_row = wrap_snapshot
27650 .make_wrap_point(current_selection_head, Bias::Left)
27651 .row();
27652 let wrap_row = wrap_snapshot
27653 .make_wrap_point(first_visible_row, Bias::Left)
27654 .row();
27655
27656 wrap_row.0 as i64 - base_wrap_row.0 as i64
27657 } else {
27658 let fold_snapshot = self.fold_snapshot();
27659 let base_fold_row = fold_snapshot
27660 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27661 .row();
27662 let fold_row = fold_snapshot
27663 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27664 .row();
27665
27666 fold_row as i64 - base_fold_row as i64
27667 }
27668 }
27669
27670 /// Returns the unsigned relative line number to display for each row in `rows`.
27671 ///
27672 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27673 pub fn calculate_relative_line_numbers(
27674 &self,
27675 rows: &Range<DisplayRow>,
27676 current_selection_head: DisplayRow,
27677 count_wrapped_lines: bool,
27678 ) -> HashMap<DisplayRow, u32> {
27679 let initial_offset =
27680 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27681
27682 self.row_infos(rows.start)
27683 .take(rows.len())
27684 .enumerate()
27685 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27686 .filter(|(_row, row_info)| {
27687 row_info.buffer_row.is_some()
27688 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27689 })
27690 .enumerate()
27691 .filter_map(|(i, (row, row_info))| {
27692 // We want to ensure here that the current line has absolute
27693 // numbering, even if we are in a soft-wrapped line. With the
27694 // exception that if we are in a deleted line, we should number this
27695 // relative with 0, as otherwise it would have no line number at all
27696 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27697
27698 (relative_line_number != 0
27699 || row_info
27700 .diff_status
27701 .is_some_and(|status| status.is_deleted()))
27702 .then_some((row, relative_line_number))
27703 })
27704 .collect()
27705 }
27706}
27707
27708pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27709 let font_size = style.text.font_size.to_pixels(window.rem_size());
27710 let layout = window.text_system().shape_line(
27711 SharedString::from(" ".repeat(column)),
27712 font_size,
27713 &[TextRun {
27714 len: column,
27715 font: style.text.font(),
27716 color: Hsla::default(),
27717 ..Default::default()
27718 }],
27719 None,
27720 );
27721
27722 layout.width
27723}
27724
27725impl Deref for EditorSnapshot {
27726 type Target = DisplaySnapshot;
27727
27728 fn deref(&self) -> &Self::Target {
27729 &self.display_snapshot
27730 }
27731}
27732
27733#[derive(Clone, Debug, PartialEq, Eq)]
27734pub enum EditorEvent {
27735 /// Emitted when the stored review comments change (added, removed, or updated).
27736 ReviewCommentsChanged {
27737 /// The new total count of review comments.
27738 total_count: usize,
27739 },
27740 InputIgnored {
27741 text: Arc<str>,
27742 },
27743 InputHandled {
27744 utf16_range_to_replace: Option<Range<isize>>,
27745 text: Arc<str>,
27746 },
27747 ExcerptsAdded {
27748 buffer: Entity<Buffer>,
27749 predecessor: ExcerptId,
27750 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27751 },
27752 ExcerptsRemoved {
27753 ids: Vec<ExcerptId>,
27754 removed_buffer_ids: Vec<BufferId>,
27755 },
27756 BufferFoldToggled {
27757 ids: Vec<ExcerptId>,
27758 folded: bool,
27759 },
27760 ExcerptsEdited {
27761 ids: Vec<ExcerptId>,
27762 },
27763 ExcerptsExpanded {
27764 ids: Vec<ExcerptId>,
27765 },
27766 ExpandExcerptsRequested {
27767 excerpt_ids: Vec<ExcerptId>,
27768 lines: u32,
27769 direction: ExpandExcerptDirection,
27770 },
27771 StageOrUnstageRequested {
27772 stage: bool,
27773 hunks: Vec<MultiBufferDiffHunk>,
27774 },
27775 OpenExcerptsRequested {
27776 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
27777 split: bool,
27778 },
27779 RestoreRequested {
27780 hunks: Vec<MultiBufferDiffHunk>,
27781 },
27782 BufferEdited,
27783 Edited {
27784 transaction_id: clock::Lamport,
27785 },
27786 Reparsed(BufferId),
27787 Focused,
27788 FocusedIn,
27789 Blurred,
27790 DirtyChanged,
27791 Saved,
27792 TitleChanged,
27793 SelectionsChanged {
27794 local: bool,
27795 },
27796 ScrollPositionChanged {
27797 local: bool,
27798 autoscroll: bool,
27799 },
27800 TransactionUndone {
27801 transaction_id: clock::Lamport,
27802 },
27803 TransactionBegun {
27804 transaction_id: clock::Lamport,
27805 },
27806 CursorShapeChanged,
27807 BreadcrumbsChanged,
27808 OutlineSymbolsChanged,
27809 PushedToNavHistory {
27810 anchor: Anchor,
27811 is_deactivate: bool,
27812 },
27813}
27814
27815impl EventEmitter<EditorEvent> for Editor {}
27816
27817impl Focusable for Editor {
27818 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27819 self.focus_handle.clone()
27820 }
27821}
27822
27823impl Render for Editor {
27824 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27825 EditorElement::new(&cx.entity(), self.create_style(cx))
27826 }
27827}
27828
27829impl EntityInputHandler for Editor {
27830 fn text_for_range(
27831 &mut self,
27832 range_utf16: Range<usize>,
27833 adjusted_range: &mut Option<Range<usize>>,
27834 _: &mut Window,
27835 cx: &mut Context<Self>,
27836 ) -> Option<String> {
27837 let snapshot = self.buffer.read(cx).read(cx);
27838 let start = snapshot.clip_offset_utf16(
27839 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27840 Bias::Left,
27841 );
27842 let end = snapshot.clip_offset_utf16(
27843 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27844 Bias::Right,
27845 );
27846 if (start.0.0..end.0.0) != range_utf16 {
27847 adjusted_range.replace(start.0.0..end.0.0);
27848 }
27849 Some(snapshot.text_for_range(start..end).collect())
27850 }
27851
27852 fn selected_text_range(
27853 &mut self,
27854 ignore_disabled_input: bool,
27855 _: &mut Window,
27856 cx: &mut Context<Self>,
27857 ) -> Option<UTF16Selection> {
27858 // Prevent the IME menu from appearing when holding down an alphabetic key
27859 // while input is disabled.
27860 if !ignore_disabled_input && !self.input_enabled {
27861 return None;
27862 }
27863
27864 let selection = self
27865 .selections
27866 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27867 let range = selection.range();
27868
27869 Some(UTF16Selection {
27870 range: range.start.0.0..range.end.0.0,
27871 reversed: selection.reversed,
27872 })
27873 }
27874
27875 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27876 let snapshot = self.buffer.read(cx).read(cx);
27877 let range = self
27878 .text_highlights(HighlightKey::InputComposition, cx)?
27879 .1
27880 .first()?;
27881 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27882 }
27883
27884 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27885 self.clear_highlights(HighlightKey::InputComposition, cx);
27886 self.ime_transaction.take();
27887 }
27888
27889 fn replace_text_in_range(
27890 &mut self,
27891 range_utf16: Option<Range<usize>>,
27892 text: &str,
27893 window: &mut Window,
27894 cx: &mut Context<Self>,
27895 ) {
27896 if !self.input_enabled {
27897 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27898 return;
27899 }
27900
27901 self.transact(window, cx, |this, window, cx| {
27902 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27903 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27904 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27905 Some(this.selection_replacement_ranges(range_utf16, cx))
27906 } else {
27907 this.marked_text_ranges(cx)
27908 };
27909
27910 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27911 let newest_selection_id = this.selections.newest_anchor().id;
27912 this.selections
27913 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27914 .iter()
27915 .zip(ranges_to_replace.iter())
27916 .find_map(|(selection, range)| {
27917 if selection.id == newest_selection_id {
27918 Some(
27919 (range.start.0.0 as isize - selection.head().0.0 as isize)
27920 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27921 )
27922 } else {
27923 None
27924 }
27925 })
27926 });
27927
27928 cx.emit(EditorEvent::InputHandled {
27929 utf16_range_to_replace: range_to_replace,
27930 text: text.into(),
27931 });
27932
27933 if let Some(new_selected_ranges) = new_selected_ranges {
27934 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27935 selections.select_ranges(new_selected_ranges)
27936 });
27937 this.backspace(&Default::default(), window, cx);
27938 }
27939
27940 this.handle_input(text, window, cx);
27941 });
27942
27943 if let Some(transaction) = self.ime_transaction {
27944 self.buffer.update(cx, |buffer, cx| {
27945 buffer.group_until_transaction(transaction, cx);
27946 });
27947 }
27948
27949 self.unmark_text(window, cx);
27950 }
27951
27952 fn replace_and_mark_text_in_range(
27953 &mut self,
27954 range_utf16: Option<Range<usize>>,
27955 text: &str,
27956 new_selected_range_utf16: Option<Range<usize>>,
27957 window: &mut Window,
27958 cx: &mut Context<Self>,
27959 ) {
27960 if !self.input_enabled {
27961 return;
27962 }
27963
27964 let transaction = self.transact(window, cx, |this, window, cx| {
27965 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27966 let snapshot = this.buffer.read(cx).read(cx);
27967 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27968 for marked_range in &mut marked_ranges {
27969 marked_range.end = marked_range.start + relative_range_utf16.end;
27970 marked_range.start += relative_range_utf16.start;
27971 marked_range.start =
27972 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27973 marked_range.end =
27974 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27975 }
27976 }
27977 Some(marked_ranges)
27978 } else if let Some(range_utf16) = range_utf16 {
27979 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27980 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27981 Some(this.selection_replacement_ranges(range_utf16, cx))
27982 } else {
27983 None
27984 };
27985
27986 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27987 let newest_selection_id = this.selections.newest_anchor().id;
27988 this.selections
27989 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27990 .iter()
27991 .zip(ranges_to_replace.iter())
27992 .find_map(|(selection, range)| {
27993 if selection.id == newest_selection_id {
27994 Some(
27995 (range.start.0.0 as isize - selection.head().0.0 as isize)
27996 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27997 )
27998 } else {
27999 None
28000 }
28001 })
28002 });
28003
28004 cx.emit(EditorEvent::InputHandled {
28005 utf16_range_to_replace: range_to_replace,
28006 text: text.into(),
28007 });
28008
28009 if let Some(ranges) = ranges_to_replace {
28010 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28011 s.select_ranges(ranges)
28012 });
28013 }
28014
28015 let marked_ranges = {
28016 let snapshot = this.buffer.read(cx).read(cx);
28017 this.selections
28018 .disjoint_anchors_arc()
28019 .iter()
28020 .map(|selection| {
28021 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28022 })
28023 .collect::<Vec<_>>()
28024 };
28025
28026 if text.is_empty() {
28027 this.unmark_text(window, cx);
28028 } else {
28029 this.highlight_text(
28030 HighlightKey::InputComposition,
28031 marked_ranges.clone(),
28032 HighlightStyle {
28033 underline: Some(UnderlineStyle {
28034 thickness: px(1.),
28035 color: None,
28036 wavy: false,
28037 }),
28038 ..Default::default()
28039 },
28040 cx,
28041 );
28042 }
28043
28044 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28045 let use_autoclose = this.use_autoclose;
28046 let use_auto_surround = this.use_auto_surround;
28047 this.set_use_autoclose(false);
28048 this.set_use_auto_surround(false);
28049 this.handle_input(text, window, cx);
28050 this.set_use_autoclose(use_autoclose);
28051 this.set_use_auto_surround(use_auto_surround);
28052
28053 if let Some(new_selected_range) = new_selected_range_utf16 {
28054 let snapshot = this.buffer.read(cx).read(cx);
28055 let new_selected_ranges = marked_ranges
28056 .into_iter()
28057 .map(|marked_range| {
28058 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28059 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28060 insertion_start.0 + new_selected_range.start,
28061 ));
28062 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28063 insertion_start.0 + new_selected_range.end,
28064 ));
28065 snapshot.clip_offset_utf16(new_start, Bias::Left)
28066 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28067 })
28068 .collect::<Vec<_>>();
28069
28070 drop(snapshot);
28071 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28072 selections.select_ranges(new_selected_ranges)
28073 });
28074 }
28075 });
28076
28077 self.ime_transaction = self.ime_transaction.or(transaction);
28078 if let Some(transaction) = self.ime_transaction {
28079 self.buffer.update(cx, |buffer, cx| {
28080 buffer.group_until_transaction(transaction, cx);
28081 });
28082 }
28083
28084 if self
28085 .text_highlights(HighlightKey::InputComposition, cx)
28086 .is_none()
28087 {
28088 self.ime_transaction.take();
28089 }
28090 }
28091
28092 fn bounds_for_range(
28093 &mut self,
28094 range_utf16: Range<usize>,
28095 element_bounds: gpui::Bounds<Pixels>,
28096 window: &mut Window,
28097 cx: &mut Context<Self>,
28098 ) -> Option<gpui::Bounds<Pixels>> {
28099 let text_layout_details = self.text_layout_details(window, cx);
28100 let CharacterDimensions {
28101 em_width,
28102 em_advance,
28103 line_height,
28104 } = self.character_dimensions(window, cx);
28105
28106 let snapshot = self.snapshot(window, cx);
28107 let scroll_position = snapshot.scroll_position();
28108 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28109
28110 let start =
28111 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28112 let x = Pixels::from(
28113 ScrollOffset::from(
28114 snapshot.x_for_display_point(start, &text_layout_details)
28115 + self.gutter_dimensions.full_width(),
28116 ) - scroll_left,
28117 );
28118 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28119
28120 Some(Bounds {
28121 origin: element_bounds.origin + point(x, y),
28122 size: size(em_width, line_height),
28123 })
28124 }
28125
28126 fn character_index_for_point(
28127 &mut self,
28128 point: gpui::Point<Pixels>,
28129 _window: &mut Window,
28130 _cx: &mut Context<Self>,
28131 ) -> Option<usize> {
28132 let position_map = self.last_position_map.as_ref()?;
28133 if !position_map.text_hitbox.contains(&point) {
28134 return None;
28135 }
28136 let display_point = position_map.point_for_position(point).previous_valid;
28137 let anchor = position_map
28138 .snapshot
28139 .display_point_to_anchor(display_point, Bias::Left);
28140 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28141 Some(utf16_offset.0.0)
28142 }
28143
28144 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28145 self.input_enabled
28146 }
28147}
28148
28149trait SelectionExt {
28150 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28151 fn spanned_rows(
28152 &self,
28153 include_end_if_at_line_start: bool,
28154 map: &DisplaySnapshot,
28155 ) -> Range<MultiBufferRow>;
28156}
28157
28158impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28159 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28160 let start = self
28161 .start
28162 .to_point(map.buffer_snapshot())
28163 .to_display_point(map);
28164 let end = self
28165 .end
28166 .to_point(map.buffer_snapshot())
28167 .to_display_point(map);
28168 if self.reversed {
28169 end..start
28170 } else {
28171 start..end
28172 }
28173 }
28174
28175 fn spanned_rows(
28176 &self,
28177 include_end_if_at_line_start: bool,
28178 map: &DisplaySnapshot,
28179 ) -> Range<MultiBufferRow> {
28180 let start = self.start.to_point(map.buffer_snapshot());
28181 let mut end = self.end.to_point(map.buffer_snapshot());
28182 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28183 end.row -= 1;
28184 }
28185
28186 let buffer_start = map.prev_line_boundary(start).0;
28187 let buffer_end = map.next_line_boundary(end).0;
28188 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28189 }
28190}
28191
28192impl<T: InvalidationRegion> InvalidationStack<T> {
28193 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28194 where
28195 S: Clone + ToOffset,
28196 {
28197 while let Some(region) = self.last() {
28198 let all_selections_inside_invalidation_ranges =
28199 if selections.len() == region.ranges().len() {
28200 selections
28201 .iter()
28202 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28203 .all(|(selection, invalidation_range)| {
28204 let head = selection.head().to_offset(buffer);
28205 invalidation_range.start <= head && invalidation_range.end >= head
28206 })
28207 } else {
28208 false
28209 };
28210
28211 if all_selections_inside_invalidation_ranges {
28212 break;
28213 } else {
28214 self.pop();
28215 }
28216 }
28217 }
28218}
28219
28220#[derive(Clone)]
28221struct ErasedEditorImpl(Entity<Editor>);
28222
28223impl ui_input::ErasedEditor for ErasedEditorImpl {
28224 fn text(&self, cx: &App) -> String {
28225 self.0.read(cx).text(cx)
28226 }
28227
28228 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28229 self.0.update(cx, |this, cx| {
28230 this.set_text(text, window, cx);
28231 })
28232 }
28233
28234 fn clear(&self, window: &mut Window, cx: &mut App) {
28235 self.0.update(cx, |this, cx| this.clear(window, cx));
28236 }
28237
28238 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28239 self.0.update(cx, |this, cx| {
28240 this.set_placeholder_text(text, window, cx);
28241 });
28242 }
28243
28244 fn focus_handle(&self, cx: &App) -> FocusHandle {
28245 self.0.read(cx).focus_handle(cx)
28246 }
28247
28248 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28249 let settings = ThemeSettings::get_global(cx);
28250 let theme_color = cx.theme().colors();
28251
28252 let text_style = TextStyle {
28253 font_family: settings.ui_font.family.clone(),
28254 font_features: settings.ui_font.features.clone(),
28255 font_size: rems(0.875).into(),
28256 font_weight: settings.buffer_font.weight,
28257 font_style: FontStyle::Normal,
28258 line_height: relative(1.2),
28259 color: theme_color.text,
28260 ..Default::default()
28261 };
28262 let editor_style = EditorStyle {
28263 background: theme_color.ghost_element_background,
28264 local_player: cx.theme().players().local(),
28265 syntax: cx.theme().syntax().clone(),
28266 text: text_style,
28267 ..Default::default()
28268 };
28269 EditorElement::new(&self.0, editor_style).into_any()
28270 }
28271
28272 fn as_any(&self) -> &dyn Any {
28273 &self.0
28274 }
28275
28276 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28277 self.0.update(cx, |editor, cx| {
28278 let editor_offset = editor.buffer().read(cx).len(cx);
28279 editor.change_selections(
28280 SelectionEffects::scroll(Autoscroll::Next),
28281 window,
28282 cx,
28283 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28284 );
28285 });
28286 }
28287
28288 fn subscribe(
28289 &self,
28290 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28291 window: &mut Window,
28292 cx: &mut App,
28293 ) -> Subscription {
28294 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28295 let event = match event {
28296 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28297 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28298 _ => return,
28299 };
28300 (callback)(event, window, cx);
28301 })
28302 }
28303
28304 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28305 self.0.update(cx, |editor, cx| {
28306 editor.set_masked(masked, cx);
28307 });
28308 }
28309}
28310impl<T> Default for InvalidationStack<T> {
28311 fn default() -> Self {
28312 Self(Default::default())
28313 }
28314}
28315
28316impl<T> Deref for InvalidationStack<T> {
28317 type Target = Vec<T>;
28318
28319 fn deref(&self) -> &Self::Target {
28320 &self.0
28321 }
28322}
28323
28324impl<T> DerefMut for InvalidationStack<T> {
28325 fn deref_mut(&mut self) -> &mut Self::Target {
28326 &mut self.0
28327 }
28328}
28329
28330impl InvalidationRegion for SnippetState {
28331 fn ranges(&self) -> &[Range<Anchor>] {
28332 &self.ranges[self.active_index]
28333 }
28334}
28335
28336fn edit_prediction_edit_text(
28337 current_snapshot: &BufferSnapshot,
28338 edits: &[(Range<Anchor>, impl AsRef<str>)],
28339 edit_preview: &EditPreview,
28340 include_deletions: bool,
28341 cx: &App,
28342) -> HighlightedText {
28343 let edits = edits
28344 .iter()
28345 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28346 .collect::<Vec<_>>();
28347
28348 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28349}
28350
28351fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28352 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
28353 // Just show the raw edit text with basic styling
28354 let mut text = String::new();
28355 let mut highlights = Vec::new();
28356
28357 let insertion_highlight_style = HighlightStyle {
28358 color: Some(cx.theme().colors().text),
28359 ..Default::default()
28360 };
28361
28362 for (_, edit_text) in edits {
28363 let start_offset = text.len();
28364 text.push_str(edit_text);
28365 let end_offset = text.len();
28366
28367 if start_offset < end_offset {
28368 highlights.push((start_offset..end_offset, insertion_highlight_style));
28369 }
28370 }
28371
28372 HighlightedText {
28373 text: text.into(),
28374 highlights,
28375 }
28376}
28377
28378pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28379 match severity {
28380 lsp::DiagnosticSeverity::ERROR => colors.error,
28381 lsp::DiagnosticSeverity::WARNING => colors.warning,
28382 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28383 lsp::DiagnosticSeverity::HINT => colors.info,
28384 _ => colors.ignored,
28385 }
28386}
28387
28388pub fn styled_runs_for_code_label<'a>(
28389 label: &'a CodeLabel,
28390 syntax_theme: &'a theme::SyntaxTheme,
28391 local_player: &'a theme::PlayerColor,
28392) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28393 let fade_out = HighlightStyle {
28394 fade_out: Some(0.35),
28395 ..Default::default()
28396 };
28397
28398 let mut prev_end = label.filter_range.end;
28399 label
28400 .runs
28401 .iter()
28402 .enumerate()
28403 .flat_map(move |(ix, (range, highlight_id))| {
28404 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28405 HighlightStyle {
28406 color: Some(local_player.cursor),
28407 ..Default::default()
28408 }
28409 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28410 HighlightStyle {
28411 background_color: Some(local_player.selection),
28412 ..Default::default()
28413 }
28414 } else if let Some(style) = highlight_id.style(syntax_theme) {
28415 style
28416 } else {
28417 return Default::default();
28418 };
28419 let muted_style = style.highlight(fade_out);
28420
28421 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28422 if range.start >= label.filter_range.end {
28423 if range.start > prev_end {
28424 runs.push((prev_end..range.start, fade_out));
28425 }
28426 runs.push((range.clone(), muted_style));
28427 } else if range.end <= label.filter_range.end {
28428 runs.push((range.clone(), style));
28429 } else {
28430 runs.push((range.start..label.filter_range.end, style));
28431 runs.push((label.filter_range.end..range.end, muted_style));
28432 }
28433 prev_end = cmp::max(prev_end, range.end);
28434
28435 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28436 runs.push((prev_end..label.text.len(), fade_out));
28437 }
28438
28439 runs
28440 })
28441}
28442
28443pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28444 let mut prev_index = 0;
28445 let mut prev_codepoint: Option<char> = None;
28446 text.char_indices()
28447 .chain([(text.len(), '\0')])
28448 .filter_map(move |(index, codepoint)| {
28449 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28450 let is_boundary = index == text.len()
28451 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28452 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28453 if is_boundary {
28454 let chunk = &text[prev_index..index];
28455 prev_index = index;
28456 Some(chunk)
28457 } else {
28458 None
28459 }
28460 })
28461}
28462
28463/// Given a string of text immediately before the cursor, iterates over possible
28464/// strings a snippet could match to. More precisely: returns an iterator over
28465/// suffixes of `text` created by splitting at word boundaries (before & after
28466/// every non-word character).
28467///
28468/// Shorter suffixes are returned first.
28469pub(crate) fn snippet_candidate_suffixes(
28470 text: &str,
28471 is_word_char: impl Fn(char) -> bool,
28472) -> impl std::iter::Iterator<Item = &str> {
28473 let mut prev_index = text.len();
28474 let mut prev_codepoint = None;
28475 text.char_indices()
28476 .rev()
28477 .chain([(0, '\0')])
28478 .filter_map(move |(index, codepoint)| {
28479 let prev_index = std::mem::replace(&mut prev_index, index);
28480 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28481 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28482 None
28483 } else {
28484 let chunk = &text[prev_index..]; // go to end of string
28485 Some(chunk)
28486 }
28487 })
28488}
28489
28490pub trait RangeToAnchorExt: Sized {
28491 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28492
28493 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28494 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28495 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28496 }
28497}
28498
28499impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28500 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28501 let start_offset = self.start.to_offset(snapshot);
28502 let end_offset = self.end.to_offset(snapshot);
28503 if start_offset == end_offset {
28504 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28505 } else {
28506 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28507 }
28508 }
28509}
28510
28511pub trait RowExt {
28512 fn as_f64(&self) -> f64;
28513
28514 fn next_row(&self) -> Self;
28515
28516 fn previous_row(&self) -> Self;
28517
28518 fn minus(&self, other: Self) -> u32;
28519}
28520
28521impl RowExt for DisplayRow {
28522 fn as_f64(&self) -> f64 {
28523 self.0 as _
28524 }
28525
28526 fn next_row(&self) -> Self {
28527 Self(self.0 + 1)
28528 }
28529
28530 fn previous_row(&self) -> Self {
28531 Self(self.0.saturating_sub(1))
28532 }
28533
28534 fn minus(&self, other: Self) -> u32 {
28535 self.0 - other.0
28536 }
28537}
28538
28539impl RowExt for MultiBufferRow {
28540 fn as_f64(&self) -> f64 {
28541 self.0 as _
28542 }
28543
28544 fn next_row(&self) -> Self {
28545 Self(self.0 + 1)
28546 }
28547
28548 fn previous_row(&self) -> Self {
28549 Self(self.0.saturating_sub(1))
28550 }
28551
28552 fn minus(&self, other: Self) -> u32 {
28553 self.0 - other.0
28554 }
28555}
28556
28557trait RowRangeExt {
28558 type Row;
28559
28560 fn len(&self) -> usize;
28561
28562 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28563}
28564
28565impl RowRangeExt for Range<MultiBufferRow> {
28566 type Row = MultiBufferRow;
28567
28568 fn len(&self) -> usize {
28569 (self.end.0 - self.start.0) as usize
28570 }
28571
28572 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28573 (self.start.0..self.end.0).map(MultiBufferRow)
28574 }
28575}
28576
28577impl RowRangeExt for Range<DisplayRow> {
28578 type Row = DisplayRow;
28579
28580 fn len(&self) -> usize {
28581 (self.end.0 - self.start.0) as usize
28582 }
28583
28584 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28585 (self.start.0..self.end.0).map(DisplayRow)
28586 }
28587}
28588
28589/// If select range has more than one line, we
28590/// just point the cursor to range.start.
28591fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28592 if range.start.row == range.end.row {
28593 range
28594 } else {
28595 range.start..range.start
28596 }
28597}
28598pub struct KillRing(ClipboardItem);
28599impl Global for KillRing {}
28600
28601const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28602
28603enum BreakpointPromptEditAction {
28604 Log,
28605 Condition,
28606 HitCondition,
28607}
28608
28609struct BreakpointPromptEditor {
28610 pub(crate) prompt: Entity<Editor>,
28611 editor: WeakEntity<Editor>,
28612 breakpoint_anchor: Anchor,
28613 breakpoint: Breakpoint,
28614 edit_action: BreakpointPromptEditAction,
28615 block_ids: HashSet<CustomBlockId>,
28616 editor_margins: Arc<Mutex<EditorMargins>>,
28617 _subscriptions: Vec<Subscription>,
28618}
28619
28620impl BreakpointPromptEditor {
28621 const MAX_LINES: u8 = 4;
28622
28623 fn new(
28624 editor: WeakEntity<Editor>,
28625 breakpoint_anchor: Anchor,
28626 breakpoint: Breakpoint,
28627 edit_action: BreakpointPromptEditAction,
28628 window: &mut Window,
28629 cx: &mut Context<Self>,
28630 ) -> Self {
28631 let base_text = match edit_action {
28632 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28633 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28634 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28635 }
28636 .map(|msg| msg.to_string())
28637 .unwrap_or_default();
28638
28639 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28640 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28641
28642 let prompt = cx.new(|cx| {
28643 let mut prompt = Editor::new(
28644 EditorMode::AutoHeight {
28645 min_lines: 1,
28646 max_lines: Some(Self::MAX_LINES as usize),
28647 },
28648 buffer,
28649 None,
28650 window,
28651 cx,
28652 );
28653 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28654 prompt.set_show_cursor_when_unfocused(false, cx);
28655 prompt.set_placeholder_text(
28656 match edit_action {
28657 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28658 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28659 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28660 },
28661 window,
28662 cx,
28663 );
28664
28665 prompt
28666 });
28667
28668 Self {
28669 prompt,
28670 editor,
28671 breakpoint_anchor,
28672 breakpoint,
28673 edit_action,
28674 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28675 block_ids: Default::default(),
28676 _subscriptions: vec![],
28677 }
28678 }
28679
28680 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28681 self.block_ids.extend(block_ids)
28682 }
28683
28684 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28685 if let Some(editor) = self.editor.upgrade() {
28686 let message = self
28687 .prompt
28688 .read(cx)
28689 .buffer
28690 .read(cx)
28691 .as_singleton()
28692 .expect("A multi buffer in breakpoint prompt isn't possible")
28693 .read(cx)
28694 .as_rope()
28695 .to_string();
28696
28697 editor.update(cx, |editor, cx| {
28698 editor.edit_breakpoint_at_anchor(
28699 self.breakpoint_anchor,
28700 self.breakpoint.clone(),
28701 match self.edit_action {
28702 BreakpointPromptEditAction::Log => {
28703 BreakpointEditAction::EditLogMessage(message.into())
28704 }
28705 BreakpointPromptEditAction::Condition => {
28706 BreakpointEditAction::EditCondition(message.into())
28707 }
28708 BreakpointPromptEditAction::HitCondition => {
28709 BreakpointEditAction::EditHitCondition(message.into())
28710 }
28711 },
28712 cx,
28713 );
28714
28715 editor.remove_blocks(self.block_ids.clone(), None, cx);
28716 cx.focus_self(window);
28717 });
28718 }
28719 }
28720
28721 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28722 self.editor
28723 .update(cx, |editor, cx| {
28724 editor.remove_blocks(self.block_ids.clone(), None, cx);
28725 window.focus(&editor.focus_handle, cx);
28726 })
28727 .log_err();
28728 }
28729
28730 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28731 let settings = ThemeSettings::get_global(cx);
28732 let text_style = TextStyle {
28733 color: if self.prompt.read(cx).read_only(cx) {
28734 cx.theme().colors().text_disabled
28735 } else {
28736 cx.theme().colors().text
28737 },
28738 font_family: settings.buffer_font.family.clone(),
28739 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28740 font_size: settings.buffer_font_size(cx).into(),
28741 font_weight: settings.buffer_font.weight,
28742 line_height: relative(settings.buffer_line_height.value()),
28743 ..Default::default()
28744 };
28745 EditorElement::new(
28746 &self.prompt,
28747 EditorStyle {
28748 background: cx.theme().colors().editor_background,
28749 local_player: cx.theme().players().local(),
28750 text: text_style,
28751 ..Default::default()
28752 },
28753 )
28754 }
28755}
28756
28757impl Render for BreakpointPromptEditor {
28758 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28759 let editor_margins = *self.editor_margins.lock();
28760 let gutter_dimensions = editor_margins.gutter;
28761 h_flex()
28762 .key_context("Editor")
28763 .bg(cx.theme().colors().editor_background)
28764 .border_y_1()
28765 .border_color(cx.theme().status().info_border)
28766 .size_full()
28767 .py(window.line_height() / 2.5)
28768 .on_action(cx.listener(Self::confirm))
28769 .on_action(cx.listener(Self::cancel))
28770 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28771 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28772 }
28773}
28774
28775impl Focusable for BreakpointPromptEditor {
28776 fn focus_handle(&self, cx: &App) -> FocusHandle {
28777 self.prompt.focus_handle(cx)
28778 }
28779}
28780
28781fn all_edits_insertions_or_deletions(
28782 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28783 snapshot: &MultiBufferSnapshot,
28784) -> bool {
28785 let mut all_insertions = true;
28786 let mut all_deletions = true;
28787
28788 for (range, new_text) in edits.iter() {
28789 let range_is_empty = range.to_offset(snapshot).is_empty();
28790 let text_is_empty = new_text.is_empty();
28791
28792 if range_is_empty != text_is_empty {
28793 if range_is_empty {
28794 all_deletions = false;
28795 } else {
28796 all_insertions = false;
28797 }
28798 } else {
28799 return false;
28800 }
28801
28802 if !all_insertions && !all_deletions {
28803 return false;
28804 }
28805 }
28806 all_insertions || all_deletions
28807}
28808
28809struct MissingEditPredictionKeybindingTooltip;
28810
28811impl Render for MissingEditPredictionKeybindingTooltip {
28812 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28813 ui::tooltip_container(cx, |container, cx| {
28814 container
28815 .flex_shrink_0()
28816 .max_w_80()
28817 .min_h(rems_from_px(124.))
28818 .justify_between()
28819 .child(
28820 v_flex()
28821 .flex_1()
28822 .text_ui_sm(cx)
28823 .child(Label::new("Conflict with Accept Keybinding"))
28824 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28825 )
28826 .child(
28827 h_flex()
28828 .pb_1()
28829 .gap_1()
28830 .items_end()
28831 .w_full()
28832 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28833 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28834 }))
28835 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28836 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28837 })),
28838 )
28839 })
28840 }
28841}
28842
28843#[derive(Debug, Clone, Copy, PartialEq)]
28844pub struct LineHighlight {
28845 pub background: Background,
28846 pub border: Option<gpui::Hsla>,
28847 pub include_gutter: bool,
28848 pub type_id: Option<TypeId>,
28849}
28850
28851struct LineManipulationResult {
28852 pub new_text: String,
28853 pub line_count_before: usize,
28854 pub line_count_after: usize,
28855}
28856
28857fn render_diff_hunk_controls(
28858 row: u32,
28859 status: &DiffHunkStatus,
28860 hunk_range: Range<Anchor>,
28861 is_created_file: bool,
28862 line_height: Pixels,
28863 editor: &Entity<Editor>,
28864 _window: &mut Window,
28865 cx: &mut App,
28866) -> AnyElement {
28867 h_flex()
28868 .h(line_height)
28869 .mr_1()
28870 .gap_1()
28871 .px_0p5()
28872 .pb_1()
28873 .border_x_1()
28874 .border_b_1()
28875 .border_color(cx.theme().colors().border_variant)
28876 .rounded_b_lg()
28877 .bg(cx.theme().colors().editor_background)
28878 .gap_1()
28879 .block_mouse_except_scroll()
28880 .shadow_md()
28881 .child(if status.has_secondary_hunk() {
28882 Button::new(("stage", row as u64), "Stage")
28883 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28884 .tooltip({
28885 let focus_handle = editor.focus_handle(cx);
28886 move |_window, cx| {
28887 Tooltip::for_action_in(
28888 "Stage Hunk",
28889 &::git::ToggleStaged,
28890 &focus_handle,
28891 cx,
28892 )
28893 }
28894 })
28895 .on_click({
28896 let editor = editor.clone();
28897 move |_event, _window, cx| {
28898 editor.update(cx, |editor, cx| {
28899 editor.stage_or_unstage_diff_hunks(
28900 true,
28901 vec![hunk_range.start..hunk_range.start],
28902 cx,
28903 );
28904 });
28905 }
28906 })
28907 } else {
28908 Button::new(("unstage", row as u64), "Unstage")
28909 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28910 .tooltip({
28911 let focus_handle = editor.focus_handle(cx);
28912 move |_window, cx| {
28913 Tooltip::for_action_in(
28914 "Unstage Hunk",
28915 &::git::ToggleStaged,
28916 &focus_handle,
28917 cx,
28918 )
28919 }
28920 })
28921 .on_click({
28922 let editor = editor.clone();
28923 move |_event, _window, cx| {
28924 editor.update(cx, |editor, cx| {
28925 editor.stage_or_unstage_diff_hunks(
28926 false,
28927 vec![hunk_range.start..hunk_range.start],
28928 cx,
28929 );
28930 });
28931 }
28932 })
28933 })
28934 .child(
28935 Button::new(("restore", row as u64), "Restore")
28936 .tooltip({
28937 let focus_handle = editor.focus_handle(cx);
28938 move |_window, cx| {
28939 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28940 }
28941 })
28942 .on_click({
28943 let editor = editor.clone();
28944 move |_event, window, cx| {
28945 editor.update(cx, |editor, cx| {
28946 let snapshot = editor.snapshot(window, cx);
28947 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28948 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28949 });
28950 }
28951 })
28952 .disabled(is_created_file),
28953 )
28954 .when(
28955 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28956 |el| {
28957 el.child(
28958 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28959 .shape(IconButtonShape::Square)
28960 .icon_size(IconSize::Small)
28961 // .disabled(!has_multiple_hunks)
28962 .tooltip({
28963 let focus_handle = editor.focus_handle(cx);
28964 move |_window, cx| {
28965 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28966 }
28967 })
28968 .on_click({
28969 let editor = editor.clone();
28970 move |_event, window, cx| {
28971 editor.update(cx, |editor, cx| {
28972 let snapshot = editor.snapshot(window, cx);
28973 let position =
28974 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28975 editor.go_to_hunk_before_or_after_position(
28976 &snapshot,
28977 position,
28978 Direction::Next,
28979 window,
28980 cx,
28981 );
28982 editor.expand_selected_diff_hunks(cx);
28983 });
28984 }
28985 }),
28986 )
28987 .child(
28988 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28989 .shape(IconButtonShape::Square)
28990 .icon_size(IconSize::Small)
28991 // .disabled(!has_multiple_hunks)
28992 .tooltip({
28993 let focus_handle = editor.focus_handle(cx);
28994 move |_window, cx| {
28995 Tooltip::for_action_in(
28996 "Previous Hunk",
28997 &GoToPreviousHunk,
28998 &focus_handle,
28999 cx,
29000 )
29001 }
29002 })
29003 .on_click({
29004 let editor = editor.clone();
29005 move |_event, window, cx| {
29006 editor.update(cx, |editor, cx| {
29007 let snapshot = editor.snapshot(window, cx);
29008 let point =
29009 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29010 editor.go_to_hunk_before_or_after_position(
29011 &snapshot,
29012 point,
29013 Direction::Prev,
29014 window,
29015 cx,
29016 );
29017 editor.expand_selected_diff_hunks(cx);
29018 });
29019 }
29020 }),
29021 )
29022 },
29023 )
29024 .into_any_element()
29025}
29026
29027pub fn multibuffer_context_lines(cx: &App) -> u32 {
29028 EditorSettings::try_get(cx)
29029 .map(|settings| settings.excerpt_context_lines)
29030 .unwrap_or(2)
29031 .min(32)
29032}