1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod semantic_tokens;
42mod split;
43pub mod split_editor_view;
44pub mod tasks;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
65 ShowMinimap,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
80 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
81 ToPoint,
82};
83pub use split::{SplittableEditor, ToggleSplitDiff};
84pub use split_editor_view::SplitEditorView;
85pub use text::Bias;
86
87use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
88use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
89use anyhow::{Context as _, Result, anyhow, bail};
90use blink_manager::BlinkManager;
91use buffer_diff::DiffHunkStatus;
92use client::{Collaborator, ParticipantIndex, parse_zed_link};
93use clock::ReplicaId;
94use code_context_menus::{
95 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
96 CompletionsMenu, ContextMenuOrigin,
97};
98use collections::{BTreeMap, HashMap, HashSet, VecDeque};
99use convert_case::{Case, Casing};
100use dap::TelemetrySpawnLocation;
101use display_map::*;
102use document_colors::LspColorData;
103use edit_prediction_types::{
104 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
105 EditPredictionGranularity, SuggestionDisplayType,
106};
107use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
108use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
109use futures::{
110 FutureExt,
111 future::{self, Shared, join},
112};
113use fuzzy::{StringMatch, StringMatchCandidate};
114use git::blame::{GitBlame, GlobalBlameRenderer};
115use gpui::{
116 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
117 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
118 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
119 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
120 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
121 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
122 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
123 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
124 pulsating_between, px, relative, size,
125};
126use hover_links::{HoverLink, HoveredLinkState, find_file};
127use hover_popover::{HoverState, hide_hover};
128use indent_guides::ActiveIndentGuidesState;
129use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
130use itertools::{Either, Itertools};
131use language::{
132 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
133 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
134 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
135 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, LocalFile, OffsetRangeExt,
136 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
137 TreeSitterOptions, WordsQuery,
138 language_settings::{
139 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
140 all_language_settings, language_settings,
141 },
142 point_from_lsp, point_to_lsp, text_diff_with_options,
143};
144use linked_editing_ranges::refresh_linked_ranges;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId,
148};
149use markdown::Markdown;
150use mouse_context_menu::MouseContextMenu;
151use movement::TextLayoutDetails;
152use multi_buffer::{
153 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
154};
155use parking_lot::Mutex;
156use persistence::DB;
157use project::{
158 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
159 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
160 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
161 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
162 debugger::{
163 breakpoint_store::{
164 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
165 BreakpointStore, BreakpointStoreEvent,
166 },
167 session::{Session, SessionEvent},
168 },
169 git_store::GitStoreEvent,
170 lsp_store::{
171 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
172 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
173 },
174 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
175};
176use rand::seq::SliceRandom;
177use regex::Regex;
178use rpc::{ErrorCode, ErrorExt, proto::PeerId};
179use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
180use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
181use serde::{Deserialize, Serialize};
182use settings::{
183 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
184 update_settings_file,
185};
186use smallvec::{SmallVec, smallvec};
187use snippet::Snippet;
188use std::{
189 any::{Any, TypeId},
190 borrow::Cow,
191 cell::{OnceCell, RefCell},
192 cmp::{self, Ordering, Reverse},
193 collections::hash_map,
194 iter::{self, Peekable},
195 mem,
196 num::NonZeroU32,
197 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
198 path::{Path, PathBuf},
199 rc::Rc,
200 sync::Arc,
201 time::{Duration, Instant},
202};
203use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
204use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
205use theme::{
206 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
207 ThemeSettings, observe_buffer_font_size_adjustment,
208};
209use ui::{
210 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
211 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
212};
213use ui_input::ErasedEditor;
214use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
215use workspace::{
216 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
217 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
218 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
219 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
220 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
221 searchable::SearchEvent,
222};
223use zed_actions::editor::{MoveDown, MoveUp};
224
225use crate::{
226 code_context_menus::CompletionsMenuSource,
227 editor_settings::MultiCursorModifier,
228 hover_links::{find_url, find_url_from_range},
229 inlays::{
230 InlineValueCache,
231 inlay_hints::{LspInlayHintData, inlay_hint_settings},
232 },
233 scroll::{ScrollOffset, ScrollPixelOffset},
234 selections_collection::resolve_selections_wrapping_blocks,
235 semantic_tokens::SemanticTokenState,
236 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
237};
238
239pub const FILE_HEADER_HEIGHT: u32 = 2;
240pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
241pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
242const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
243const MAX_LINE_LEN: usize = 1024;
244const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
245const MAX_SELECTION_HISTORY_LEN: usize = 1024;
246pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
247#[doc(hidden)]
248pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
249pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
250
251pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
252pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
253pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
254pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
255
256pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
257pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
258pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
259
260pub type RenderDiffHunkControlsFn = Arc<
261 dyn Fn(
262 u32,
263 &DiffHunkStatus,
264 Range<Anchor>,
265 bool,
266 Pixels,
267 &Entity<Editor>,
268 &mut Window,
269 &mut App,
270 ) -> AnyElement,
271>;
272
273enum ReportEditorEvent {
274 Saved { auto_saved: bool },
275 EditorOpened,
276 Closed,
277}
278
279impl ReportEditorEvent {
280 pub fn event_type(&self) -> &'static str {
281 match self {
282 Self::Saved { .. } => "Editor Saved",
283 Self::EditorOpened => "Editor Opened",
284 Self::Closed => "Editor Closed",
285 }
286 }
287}
288
289pub enum ActiveDebugLine {}
290pub enum DebugStackFrameLine {}
291
292pub enum ConflictsOuter {}
293pub enum ConflictsOurs {}
294pub enum ConflictsTheirs {}
295pub enum ConflictsOursMarker {}
296pub enum ConflictsTheirsMarker {}
297
298pub struct HunkAddedColor;
299pub struct HunkRemovedColor;
300
301#[derive(Debug, Copy, Clone, PartialEq, Eq)]
302pub enum Navigated {
303 Yes,
304 No,
305}
306
307impl Navigated {
308 pub fn from_bool(yes: bool) -> Navigated {
309 if yes { Navigated::Yes } else { Navigated::No }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
314enum DisplayDiffHunk {
315 Folded {
316 display_row: DisplayRow,
317 },
318 Unfolded {
319 is_created_file: bool,
320 diff_base_byte_range: Range<usize>,
321 display_row_range: Range<DisplayRow>,
322 multi_buffer_range: Range<Anchor>,
323 status: DiffHunkStatus,
324 word_diffs: Vec<Range<MultiBufferOffset>>,
325 },
326}
327
328pub enum HideMouseCursorOrigin {
329 TypingAction,
330 MovementAction,
331}
332
333pub fn init(cx: &mut App) {
334 cx.set_global(GlobalBlameRenderer(Arc::new(())));
335 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
336
337 workspace::register_project_item::<Editor>(cx);
338 workspace::FollowableViewRegistry::register::<Editor>(cx);
339 workspace::register_serializable_item::<Editor>(cx);
340
341 cx.observe_new(
342 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
343 workspace.register_action(Editor::new_file);
344 workspace.register_action(Editor::new_file_split);
345 workspace.register_action(Editor::new_file_vertical);
346 workspace.register_action(Editor::new_file_horizontal);
347 workspace.register_action(Editor::cancel_language_server_work);
348 workspace.register_action(Editor::toggle_focus);
349 },
350 )
351 .detach();
352
353 cx.on_action(move |_: &workspace::NewFile, cx| {
354 let app_state = workspace::AppState::global(cx);
355 if let Some(app_state) = app_state.upgrade() {
356 workspace::open_new(
357 Default::default(),
358 app_state,
359 cx,
360 |workspace, window, cx| {
361 Editor::new_file(workspace, &Default::default(), window, cx)
362 },
363 )
364 .detach_and_log_err(cx);
365 }
366 })
367 .on_action(move |_: &workspace::NewWindow, cx| {
368 let app_state = workspace::AppState::global(cx);
369 if let Some(app_state) = app_state.upgrade() {
370 workspace::open_new(
371 Default::default(),
372 app_state,
373 cx,
374 |workspace, window, cx| {
375 cx.activate(true);
376 Editor::new_file(workspace, &Default::default(), window, cx)
377 },
378 )
379 .detach_and_log_err(cx);
380 }
381 });
382 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
383 Arc::new(ErasedEditorImpl(
384 cx.new(|cx| Editor::single_line(window, cx)),
385 )) as Arc<dyn ErasedEditor>
386 });
387 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 language_registry: Option<Arc<LanguageRegistry>>,
402 cx: &mut App,
403 ) -> Vec<BlockProperties<Anchor>>;
404
405 fn render_hover(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
408 range: Range<Point>,
409 buffer_id: BufferId,
410 language_registry: Option<Arc<LanguageRegistry>>,
411 cx: &mut App,
412 ) -> Option<Entity<markdown::Markdown>>;
413
414 fn open_link(
415 &self,
416 editor: &mut Editor,
417 link: SharedString,
418 window: &mut Window,
419 cx: &mut Context<Editor>,
420 );
421}
422
423pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
424
425impl GlobalDiagnosticRenderer {
426 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
427 cx.try_global::<Self>().map(|g| g.0.clone())
428 }
429}
430
431impl gpui::Global for GlobalDiagnosticRenderer {}
432pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
433 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
434}
435
436pub struct SearchWithinRange;
437
438trait InvalidationRegion {
439 fn ranges(&self) -> &[Range<Anchor>];
440}
441
442#[derive(Clone, Debug, PartialEq)]
443pub enum SelectPhase {
444 Begin {
445 position: DisplayPoint,
446 add: bool,
447 click_count: usize,
448 },
449 BeginColumnar {
450 position: DisplayPoint,
451 reset: bool,
452 mode: ColumnarMode,
453 goal_column: u32,
454 },
455 Extend {
456 position: DisplayPoint,
457 click_count: usize,
458 },
459 Update {
460 position: DisplayPoint,
461 goal_column: u32,
462 scroll_delta: gpui::Point<f32>,
463 },
464 End,
465}
466
467#[derive(Clone, Debug, PartialEq)]
468pub enum ColumnarMode {
469 FromMouse,
470 FromSelection,
471}
472
473#[derive(Clone, Debug)]
474pub enum SelectMode {
475 Character,
476 Word(Range<Anchor>),
477 Line(Range<Anchor>),
478 All,
479}
480
481#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
482pub enum SizingBehavior {
483 /// The editor will layout itself using `size_full` and will include the vertical
484 /// scroll margin as requested by user settings.
485 #[default]
486 Default,
487 /// The editor will layout itself using `size_full`, but will not have any
488 /// vertical overscroll.
489 ExcludeOverscrollMargin,
490 /// The editor will request a vertical size according to its content and will be
491 /// layouted without a vertical scroll margin.
492 SizeByContent,
493}
494
495#[derive(Clone, PartialEq, Eq, Debug)]
496pub enum EditorMode {
497 SingleLine,
498 AutoHeight {
499 min_lines: usize,
500 max_lines: Option<usize>,
501 },
502 Full {
503 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
504 scale_ui_elements_with_buffer_font_size: bool,
505 /// When set to `true`, the editor will render a background for the active line.
506 show_active_line_background: bool,
507 /// Determines the sizing behavior for this editor
508 sizing_behavior: SizingBehavior,
509 },
510 Minimap {
511 parent: WeakEntity<Editor>,
512 },
513}
514
515impl EditorMode {
516 pub fn full() -> Self {
517 Self::Full {
518 scale_ui_elements_with_buffer_font_size: true,
519 show_active_line_background: true,
520 sizing_behavior: SizingBehavior::Default,
521 }
522 }
523
524 #[inline]
525 pub fn is_full(&self) -> bool {
526 matches!(self, Self::Full { .. })
527 }
528
529 #[inline]
530 pub fn is_single_line(&self) -> bool {
531 matches!(self, Self::SingleLine { .. })
532 }
533
534 #[inline]
535 fn is_minimap(&self) -> bool {
536 matches!(self, Self::Minimap { .. })
537 }
538}
539
540#[derive(Copy, Clone, Debug)]
541pub enum SoftWrap {
542 /// Prefer not to wrap at all.
543 ///
544 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
545 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
546 GitDiff,
547 /// Prefer a single line generally, unless an overly long line is encountered.
548 None,
549 /// Soft wrap lines that exceed the editor width.
550 EditorWidth,
551 /// Soft wrap lines at the preferred line length.
552 Column(u32),
553 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
554 Bounded(u32),
555}
556
557#[derive(Clone)]
558pub struct EditorStyle {
559 pub background: Hsla,
560 pub border: Hsla,
561 pub local_player: PlayerColor,
562 pub text: TextStyle,
563 pub scrollbar_width: Pixels,
564 pub syntax: Arc<SyntaxTheme>,
565 pub status: StatusColors,
566 pub inlay_hints_style: HighlightStyle,
567 pub edit_prediction_styles: EditPredictionStyles,
568 pub unnecessary_code_fade: f32,
569 pub show_underlines: bool,
570}
571
572impl Default for EditorStyle {
573 fn default() -> Self {
574 Self {
575 background: Hsla::default(),
576 border: Hsla::default(),
577 local_player: PlayerColor::default(),
578 text: TextStyle::default(),
579 scrollbar_width: Pixels::default(),
580 syntax: Default::default(),
581 // HACK: Status colors don't have a real default.
582 // We should look into removing the status colors from the editor
583 // style and retrieve them directly from the theme.
584 status: StatusColors::dark(),
585 inlay_hints_style: HighlightStyle::default(),
586 edit_prediction_styles: EditPredictionStyles {
587 insertion: HighlightStyle::default(),
588 whitespace: HighlightStyle::default(),
589 },
590 unnecessary_code_fade: Default::default(),
591 show_underlines: true,
592 }
593 }
594}
595
596pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
597 let show_background = language_settings::language_settings(None, None, cx)
598 .inlay_hints
599 .show_background;
600
601 let mut style = cx.theme().syntax().get("hint");
602
603 if style.color.is_none() {
604 style.color = Some(cx.theme().status().hint);
605 }
606
607 if !show_background {
608 style.background_color = None;
609 return style;
610 }
611
612 if style.background_color.is_none() {
613 style.background_color = Some(cx.theme().status().hint_background);
614 }
615
616 style
617}
618
619pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
620 EditPredictionStyles {
621 insertion: HighlightStyle {
622 color: Some(cx.theme().status().predictive),
623 ..HighlightStyle::default()
624 },
625 whitespace: HighlightStyle {
626 background_color: Some(cx.theme().status().created_background),
627 ..HighlightStyle::default()
628 },
629 }
630}
631
632type CompletionId = usize;
633
634pub(crate) enum EditDisplayMode {
635 TabAccept,
636 DiffPopover,
637 Inline,
638}
639
640enum EditPrediction {
641 Edit {
642 edits: Vec<(Range<Anchor>, Arc<str>)>,
643 /// Predicted cursor position as (anchor, offset_from_anchor).
644 /// The anchor is in multibuffer coordinates; after applying edits,
645 /// resolve the anchor and add the offset to get the final cursor position.
646 cursor_position: Option<(Anchor, usize)>,
647 edit_preview: Option<EditPreview>,
648 display_mode: EditDisplayMode,
649 snapshot: BufferSnapshot,
650 },
651 /// Move to a specific location in the active editor
652 MoveWithin {
653 target: Anchor,
654 snapshot: BufferSnapshot,
655 },
656 /// Move to a specific location in a different editor (not the active one)
657 MoveOutside {
658 target: language::Anchor,
659 snapshot: BufferSnapshot,
660 },
661}
662
663struct EditPredictionState {
664 inlay_ids: Vec<InlayId>,
665 completion: EditPrediction,
666 completion_id: Option<SharedString>,
667 invalidation_range: Option<Range<Anchor>>,
668}
669
670enum EditPredictionSettings {
671 Disabled,
672 Enabled {
673 show_in_menu: bool,
674 preview_requires_modifier: bool,
675 },
676}
677
678#[derive(Debug, Clone)]
679struct InlineDiagnostic {
680 message: SharedString,
681 group_id: usize,
682 is_primary: bool,
683 start: Point,
684 severity: lsp::DiagnosticSeverity,
685}
686
687pub enum MenuEditPredictionsPolicy {
688 Never,
689 ByProvider,
690}
691
692pub enum EditPredictionPreview {
693 /// Modifier is not pressed
694 Inactive { released_too_fast: bool },
695 /// Modifier pressed
696 Active {
697 since: Instant,
698 previous_scroll_position: Option<SharedScrollAnchor>,
699 },
700}
701
702impl EditPredictionPreview {
703 pub fn released_too_fast(&self) -> bool {
704 match self {
705 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
706 EditPredictionPreview::Active { .. } => false,
707 }
708 }
709
710 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
711 if let EditPredictionPreview::Active {
712 previous_scroll_position,
713 ..
714 } = self
715 {
716 *previous_scroll_position = scroll_position;
717 }
718 }
719}
720
721pub struct ContextMenuOptions {
722 pub min_entries_visible: usize,
723 pub max_entries_visible: usize,
724 pub placement: Option<ContextMenuPlacement>,
725}
726
727#[derive(Debug, Clone, PartialEq, Eq)]
728pub enum ContextMenuPlacement {
729 Above,
730 Below,
731}
732
733#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
734struct EditorActionId(usize);
735
736impl EditorActionId {
737 pub fn post_inc(&mut self) -> Self {
738 let answer = self.0;
739
740 *self = Self(answer + 1);
741
742 Self(answer)
743 }
744}
745
746// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
747// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
748
749type BackgroundHighlight = (
750 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
751 Arc<[Range<Anchor>]>,
752);
753type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
754
755#[derive(Default)]
756struct ScrollbarMarkerState {
757 scrollbar_size: Size<Pixels>,
758 dirty: bool,
759 markers: Arc<[PaintQuad]>,
760 pending_refresh: Option<Task<Result<()>>>,
761}
762
763impl ScrollbarMarkerState {
764 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
765 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
766 }
767}
768
769#[derive(Clone, Copy, PartialEq, Eq)]
770pub enum MinimapVisibility {
771 Disabled,
772 Enabled {
773 /// The configuration currently present in the users settings.
774 setting_configuration: bool,
775 /// Whether to override the currently set visibility from the users setting.
776 toggle_override: bool,
777 },
778}
779
780impl MinimapVisibility {
781 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
782 if mode.is_full() {
783 Self::Enabled {
784 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
785 toggle_override: false,
786 }
787 } else {
788 Self::Disabled
789 }
790 }
791
792 fn hidden(&self) -> Self {
793 match *self {
794 Self::Enabled {
795 setting_configuration,
796 ..
797 } => Self::Enabled {
798 setting_configuration,
799 toggle_override: setting_configuration,
800 },
801 Self::Disabled => Self::Disabled,
802 }
803 }
804
805 fn disabled(&self) -> bool {
806 matches!(*self, Self::Disabled)
807 }
808
809 fn settings_visibility(&self) -> bool {
810 match *self {
811 Self::Enabled {
812 setting_configuration,
813 ..
814 } => setting_configuration,
815 _ => false,
816 }
817 }
818
819 fn visible(&self) -> bool {
820 match *self {
821 Self::Enabled {
822 setting_configuration,
823 toggle_override,
824 } => setting_configuration ^ toggle_override,
825 _ => false,
826 }
827 }
828
829 fn toggle_visibility(&self) -> Self {
830 match *self {
831 Self::Enabled {
832 toggle_override,
833 setting_configuration,
834 } => Self::Enabled {
835 setting_configuration,
836 toggle_override: !toggle_override,
837 },
838 Self::Disabled => Self::Disabled,
839 }
840 }
841}
842
843#[derive(Debug, Clone, Copy, PartialEq, Eq)]
844pub enum BufferSerialization {
845 All,
846 NonDirtyBuffers,
847}
848
849impl BufferSerialization {
850 fn new(restore_unsaved_buffers: bool) -> Self {
851 if restore_unsaved_buffers {
852 Self::All
853 } else {
854 Self::NonDirtyBuffers
855 }
856 }
857}
858
859#[derive(Clone, Debug)]
860struct RunnableTasks {
861 templates: Vec<(TaskSourceKind, TaskTemplate)>,
862 offset: multi_buffer::Anchor,
863 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
864 column: u32,
865 // Values of all named captures, including those starting with '_'
866 extra_variables: HashMap<String, String>,
867 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
868 context_range: Range<BufferOffset>,
869}
870
871impl RunnableTasks {
872 fn resolve<'a>(
873 &'a self,
874 cx: &'a task::TaskContext,
875 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
876 self.templates.iter().filter_map(|(kind, template)| {
877 template
878 .resolve_task(&kind.to_id_base(), cx)
879 .map(|task| (kind.clone(), task))
880 })
881 }
882}
883
884#[derive(Clone)]
885pub struct ResolvedTasks {
886 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
887 position: Anchor,
888}
889
890/// Addons allow storing per-editor state in other crates (e.g. Vim)
891pub trait Addon: 'static {
892 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
893
894 fn render_buffer_header_controls(
895 &self,
896 _: &ExcerptInfo,
897 _: &Window,
898 _: &App,
899 ) -> Option<AnyElement> {
900 None
901 }
902
903 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
904 None
905 }
906
907 fn to_any(&self) -> &dyn std::any::Any;
908
909 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
910 None
911 }
912}
913
914struct ChangeLocation {
915 current: Option<Vec<Anchor>>,
916 original: Vec<Anchor>,
917}
918impl ChangeLocation {
919 fn locations(&self) -> &[Anchor] {
920 self.current.as_ref().unwrap_or(&self.original)
921 }
922}
923
924/// A set of caret positions, registered when the editor was edited.
925pub struct ChangeList {
926 changes: Vec<ChangeLocation>,
927 /// Currently "selected" change.
928 position: Option<usize>,
929}
930
931impl ChangeList {
932 pub fn new() -> Self {
933 Self {
934 changes: Vec::new(),
935 position: None,
936 }
937 }
938
939 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
940 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
941 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
942 if self.changes.is_empty() {
943 return None;
944 }
945
946 let prev = self.position.unwrap_or(self.changes.len());
947 let next = if direction == Direction::Prev {
948 prev.saturating_sub(count)
949 } else {
950 (prev + count).min(self.changes.len() - 1)
951 };
952 self.position = Some(next);
953 self.changes.get(next).map(|change| change.locations())
954 }
955
956 /// Adds a new change to the list, resetting the change list position.
957 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
958 self.position.take();
959 if let Some(last) = self.changes.last_mut()
960 && group
961 {
962 last.current = Some(new_positions)
963 } else {
964 self.changes.push(ChangeLocation {
965 original: new_positions,
966 current: None,
967 });
968 }
969 }
970
971 pub fn last(&self) -> Option<&[Anchor]> {
972 self.changes.last().map(|change| change.locations())
973 }
974
975 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
976 self.changes.last().map(|change| change.original.as_slice())
977 }
978
979 pub fn invert_last_group(&mut self) {
980 if let Some(last) = self.changes.last_mut()
981 && let Some(current) = last.current.as_mut()
982 {
983 mem::swap(&mut last.original, current);
984 }
985 }
986}
987
988#[derive(Clone)]
989struct InlineBlamePopoverState {
990 scroll_handle: ScrollHandle,
991 commit_message: Option<ParsedCommitMessage>,
992 markdown: Entity<Markdown>,
993}
994
995struct InlineBlamePopover {
996 position: gpui::Point<Pixels>,
997 hide_task: Option<Task<()>>,
998 popover_bounds: Option<Bounds<Pixels>>,
999 popover_state: InlineBlamePopoverState,
1000 keyboard_grace: bool,
1001}
1002
1003enum SelectionDragState {
1004 /// State when no drag related activity is detected.
1005 None,
1006 /// State when the mouse is down on a selection that is about to be dragged.
1007 ReadyToDrag {
1008 selection: Selection<Anchor>,
1009 click_position: gpui::Point<Pixels>,
1010 mouse_down_time: Instant,
1011 },
1012 /// State when the mouse is dragging the selection in the editor.
1013 Dragging {
1014 selection: Selection<Anchor>,
1015 drop_cursor: Selection<Anchor>,
1016 hide_drop_cursor: bool,
1017 },
1018}
1019
1020enum ColumnarSelectionState {
1021 FromMouse {
1022 selection_tail: Anchor,
1023 display_point: Option<DisplayPoint>,
1024 },
1025 FromSelection {
1026 selection_tail: Anchor,
1027 },
1028}
1029
1030/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1031/// a breakpoint on them.
1032#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1033struct PhantomBreakpointIndicator {
1034 display_row: DisplayRow,
1035 /// There's a small debounce between hovering over the line and showing the indicator.
1036 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1037 is_active: bool,
1038 collides_with_existing_breakpoint: bool,
1039}
1040
1041/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1042/// in diff view mode.
1043#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1044pub(crate) struct PhantomDiffReviewIndicator {
1045 /// The starting anchor of the selection (or the only row if not dragging).
1046 pub start: Anchor,
1047 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1048 pub end: Anchor,
1049 /// There's a small debounce between hovering over the line and showing the indicator.
1050 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1051 pub is_active: bool,
1052}
1053
1054#[derive(Clone, Debug)]
1055pub(crate) struct DiffReviewDragState {
1056 pub start_anchor: Anchor,
1057 pub current_anchor: Anchor,
1058}
1059
1060impl DiffReviewDragState {
1061 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1062 let start = self.start_anchor.to_display_point(snapshot).row();
1063 let current = self.current_anchor.to_display_point(snapshot).row();
1064
1065 (start..=current).sorted()
1066 }
1067}
1068
1069/// Identifies a specific hunk in the diff buffer.
1070/// Used as a key to group comments by their location.
1071#[derive(Clone, Debug)]
1072pub struct DiffHunkKey {
1073 /// The file path (relative to worktree) this hunk belongs to.
1074 pub file_path: Arc<util::rel_path::RelPath>,
1075 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1076 pub hunk_start_anchor: Anchor,
1077}
1078
1079/// A review comment stored locally before being sent to the Agent panel.
1080#[derive(Clone)]
1081pub struct StoredReviewComment {
1082 /// Unique identifier for this comment (for edit/delete operations).
1083 pub id: usize,
1084 /// The comment text entered by the user.
1085 pub comment: String,
1086 /// Anchors for the code range being reviewed.
1087 pub range: Range<Anchor>,
1088 /// Timestamp when the comment was created (for chronological ordering).
1089 pub created_at: Instant,
1090 /// Whether this comment is currently being edited inline.
1091 pub is_editing: bool,
1092}
1093
1094impl StoredReviewComment {
1095 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1096 Self {
1097 id,
1098 comment,
1099 range: anchor_range,
1100 created_at: Instant::now(),
1101 is_editing: false,
1102 }
1103 }
1104}
1105
1106/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1107pub(crate) struct DiffReviewOverlay {
1108 pub anchor_range: Range<Anchor>,
1109 /// The block ID for the overlay.
1110 pub block_id: CustomBlockId,
1111 /// The editor entity for the review input.
1112 pub prompt_editor: Entity<Editor>,
1113 /// The hunk key this overlay belongs to.
1114 pub hunk_key: DiffHunkKey,
1115 /// Whether the comments section is expanded.
1116 pub comments_expanded: bool,
1117 /// Editors for comments currently being edited inline.
1118 /// Key: comment ID, Value: Editor entity for inline editing.
1119 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1120 /// Subscriptions for inline edit editors' action handlers.
1121 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1122 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1123 /// The current user's avatar URI for display in comment rows.
1124 pub user_avatar_uri: Option<SharedUri>,
1125 /// Subscription to keep the action handler alive.
1126 _subscription: Subscription,
1127}
1128
1129/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1130///
1131/// See the [module level documentation](self) for more information.
1132pub struct Editor {
1133 focus_handle: FocusHandle,
1134 last_focused_descendant: Option<WeakFocusHandle>,
1135 /// The text buffer being edited
1136 buffer: Entity<MultiBuffer>,
1137 /// Map of how text in the buffer should be displayed.
1138 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1139 pub display_map: Entity<DisplayMap>,
1140 placeholder_display_map: Option<Entity<DisplayMap>>,
1141 pub selections: SelectionsCollection,
1142 pub scroll_manager: ScrollManager,
1143 /// When inline assist editors are linked, they all render cursors because
1144 /// typing enters text into each of them, even the ones that aren't focused.
1145 pub(crate) show_cursor_when_unfocused: bool,
1146 columnar_selection_state: Option<ColumnarSelectionState>,
1147 add_selections_state: Option<AddSelectionsState>,
1148 select_next_state: Option<SelectNextState>,
1149 select_prev_state: Option<SelectNextState>,
1150 selection_history: SelectionHistory,
1151 defer_selection_effects: bool,
1152 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1153 autoclose_regions: Vec<AutocloseRegion>,
1154 snippet_stack: InvalidationStack<SnippetState>,
1155 select_syntax_node_history: SelectSyntaxNodeHistory,
1156 ime_transaction: Option<TransactionId>,
1157 pub diagnostics_max_severity: DiagnosticSeverity,
1158 active_diagnostics: ActiveDiagnostic,
1159 show_inline_diagnostics: bool,
1160 inline_diagnostics_update: Task<()>,
1161 inline_diagnostics_enabled: bool,
1162 diagnostics_enabled: bool,
1163 word_completions_enabled: bool,
1164 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1165 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1166 hard_wrap: Option<usize>,
1167 project: Option<Entity<Project>>,
1168 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1169 completion_provider: Option<Rc<dyn CompletionProvider>>,
1170 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1171 blink_manager: Entity<BlinkManager>,
1172 show_cursor_names: bool,
1173 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1174 pub show_local_selections: bool,
1175 mode: EditorMode,
1176 show_breadcrumbs: bool,
1177 show_gutter: bool,
1178 show_scrollbars: ScrollbarAxes,
1179 minimap_visibility: MinimapVisibility,
1180 offset_content: bool,
1181 disable_expand_excerpt_buttons: bool,
1182 delegate_expand_excerpts: bool,
1183 delegate_stage_and_restore: bool,
1184 delegate_open_excerpts: bool,
1185 enable_lsp_data: bool,
1186 enable_runnables: bool,
1187 show_line_numbers: Option<bool>,
1188 use_relative_line_numbers: Option<bool>,
1189 show_git_diff_gutter: Option<bool>,
1190 show_code_actions: Option<bool>,
1191 show_runnables: Option<bool>,
1192 show_breakpoints: Option<bool>,
1193 show_diff_review_button: bool,
1194 show_wrap_guides: Option<bool>,
1195 show_indent_guides: Option<bool>,
1196 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1197 highlight_order: usize,
1198 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1199 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1200 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1201 scrollbar_marker_state: ScrollbarMarkerState,
1202 active_indent_guides_state: ActiveIndentGuidesState,
1203 nav_history: Option<ItemNavHistory>,
1204 context_menu: RefCell<Option<CodeContextMenu>>,
1205 context_menu_options: Option<ContextMenuOptions>,
1206 mouse_context_menu: Option<MouseContextMenu>,
1207 completion_tasks: Vec<(CompletionId, Task<()>)>,
1208 inline_blame_popover: Option<InlineBlamePopover>,
1209 inline_blame_popover_show_task: Option<Task<()>>,
1210 signature_help_state: SignatureHelpState,
1211 auto_signature_help: Option<bool>,
1212 find_all_references_task_sources: Vec<Anchor>,
1213 next_completion_id: CompletionId,
1214 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1215 code_actions_task: Option<Task<Result<()>>>,
1216 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1217 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1218 debounced_selection_highlight_complete: bool,
1219 document_highlights_task: Option<Task<()>>,
1220 linked_editing_range_task: Option<Task<Option<()>>>,
1221 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1222 pending_rename: Option<RenameState>,
1223 searchable: bool,
1224 cursor_shape: CursorShape,
1225 /// Whether the cursor is offset one character to the left when something is
1226 /// selected (needed for vim visual mode)
1227 cursor_offset_on_selection: bool,
1228 current_line_highlight: Option<CurrentLineHighlight>,
1229 /// Whether to collapse search match ranges to just their start position.
1230 /// When true, navigating to a match positions the cursor at the match
1231 /// without selecting the matched text.
1232 collapse_matches: bool,
1233 autoindent_mode: Option<AutoindentMode>,
1234 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1235 input_enabled: bool,
1236 use_modal_editing: bool,
1237 read_only: bool,
1238 leader_id: Option<CollaboratorId>,
1239 remote_id: Option<ViewId>,
1240 pub hover_state: HoverState,
1241 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1242 prev_pressure_stage: Option<PressureStage>,
1243 gutter_hovered: bool,
1244 hovered_link_state: Option<HoveredLinkState>,
1245 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1246 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1247 active_edit_prediction: Option<EditPredictionState>,
1248 /// Used to prevent flickering as the user types while the menu is open
1249 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1250 edit_prediction_settings: EditPredictionSettings,
1251 edit_predictions_hidden_for_vim_mode: bool,
1252 show_edit_predictions_override: Option<bool>,
1253 show_completions_on_input_override: Option<bool>,
1254 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1255 edit_prediction_preview: EditPredictionPreview,
1256 edit_prediction_indent_conflict: bool,
1257 edit_prediction_requires_modifier_in_indent_conflict: bool,
1258 next_inlay_id: usize,
1259 next_color_inlay_id: usize,
1260 _subscriptions: Vec<Subscription>,
1261 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1262 gutter_dimensions: GutterDimensions,
1263 style: Option<EditorStyle>,
1264 text_style_refinement: Option<TextStyleRefinement>,
1265 next_editor_action_id: EditorActionId,
1266 editor_actions: Rc<
1267 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1268 >,
1269 use_autoclose: bool,
1270 use_auto_surround: bool,
1271 auto_replace_emoji_shortcode: bool,
1272 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1273 show_git_blame_gutter: bool,
1274 show_git_blame_inline: bool,
1275 show_git_blame_inline_delay_task: Option<Task<()>>,
1276 git_blame_inline_enabled: bool,
1277 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1278 buffer_serialization: Option<BufferSerialization>,
1279 show_selection_menu: Option<bool>,
1280 blame: Option<Entity<GitBlame>>,
1281 blame_subscription: Option<Subscription>,
1282 custom_context_menu: Option<
1283 Box<
1284 dyn 'static
1285 + Fn(
1286 &mut Self,
1287 DisplayPoint,
1288 &mut Window,
1289 &mut Context<Self>,
1290 ) -> Option<Entity<ui::ContextMenu>>,
1291 >,
1292 >,
1293 last_bounds: Option<Bounds<Pixels>>,
1294 last_position_map: Option<Rc<PositionMap>>,
1295 expect_bounds_change: Option<Bounds<Pixels>>,
1296 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1297 tasks_update_task: Option<Task<()>>,
1298 breakpoint_store: Option<Entity<BreakpointStore>>,
1299 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1300 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1301 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1302 /// Active diff review overlays. Multiple overlays can be open simultaneously
1303 /// when hunks have comments stored.
1304 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1305 /// Stored review comments grouped by hunk.
1306 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1307 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1308 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1309 /// Counter for generating unique comment IDs.
1310 next_review_comment_id: usize,
1311 hovered_diff_hunk_row: Option<DisplayRow>,
1312 pull_diagnostics_task: Task<()>,
1313 in_project_search: bool,
1314 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1315 breadcrumb_header: Option<String>,
1316 focused_block: Option<FocusedBlock>,
1317 next_scroll_position: NextScrollCursorCenterTopBottom,
1318 addons: HashMap<TypeId, Box<dyn Addon>>,
1319 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1320 load_diff_task: Option<Shared<Task<()>>>,
1321 /// Whether we are temporarily displaying a diff other than git's
1322 temporary_diff_override: bool,
1323 selection_mark_mode: bool,
1324 toggle_fold_multiple_buffers: Task<()>,
1325 _scroll_cursor_center_top_bottom_task: Task<()>,
1326 serialize_selections: Task<()>,
1327 serialize_folds: Task<()>,
1328 mouse_cursor_hidden: bool,
1329 minimap: Option<Entity<Self>>,
1330 hide_mouse_mode: HideMouseMode,
1331 pub change_list: ChangeList,
1332 inline_value_cache: InlineValueCache,
1333 number_deleted_lines: bool,
1334
1335 selection_drag_state: SelectionDragState,
1336 colors: Option<LspColorData>,
1337 post_scroll_update: Task<()>,
1338 refresh_colors_task: Task<()>,
1339 use_document_folding_ranges: bool,
1340 refresh_folding_ranges_task: Task<()>,
1341 inlay_hints: Option<LspInlayHintData>,
1342 folding_newlines: Task<()>,
1343 select_next_is_case_sensitive: Option<bool>,
1344 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1345 on_local_selections_changed:
1346 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1347 suppress_selection_callback: bool,
1348 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1349 accent_data: Option<AccentData>,
1350 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1351 semantic_token_state: SemanticTokenState,
1352 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1353 refresh_document_symbols_task: Shared<Task<()>>,
1354 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1355 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1356 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1357 sticky_headers_task: Task<()>,
1358 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1359}
1360
1361#[derive(Debug, PartialEq)]
1362struct AccentData {
1363 colors: AccentColors,
1364 overrides: Vec<SharedString>,
1365}
1366
1367fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1368 if debounce_ms > 0 {
1369 Some(Duration::from_millis(debounce_ms))
1370 } else {
1371 None
1372 }
1373}
1374
1375#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1376enum NextScrollCursorCenterTopBottom {
1377 #[default]
1378 Center,
1379 Top,
1380 Bottom,
1381}
1382
1383impl NextScrollCursorCenterTopBottom {
1384 fn next(&self) -> Self {
1385 match self {
1386 Self::Center => Self::Top,
1387 Self::Top => Self::Bottom,
1388 Self::Bottom => Self::Center,
1389 }
1390 }
1391}
1392
1393#[derive(Clone)]
1394pub struct EditorSnapshot {
1395 pub mode: EditorMode,
1396 show_gutter: bool,
1397 offset_content: bool,
1398 show_line_numbers: Option<bool>,
1399 number_deleted_lines: bool,
1400 show_git_diff_gutter: Option<bool>,
1401 show_code_actions: Option<bool>,
1402 show_runnables: Option<bool>,
1403 show_breakpoints: Option<bool>,
1404 git_blame_gutter_max_author_length: Option<usize>,
1405 pub display_snapshot: DisplaySnapshot,
1406 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1407 is_focused: bool,
1408 scroll_anchor: SharedScrollAnchor,
1409 ongoing_scroll: OngoingScroll,
1410 current_line_highlight: CurrentLineHighlight,
1411 gutter_hovered: bool,
1412 semantic_tokens_enabled: bool,
1413}
1414
1415#[derive(Default, Debug, Clone, Copy)]
1416pub struct GutterDimensions {
1417 pub left_padding: Pixels,
1418 pub right_padding: Pixels,
1419 pub width: Pixels,
1420 pub margin: Pixels,
1421 pub git_blame_entries_width: Option<Pixels>,
1422}
1423
1424impl GutterDimensions {
1425 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1426 Self {
1427 margin: Self::default_gutter_margin(font_id, font_size, cx),
1428 ..Default::default()
1429 }
1430 }
1431
1432 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1433 -cx.text_system().descent(font_id, font_size)
1434 }
1435 /// The full width of the space taken up by the gutter.
1436 pub fn full_width(&self) -> Pixels {
1437 self.margin + self.width
1438 }
1439
1440 /// The width of the space reserved for the fold indicators,
1441 /// use alongside 'justify_end' and `gutter_width` to
1442 /// right align content with the line numbers
1443 pub fn fold_area_width(&self) -> Pixels {
1444 self.margin + self.right_padding
1445 }
1446}
1447
1448struct CharacterDimensions {
1449 em_width: Pixels,
1450 em_advance: Pixels,
1451 line_height: Pixels,
1452}
1453
1454#[derive(Debug)]
1455pub struct RemoteSelection {
1456 pub replica_id: ReplicaId,
1457 pub selection: Selection<Anchor>,
1458 pub cursor_shape: CursorShape,
1459 pub collaborator_id: CollaboratorId,
1460 pub line_mode: bool,
1461 pub user_name: Option<SharedString>,
1462 pub color: PlayerColor,
1463}
1464
1465#[derive(Clone, Debug)]
1466struct SelectionHistoryEntry {
1467 selections: Arc<[Selection<Anchor>]>,
1468 select_next_state: Option<SelectNextState>,
1469 select_prev_state: Option<SelectNextState>,
1470 add_selections_state: Option<AddSelectionsState>,
1471}
1472
1473#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1474enum SelectionHistoryMode {
1475 #[default]
1476 Normal,
1477 Undoing,
1478 Redoing,
1479 Skipping,
1480}
1481
1482#[derive(Clone, PartialEq, Eq, Hash)]
1483struct HoveredCursor {
1484 replica_id: ReplicaId,
1485 selection_id: usize,
1486}
1487
1488#[derive(Debug)]
1489/// SelectionEffects controls the side-effects of updating the selection.
1490///
1491/// The default behaviour does "what you mostly want":
1492/// - it pushes to the nav history if the cursor moved by >10 lines
1493/// - it re-triggers completion requests
1494/// - it scrolls to fit
1495///
1496/// You might want to modify these behaviours. For example when doing a "jump"
1497/// like go to definition, we always want to add to nav history; but when scrolling
1498/// in vim mode we never do.
1499///
1500/// Similarly, you might want to disable scrolling if you don't want the viewport to
1501/// move.
1502#[derive(Clone)]
1503pub struct SelectionEffects {
1504 nav_history: Option<bool>,
1505 completions: bool,
1506 scroll: Option<Autoscroll>,
1507}
1508
1509impl Default for SelectionEffects {
1510 fn default() -> Self {
1511 Self {
1512 nav_history: None,
1513 completions: true,
1514 scroll: Some(Autoscroll::fit()),
1515 }
1516 }
1517}
1518impl SelectionEffects {
1519 pub fn scroll(scroll: Autoscroll) -> Self {
1520 Self {
1521 scroll: Some(scroll),
1522 ..Default::default()
1523 }
1524 }
1525
1526 pub fn no_scroll() -> Self {
1527 Self {
1528 scroll: None,
1529 ..Default::default()
1530 }
1531 }
1532
1533 pub fn completions(self, completions: bool) -> Self {
1534 Self {
1535 completions,
1536 ..self
1537 }
1538 }
1539
1540 pub fn nav_history(self, nav_history: bool) -> Self {
1541 Self {
1542 nav_history: Some(nav_history),
1543 ..self
1544 }
1545 }
1546}
1547
1548struct DeferredSelectionEffectsState {
1549 changed: bool,
1550 effects: SelectionEffects,
1551 old_cursor_position: Anchor,
1552 history_entry: SelectionHistoryEntry,
1553}
1554
1555#[derive(Default)]
1556struct SelectionHistory {
1557 #[allow(clippy::type_complexity)]
1558 selections_by_transaction:
1559 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1560 mode: SelectionHistoryMode,
1561 undo_stack: VecDeque<SelectionHistoryEntry>,
1562 redo_stack: VecDeque<SelectionHistoryEntry>,
1563}
1564
1565impl SelectionHistory {
1566 #[track_caller]
1567 fn insert_transaction(
1568 &mut self,
1569 transaction_id: TransactionId,
1570 selections: Arc<[Selection<Anchor>]>,
1571 ) {
1572 if selections.is_empty() {
1573 log::error!(
1574 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1575 std::panic::Location::caller()
1576 );
1577 return;
1578 }
1579 self.selections_by_transaction
1580 .insert(transaction_id, (selections, None));
1581 }
1582
1583 #[allow(clippy::type_complexity)]
1584 fn transaction(
1585 &self,
1586 transaction_id: TransactionId,
1587 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1588 self.selections_by_transaction.get(&transaction_id)
1589 }
1590
1591 #[allow(clippy::type_complexity)]
1592 fn transaction_mut(
1593 &mut self,
1594 transaction_id: TransactionId,
1595 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1596 self.selections_by_transaction.get_mut(&transaction_id)
1597 }
1598
1599 fn push(&mut self, entry: SelectionHistoryEntry) {
1600 if !entry.selections.is_empty() {
1601 match self.mode {
1602 SelectionHistoryMode::Normal => {
1603 self.push_undo(entry);
1604 self.redo_stack.clear();
1605 }
1606 SelectionHistoryMode::Undoing => self.push_redo(entry),
1607 SelectionHistoryMode::Redoing => self.push_undo(entry),
1608 SelectionHistoryMode::Skipping => {}
1609 }
1610 }
1611 }
1612
1613 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1614 if self
1615 .undo_stack
1616 .back()
1617 .is_none_or(|e| e.selections != entry.selections)
1618 {
1619 self.undo_stack.push_back(entry);
1620 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1621 self.undo_stack.pop_front();
1622 }
1623 }
1624 }
1625
1626 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1627 if self
1628 .redo_stack
1629 .back()
1630 .is_none_or(|e| e.selections != entry.selections)
1631 {
1632 self.redo_stack.push_back(entry);
1633 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1634 self.redo_stack.pop_front();
1635 }
1636 }
1637 }
1638}
1639
1640#[derive(Clone, Copy)]
1641pub struct RowHighlightOptions {
1642 pub autoscroll: bool,
1643 pub include_gutter: bool,
1644}
1645
1646impl Default for RowHighlightOptions {
1647 fn default() -> Self {
1648 Self {
1649 autoscroll: Default::default(),
1650 include_gutter: true,
1651 }
1652 }
1653}
1654
1655struct RowHighlight {
1656 index: usize,
1657 range: Range<Anchor>,
1658 color: Hsla,
1659 options: RowHighlightOptions,
1660 type_id: TypeId,
1661}
1662
1663#[derive(Clone, Debug)]
1664struct AddSelectionsState {
1665 groups: Vec<AddSelectionsGroup>,
1666}
1667
1668#[derive(Clone, Debug)]
1669struct AddSelectionsGroup {
1670 above: bool,
1671 stack: Vec<usize>,
1672}
1673
1674#[derive(Clone)]
1675struct SelectNextState {
1676 query: AhoCorasick,
1677 wordwise: bool,
1678 done: bool,
1679}
1680
1681impl std::fmt::Debug for SelectNextState {
1682 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1683 f.debug_struct(std::any::type_name::<Self>())
1684 .field("wordwise", &self.wordwise)
1685 .field("done", &self.done)
1686 .finish()
1687 }
1688}
1689
1690#[derive(Debug)]
1691struct AutocloseRegion {
1692 selection_id: usize,
1693 range: Range<Anchor>,
1694 pair: BracketPair,
1695}
1696
1697#[derive(Debug)]
1698struct SnippetState {
1699 ranges: Vec<Vec<Range<Anchor>>>,
1700 active_index: usize,
1701 choices: Vec<Option<Vec<String>>>,
1702}
1703
1704#[doc(hidden)]
1705pub struct RenameState {
1706 pub range: Range<Anchor>,
1707 pub old_name: Arc<str>,
1708 pub editor: Entity<Editor>,
1709 block_id: CustomBlockId,
1710}
1711
1712struct InvalidationStack<T>(Vec<T>);
1713
1714struct RegisteredEditPredictionDelegate {
1715 provider: Arc<dyn EditPredictionDelegateHandle>,
1716 _subscription: Subscription,
1717}
1718
1719#[derive(Debug, PartialEq, Eq)]
1720pub struct ActiveDiagnosticGroup {
1721 pub active_range: Range<Anchor>,
1722 pub active_message: String,
1723 pub group_id: usize,
1724 pub blocks: HashSet<CustomBlockId>,
1725}
1726
1727#[derive(Debug, PartialEq, Eq)]
1728
1729pub(crate) enum ActiveDiagnostic {
1730 None,
1731 All,
1732 Group(ActiveDiagnosticGroup),
1733}
1734
1735#[derive(Serialize, Deserialize, Clone, Debug)]
1736pub struct ClipboardSelection {
1737 /// The number of bytes in this selection.
1738 pub len: usize,
1739 /// Whether this was a full-line selection.
1740 pub is_entire_line: bool,
1741 /// The indentation of the first line when this content was originally copied.
1742 pub first_line_indent: u32,
1743 #[serde(default)]
1744 pub file_path: Option<PathBuf>,
1745 #[serde(default)]
1746 pub line_range: Option<RangeInclusive<u32>>,
1747}
1748
1749impl ClipboardSelection {
1750 pub fn for_buffer(
1751 len: usize,
1752 is_entire_line: bool,
1753 range: Range<Point>,
1754 buffer: &MultiBufferSnapshot,
1755 project: Option<&Entity<Project>>,
1756 cx: &App,
1757 ) -> Self {
1758 let first_line_indent = buffer
1759 .indent_size_for_line(MultiBufferRow(range.start.row))
1760 .len;
1761
1762 let file_path = util::maybe!({
1763 let project = project?.read(cx);
1764 let file = buffer.file_at(range.start)?;
1765 let project_path = ProjectPath {
1766 worktree_id: file.worktree_id(cx),
1767 path: file.path().clone(),
1768 };
1769 project.absolute_path(&project_path, cx)
1770 });
1771
1772 let line_range = file_path.as_ref().and_then(|_| {
1773 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1774 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1775 if start_excerpt_id == end_excerpt_id {
1776 Some(start_point.row..=end_point.row)
1777 } else {
1778 None
1779 }
1780 });
1781
1782 Self {
1783 len,
1784 is_entire_line,
1785 first_line_indent,
1786 file_path,
1787 line_range,
1788 }
1789 }
1790}
1791
1792// selections, scroll behavior, was newest selection reversed
1793type SelectSyntaxNodeHistoryState = (
1794 Box<[Selection<Anchor>]>,
1795 SelectSyntaxNodeScrollBehavior,
1796 bool,
1797);
1798
1799#[derive(Default)]
1800struct SelectSyntaxNodeHistory {
1801 stack: Vec<SelectSyntaxNodeHistoryState>,
1802 // disable temporarily to allow changing selections without losing the stack
1803 pub disable_clearing: bool,
1804}
1805
1806impl SelectSyntaxNodeHistory {
1807 pub fn try_clear(&mut self) {
1808 if !self.disable_clearing {
1809 self.stack.clear();
1810 }
1811 }
1812
1813 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1814 self.stack.push(selection);
1815 }
1816
1817 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1818 self.stack.pop()
1819 }
1820}
1821
1822enum SelectSyntaxNodeScrollBehavior {
1823 CursorTop,
1824 FitSelection,
1825 CursorBottom,
1826}
1827
1828#[derive(Debug, Clone, Copy)]
1829pub(crate) struct NavigationData {
1830 cursor_anchor: Anchor,
1831 cursor_position: Point,
1832 scroll_anchor: ScrollAnchor,
1833 scroll_top_row: u32,
1834}
1835
1836#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1837pub enum GotoDefinitionKind {
1838 Symbol,
1839 Declaration,
1840 Type,
1841 Implementation,
1842}
1843
1844pub enum FormatTarget {
1845 Buffers(HashSet<Entity<Buffer>>),
1846 Ranges(Vec<Range<MultiBufferPoint>>),
1847}
1848
1849pub(crate) struct FocusedBlock {
1850 id: BlockId,
1851 focus_handle: WeakFocusHandle,
1852}
1853
1854#[derive(Clone, Debug)]
1855pub enum JumpData {
1856 MultiBufferRow {
1857 row: MultiBufferRow,
1858 line_offset_from_top: u32,
1859 },
1860 MultiBufferPoint {
1861 excerpt_id: ExcerptId,
1862 position: Point,
1863 anchor: text::Anchor,
1864 line_offset_from_top: u32,
1865 },
1866}
1867
1868pub enum MultibufferSelectionMode {
1869 First,
1870 All,
1871}
1872
1873#[derive(Clone, Copy, Debug, Default)]
1874pub struct RewrapOptions {
1875 pub override_language_settings: bool,
1876 pub preserve_existing_whitespace: bool,
1877}
1878
1879impl Editor {
1880 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1881 let buffer = cx.new(|cx| Buffer::local("", cx));
1882 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1883 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1884 }
1885
1886 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1887 let buffer = cx.new(|cx| Buffer::local("", cx));
1888 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1889 Self::new(EditorMode::full(), buffer, None, window, cx)
1890 }
1891
1892 pub fn auto_height(
1893 min_lines: usize,
1894 max_lines: usize,
1895 window: &mut Window,
1896 cx: &mut Context<Self>,
1897 ) -> Self {
1898 let buffer = cx.new(|cx| Buffer::local("", cx));
1899 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1900 Self::new(
1901 EditorMode::AutoHeight {
1902 min_lines,
1903 max_lines: Some(max_lines),
1904 },
1905 buffer,
1906 None,
1907 window,
1908 cx,
1909 )
1910 }
1911
1912 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1913 /// The editor grows as tall as needed to fit its content.
1914 pub fn auto_height_unbounded(
1915 min_lines: usize,
1916 window: &mut Window,
1917 cx: &mut Context<Self>,
1918 ) -> Self {
1919 let buffer = cx.new(|cx| Buffer::local("", cx));
1920 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1921 Self::new(
1922 EditorMode::AutoHeight {
1923 min_lines,
1924 max_lines: None,
1925 },
1926 buffer,
1927 None,
1928 window,
1929 cx,
1930 )
1931 }
1932
1933 pub fn for_buffer(
1934 buffer: Entity<Buffer>,
1935 project: Option<Entity<Project>>,
1936 window: &mut Window,
1937 cx: &mut Context<Self>,
1938 ) -> Self {
1939 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1940 Self::new(EditorMode::full(), buffer, project, window, cx)
1941 }
1942
1943 pub fn for_multibuffer(
1944 buffer: Entity<MultiBuffer>,
1945 project: Option<Entity<Project>>,
1946 window: &mut Window,
1947 cx: &mut Context<Self>,
1948 ) -> Self {
1949 Self::new(EditorMode::full(), buffer, project, window, cx)
1950 }
1951
1952 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1953 let mut clone = Self::new(
1954 self.mode.clone(),
1955 self.buffer.clone(),
1956 self.project.clone(),
1957 window,
1958 cx,
1959 );
1960 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1961 let snapshot = display_map.snapshot(cx);
1962 clone.display_map.update(cx, |display_map, cx| {
1963 display_map.set_state(&snapshot, cx);
1964 });
1965 snapshot
1966 });
1967 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1968 clone.folds_did_change(cx);
1969 clone.selections.clone_state(&self.selections);
1970 clone
1971 .scroll_manager
1972 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1973 clone.searchable = self.searchable;
1974 clone.read_only = self.read_only;
1975 clone
1976 }
1977
1978 pub fn new(
1979 mode: EditorMode,
1980 buffer: Entity<MultiBuffer>,
1981 project: Option<Entity<Project>>,
1982 window: &mut Window,
1983 cx: &mut Context<Self>,
1984 ) -> Self {
1985 Editor::new_internal(mode, buffer, project, None, window, cx)
1986 }
1987
1988 pub fn refresh_sticky_headers(
1989 &mut self,
1990 display_snapshot: &DisplaySnapshot,
1991 cx: &mut Context<Editor>,
1992 ) {
1993 if !self.mode.is_full() {
1994 return;
1995 }
1996 let multi_buffer = display_snapshot.buffer_snapshot();
1997 let scroll_anchor = self
1998 .scroll_manager
1999 .native_anchor(display_snapshot, cx)
2000 .anchor;
2001 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2002 return;
2003 };
2004 let buffer = buffer.clone();
2005
2006 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2007 let max_row = buffer.max_point().row;
2008 let start_row = buffer_visible_start.row.min(max_row);
2009 let end_row = (buffer_visible_start.row + 10).min(max_row);
2010
2011 let syntax = self.style(cx).syntax.clone();
2012 let background_task = cx.background_spawn(async move {
2013 buffer
2014 .outline_items_containing(
2015 Point::new(start_row, 0)..Point::new(end_row, 0),
2016 true,
2017 Some(syntax.as_ref()),
2018 )
2019 .into_iter()
2020 .map(|outline_item| OutlineItem {
2021 depth: outline_item.depth,
2022 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2023 source_range_for_text: Anchor::range_in_buffer(
2024 excerpt_id,
2025 outline_item.source_range_for_text,
2026 ),
2027 text: outline_item.text,
2028 highlight_ranges: outline_item.highlight_ranges,
2029 name_ranges: outline_item.name_ranges,
2030 body_range: outline_item
2031 .body_range
2032 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2033 annotation_range: outline_item
2034 .annotation_range
2035 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2036 })
2037 .collect()
2038 });
2039 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2040 let sticky_headers = background_task.await;
2041 this.update(cx, |this, cx| {
2042 this.sticky_headers = Some(sticky_headers);
2043 cx.notify();
2044 })
2045 .ok();
2046 });
2047 }
2048
2049 fn new_internal(
2050 mode: EditorMode,
2051 multi_buffer: Entity<MultiBuffer>,
2052 project: Option<Entity<Project>>,
2053 display_map: Option<Entity<DisplayMap>>,
2054 window: &mut Window,
2055 cx: &mut Context<Self>,
2056 ) -> Self {
2057 debug_assert!(
2058 display_map.is_none() || mode.is_minimap(),
2059 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2060 );
2061
2062 let full_mode = mode.is_full();
2063 let is_minimap = mode.is_minimap();
2064 let diagnostics_max_severity = if full_mode {
2065 EditorSettings::get_global(cx)
2066 .diagnostics_max_severity
2067 .unwrap_or(DiagnosticSeverity::Hint)
2068 } else {
2069 DiagnosticSeverity::Off
2070 };
2071 let style = window.text_style();
2072 let font_size = style.font_size.to_pixels(window.rem_size());
2073 let editor = cx.entity().downgrade();
2074 let fold_placeholder = FoldPlaceholder {
2075 constrain_width: false,
2076 render: Arc::new(move |fold_id, fold_range, cx| {
2077 let editor = editor.clone();
2078 FoldPlaceholder::fold_element(fold_id, cx)
2079 .cursor_pointer()
2080 .child("⋯")
2081 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2082 .on_click(move |_, _window, cx| {
2083 editor
2084 .update(cx, |editor, cx| {
2085 editor.unfold_ranges(
2086 &[fold_range.start..fold_range.end],
2087 true,
2088 false,
2089 cx,
2090 );
2091 cx.stop_propagation();
2092 })
2093 .ok();
2094 })
2095 .into_any()
2096 }),
2097 merge_adjacent: true,
2098 ..FoldPlaceholder::default()
2099 };
2100 let display_map = display_map.unwrap_or_else(|| {
2101 cx.new(|cx| {
2102 DisplayMap::new(
2103 multi_buffer.clone(),
2104 style.font(),
2105 font_size,
2106 None,
2107 FILE_HEADER_HEIGHT,
2108 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2109 fold_placeholder,
2110 diagnostics_max_severity,
2111 cx,
2112 )
2113 })
2114 });
2115
2116 let selections = SelectionsCollection::new();
2117
2118 let blink_manager = cx.new(|cx| {
2119 let mut blink_manager = BlinkManager::new(
2120 CURSOR_BLINK_INTERVAL,
2121 |cx| EditorSettings::get_global(cx).cursor_blink,
2122 cx,
2123 );
2124 if is_minimap {
2125 blink_manager.disable(cx);
2126 }
2127 blink_manager
2128 });
2129
2130 let soft_wrap_mode_override =
2131 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2132
2133 let mut project_subscriptions = Vec::new();
2134 if full_mode && let Some(project) = project.as_ref() {
2135 project_subscriptions.push(cx.subscribe_in(
2136 project,
2137 window,
2138 |editor, _, event, window, cx| match event {
2139 project::Event::RefreshCodeLens => {
2140 // we always query lens with actions, without storing them, always refreshing them
2141 }
2142 project::Event::RefreshInlayHints {
2143 server_id,
2144 request_id,
2145 } => {
2146 editor.refresh_inlay_hints(
2147 InlayHintRefreshReason::RefreshRequested {
2148 server_id: *server_id,
2149 request_id: *request_id,
2150 },
2151 cx,
2152 );
2153 }
2154 project::Event::RefreshSemanticTokens {
2155 server_id,
2156 request_id,
2157 } => {
2158 editor.refresh_semantic_tokens(
2159 None,
2160 Some(RefreshForServer {
2161 server_id: *server_id,
2162 request_id: *request_id,
2163 }),
2164 cx,
2165 );
2166 }
2167 project::Event::LanguageServerRemoved(_) => {
2168 editor.registered_buffers.clear();
2169 editor.register_visible_buffers(cx);
2170 editor.invalidate_semantic_tokens(None);
2171 editor.update_lsp_data(None, window, cx);
2172 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2173 if editor.tasks_update_task.is_none() {
2174 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2175 }
2176 }
2177 project::Event::LanguageServerAdded(..) => {
2178 if editor.tasks_update_task.is_none() {
2179 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2180 }
2181 }
2182 project::Event::SnippetEdit(id, snippet_edits) => {
2183 // todo(lw): Non singletons
2184 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2185 let snapshot = buffer.read(cx).snapshot();
2186 let focus_handle = editor.focus_handle(cx);
2187 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2188 for (range, snippet) in snippet_edits {
2189 let buffer_range =
2190 language::range_from_lsp(*range).to_offset(&snapshot);
2191 editor
2192 .insert_snippet(
2193 &[MultiBufferOffset(buffer_range.start)
2194 ..MultiBufferOffset(buffer_range.end)],
2195 snippet.clone(),
2196 window,
2197 cx,
2198 )
2199 .ok();
2200 }
2201 }
2202 }
2203 }
2204 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2205 let buffer_id = *buffer_id;
2206 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2207 editor.register_buffer(buffer_id, cx);
2208 editor.update_lsp_data(Some(buffer_id), window, cx);
2209 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2210 refresh_linked_ranges(editor, window, cx);
2211 editor.refresh_code_actions(window, cx);
2212 editor.refresh_document_highlights(cx);
2213 }
2214 }
2215
2216 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2217 let Some(workspace) = editor.workspace() else {
2218 return;
2219 };
2220 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2221 else {
2222 return;
2223 };
2224
2225 if active_editor.entity_id() == cx.entity_id() {
2226 let entity_id = cx.entity_id();
2227 workspace.update(cx, |this, cx| {
2228 this.panes_mut()
2229 .iter_mut()
2230 .filter(|pane| pane.entity_id() != entity_id)
2231 .for_each(|p| {
2232 p.update(cx, |pane, _| {
2233 pane.nav_history_mut().rename_item(
2234 entity_id,
2235 project_path.clone(),
2236 abs_path.clone().into(),
2237 );
2238 })
2239 });
2240 });
2241
2242 Self::open_transaction_for_hidden_buffers(
2243 workspace,
2244 transaction.clone(),
2245 "Rename".to_string(),
2246 window,
2247 cx,
2248 );
2249 }
2250 }
2251
2252 project::Event::WorkspaceEditApplied(transaction) => {
2253 let Some(workspace) = editor.workspace() else {
2254 return;
2255 };
2256 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2257 else {
2258 return;
2259 };
2260
2261 if active_editor.entity_id() == cx.entity_id() {
2262 Self::open_transaction_for_hidden_buffers(
2263 workspace,
2264 transaction.clone(),
2265 "LSP Edit".to_string(),
2266 window,
2267 cx,
2268 );
2269 }
2270 }
2271
2272 _ => {}
2273 },
2274 ));
2275 if let Some(task_inventory) = project
2276 .read(cx)
2277 .task_store()
2278 .read(cx)
2279 .task_inventory()
2280 .cloned()
2281 {
2282 project_subscriptions.push(cx.observe_in(
2283 &task_inventory,
2284 window,
2285 |editor, _, window, cx| {
2286 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2287 },
2288 ));
2289 };
2290
2291 project_subscriptions.push(cx.subscribe_in(
2292 &project.read(cx).breakpoint_store(),
2293 window,
2294 |editor, _, event, window, cx| match event {
2295 BreakpointStoreEvent::ClearDebugLines => {
2296 editor.clear_row_highlights::<ActiveDebugLine>();
2297 editor.refresh_inline_values(cx);
2298 }
2299 BreakpointStoreEvent::SetDebugLine => {
2300 if editor.go_to_active_debug_line(window, cx) {
2301 cx.stop_propagation();
2302 }
2303
2304 editor.refresh_inline_values(cx);
2305 }
2306 _ => {}
2307 },
2308 ));
2309 let git_store = project.read(cx).git_store().clone();
2310 let project = project.clone();
2311 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2312 if let GitStoreEvent::RepositoryAdded = event {
2313 this.load_diff_task = Some(
2314 update_uncommitted_diff_for_buffer(
2315 cx.entity(),
2316 &project,
2317 this.buffer.read(cx).all_buffers(),
2318 this.buffer.clone(),
2319 cx,
2320 )
2321 .shared(),
2322 );
2323 }
2324 }));
2325 }
2326
2327 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2328
2329 let inlay_hint_settings =
2330 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2331 let focus_handle = cx.focus_handle();
2332 if !is_minimap {
2333 cx.on_focus(&focus_handle, window, Self::handle_focus)
2334 .detach();
2335 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2336 .detach();
2337 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2338 .detach();
2339 cx.on_blur(&focus_handle, window, Self::handle_blur)
2340 .detach();
2341 cx.observe_pending_input(window, Self::observe_pending_input)
2342 .detach();
2343 }
2344
2345 let show_indent_guides =
2346 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2347 Some(false)
2348 } else {
2349 None
2350 };
2351
2352 let breakpoint_store = match (&mode, project.as_ref()) {
2353 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2354 _ => None,
2355 };
2356
2357 let mut code_action_providers = Vec::new();
2358 let mut load_uncommitted_diff = None;
2359 if let Some(project) = project.clone() {
2360 load_uncommitted_diff = Some(
2361 update_uncommitted_diff_for_buffer(
2362 cx.entity(),
2363 &project,
2364 multi_buffer.read(cx).all_buffers(),
2365 multi_buffer.clone(),
2366 cx,
2367 )
2368 .shared(),
2369 );
2370 code_action_providers.push(Rc::new(project) as Rc<_>);
2371 }
2372
2373 let mut editor = Self {
2374 focus_handle,
2375 show_cursor_when_unfocused: false,
2376 last_focused_descendant: None,
2377 buffer: multi_buffer.clone(),
2378 display_map: display_map.clone(),
2379 placeholder_display_map: None,
2380 selections,
2381 scroll_manager: ScrollManager::new(cx),
2382 columnar_selection_state: None,
2383 add_selections_state: None,
2384 select_next_state: None,
2385 select_prev_state: None,
2386 selection_history: SelectionHistory::default(),
2387 defer_selection_effects: false,
2388 deferred_selection_effects_state: None,
2389 autoclose_regions: Vec::new(),
2390 snippet_stack: InvalidationStack::default(),
2391 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2392 ime_transaction: None,
2393 active_diagnostics: ActiveDiagnostic::None,
2394 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2395 inline_diagnostics_update: Task::ready(()),
2396 inline_diagnostics: Vec::new(),
2397 soft_wrap_mode_override,
2398 diagnostics_max_severity,
2399 hard_wrap: None,
2400 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2401 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2402 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2403 project,
2404 blink_manager: blink_manager.clone(),
2405 show_local_selections: true,
2406 show_scrollbars: ScrollbarAxes {
2407 horizontal: full_mode,
2408 vertical: full_mode,
2409 },
2410 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2411 offset_content: !matches!(mode, EditorMode::SingleLine),
2412 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2413 show_gutter: full_mode,
2414 show_line_numbers: (!full_mode).then_some(false),
2415 use_relative_line_numbers: None,
2416 disable_expand_excerpt_buttons: !full_mode,
2417 delegate_expand_excerpts: false,
2418 delegate_stage_and_restore: false,
2419 delegate_open_excerpts: false,
2420 enable_lsp_data: true,
2421 enable_runnables: true,
2422 show_git_diff_gutter: None,
2423 show_code_actions: None,
2424 show_runnables: None,
2425 show_breakpoints: None,
2426 show_diff_review_button: false,
2427 show_wrap_guides: None,
2428 show_indent_guides,
2429 buffers_with_disabled_indent_guides: HashSet::default(),
2430 highlight_order: 0,
2431 highlighted_rows: HashMap::default(),
2432 background_highlights: HashMap::default(),
2433 gutter_highlights: HashMap::default(),
2434 scrollbar_marker_state: ScrollbarMarkerState::default(),
2435 active_indent_guides_state: ActiveIndentGuidesState::default(),
2436 nav_history: None,
2437 context_menu: RefCell::new(None),
2438 context_menu_options: None,
2439 mouse_context_menu: None,
2440 completion_tasks: Vec::new(),
2441 inline_blame_popover: None,
2442 inline_blame_popover_show_task: None,
2443 signature_help_state: SignatureHelpState::default(),
2444 auto_signature_help: None,
2445 find_all_references_task_sources: Vec::new(),
2446 next_completion_id: 0,
2447 next_inlay_id: 0,
2448 code_action_providers,
2449 available_code_actions: None,
2450 code_actions_task: None,
2451 quick_selection_highlight_task: None,
2452 debounced_selection_highlight_task: None,
2453 debounced_selection_highlight_complete: false,
2454 document_highlights_task: None,
2455 linked_editing_range_task: None,
2456 pending_rename: None,
2457 searchable: !is_minimap,
2458 cursor_shape: EditorSettings::get_global(cx)
2459 .cursor_shape
2460 .unwrap_or_default(),
2461 cursor_offset_on_selection: false,
2462 current_line_highlight: None,
2463 autoindent_mode: Some(AutoindentMode::EachLine),
2464 collapse_matches: false,
2465 workspace: None,
2466 input_enabled: !is_minimap,
2467 use_modal_editing: full_mode,
2468 read_only: is_minimap,
2469 use_autoclose: true,
2470 use_auto_surround: true,
2471 auto_replace_emoji_shortcode: false,
2472 jsx_tag_auto_close_enabled_in_any_buffer: false,
2473 leader_id: None,
2474 remote_id: None,
2475 hover_state: HoverState::default(),
2476 pending_mouse_down: None,
2477 prev_pressure_stage: None,
2478 hovered_link_state: None,
2479 edit_prediction_provider: None,
2480 active_edit_prediction: None,
2481 stale_edit_prediction_in_menu: None,
2482 edit_prediction_preview: EditPredictionPreview::Inactive {
2483 released_too_fast: false,
2484 },
2485 inline_diagnostics_enabled: full_mode,
2486 diagnostics_enabled: full_mode,
2487 word_completions_enabled: full_mode,
2488 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2489 gutter_hovered: false,
2490 pixel_position_of_newest_cursor: None,
2491 last_bounds: None,
2492 last_position_map: None,
2493 expect_bounds_change: None,
2494 gutter_dimensions: GutterDimensions::default(),
2495 style: None,
2496 show_cursor_names: false,
2497 hovered_cursors: HashMap::default(),
2498 next_editor_action_id: EditorActionId::default(),
2499 editor_actions: Rc::default(),
2500 edit_predictions_hidden_for_vim_mode: false,
2501 show_edit_predictions_override: None,
2502 show_completions_on_input_override: None,
2503 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2504 edit_prediction_settings: EditPredictionSettings::Disabled,
2505 edit_prediction_indent_conflict: false,
2506 edit_prediction_requires_modifier_in_indent_conflict: true,
2507 custom_context_menu: None,
2508 show_git_blame_gutter: false,
2509 show_git_blame_inline: false,
2510 show_selection_menu: None,
2511 show_git_blame_inline_delay_task: None,
2512 git_blame_inline_enabled: full_mode
2513 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2514 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2515 buffer_serialization: is_minimap.not().then(|| {
2516 BufferSerialization::new(
2517 ProjectSettings::get_global(cx)
2518 .session
2519 .restore_unsaved_buffers,
2520 )
2521 }),
2522 blame: None,
2523 blame_subscription: None,
2524 tasks: BTreeMap::default(),
2525
2526 breakpoint_store,
2527 gutter_breakpoint_indicator: (None, None),
2528 gutter_diff_review_indicator: (None, None),
2529 diff_review_drag_state: None,
2530 diff_review_overlays: Vec::new(),
2531 stored_review_comments: Vec::new(),
2532 next_review_comment_id: 0,
2533 hovered_diff_hunk_row: None,
2534 _subscriptions: (!is_minimap)
2535 .then(|| {
2536 vec![
2537 cx.observe(&multi_buffer, Self::on_buffer_changed),
2538 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2539 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2540 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2541 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2542 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2543 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2544 cx.observe_window_activation(window, |editor, window, cx| {
2545 let active = window.is_window_active();
2546 editor.blink_manager.update(cx, |blink_manager, cx| {
2547 if active {
2548 blink_manager.enable(cx);
2549 } else {
2550 blink_manager.disable(cx);
2551 }
2552 });
2553 if active {
2554 editor.show_mouse_cursor(cx);
2555 }
2556 }),
2557 ]
2558 })
2559 .unwrap_or_default(),
2560 tasks_update_task: None,
2561 pull_diagnostics_task: Task::ready(()),
2562 colors: None,
2563 refresh_colors_task: Task::ready(()),
2564 use_document_folding_ranges: false,
2565 refresh_folding_ranges_task: Task::ready(()),
2566 inlay_hints: None,
2567 next_color_inlay_id: 0,
2568 post_scroll_update: Task::ready(()),
2569 linked_edit_ranges: Default::default(),
2570 in_project_search: false,
2571 previous_search_ranges: None,
2572 breadcrumb_header: None,
2573 focused_block: None,
2574 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2575 addons: HashMap::default(),
2576 registered_buffers: HashMap::default(),
2577 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2578 selection_mark_mode: false,
2579 toggle_fold_multiple_buffers: Task::ready(()),
2580 serialize_selections: Task::ready(()),
2581 serialize_folds: Task::ready(()),
2582 text_style_refinement: None,
2583 load_diff_task: load_uncommitted_diff,
2584 temporary_diff_override: false,
2585 mouse_cursor_hidden: false,
2586 minimap: None,
2587 hide_mouse_mode: EditorSettings::get_global(cx)
2588 .hide_mouse
2589 .unwrap_or_default(),
2590 change_list: ChangeList::new(),
2591 mode,
2592 selection_drag_state: SelectionDragState::None,
2593 folding_newlines: Task::ready(()),
2594 lookup_key: None,
2595 select_next_is_case_sensitive: None,
2596 on_local_selections_changed: None,
2597 suppress_selection_callback: false,
2598 applicable_language_settings: HashMap::default(),
2599 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2600 accent_data: None,
2601 fetched_tree_sitter_chunks: HashMap::default(),
2602 number_deleted_lines: false,
2603 refresh_matching_bracket_highlights_task: Task::ready(()),
2604 refresh_document_symbols_task: Task::ready(()).shared(),
2605 lsp_document_symbols: HashMap::default(),
2606 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2607 outline_symbols_at_cursor: None,
2608 sticky_headers_task: Task::ready(()),
2609 sticky_headers: None,
2610 };
2611
2612 if is_minimap {
2613 return editor;
2614 }
2615
2616 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2617 editor.accent_data = editor.fetch_accent_data(cx);
2618
2619 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2620 editor
2621 ._subscriptions
2622 .push(cx.observe(breakpoints, |_, _, cx| {
2623 cx.notify();
2624 }));
2625 }
2626 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2627 editor._subscriptions.extend(project_subscriptions);
2628
2629 editor._subscriptions.push(cx.subscribe_in(
2630 &cx.entity(),
2631 window,
2632 |editor, _, e: &EditorEvent, window, cx| match e {
2633 EditorEvent::ScrollPositionChanged { local, .. } => {
2634 if *local {
2635 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2636 editor.inline_blame_popover.take();
2637 let snapshot = editor.snapshot(window, cx);
2638 let new_anchor = editor
2639 .scroll_manager
2640 .native_anchor(&snapshot.display_snapshot, cx);
2641 editor.update_restoration_data(cx, move |data| {
2642 data.scroll_position = (
2643 new_anchor.top_row(snapshot.buffer_snapshot()),
2644 new_anchor.offset,
2645 );
2646 });
2647
2648 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2649 cx.background_executor()
2650 .timer(Duration::from_millis(50))
2651 .await;
2652 editor
2653 .update_in(cx, |editor, window, cx| {
2654 editor.register_visible_buffers(cx);
2655 editor.colorize_brackets(false, cx);
2656 editor.refresh_inlay_hints(
2657 InlayHintRefreshReason::NewLinesShown,
2658 cx,
2659 );
2660 if !editor.buffer().read(cx).is_singleton() {
2661 editor.update_lsp_data(None, window, cx);
2662 }
2663 })
2664 .ok();
2665 });
2666 }
2667 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2668 }
2669 EditorEvent::Edited { .. } => {
2670 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2671 .map(|vim_mode| vim_mode.0)
2672 .unwrap_or(false);
2673 if !vim_mode {
2674 let display_map = editor.display_snapshot(cx);
2675 let selections = editor.selections.all_adjusted_display(&display_map);
2676 let pop_state = editor
2677 .change_list
2678 .last()
2679 .map(|previous| {
2680 previous.len() == selections.len()
2681 && previous.iter().enumerate().all(|(ix, p)| {
2682 p.to_display_point(&display_map).row()
2683 == selections[ix].head().row()
2684 })
2685 })
2686 .unwrap_or(false);
2687 let new_positions = selections
2688 .into_iter()
2689 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2690 .collect();
2691 editor
2692 .change_list
2693 .push_to_change_list(pop_state, new_positions);
2694 }
2695 }
2696 _ => (),
2697 },
2698 ));
2699
2700 if let Some(dap_store) = editor
2701 .project
2702 .as_ref()
2703 .map(|project| project.read(cx).dap_store())
2704 {
2705 let weak_editor = cx.weak_entity();
2706
2707 editor
2708 ._subscriptions
2709 .push(
2710 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2711 let session_entity = cx.entity();
2712 weak_editor
2713 .update(cx, |editor, cx| {
2714 editor._subscriptions.push(
2715 cx.subscribe(&session_entity, Self::on_debug_session_event),
2716 );
2717 })
2718 .ok();
2719 }),
2720 );
2721
2722 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2723 editor
2724 ._subscriptions
2725 .push(cx.subscribe(&session, Self::on_debug_session_event));
2726 }
2727 }
2728
2729 // skip adding the initial selection to selection history
2730 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2731 editor.end_selection(window, cx);
2732 editor.selection_history.mode = SelectionHistoryMode::Normal;
2733
2734 editor.scroll_manager.show_scrollbars(window, cx);
2735 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2736
2737 if full_mode {
2738 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2739 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2740
2741 if editor.git_blame_inline_enabled {
2742 editor.start_git_blame_inline(false, window, cx);
2743 }
2744
2745 editor.go_to_active_debug_line(window, cx);
2746
2747 editor.minimap =
2748 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2749 editor.colors = Some(LspColorData::new(cx));
2750 editor.use_document_folding_ranges = true;
2751 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2752
2753 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2754 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2755 }
2756 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2757 }
2758
2759 editor
2760 }
2761
2762 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2763 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2764 }
2765
2766 pub fn deploy_mouse_context_menu(
2767 &mut self,
2768 position: gpui::Point<Pixels>,
2769 context_menu: Entity<ContextMenu>,
2770 window: &mut Window,
2771 cx: &mut Context<Self>,
2772 ) {
2773 self.mouse_context_menu = Some(MouseContextMenu::new(
2774 self,
2775 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2776 context_menu,
2777 window,
2778 cx,
2779 ));
2780 }
2781
2782 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2783 self.mouse_context_menu
2784 .as_ref()
2785 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2786 }
2787
2788 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2789 if self
2790 .selections
2791 .pending_anchor()
2792 .is_some_and(|pending_selection| {
2793 let snapshot = self.buffer().read(cx).snapshot(cx);
2794 pending_selection.range().includes(range, &snapshot)
2795 })
2796 {
2797 return true;
2798 }
2799
2800 self.selections
2801 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2802 .into_iter()
2803 .any(|selection| {
2804 // This is needed to cover a corner case, if we just check for an existing
2805 // selection in the fold range, having a cursor at the start of the fold
2806 // marks it as selected. Non-empty selections don't cause this.
2807 let length = selection.end - selection.start;
2808 length > 0
2809 })
2810 }
2811
2812 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2813 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2814 }
2815
2816 fn key_context_internal(
2817 &self,
2818 has_active_edit_prediction: bool,
2819 window: &mut Window,
2820 cx: &mut App,
2821 ) -> KeyContext {
2822 let mut key_context = KeyContext::new_with_defaults();
2823 key_context.add("Editor");
2824 let mode = match self.mode {
2825 EditorMode::SingleLine => "single_line",
2826 EditorMode::AutoHeight { .. } => "auto_height",
2827 EditorMode::Minimap { .. } => "minimap",
2828 EditorMode::Full { .. } => "full",
2829 };
2830
2831 if EditorSettings::jupyter_enabled(cx) {
2832 key_context.add("jupyter");
2833 }
2834
2835 key_context.set("mode", mode);
2836 if self.pending_rename.is_some() {
2837 key_context.add("renaming");
2838 }
2839
2840 if let Some(snippet_stack) = self.snippet_stack.last() {
2841 key_context.add("in_snippet");
2842
2843 if snippet_stack.active_index > 0 {
2844 key_context.add("has_previous_tabstop");
2845 }
2846
2847 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2848 key_context.add("has_next_tabstop");
2849 }
2850 }
2851
2852 match self.context_menu.borrow().as_ref() {
2853 Some(CodeContextMenu::Completions(menu)) => {
2854 if menu.visible() {
2855 key_context.add("menu");
2856 key_context.add("showing_completions");
2857 }
2858 }
2859 Some(CodeContextMenu::CodeActions(menu)) => {
2860 if menu.visible() {
2861 key_context.add("menu");
2862 key_context.add("showing_code_actions")
2863 }
2864 }
2865 None => {}
2866 }
2867
2868 if self.signature_help_state.has_multiple_signatures() {
2869 key_context.add("showing_signature_help");
2870 }
2871
2872 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2873 if !self.focus_handle(cx).contains_focused(window, cx)
2874 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2875 {
2876 for addon in self.addons.values() {
2877 addon.extend_key_context(&mut key_context, cx)
2878 }
2879 }
2880
2881 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2882 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2883 Some(
2884 file.full_path(cx)
2885 .extension()?
2886 .to_string_lossy()
2887 .to_lowercase(),
2888 )
2889 }) {
2890 key_context.set("extension", extension);
2891 }
2892 } else {
2893 key_context.add("multibuffer");
2894 }
2895
2896 if has_active_edit_prediction {
2897 if self.edit_prediction_in_conflict() {
2898 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2899 } else {
2900 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2901 key_context.add("copilot_suggestion");
2902 }
2903 }
2904
2905 if self.selection_mark_mode {
2906 key_context.add("selection_mode");
2907 }
2908
2909 let disjoint = self.selections.disjoint_anchors();
2910 let snapshot = self.snapshot(window, cx);
2911 let snapshot = snapshot.buffer_snapshot();
2912 if self.mode == EditorMode::SingleLine
2913 && let [selection] = disjoint
2914 && selection.start == selection.end
2915 && selection.end.to_offset(snapshot) == snapshot.len()
2916 {
2917 key_context.add("end_of_input");
2918 }
2919
2920 if self.has_any_expanded_diff_hunks(cx) {
2921 key_context.add("diffs_expanded");
2922 }
2923
2924 key_context
2925 }
2926
2927 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2928 self.last_bounds.as_ref()
2929 }
2930
2931 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2932 if self.mouse_cursor_hidden {
2933 self.mouse_cursor_hidden = false;
2934 cx.notify();
2935 }
2936 }
2937
2938 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2939 let hide_mouse_cursor = match origin {
2940 HideMouseCursorOrigin::TypingAction => {
2941 matches!(
2942 self.hide_mouse_mode,
2943 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2944 )
2945 }
2946 HideMouseCursorOrigin::MovementAction => {
2947 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2948 }
2949 };
2950 if self.mouse_cursor_hidden != hide_mouse_cursor {
2951 self.mouse_cursor_hidden = hide_mouse_cursor;
2952 cx.notify();
2953 }
2954 }
2955
2956 pub fn edit_prediction_in_conflict(&self) -> bool {
2957 if !self.show_edit_predictions_in_menu() {
2958 return false;
2959 }
2960
2961 let showing_completions = self
2962 .context_menu
2963 .borrow()
2964 .as_ref()
2965 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2966
2967 showing_completions
2968 || self.edit_prediction_requires_modifier()
2969 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2970 // bindings to insert tab characters.
2971 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2972 }
2973
2974 pub fn accept_edit_prediction_keybind(
2975 &self,
2976 granularity: EditPredictionGranularity,
2977 window: &mut Window,
2978 cx: &mut App,
2979 ) -> AcceptEditPredictionBinding {
2980 let key_context = self.key_context_internal(true, window, cx);
2981 let in_conflict = self.edit_prediction_in_conflict();
2982
2983 let bindings =
2984 match granularity {
2985 EditPredictionGranularity::Word => window
2986 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2987 EditPredictionGranularity::Line => window
2988 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2989 EditPredictionGranularity::Full => {
2990 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2991 }
2992 };
2993
2994 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2995 !in_conflict
2996 || binding
2997 .keystrokes()
2998 .first()
2999 .is_some_and(|keystroke| keystroke.modifiers().modified())
3000 }))
3001 }
3002
3003 pub fn new_file(
3004 workspace: &mut Workspace,
3005 _: &workspace::NewFile,
3006 window: &mut Window,
3007 cx: &mut Context<Workspace>,
3008 ) {
3009 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3010 "Failed to create buffer",
3011 window,
3012 cx,
3013 |e, _, _| match e.error_code() {
3014 ErrorCode::RemoteUpgradeRequired => Some(format!(
3015 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3016 e.error_tag("required").unwrap_or("the latest version")
3017 )),
3018 _ => None,
3019 },
3020 );
3021 }
3022
3023 pub fn new_in_workspace(
3024 workspace: &mut Workspace,
3025 window: &mut Window,
3026 cx: &mut Context<Workspace>,
3027 ) -> Task<Result<Entity<Editor>>> {
3028 let project = workspace.project().clone();
3029 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3030
3031 cx.spawn_in(window, async move |workspace, cx| {
3032 let buffer = create.await?;
3033 workspace.update_in(cx, |workspace, window, cx| {
3034 let editor =
3035 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3036 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3037 editor
3038 })
3039 })
3040 }
3041
3042 fn new_file_vertical(
3043 workspace: &mut Workspace,
3044 _: &workspace::NewFileSplitVertical,
3045 window: &mut Window,
3046 cx: &mut Context<Workspace>,
3047 ) {
3048 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3049 }
3050
3051 fn new_file_horizontal(
3052 workspace: &mut Workspace,
3053 _: &workspace::NewFileSplitHorizontal,
3054 window: &mut Window,
3055 cx: &mut Context<Workspace>,
3056 ) {
3057 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3058 }
3059
3060 fn new_file_split(
3061 workspace: &mut Workspace,
3062 action: &workspace::NewFileSplit,
3063 window: &mut Window,
3064 cx: &mut Context<Workspace>,
3065 ) {
3066 Self::new_file_in_direction(workspace, action.0, window, cx)
3067 }
3068
3069 fn new_file_in_direction(
3070 workspace: &mut Workspace,
3071 direction: SplitDirection,
3072 window: &mut Window,
3073 cx: &mut Context<Workspace>,
3074 ) {
3075 let project = workspace.project().clone();
3076 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3077
3078 cx.spawn_in(window, async move |workspace, cx| {
3079 let buffer = create.await?;
3080 workspace.update_in(cx, move |workspace, window, cx| {
3081 workspace.split_item(
3082 direction,
3083 Box::new(
3084 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3085 ),
3086 window,
3087 cx,
3088 )
3089 })?;
3090 anyhow::Ok(())
3091 })
3092 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3093 match e.error_code() {
3094 ErrorCode::RemoteUpgradeRequired => Some(format!(
3095 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3096 e.error_tag("required").unwrap_or("the latest version")
3097 )),
3098 _ => None,
3099 }
3100 });
3101 }
3102
3103 pub fn leader_id(&self) -> Option<CollaboratorId> {
3104 self.leader_id
3105 }
3106
3107 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3108 &self.buffer
3109 }
3110
3111 pub fn project(&self) -> Option<&Entity<Project>> {
3112 self.project.as_ref()
3113 }
3114
3115 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3116 self.workspace.as_ref()?.0.upgrade()
3117 }
3118
3119 /// Detaches a task and shows an error notification in the workspace if available,
3120 /// otherwise just logs the error.
3121 pub fn detach_and_notify_err<R, E>(
3122 &self,
3123 task: Task<Result<R, E>>,
3124 window: &mut Window,
3125 cx: &mut App,
3126 ) where
3127 E: std::fmt::Debug + std::fmt::Display + 'static,
3128 R: 'static,
3129 {
3130 if let Some(workspace) = self.workspace() {
3131 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3132 } else {
3133 task.detach_and_log_err(cx);
3134 }
3135 }
3136
3137 /// Returns the workspace serialization ID if this editor should be serialized.
3138 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3139 self.workspace
3140 .as_ref()
3141 .filter(|_| self.should_serialize_buffer())
3142 .and_then(|workspace| workspace.1)
3143 }
3144
3145 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3146 self.buffer().read(cx).title(cx)
3147 }
3148
3149 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3150 let git_blame_gutter_max_author_length = self
3151 .render_git_blame_gutter(cx)
3152 .then(|| {
3153 if let Some(blame) = self.blame.as_ref() {
3154 let max_author_length =
3155 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3156 Some(max_author_length)
3157 } else {
3158 None
3159 }
3160 })
3161 .flatten();
3162
3163 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3164
3165 EditorSnapshot {
3166 mode: self.mode.clone(),
3167 show_gutter: self.show_gutter,
3168 offset_content: self.offset_content,
3169 show_line_numbers: self.show_line_numbers,
3170 number_deleted_lines: self.number_deleted_lines,
3171 show_git_diff_gutter: self.show_git_diff_gutter,
3172 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3173 show_code_actions: self.show_code_actions,
3174 show_runnables: self.show_runnables,
3175 show_breakpoints: self.show_breakpoints,
3176 git_blame_gutter_max_author_length,
3177 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3178 display_snapshot,
3179 placeholder_display_snapshot: self
3180 .placeholder_display_map
3181 .as_ref()
3182 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3183 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3184 is_focused: self.focus_handle.is_focused(window),
3185 current_line_highlight: self
3186 .current_line_highlight
3187 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3188 gutter_hovered: self.gutter_hovered,
3189 }
3190 }
3191
3192 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3193 self.buffer.read(cx).language_at(point, cx)
3194 }
3195
3196 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3197 self.buffer.read(cx).read(cx).file_at(point).cloned()
3198 }
3199
3200 pub fn active_excerpt(
3201 &self,
3202 cx: &App,
3203 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3204 self.buffer
3205 .read(cx)
3206 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3207 }
3208
3209 pub fn mode(&self) -> &EditorMode {
3210 &self.mode
3211 }
3212
3213 pub fn set_mode(&mut self, mode: EditorMode) {
3214 self.mode = mode;
3215 }
3216
3217 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3218 self.collaboration_hub.as_deref()
3219 }
3220
3221 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3222 self.collaboration_hub = Some(hub);
3223 }
3224
3225 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3226 self.in_project_search = in_project_search;
3227 }
3228
3229 pub fn set_custom_context_menu(
3230 &mut self,
3231 f: impl 'static
3232 + Fn(
3233 &mut Self,
3234 DisplayPoint,
3235 &mut Window,
3236 &mut Context<Self>,
3237 ) -> Option<Entity<ui::ContextMenu>>,
3238 ) {
3239 self.custom_context_menu = Some(Box::new(f))
3240 }
3241
3242 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3243 self.completion_provider = provider;
3244 }
3245
3246 #[cfg(any(test, feature = "test-support"))]
3247 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3248 self.completion_provider.clone()
3249 }
3250
3251 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3252 self.semantics_provider.clone()
3253 }
3254
3255 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3256 self.semantics_provider = provider;
3257 }
3258
3259 pub fn set_edit_prediction_provider<T>(
3260 &mut self,
3261 provider: Option<Entity<T>>,
3262 window: &mut Window,
3263 cx: &mut Context<Self>,
3264 ) where
3265 T: EditPredictionDelegate,
3266 {
3267 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3268 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3269 if this.focus_handle.is_focused(window) {
3270 this.update_visible_edit_prediction(window, cx);
3271 }
3272 }),
3273 provider: Arc::new(provider),
3274 });
3275 self.update_edit_prediction_settings(cx);
3276 self.refresh_edit_prediction(false, false, window, cx);
3277 }
3278
3279 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3280 self.placeholder_display_map
3281 .as_ref()
3282 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3283 }
3284
3285 pub fn set_placeholder_text(
3286 &mut self,
3287 placeholder_text: &str,
3288 window: &mut Window,
3289 cx: &mut Context<Self>,
3290 ) {
3291 let multibuffer = cx
3292 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3293
3294 let style = window.text_style();
3295
3296 self.placeholder_display_map = Some(cx.new(|cx| {
3297 DisplayMap::new(
3298 multibuffer,
3299 style.font(),
3300 style.font_size.to_pixels(window.rem_size()),
3301 None,
3302 FILE_HEADER_HEIGHT,
3303 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3304 Default::default(),
3305 DiagnosticSeverity::Off,
3306 cx,
3307 )
3308 }));
3309 cx.notify();
3310 }
3311
3312 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3313 self.cursor_shape = cursor_shape;
3314
3315 // Disrupt blink for immediate user feedback that the cursor shape has changed
3316 self.blink_manager.update(cx, BlinkManager::show_cursor);
3317
3318 cx.notify();
3319 }
3320
3321 pub fn cursor_shape(&self) -> CursorShape {
3322 self.cursor_shape
3323 }
3324
3325 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3326 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3327 }
3328
3329 pub fn set_current_line_highlight(
3330 &mut self,
3331 current_line_highlight: Option<CurrentLineHighlight>,
3332 ) {
3333 self.current_line_highlight = current_line_highlight;
3334 }
3335
3336 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3337 self.collapse_matches = collapse_matches;
3338 }
3339
3340 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3341 if self.collapse_matches {
3342 return range.start..range.start;
3343 }
3344 range.clone()
3345 }
3346
3347 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3348 self.display_map.read(cx).clip_at_line_ends
3349 }
3350
3351 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3352 if self.display_map.read(cx).clip_at_line_ends != clip {
3353 self.display_map
3354 .update(cx, |map, _| map.clip_at_line_ends = clip);
3355 }
3356 }
3357
3358 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3359 self.input_enabled = input_enabled;
3360 }
3361
3362 pub fn set_edit_predictions_hidden_for_vim_mode(
3363 &mut self,
3364 hidden: bool,
3365 window: &mut Window,
3366 cx: &mut Context<Self>,
3367 ) {
3368 if hidden != self.edit_predictions_hidden_for_vim_mode {
3369 self.edit_predictions_hidden_for_vim_mode = hidden;
3370 if hidden {
3371 self.update_visible_edit_prediction(window, cx);
3372 } else {
3373 self.refresh_edit_prediction(true, false, window, cx);
3374 }
3375 }
3376 }
3377
3378 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3379 self.menu_edit_predictions_policy = value;
3380 }
3381
3382 pub fn set_autoindent(&mut self, autoindent: bool) {
3383 if autoindent {
3384 self.autoindent_mode = Some(AutoindentMode::EachLine);
3385 } else {
3386 self.autoindent_mode = None;
3387 }
3388 }
3389
3390 pub fn capability(&self, cx: &App) -> Capability {
3391 if self.read_only {
3392 Capability::ReadOnly
3393 } else {
3394 self.buffer.read(cx).capability()
3395 }
3396 }
3397
3398 pub fn read_only(&self, cx: &App) -> bool {
3399 self.read_only || self.buffer.read(cx).read_only()
3400 }
3401
3402 pub fn set_read_only(&mut self, read_only: bool) {
3403 self.read_only = read_only;
3404 }
3405
3406 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3407 self.use_autoclose = autoclose;
3408 }
3409
3410 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3411 self.use_auto_surround = auto_surround;
3412 }
3413
3414 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3415 self.auto_replace_emoji_shortcode = auto_replace;
3416 }
3417
3418 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3419 self.buffer_serialization = should_serialize.then(|| {
3420 BufferSerialization::new(
3421 ProjectSettings::get_global(cx)
3422 .session
3423 .restore_unsaved_buffers,
3424 )
3425 })
3426 }
3427
3428 fn should_serialize_buffer(&self) -> bool {
3429 self.buffer_serialization.is_some()
3430 }
3431
3432 pub fn toggle_edit_predictions(
3433 &mut self,
3434 _: &ToggleEditPrediction,
3435 window: &mut Window,
3436 cx: &mut Context<Self>,
3437 ) {
3438 if self.show_edit_predictions_override.is_some() {
3439 self.set_show_edit_predictions(None, window, cx);
3440 } else {
3441 let show_edit_predictions = !self.edit_predictions_enabled();
3442 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3443 }
3444 }
3445
3446 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3447 self.show_completions_on_input_override = show_completions_on_input;
3448 }
3449
3450 pub fn set_show_edit_predictions(
3451 &mut self,
3452 show_edit_predictions: Option<bool>,
3453 window: &mut Window,
3454 cx: &mut Context<Self>,
3455 ) {
3456 self.show_edit_predictions_override = show_edit_predictions;
3457 self.update_edit_prediction_settings(cx);
3458
3459 if let Some(false) = show_edit_predictions {
3460 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3461 } else {
3462 self.refresh_edit_prediction(false, true, window, cx);
3463 }
3464 }
3465
3466 fn edit_predictions_disabled_in_scope(
3467 &self,
3468 buffer: &Entity<Buffer>,
3469 buffer_position: language::Anchor,
3470 cx: &App,
3471 ) -> bool {
3472 let snapshot = buffer.read(cx).snapshot();
3473 let settings = snapshot.settings_at(buffer_position, cx);
3474
3475 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3476 return false;
3477 };
3478
3479 scope.override_name().is_some_and(|scope_name| {
3480 settings
3481 .edit_predictions_disabled_in
3482 .iter()
3483 .any(|s| s == scope_name)
3484 })
3485 }
3486
3487 pub fn set_use_modal_editing(&mut self, to: bool) {
3488 self.use_modal_editing = to;
3489 }
3490
3491 pub fn use_modal_editing(&self) -> bool {
3492 self.use_modal_editing
3493 }
3494
3495 fn selections_did_change(
3496 &mut self,
3497 local: bool,
3498 old_cursor_position: &Anchor,
3499 effects: SelectionEffects,
3500 window: &mut Window,
3501 cx: &mut Context<Self>,
3502 ) {
3503 window.invalidate_character_coordinates();
3504
3505 // Copy selections to primary selection buffer
3506 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3507 if local {
3508 let selections = self
3509 .selections
3510 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3511 let buffer_handle = self.buffer.read(cx).read(cx);
3512
3513 let mut text = String::new();
3514 for (index, selection) in selections.iter().enumerate() {
3515 let text_for_selection = buffer_handle
3516 .text_for_range(selection.start..selection.end)
3517 .collect::<String>();
3518
3519 text.push_str(&text_for_selection);
3520 if index != selections.len() - 1 {
3521 text.push('\n');
3522 }
3523 }
3524
3525 if !text.is_empty() {
3526 cx.write_to_primary(ClipboardItem::new_string(text));
3527 }
3528 }
3529
3530 let selection_anchors = self.selections.disjoint_anchors_arc();
3531
3532 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3533 self.buffer.update(cx, |buffer, cx| {
3534 buffer.set_active_selections(
3535 &selection_anchors,
3536 self.selections.line_mode(),
3537 self.cursor_shape,
3538 cx,
3539 )
3540 });
3541 }
3542 let display_map = self
3543 .display_map
3544 .update(cx, |display_map, cx| display_map.snapshot(cx));
3545 let buffer = display_map.buffer_snapshot();
3546 if self.selections.count() == 1 {
3547 self.add_selections_state = None;
3548 }
3549 self.select_next_state = None;
3550 self.select_prev_state = None;
3551 self.select_syntax_node_history.try_clear();
3552 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3553 self.snippet_stack.invalidate(&selection_anchors, buffer);
3554 self.take_rename(false, window, cx);
3555
3556 let newest_selection = self.selections.newest_anchor();
3557 let new_cursor_position = newest_selection.head();
3558 let selection_start = newest_selection.start;
3559
3560 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3561 self.push_to_nav_history(
3562 *old_cursor_position,
3563 Some(new_cursor_position.to_point(buffer)),
3564 false,
3565 effects.nav_history == Some(true),
3566 cx,
3567 );
3568 }
3569
3570 if local {
3571 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3572 self.register_buffer(buffer_id, cx);
3573 }
3574
3575 let mut context_menu = self.context_menu.borrow_mut();
3576 let completion_menu = match context_menu.as_ref() {
3577 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3578 Some(CodeContextMenu::CodeActions(_)) => {
3579 *context_menu = None;
3580 None
3581 }
3582 None => None,
3583 };
3584 let completion_position = completion_menu.map(|menu| menu.initial_position);
3585 drop(context_menu);
3586
3587 if effects.completions
3588 && let Some(completion_position) = completion_position
3589 {
3590 let start_offset = selection_start.to_offset(buffer);
3591 let position_matches = start_offset == completion_position.to_offset(buffer);
3592 let continue_showing = if let Some((snap, ..)) =
3593 buffer.point_to_buffer_offset(completion_position)
3594 && !snap.capability.editable()
3595 {
3596 false
3597 } else if position_matches {
3598 if self.snippet_stack.is_empty() {
3599 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3600 == Some(CharKind::Word)
3601 } else {
3602 // Snippet choices can be shown even when the cursor is in whitespace.
3603 // Dismissing the menu with actions like backspace is handled by
3604 // invalidation regions.
3605 true
3606 }
3607 } else {
3608 false
3609 };
3610
3611 if continue_showing {
3612 self.open_or_update_completions_menu(None, None, false, window, cx);
3613 } else {
3614 self.hide_context_menu(window, cx);
3615 }
3616 }
3617
3618 hide_hover(self, cx);
3619
3620 if old_cursor_position.to_display_point(&display_map).row()
3621 != new_cursor_position.to_display_point(&display_map).row()
3622 {
3623 self.available_code_actions.take();
3624 }
3625 self.refresh_code_actions(window, cx);
3626 self.refresh_document_highlights(cx);
3627 refresh_linked_ranges(self, window, cx);
3628
3629 self.refresh_selected_text_highlights(false, window, cx);
3630 self.refresh_matching_bracket_highlights(&display_map, cx);
3631 self.refresh_outline_symbols_at_cursor(cx);
3632 self.update_visible_edit_prediction(window, cx);
3633 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3634 self.inline_blame_popover.take();
3635 if self.git_blame_inline_enabled {
3636 self.start_inline_blame_timer(window, cx);
3637 }
3638 }
3639
3640 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3641
3642 if local && !self.suppress_selection_callback {
3643 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3644 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3645 callback(cursor_position, window, cx);
3646 }
3647 }
3648
3649 cx.emit(EditorEvent::SelectionsChanged { local });
3650
3651 let selections = &self.selections.disjoint_anchors_arc();
3652 if selections.len() == 1 {
3653 cx.emit(SearchEvent::ActiveMatchChanged)
3654 }
3655 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3656 let inmemory_selections = selections
3657 .iter()
3658 .map(|s| {
3659 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3660 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3661 })
3662 .collect();
3663 self.update_restoration_data(cx, |data| {
3664 data.selections = inmemory_selections;
3665 });
3666
3667 if WorkspaceSettings::get(None, cx).restore_on_startup
3668 != RestoreOnStartupBehavior::EmptyTab
3669 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3670 {
3671 let snapshot = self.buffer().read(cx).snapshot(cx);
3672 let selections = selections.clone();
3673 let background_executor = cx.background_executor().clone();
3674 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3675 self.serialize_selections = cx.background_spawn(async move {
3676 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3677 let db_selections = selections
3678 .iter()
3679 .map(|selection| {
3680 (
3681 selection.start.to_offset(&snapshot).0,
3682 selection.end.to_offset(&snapshot).0,
3683 )
3684 })
3685 .collect();
3686
3687 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3688 .await
3689 .with_context(|| {
3690 format!(
3691 "persisting editor selections for editor {editor_id}, \
3692 workspace {workspace_id:?}"
3693 )
3694 })
3695 .log_err();
3696 });
3697 }
3698 }
3699
3700 cx.notify();
3701 }
3702
3703 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3704 use text::ToOffset as _;
3705 use text::ToPoint as _;
3706
3707 if self.mode.is_minimap()
3708 || WorkspaceSettings::get(None, cx).restore_on_startup
3709 == RestoreOnStartupBehavior::EmptyTab
3710 {
3711 return;
3712 }
3713
3714 if !self.buffer().read(cx).is_singleton() {
3715 return;
3716 }
3717
3718 let display_snapshot = self
3719 .display_map
3720 .update(cx, |display_map, cx| display_map.snapshot(cx));
3721 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3722 return;
3723 };
3724 let inmemory_folds = display_snapshot
3725 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3726 .map(|fold| {
3727 fold.range.start.text_anchor.to_point(&snapshot)
3728 ..fold.range.end.text_anchor.to_point(&snapshot)
3729 })
3730 .collect();
3731 self.update_restoration_data(cx, |data| {
3732 data.folds = inmemory_folds;
3733 });
3734
3735 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3736 return;
3737 };
3738
3739 // Get file path for path-based fold storage (survives tab close)
3740 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3741 project::File::from_dyn(buffer.read(cx).file())
3742 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3743 }) else {
3744 return;
3745 };
3746
3747 let background_executor = cx.background_executor().clone();
3748 const FINGERPRINT_LEN: usize = 32;
3749 let db_folds = display_snapshot
3750 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3751 .map(|fold| {
3752 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3753 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3754
3755 // Extract fingerprints - content at fold boundaries for validation on restore
3756 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3757 // content that might change independently.
3758 // start_fp: first min(32, fold_len) bytes of fold content
3759 // end_fp: last min(32, fold_len) bytes of fold content
3760 // Clip to character boundaries to handle multibyte UTF-8 characters.
3761 let fold_len = end - start;
3762 let start_fp_end = snapshot
3763 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3764 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3765 let end_fp_start = snapshot
3766 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3767 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3768
3769 (start, end, start_fp, end_fp)
3770 })
3771 .collect::<Vec<_>>();
3772 self.serialize_folds = cx.background_spawn(async move {
3773 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3774 if db_folds.is_empty() {
3775 // No folds - delete any persisted folds for this file
3776 DB.delete_file_folds(workspace_id, file_path)
3777 .await
3778 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3779 .log_err();
3780 } else {
3781 DB.save_file_folds(workspace_id, file_path, db_folds)
3782 .await
3783 .with_context(|| {
3784 format!("persisting file folds for workspace {workspace_id:?}")
3785 })
3786 .log_err();
3787 }
3788 });
3789 }
3790
3791 pub fn sync_selections(
3792 &mut self,
3793 other: Entity<Editor>,
3794 cx: &mut Context<Self>,
3795 ) -> gpui::Subscription {
3796 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3797 if !other_selections.is_empty() {
3798 self.selections
3799 .change_with(&self.display_snapshot(cx), |selections| {
3800 selections.select_anchors(other_selections);
3801 });
3802 }
3803
3804 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3805 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3806 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3807 if other_selections.is_empty() {
3808 return;
3809 }
3810 let snapshot = this.display_snapshot(cx);
3811 this.selections.change_with(&snapshot, |selections| {
3812 selections.select_anchors(other_selections);
3813 });
3814 }
3815 });
3816
3817 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3818 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3819 let these_selections = this.selections.disjoint_anchors().to_vec();
3820 if these_selections.is_empty() {
3821 return;
3822 }
3823 other.update(cx, |other_editor, cx| {
3824 let snapshot = other_editor.display_snapshot(cx);
3825 other_editor
3826 .selections
3827 .change_with(&snapshot, |selections| {
3828 selections.select_anchors(these_selections);
3829 })
3830 });
3831 }
3832 });
3833
3834 Subscription::join(other_subscription, this_subscription)
3835 }
3836
3837 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3838 if self.buffer().read(cx).is_singleton() {
3839 return;
3840 }
3841 let snapshot = self.buffer.read(cx).snapshot(cx);
3842 let buffer_ids: HashSet<BufferId> = self
3843 .selections
3844 .disjoint_anchor_ranges()
3845 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3846 .collect();
3847 for buffer_id in buffer_ids {
3848 self.unfold_buffer(buffer_id, cx);
3849 }
3850 }
3851
3852 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3853 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3854 /// effects of selection change occur at the end of the transaction.
3855 pub fn change_selections<R>(
3856 &mut self,
3857 effects: SelectionEffects,
3858 window: &mut Window,
3859 cx: &mut Context<Self>,
3860 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3861 ) -> R {
3862 let snapshot = self.display_snapshot(cx);
3863 if let Some(state) = &mut self.deferred_selection_effects_state {
3864 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3865 state.effects.completions = effects.completions;
3866 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3867 let (changed, result) = self.selections.change_with(&snapshot, change);
3868 state.changed |= changed;
3869 return result;
3870 }
3871 let mut state = DeferredSelectionEffectsState {
3872 changed: false,
3873 effects,
3874 old_cursor_position: self.selections.newest_anchor().head(),
3875 history_entry: SelectionHistoryEntry {
3876 selections: self.selections.disjoint_anchors_arc(),
3877 select_next_state: self.select_next_state.clone(),
3878 select_prev_state: self.select_prev_state.clone(),
3879 add_selections_state: self.add_selections_state.clone(),
3880 },
3881 };
3882 let (changed, result) = self.selections.change_with(&snapshot, change);
3883 state.changed = state.changed || changed;
3884 if self.defer_selection_effects {
3885 self.deferred_selection_effects_state = Some(state);
3886 } else {
3887 self.apply_selection_effects(state, window, cx);
3888 }
3889 result
3890 }
3891
3892 /// Defers the effects of selection change, so that the effects of multiple calls to
3893 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3894 /// to selection history and the state of popovers based on selection position aren't
3895 /// erroneously updated.
3896 pub fn with_selection_effects_deferred<R>(
3897 &mut self,
3898 window: &mut Window,
3899 cx: &mut Context<Self>,
3900 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3901 ) -> R {
3902 let already_deferred = self.defer_selection_effects;
3903 self.defer_selection_effects = true;
3904 let result = update(self, window, cx);
3905 if !already_deferred {
3906 self.defer_selection_effects = false;
3907 if let Some(state) = self.deferred_selection_effects_state.take() {
3908 self.apply_selection_effects(state, window, cx);
3909 }
3910 }
3911 result
3912 }
3913
3914 fn apply_selection_effects(
3915 &mut self,
3916 state: DeferredSelectionEffectsState,
3917 window: &mut Window,
3918 cx: &mut Context<Self>,
3919 ) {
3920 if state.changed {
3921 self.selection_history.push(state.history_entry);
3922
3923 if let Some(autoscroll) = state.effects.scroll {
3924 self.request_autoscroll(autoscroll, cx);
3925 }
3926
3927 let old_cursor_position = &state.old_cursor_position;
3928
3929 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3930
3931 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3932 self.show_signature_help_auto(window, cx);
3933 }
3934 }
3935 }
3936
3937 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3938 where
3939 I: IntoIterator<Item = (Range<S>, T)>,
3940 S: ToOffset,
3941 T: Into<Arc<str>>,
3942 {
3943 if self.read_only(cx) {
3944 return;
3945 }
3946
3947 self.buffer
3948 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3949 }
3950
3951 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3952 where
3953 I: IntoIterator<Item = (Range<S>, T)>,
3954 S: ToOffset,
3955 T: Into<Arc<str>>,
3956 {
3957 if self.read_only(cx) {
3958 return;
3959 }
3960
3961 self.buffer.update(cx, |buffer, cx| {
3962 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3963 });
3964 }
3965
3966 pub fn edit_with_block_indent<I, S, T>(
3967 &mut self,
3968 edits: I,
3969 original_indent_columns: Vec<Option<u32>>,
3970 cx: &mut Context<Self>,
3971 ) where
3972 I: IntoIterator<Item = (Range<S>, T)>,
3973 S: ToOffset,
3974 T: Into<Arc<str>>,
3975 {
3976 if self.read_only(cx) {
3977 return;
3978 }
3979
3980 self.buffer.update(cx, |buffer, cx| {
3981 buffer.edit(
3982 edits,
3983 Some(AutoindentMode::Block {
3984 original_indent_columns,
3985 }),
3986 cx,
3987 )
3988 });
3989 }
3990
3991 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3992 self.hide_context_menu(window, cx);
3993
3994 match phase {
3995 SelectPhase::Begin {
3996 position,
3997 add,
3998 click_count,
3999 } => self.begin_selection(position, add, click_count, window, cx),
4000 SelectPhase::BeginColumnar {
4001 position,
4002 goal_column,
4003 reset,
4004 mode,
4005 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4006 SelectPhase::Extend {
4007 position,
4008 click_count,
4009 } => self.extend_selection(position, click_count, window, cx),
4010 SelectPhase::Update {
4011 position,
4012 goal_column,
4013 scroll_delta,
4014 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4015 SelectPhase::End => self.end_selection(window, cx),
4016 }
4017 }
4018
4019 fn extend_selection(
4020 &mut self,
4021 position: DisplayPoint,
4022 click_count: usize,
4023 window: &mut Window,
4024 cx: &mut Context<Self>,
4025 ) {
4026 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4027 let tail = self
4028 .selections
4029 .newest::<MultiBufferOffset>(&display_map)
4030 .tail();
4031 let click_count = click_count.max(match self.selections.select_mode() {
4032 SelectMode::Character => 1,
4033 SelectMode::Word(_) => 2,
4034 SelectMode::Line(_) => 3,
4035 SelectMode::All => 4,
4036 });
4037 self.begin_selection(position, false, click_count, window, cx);
4038
4039 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4040
4041 let current_selection = match self.selections.select_mode() {
4042 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4043 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4044 };
4045
4046 let mut pending_selection = self
4047 .selections
4048 .pending_anchor()
4049 .cloned()
4050 .expect("extend_selection not called with pending selection");
4051
4052 if pending_selection
4053 .start
4054 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4055 == Ordering::Greater
4056 {
4057 pending_selection.start = current_selection.start;
4058 }
4059 if pending_selection
4060 .end
4061 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4062 == Ordering::Less
4063 {
4064 pending_selection.end = current_selection.end;
4065 pending_selection.reversed = true;
4066 }
4067
4068 let mut pending_mode = self.selections.pending_mode().unwrap();
4069 match &mut pending_mode {
4070 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4071 _ => {}
4072 }
4073
4074 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4075 SelectionEffects::scroll(Autoscroll::fit())
4076 } else {
4077 SelectionEffects::no_scroll()
4078 };
4079
4080 self.change_selections(effects, window, cx, |s| {
4081 s.set_pending(pending_selection.clone(), pending_mode);
4082 s.set_is_extending(true);
4083 });
4084 }
4085
4086 fn begin_selection(
4087 &mut self,
4088 position: DisplayPoint,
4089 add: bool,
4090 click_count: usize,
4091 window: &mut Window,
4092 cx: &mut Context<Self>,
4093 ) {
4094 if !self.focus_handle.is_focused(window) {
4095 self.last_focused_descendant = None;
4096 window.focus(&self.focus_handle, cx);
4097 }
4098
4099 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4100 let buffer = display_map.buffer_snapshot();
4101 let position = display_map.clip_point(position, Bias::Left);
4102
4103 let start;
4104 let end;
4105 let mode;
4106 let mut auto_scroll;
4107 match click_count {
4108 1 => {
4109 start = buffer.anchor_before(position.to_point(&display_map));
4110 end = start;
4111 mode = SelectMode::Character;
4112 auto_scroll = true;
4113 }
4114 2 => {
4115 let position = display_map
4116 .clip_point(position, Bias::Left)
4117 .to_offset(&display_map, Bias::Left);
4118 let (range, _) = buffer.surrounding_word(position, None);
4119 start = buffer.anchor_before(range.start);
4120 end = buffer.anchor_before(range.end);
4121 mode = SelectMode::Word(start..end);
4122 auto_scroll = true;
4123 }
4124 3 => {
4125 let position = display_map
4126 .clip_point(position, Bias::Left)
4127 .to_point(&display_map);
4128 let line_start = display_map.prev_line_boundary(position).0;
4129 let next_line_start = buffer.clip_point(
4130 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4131 Bias::Left,
4132 );
4133 start = buffer.anchor_before(line_start);
4134 end = buffer.anchor_before(next_line_start);
4135 mode = SelectMode::Line(start..end);
4136 auto_scroll = true;
4137 }
4138 _ => {
4139 start = buffer.anchor_before(MultiBufferOffset(0));
4140 end = buffer.anchor_before(buffer.len());
4141 mode = SelectMode::All;
4142 auto_scroll = false;
4143 }
4144 }
4145 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4146
4147 let point_to_delete: Option<usize> = {
4148 let selected_points: Vec<Selection<Point>> =
4149 self.selections.disjoint_in_range(start..end, &display_map);
4150
4151 if !add || click_count > 1 {
4152 None
4153 } else if !selected_points.is_empty() {
4154 Some(selected_points[0].id)
4155 } else {
4156 let clicked_point_already_selected =
4157 self.selections.disjoint_anchors().iter().find(|selection| {
4158 selection.start.to_point(buffer) == start.to_point(buffer)
4159 || selection.end.to_point(buffer) == end.to_point(buffer)
4160 });
4161
4162 clicked_point_already_selected.map(|selection| selection.id)
4163 }
4164 };
4165
4166 let selections_count = self.selections.count();
4167 let effects = if auto_scroll {
4168 SelectionEffects::default()
4169 } else {
4170 SelectionEffects::no_scroll()
4171 };
4172
4173 self.change_selections(effects, window, cx, |s| {
4174 if let Some(point_to_delete) = point_to_delete {
4175 s.delete(point_to_delete);
4176
4177 if selections_count == 1 {
4178 s.set_pending_anchor_range(start..end, mode);
4179 }
4180 } else {
4181 if !add {
4182 s.clear_disjoint();
4183 }
4184
4185 s.set_pending_anchor_range(start..end, mode);
4186 }
4187 });
4188 }
4189
4190 fn begin_columnar_selection(
4191 &mut self,
4192 position: DisplayPoint,
4193 goal_column: u32,
4194 reset: bool,
4195 mode: ColumnarMode,
4196 window: &mut Window,
4197 cx: &mut Context<Self>,
4198 ) {
4199 if !self.focus_handle.is_focused(window) {
4200 self.last_focused_descendant = None;
4201 window.focus(&self.focus_handle, cx);
4202 }
4203
4204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4205
4206 if reset {
4207 let pointer_position = display_map
4208 .buffer_snapshot()
4209 .anchor_before(position.to_point(&display_map));
4210
4211 self.change_selections(
4212 SelectionEffects::scroll(Autoscroll::newest()),
4213 window,
4214 cx,
4215 |s| {
4216 s.clear_disjoint();
4217 s.set_pending_anchor_range(
4218 pointer_position..pointer_position,
4219 SelectMode::Character,
4220 );
4221 },
4222 );
4223 };
4224
4225 let tail = self.selections.newest::<Point>(&display_map).tail();
4226 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4227 self.columnar_selection_state = match mode {
4228 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4229 selection_tail: selection_anchor,
4230 display_point: if reset {
4231 if position.column() != goal_column {
4232 Some(DisplayPoint::new(position.row(), goal_column))
4233 } else {
4234 None
4235 }
4236 } else {
4237 None
4238 },
4239 }),
4240 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4241 selection_tail: selection_anchor,
4242 }),
4243 };
4244
4245 if !reset {
4246 self.select_columns(position, goal_column, &display_map, window, cx);
4247 }
4248 }
4249
4250 fn update_selection(
4251 &mut self,
4252 position: DisplayPoint,
4253 goal_column: u32,
4254 scroll_delta: gpui::Point<f32>,
4255 window: &mut Window,
4256 cx: &mut Context<Self>,
4257 ) {
4258 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4259
4260 if self.columnar_selection_state.is_some() {
4261 self.select_columns(position, goal_column, &display_map, window, cx);
4262 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4263 let buffer = display_map.buffer_snapshot();
4264 let head;
4265 let tail;
4266 let mode = self.selections.pending_mode().unwrap();
4267 match &mode {
4268 SelectMode::Character => {
4269 head = position.to_point(&display_map);
4270 tail = pending.tail().to_point(buffer);
4271 }
4272 SelectMode::Word(original_range) => {
4273 let offset = display_map
4274 .clip_point(position, Bias::Left)
4275 .to_offset(&display_map, Bias::Left);
4276 let original_range = original_range.to_offset(buffer);
4277
4278 let head_offset = if buffer.is_inside_word(offset, None)
4279 || original_range.contains(&offset)
4280 {
4281 let (word_range, _) = buffer.surrounding_word(offset, None);
4282 if word_range.start < original_range.start {
4283 word_range.start
4284 } else {
4285 word_range.end
4286 }
4287 } else {
4288 offset
4289 };
4290
4291 head = head_offset.to_point(buffer);
4292 if head_offset <= original_range.start {
4293 tail = original_range.end.to_point(buffer);
4294 } else {
4295 tail = original_range.start.to_point(buffer);
4296 }
4297 }
4298 SelectMode::Line(original_range) => {
4299 let original_range = original_range.to_point(display_map.buffer_snapshot());
4300
4301 let position = display_map
4302 .clip_point(position, Bias::Left)
4303 .to_point(&display_map);
4304 let line_start = display_map.prev_line_boundary(position).0;
4305 let next_line_start = buffer.clip_point(
4306 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4307 Bias::Left,
4308 );
4309
4310 if line_start < original_range.start {
4311 head = line_start
4312 } else {
4313 head = next_line_start
4314 }
4315
4316 if head <= original_range.start {
4317 tail = original_range.end;
4318 } else {
4319 tail = original_range.start;
4320 }
4321 }
4322 SelectMode::All => {
4323 return;
4324 }
4325 };
4326
4327 if head < tail {
4328 pending.start = buffer.anchor_before(head);
4329 pending.end = buffer.anchor_before(tail);
4330 pending.reversed = true;
4331 } else {
4332 pending.start = buffer.anchor_before(tail);
4333 pending.end = buffer.anchor_before(head);
4334 pending.reversed = false;
4335 }
4336
4337 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4338 s.set_pending(pending.clone(), mode);
4339 });
4340 } else {
4341 log::error!("update_selection dispatched with no pending selection");
4342 return;
4343 }
4344
4345 self.apply_scroll_delta(scroll_delta, window, cx);
4346 cx.notify();
4347 }
4348
4349 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4350 self.columnar_selection_state.take();
4351 if let Some(pending_mode) = self.selections.pending_mode() {
4352 let selections = self
4353 .selections
4354 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4355 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4356 s.select(selections);
4357 s.clear_pending();
4358 if s.is_extending() {
4359 s.set_is_extending(false);
4360 } else {
4361 s.set_select_mode(pending_mode);
4362 }
4363 });
4364 }
4365 }
4366
4367 fn select_columns(
4368 &mut self,
4369 head: DisplayPoint,
4370 goal_column: u32,
4371 display_map: &DisplaySnapshot,
4372 window: &mut Window,
4373 cx: &mut Context<Self>,
4374 ) {
4375 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4376 return;
4377 };
4378
4379 let tail = match columnar_state {
4380 ColumnarSelectionState::FromMouse {
4381 selection_tail,
4382 display_point,
4383 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4384 ColumnarSelectionState::FromSelection { selection_tail } => {
4385 selection_tail.to_display_point(display_map)
4386 }
4387 };
4388
4389 let start_row = cmp::min(tail.row(), head.row());
4390 let end_row = cmp::max(tail.row(), head.row());
4391 let start_column = cmp::min(tail.column(), goal_column);
4392 let end_column = cmp::max(tail.column(), goal_column);
4393 let reversed = start_column < tail.column();
4394
4395 let selection_ranges = (start_row.0..=end_row.0)
4396 .map(DisplayRow)
4397 .filter_map(|row| {
4398 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4399 || start_column <= display_map.line_len(row))
4400 && !display_map.is_block_line(row)
4401 {
4402 let start = display_map
4403 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4404 .to_point(display_map);
4405 let end = display_map
4406 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4407 .to_point(display_map);
4408 if reversed {
4409 Some(end..start)
4410 } else {
4411 Some(start..end)
4412 }
4413 } else {
4414 None
4415 }
4416 })
4417 .collect::<Vec<_>>();
4418 if selection_ranges.is_empty() {
4419 return;
4420 }
4421
4422 let ranges = match columnar_state {
4423 ColumnarSelectionState::FromMouse { .. } => {
4424 let mut non_empty_ranges = selection_ranges
4425 .iter()
4426 .filter(|selection_range| selection_range.start != selection_range.end)
4427 .peekable();
4428 if non_empty_ranges.peek().is_some() {
4429 non_empty_ranges.cloned().collect()
4430 } else {
4431 selection_ranges
4432 }
4433 }
4434 _ => selection_ranges,
4435 };
4436
4437 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4438 s.select_ranges(ranges);
4439 });
4440 cx.notify();
4441 }
4442
4443 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4444 self.selections
4445 .all_adjusted(snapshot)
4446 .iter()
4447 .any(|selection| !selection.is_empty())
4448 }
4449
4450 pub fn has_pending_nonempty_selection(&self) -> bool {
4451 let pending_nonempty_selection = match self.selections.pending_anchor() {
4452 Some(Selection { start, end, .. }) => start != end,
4453 None => false,
4454 };
4455
4456 pending_nonempty_selection
4457 || (self.columnar_selection_state.is_some()
4458 && self.selections.disjoint_anchors().len() > 1)
4459 }
4460
4461 pub fn has_pending_selection(&self) -> bool {
4462 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4463 }
4464
4465 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4466 self.selection_mark_mode = false;
4467 self.selection_drag_state = SelectionDragState::None;
4468
4469 if self.dismiss_menus_and_popups(true, window, cx) {
4470 cx.notify();
4471 return;
4472 }
4473 if self.clear_expanded_diff_hunks(cx) {
4474 cx.notify();
4475 return;
4476 }
4477 if self.show_git_blame_gutter {
4478 self.show_git_blame_gutter = false;
4479 cx.notify();
4480 return;
4481 }
4482
4483 if self.mode.is_full()
4484 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4485 {
4486 cx.notify();
4487 return;
4488 }
4489
4490 cx.propagate();
4491 }
4492
4493 pub fn dismiss_menus_and_popups(
4494 &mut self,
4495 is_user_requested: bool,
4496 window: &mut Window,
4497 cx: &mut Context<Self>,
4498 ) -> bool {
4499 let mut dismissed = false;
4500
4501 dismissed |= self.take_rename(false, window, cx).is_some();
4502 dismissed |= self.hide_blame_popover(true, cx);
4503 dismissed |= hide_hover(self, cx);
4504 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4505 dismissed |= self.hide_context_menu(window, cx).is_some();
4506 dismissed |= self.mouse_context_menu.take().is_some();
4507 dismissed |= is_user_requested
4508 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4509 dismissed |= self.snippet_stack.pop().is_some();
4510 if self.diff_review_drag_state.is_some() {
4511 self.cancel_diff_review_drag(cx);
4512 dismissed = true;
4513 }
4514 if !self.diff_review_overlays.is_empty() {
4515 self.dismiss_all_diff_review_overlays(cx);
4516 dismissed = true;
4517 }
4518
4519 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4520 self.dismiss_diagnostics(cx);
4521 dismissed = true;
4522 }
4523
4524 dismissed
4525 }
4526
4527 fn linked_editing_ranges_for(
4528 &self,
4529 selection: Range<text::Anchor>,
4530 cx: &App,
4531 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4532 if self.linked_edit_ranges.is_empty() {
4533 return None;
4534 }
4535 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4536 selection.end.buffer_id.and_then(|end_buffer_id| {
4537 if selection.start.buffer_id != Some(end_buffer_id) {
4538 return None;
4539 }
4540 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4541 let snapshot = buffer.read(cx).snapshot();
4542 self.linked_edit_ranges
4543 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4544 .map(|ranges| (ranges, snapshot, buffer))
4545 })?;
4546 use text::ToOffset as TO;
4547 // find offset from the start of current range to current cursor position
4548 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4549
4550 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4551 let start_difference = start_offset - start_byte_offset;
4552 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4553 let end_difference = end_offset - start_byte_offset;
4554
4555 // Current range has associated linked ranges.
4556 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4557 for range in linked_ranges.iter() {
4558 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4559 let end_offset = start_offset + end_difference;
4560 let start_offset = start_offset + start_difference;
4561 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4562 continue;
4563 }
4564 if self.selections.disjoint_anchor_ranges().any(|s| {
4565 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4566 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4567 {
4568 return false;
4569 }
4570 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4571 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4572 }) {
4573 continue;
4574 }
4575 let start = buffer_snapshot.anchor_after(start_offset);
4576 let end = buffer_snapshot.anchor_after(end_offset);
4577 linked_edits
4578 .entry(buffer.clone())
4579 .or_default()
4580 .push(start..end);
4581 }
4582 Some(linked_edits)
4583 }
4584
4585 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4586 let text: Arc<str> = text.into();
4587
4588 if self.read_only(cx) {
4589 return;
4590 }
4591
4592 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4593
4594 self.unfold_buffers_with_selections(cx);
4595
4596 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4597 let mut bracket_inserted = false;
4598 let mut edits = Vec::new();
4599 let mut linked_edits = LinkedEdits::new();
4600 let mut new_selections = Vec::with_capacity(selections.len());
4601 let mut new_autoclose_regions = Vec::new();
4602 let snapshot = self.buffer.read(cx).read(cx);
4603 let mut clear_linked_edit_ranges = false;
4604 let mut all_selections_read_only = true;
4605 let mut has_adjacent_edits = false;
4606 let mut in_adjacent_group = false;
4607
4608 let mut regions = self
4609 .selections_with_autoclose_regions(selections, &snapshot)
4610 .peekable();
4611
4612 while let Some((selection, autoclose_region)) = regions.next() {
4613 if snapshot
4614 .point_to_buffer_point(selection.head())
4615 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4616 {
4617 continue;
4618 }
4619 if snapshot
4620 .point_to_buffer_point(selection.tail())
4621 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4622 {
4623 // note, ideally we'd clip the tail to the closest writeable region towards the head
4624 continue;
4625 }
4626 all_selections_read_only = false;
4627
4628 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4629 // Determine if the inserted text matches the opening or closing
4630 // bracket of any of this language's bracket pairs.
4631 let mut bracket_pair = None;
4632 let mut is_bracket_pair_start = false;
4633 let mut is_bracket_pair_end = false;
4634 if !text.is_empty() {
4635 let mut bracket_pair_matching_end = None;
4636 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4637 // and they are removing the character that triggered IME popup.
4638 for (pair, enabled) in scope.brackets() {
4639 if !pair.close && !pair.surround {
4640 continue;
4641 }
4642
4643 if enabled && pair.start.ends_with(text.as_ref()) {
4644 let prefix_len = pair.start.len() - text.len();
4645 let preceding_text_matches_prefix = prefix_len == 0
4646 || (selection.start.column >= (prefix_len as u32)
4647 && snapshot.contains_str_at(
4648 Point::new(
4649 selection.start.row,
4650 selection.start.column - (prefix_len as u32),
4651 ),
4652 &pair.start[..prefix_len],
4653 ));
4654 if preceding_text_matches_prefix {
4655 bracket_pair = Some(pair.clone());
4656 is_bracket_pair_start = true;
4657 break;
4658 }
4659 }
4660 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4661 {
4662 // take first bracket pair matching end, but don't break in case a later bracket
4663 // pair matches start
4664 bracket_pair_matching_end = Some(pair.clone());
4665 }
4666 }
4667 if let Some(end) = bracket_pair_matching_end
4668 && bracket_pair.is_none()
4669 {
4670 bracket_pair = Some(end);
4671 is_bracket_pair_end = true;
4672 }
4673 }
4674
4675 if let Some(bracket_pair) = bracket_pair {
4676 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4677 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4678 let auto_surround =
4679 self.use_auto_surround && snapshot_settings.use_auto_surround;
4680 if selection.is_empty() {
4681 if is_bracket_pair_start {
4682 // If the inserted text is a suffix of an opening bracket and the
4683 // selection is preceded by the rest of the opening bracket, then
4684 // insert the closing bracket.
4685 let following_text_allows_autoclose = snapshot
4686 .chars_at(selection.start)
4687 .next()
4688 .is_none_or(|c| scope.should_autoclose_before(c));
4689
4690 let preceding_text_allows_autoclose = selection.start.column == 0
4691 || snapshot
4692 .reversed_chars_at(selection.start)
4693 .next()
4694 .is_none_or(|c| {
4695 bracket_pair.start != bracket_pair.end
4696 || !snapshot
4697 .char_classifier_at(selection.start)
4698 .is_word(c)
4699 });
4700
4701 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4702 && bracket_pair.start.len() == 1
4703 {
4704 let target = bracket_pair.start.chars().next().unwrap();
4705 let mut byte_offset = 0u32;
4706 let current_line_count = snapshot
4707 .reversed_chars_at(selection.start)
4708 .take_while(|&c| c != '\n')
4709 .filter(|c| {
4710 byte_offset += c.len_utf8() as u32;
4711 if *c != target {
4712 return false;
4713 }
4714
4715 let point = Point::new(
4716 selection.start.row,
4717 selection.start.column.saturating_sub(byte_offset),
4718 );
4719
4720 let is_enabled = snapshot
4721 .language_scope_at(point)
4722 .and_then(|scope| {
4723 scope
4724 .brackets()
4725 .find(|(pair, _)| {
4726 pair.start == bracket_pair.start
4727 })
4728 .map(|(_, enabled)| enabled)
4729 })
4730 .unwrap_or(true);
4731
4732 let is_delimiter = snapshot
4733 .language_scope_at(Point::new(
4734 point.row,
4735 point.column + 1,
4736 ))
4737 .and_then(|scope| {
4738 scope
4739 .brackets()
4740 .find(|(pair, _)| {
4741 pair.start == bracket_pair.start
4742 })
4743 .map(|(_, enabled)| !enabled)
4744 })
4745 .unwrap_or(false);
4746
4747 is_enabled && !is_delimiter
4748 })
4749 .count();
4750 current_line_count % 2 == 1
4751 } else {
4752 false
4753 };
4754
4755 if autoclose
4756 && bracket_pair.close
4757 && following_text_allows_autoclose
4758 && preceding_text_allows_autoclose
4759 && !is_closing_quote
4760 {
4761 let anchor = snapshot.anchor_before(selection.end);
4762 new_selections.push((selection.map(|_| anchor), text.len()));
4763 new_autoclose_regions.push((
4764 anchor,
4765 text.len(),
4766 selection.id,
4767 bracket_pair.clone(),
4768 ));
4769 edits.push((
4770 selection.range(),
4771 format!("{}{}", text, bracket_pair.end).into(),
4772 ));
4773 bracket_inserted = true;
4774 continue;
4775 }
4776 }
4777
4778 if let Some(region) = autoclose_region {
4779 // If the selection is followed by an auto-inserted closing bracket,
4780 // then don't insert that closing bracket again; just move the selection
4781 // past the closing bracket.
4782 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4783 && text.as_ref() == region.pair.end.as_str()
4784 && snapshot.contains_str_at(region.range.end, text.as_ref());
4785 if should_skip {
4786 let anchor = snapshot.anchor_after(selection.end);
4787 new_selections
4788 .push((selection.map(|_| anchor), region.pair.end.len()));
4789 continue;
4790 }
4791 }
4792
4793 let always_treat_brackets_as_autoclosed = snapshot
4794 .language_settings_at(selection.start, cx)
4795 .always_treat_brackets_as_autoclosed;
4796 if always_treat_brackets_as_autoclosed
4797 && is_bracket_pair_end
4798 && snapshot.contains_str_at(selection.end, text.as_ref())
4799 {
4800 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4801 // and the inserted text is a closing bracket and the selection is followed
4802 // by the closing bracket then move the selection past the closing bracket.
4803 let anchor = snapshot.anchor_after(selection.end);
4804 new_selections.push((selection.map(|_| anchor), text.len()));
4805 continue;
4806 }
4807 }
4808 // If an opening bracket is 1 character long and is typed while
4809 // text is selected, then surround that text with the bracket pair.
4810 else if auto_surround
4811 && bracket_pair.surround
4812 && is_bracket_pair_start
4813 && bracket_pair.start.chars().count() == 1
4814 {
4815 edits.push((selection.start..selection.start, text.clone()));
4816 edits.push((
4817 selection.end..selection.end,
4818 bracket_pair.end.as_str().into(),
4819 ));
4820 bracket_inserted = true;
4821 new_selections.push((
4822 Selection {
4823 id: selection.id,
4824 start: snapshot.anchor_after(selection.start),
4825 end: snapshot.anchor_before(selection.end),
4826 reversed: selection.reversed,
4827 goal: selection.goal,
4828 },
4829 0,
4830 ));
4831 continue;
4832 }
4833 }
4834 }
4835
4836 if self.auto_replace_emoji_shortcode
4837 && selection.is_empty()
4838 && text.as_ref().ends_with(':')
4839 && let Some(possible_emoji_short_code) =
4840 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4841 && !possible_emoji_short_code.is_empty()
4842 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4843 {
4844 let emoji_shortcode_start = Point::new(
4845 selection.start.row,
4846 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4847 );
4848
4849 // Remove shortcode from buffer
4850 edits.push((
4851 emoji_shortcode_start..selection.start,
4852 "".to_string().into(),
4853 ));
4854 new_selections.push((
4855 Selection {
4856 id: selection.id,
4857 start: snapshot.anchor_after(emoji_shortcode_start),
4858 end: snapshot.anchor_before(selection.start),
4859 reversed: selection.reversed,
4860 goal: selection.goal,
4861 },
4862 0,
4863 ));
4864
4865 // Insert emoji
4866 let selection_start_anchor = snapshot.anchor_after(selection.start);
4867 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4868 edits.push((selection.start..selection.end, emoji.to_string().into()));
4869
4870 continue;
4871 }
4872
4873 let next_is_adjacent = regions
4874 .peek()
4875 .is_some_and(|(next, _)| selection.end == next.start);
4876
4877 // If not handling any auto-close operation, then just replace the selected
4878 // text with the given input and move the selection to the end of the
4879 // newly inserted text.
4880 let anchor = if in_adjacent_group || next_is_adjacent {
4881 // After edits the right bias would shift those anchor to the next visible fragment
4882 // but we want to resolve to the previous one
4883 snapshot.anchor_before(selection.end)
4884 } else {
4885 snapshot.anchor_after(selection.end)
4886 };
4887
4888 if !self.linked_edit_ranges.is_empty() {
4889 let start_anchor = snapshot.anchor_before(selection.start);
4890
4891 let is_word_char = text.chars().next().is_none_or(|char| {
4892 let classifier = snapshot
4893 .char_classifier_at(start_anchor.to_offset(&snapshot))
4894 .scope_context(Some(CharScopeContext::LinkedEdit));
4895 classifier.is_word(char)
4896 });
4897
4898 if is_word_char {
4899 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
4900 linked_edits.push(&self, anchor_range, text.clone(), cx);
4901 } else {
4902 clear_linked_edit_ranges = true;
4903 }
4904 }
4905
4906 new_selections.push((selection.map(|_| anchor), 0));
4907 edits.push((selection.start..selection.end, text.clone()));
4908
4909 has_adjacent_edits |= next_is_adjacent;
4910 in_adjacent_group = next_is_adjacent;
4911 }
4912
4913 if all_selections_read_only {
4914 return;
4915 }
4916
4917 drop(regions);
4918 drop(snapshot);
4919
4920 self.transact(window, cx, |this, window, cx| {
4921 if clear_linked_edit_ranges {
4922 this.linked_edit_ranges.clear();
4923 }
4924 let initial_buffer_versions =
4925 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4926
4927 this.buffer.update(cx, |buffer, cx| {
4928 if has_adjacent_edits {
4929 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4930 } else {
4931 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4932 }
4933 });
4934 linked_edits.apply(cx);
4935 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4936 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4937 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4938 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4939 new_anchor_selections,
4940 &map,
4941 )
4942 .zip(new_selection_deltas)
4943 .map(|(selection, delta)| Selection {
4944 id: selection.id,
4945 start: selection.start + delta,
4946 end: selection.end + delta,
4947 reversed: selection.reversed,
4948 goal: SelectionGoal::None,
4949 })
4950 .collect::<Vec<_>>();
4951
4952 let mut i = 0;
4953 for (position, delta, selection_id, pair) in new_autoclose_regions {
4954 let position = position.to_offset(map.buffer_snapshot()) + delta;
4955 let start = map.buffer_snapshot().anchor_before(position);
4956 let end = map.buffer_snapshot().anchor_after(position);
4957 while let Some(existing_state) = this.autoclose_regions.get(i) {
4958 match existing_state
4959 .range
4960 .start
4961 .cmp(&start, map.buffer_snapshot())
4962 {
4963 Ordering::Less => i += 1,
4964 Ordering::Greater => break,
4965 Ordering::Equal => {
4966 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4967 Ordering::Less => i += 1,
4968 Ordering::Equal => break,
4969 Ordering::Greater => break,
4970 }
4971 }
4972 }
4973 }
4974 this.autoclose_regions.insert(
4975 i,
4976 AutocloseRegion {
4977 selection_id,
4978 range: start..end,
4979 pair,
4980 },
4981 );
4982 }
4983
4984 let had_active_edit_prediction = this.has_active_edit_prediction();
4985 this.change_selections(
4986 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4987 window,
4988 cx,
4989 |s| s.select(new_selections),
4990 );
4991
4992 if !bracket_inserted
4993 && let Some(on_type_format_task) =
4994 this.trigger_on_type_formatting(text.to_string(), window, cx)
4995 {
4996 on_type_format_task.detach_and_log_err(cx);
4997 }
4998
4999 let editor_settings = EditorSettings::get_global(cx);
5000 if bracket_inserted
5001 && (editor_settings.auto_signature_help
5002 || editor_settings.show_signature_help_after_edits)
5003 {
5004 this.show_signature_help(&ShowSignatureHelp, window, cx);
5005 }
5006
5007 let trigger_in_words =
5008 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5009 if this.hard_wrap.is_some() {
5010 let latest: Range<Point> = this.selections.newest(&map).range();
5011 if latest.is_empty()
5012 && this
5013 .buffer()
5014 .read(cx)
5015 .snapshot(cx)
5016 .line_len(MultiBufferRow(latest.start.row))
5017 == latest.start.column
5018 {
5019 this.rewrap_impl(
5020 RewrapOptions {
5021 override_language_settings: true,
5022 preserve_existing_whitespace: true,
5023 },
5024 cx,
5025 )
5026 }
5027 }
5028 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5029 refresh_linked_ranges(this, window, cx);
5030 this.refresh_edit_prediction(true, false, window, cx);
5031 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5032 });
5033 }
5034
5035 fn find_possible_emoji_shortcode_at_position(
5036 snapshot: &MultiBufferSnapshot,
5037 position: Point,
5038 ) -> Option<String> {
5039 let mut chars = Vec::new();
5040 let mut found_colon = false;
5041 for char in snapshot.reversed_chars_at(position).take(100) {
5042 // Found a possible emoji shortcode in the middle of the buffer
5043 if found_colon {
5044 if char.is_whitespace() {
5045 chars.reverse();
5046 return Some(chars.iter().collect());
5047 }
5048 // If the previous character is not a whitespace, we are in the middle of a word
5049 // and we only want to complete the shortcode if the word is made up of other emojis
5050 let mut containing_word = String::new();
5051 for ch in snapshot
5052 .reversed_chars_at(position)
5053 .skip(chars.len() + 1)
5054 .take(100)
5055 {
5056 if ch.is_whitespace() {
5057 break;
5058 }
5059 containing_word.push(ch);
5060 }
5061 let containing_word = containing_word.chars().rev().collect::<String>();
5062 if util::word_consists_of_emojis(containing_word.as_str()) {
5063 chars.reverse();
5064 return Some(chars.iter().collect());
5065 }
5066 }
5067
5068 if char.is_whitespace() || !char.is_ascii() {
5069 return None;
5070 }
5071 if char == ':' {
5072 found_colon = true;
5073 } else {
5074 chars.push(char);
5075 }
5076 }
5077 // Found a possible emoji shortcode at the beginning of the buffer
5078 chars.reverse();
5079 Some(chars.iter().collect())
5080 }
5081
5082 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5083 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5084 self.transact(window, cx, |this, window, cx| {
5085 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5086 let selections = this
5087 .selections
5088 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5089 let multi_buffer = this.buffer.read(cx);
5090 let buffer = multi_buffer.snapshot(cx);
5091 selections
5092 .iter()
5093 .map(|selection| {
5094 let start_point = selection.start.to_point(&buffer);
5095 let mut existing_indent =
5096 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5097 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5098 let start = selection.start;
5099 let end = selection.end;
5100 let selection_is_empty = start == end;
5101 let language_scope = buffer.language_scope_at(start);
5102 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5103 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5104 &buffer,
5105 start..end,
5106 language,
5107 )
5108 || NewlineConfig::insert_extra_newline_tree_sitter(
5109 &buffer,
5110 start..end,
5111 );
5112
5113 let mut newline_config = NewlineConfig::Newline {
5114 additional_indent: IndentSize::spaces(0),
5115 extra_line_additional_indent: if needs_extra_newline {
5116 Some(IndentSize::spaces(0))
5117 } else {
5118 None
5119 },
5120 prevent_auto_indent: false,
5121 };
5122
5123 let comment_delimiter = maybe!({
5124 if !selection_is_empty {
5125 return None;
5126 }
5127
5128 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5129 return None;
5130 }
5131
5132 return comment_delimiter_for_newline(
5133 &start_point,
5134 &buffer,
5135 language,
5136 );
5137 });
5138
5139 let doc_delimiter = maybe!({
5140 if !selection_is_empty {
5141 return None;
5142 }
5143
5144 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5145 return None;
5146 }
5147
5148 return documentation_delimiter_for_newline(
5149 &start_point,
5150 &buffer,
5151 language,
5152 &mut newline_config,
5153 );
5154 });
5155
5156 let list_delimiter = maybe!({
5157 if !selection_is_empty {
5158 return None;
5159 }
5160
5161 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5162 return None;
5163 }
5164
5165 return list_delimiter_for_newline(
5166 &start_point,
5167 &buffer,
5168 language,
5169 &mut newline_config,
5170 );
5171 });
5172
5173 (
5174 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5175 newline_config,
5176 )
5177 } else {
5178 (
5179 None,
5180 NewlineConfig::Newline {
5181 additional_indent: IndentSize::spaces(0),
5182 extra_line_additional_indent: None,
5183 prevent_auto_indent: false,
5184 },
5185 )
5186 };
5187
5188 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5189 NewlineConfig::ClearCurrentLine => {
5190 let row_start =
5191 buffer.point_to_offset(Point::new(start_point.row, 0));
5192 (row_start, String::new(), false)
5193 }
5194 NewlineConfig::UnindentCurrentLine { continuation } => {
5195 let row_start =
5196 buffer.point_to_offset(Point::new(start_point.row, 0));
5197 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5198 let tab_size_indent = IndentSize::spaces(tab_size.get());
5199 let reduced_indent =
5200 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5201 let mut new_text = String::new();
5202 new_text.extend(reduced_indent.chars());
5203 new_text.push_str(continuation);
5204 (row_start, new_text, true)
5205 }
5206 NewlineConfig::Newline {
5207 additional_indent,
5208 extra_line_additional_indent,
5209 prevent_auto_indent,
5210 } => {
5211 let capacity_for_delimiter =
5212 delimiter.as_deref().map(str::len).unwrap_or_default();
5213 let extra_line_len = extra_line_additional_indent
5214 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5215 .unwrap_or(0);
5216 let mut new_text = String::with_capacity(
5217 1 + capacity_for_delimiter
5218 + existing_indent.len as usize
5219 + additional_indent.len as usize
5220 + extra_line_len,
5221 );
5222 new_text.push('\n');
5223 new_text.extend(existing_indent.chars());
5224 new_text.extend(additional_indent.chars());
5225 if let Some(delimiter) = &delimiter {
5226 new_text.push_str(delimiter);
5227 }
5228 if let Some(extra_indent) = extra_line_additional_indent {
5229 new_text.push('\n');
5230 new_text.extend(existing_indent.chars());
5231 new_text.extend(extra_indent.chars());
5232 }
5233 (start, new_text, *prevent_auto_indent)
5234 }
5235 };
5236
5237 let anchor = buffer.anchor_after(end);
5238 let new_selection = selection.map(|_| anchor);
5239 (
5240 ((edit_start..end, new_text), prevent_auto_indent),
5241 (newline_config.has_extra_line(), new_selection),
5242 )
5243 })
5244 .unzip()
5245 };
5246
5247 let mut auto_indent_edits = Vec::new();
5248 let mut edits = Vec::new();
5249 for (edit, prevent_auto_indent) in edits_with_flags {
5250 if prevent_auto_indent {
5251 edits.push(edit);
5252 } else {
5253 auto_indent_edits.push(edit);
5254 }
5255 }
5256 if !edits.is_empty() {
5257 this.edit(edits, cx);
5258 }
5259 if !auto_indent_edits.is_empty() {
5260 this.edit_with_autoindent(auto_indent_edits, cx);
5261 }
5262
5263 let buffer = this.buffer.read(cx).snapshot(cx);
5264 let new_selections = selection_info
5265 .into_iter()
5266 .map(|(extra_newline_inserted, new_selection)| {
5267 let mut cursor = new_selection.end.to_point(&buffer);
5268 if extra_newline_inserted {
5269 cursor.row -= 1;
5270 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5271 }
5272 new_selection.map(|_| cursor)
5273 })
5274 .collect();
5275
5276 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5277 this.refresh_edit_prediction(true, false, window, cx);
5278 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5279 task.detach_and_log_err(cx);
5280 }
5281 });
5282 }
5283
5284 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5286
5287 let buffer = self.buffer.read(cx);
5288 let snapshot = buffer.snapshot(cx);
5289
5290 let mut edits = Vec::new();
5291 let mut rows = Vec::new();
5292
5293 for (rows_inserted, selection) in self
5294 .selections
5295 .all_adjusted(&self.display_snapshot(cx))
5296 .into_iter()
5297 .enumerate()
5298 {
5299 let cursor = selection.head();
5300 let row = cursor.row;
5301
5302 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5303
5304 let newline = "\n".to_string();
5305 edits.push((start_of_line..start_of_line, newline));
5306
5307 rows.push(row + rows_inserted as u32);
5308 }
5309
5310 self.transact(window, cx, |editor, window, cx| {
5311 editor.edit(edits, cx);
5312
5313 editor.change_selections(Default::default(), window, cx, |s| {
5314 let mut index = 0;
5315 s.move_cursors_with(&mut |map, _, _| {
5316 let row = rows[index];
5317 index += 1;
5318
5319 let point = Point::new(row, 0);
5320 let boundary = map.next_line_boundary(point).1;
5321 let clipped = map.clip_point(boundary, Bias::Left);
5322
5323 (clipped, SelectionGoal::None)
5324 });
5325 });
5326
5327 let mut indent_edits = Vec::new();
5328 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5329 for row in rows {
5330 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5331 for (row, indent) in indents {
5332 if indent.len == 0 {
5333 continue;
5334 }
5335
5336 let text = match indent.kind {
5337 IndentKind::Space => " ".repeat(indent.len as usize),
5338 IndentKind::Tab => "\t".repeat(indent.len as usize),
5339 };
5340 let point = Point::new(row.0, 0);
5341 indent_edits.push((point..point, text));
5342 }
5343 }
5344 editor.edit(indent_edits, cx);
5345 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5346 format.detach_and_log_err(cx);
5347 }
5348 });
5349 }
5350
5351 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5352 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5353
5354 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5355 let mut rows = Vec::new();
5356 let mut rows_inserted = 0;
5357
5358 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5359 let cursor = selection.head();
5360 let row = cursor.row;
5361
5362 let point = Point::new(row, 0);
5363 let Some((buffer_handle, buffer_point, _)) =
5364 self.buffer.read(cx).point_to_buffer_point(point, cx)
5365 else {
5366 continue;
5367 };
5368
5369 buffer_edits
5370 .entry(buffer_handle.entity_id())
5371 .or_insert_with(|| (buffer_handle, Vec::new()))
5372 .1
5373 .push(buffer_point);
5374
5375 rows_inserted += 1;
5376 rows.push(row + rows_inserted);
5377 }
5378
5379 self.transact(window, cx, |editor, window, cx| {
5380 for (_, (buffer_handle, points)) in &buffer_edits {
5381 buffer_handle.update(cx, |buffer, cx| {
5382 let edits: Vec<_> = points
5383 .iter()
5384 .map(|point| {
5385 let target = Point::new(point.row + 1, 0);
5386 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5387 (start_of_line..start_of_line, "\n")
5388 })
5389 .collect();
5390 buffer.edit(edits, None, cx);
5391 });
5392 }
5393
5394 editor.change_selections(Default::default(), window, cx, |s| {
5395 let mut index = 0;
5396 s.move_cursors_with(&mut |map, _, _| {
5397 let row = rows[index];
5398 index += 1;
5399
5400 let point = Point::new(row, 0);
5401 let boundary = map.next_line_boundary(point).1;
5402 let clipped = map.clip_point(boundary, Bias::Left);
5403
5404 (clipped, SelectionGoal::None)
5405 });
5406 });
5407
5408 let mut indent_edits = Vec::new();
5409 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5410 for row in rows {
5411 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5412 for (row, indent) in indents {
5413 if indent.len == 0 {
5414 continue;
5415 }
5416
5417 let text = match indent.kind {
5418 IndentKind::Space => " ".repeat(indent.len as usize),
5419 IndentKind::Tab => "\t".repeat(indent.len as usize),
5420 };
5421 let point = Point::new(row.0, 0);
5422 indent_edits.push((point..point, text));
5423 }
5424 }
5425 editor.edit(indent_edits, cx);
5426 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5427 format.detach_and_log_err(cx);
5428 }
5429 });
5430 }
5431
5432 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5433 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5434 original_indent_columns: Vec::new(),
5435 });
5436 self.replace_selections(text, autoindent, window, cx, false);
5437 }
5438
5439 /// Replaces the editor's selections with the provided `text`, applying the
5440 /// given `autoindent_mode` (`None` will skip autoindentation).
5441 ///
5442 /// Early returns if the editor is in read-only mode, without applying any
5443 /// edits.
5444 fn replace_selections(
5445 &mut self,
5446 text: &str,
5447 autoindent_mode: Option<AutoindentMode>,
5448 window: &mut Window,
5449 cx: &mut Context<Self>,
5450 apply_linked_edits: bool,
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 linked_edits = if apply_linked_edits {
5460 this.linked_edits_for_selections(text.clone(), cx)
5461 } else {
5462 LinkedEdits::new()
5463 };
5464
5465 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5466 let anchors = {
5467 let snapshot = buffer.read(cx);
5468 old_selections
5469 .iter()
5470 .map(|s| {
5471 let anchor = snapshot.anchor_after(s.head());
5472 s.map(|_| anchor)
5473 })
5474 .collect::<Vec<_>>()
5475 };
5476 buffer.edit(
5477 old_selections
5478 .iter()
5479 .map(|s| (s.start..s.end, text.clone())),
5480 autoindent_mode,
5481 cx,
5482 );
5483 anchors
5484 });
5485
5486 linked_edits.apply(cx);
5487
5488 this.change_selections(Default::default(), window, cx, |s| {
5489 s.select_anchors(selection_anchors);
5490 });
5491
5492 if apply_linked_edits {
5493 refresh_linked_ranges(this, window, cx);
5494 }
5495
5496 cx.notify();
5497 });
5498 }
5499
5500 /// Collects linked edits for the current selections, pairing each linked
5501 /// range with `text`.
5502 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5503 let mut linked_edits = LinkedEdits::new();
5504 if !self.linked_edit_ranges.is_empty() {
5505 for selection in self.selections.disjoint_anchors() {
5506 let start = selection.start.text_anchor;
5507 let end = selection.end.text_anchor;
5508 linked_edits.push(self, start..end, text.clone(), cx);
5509 }
5510 }
5511 linked_edits
5512 }
5513
5514 /// Deletes the content covered by the current selections and applies
5515 /// linked edits.
5516 pub fn delete_selections_with_linked_edits(
5517 &mut self,
5518 window: &mut Window,
5519 cx: &mut Context<Self>,
5520 ) {
5521 self.replace_selections("", None, window, cx, true);
5522 }
5523
5524 #[cfg(any(test, feature = "test-support"))]
5525 pub fn set_linked_edit_ranges_for_testing(
5526 &mut self,
5527 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5528 cx: &mut Context<Self>,
5529 ) -> Option<()> {
5530 let Some((buffer, _)) = self
5531 .buffer
5532 .read(cx)
5533 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5534 else {
5535 return None;
5536 };
5537 let buffer = buffer.read(cx);
5538 let buffer_id = buffer.remote_id();
5539 let mut linked_ranges = Vec::with_capacity(ranges.len());
5540 for (base_range, linked_ranges_points) in ranges {
5541 let base_anchor =
5542 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5543 let linked_anchors = linked_ranges_points
5544 .into_iter()
5545 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5546 .collect();
5547 linked_ranges.push((base_anchor, linked_anchors));
5548 }
5549 let mut map = HashMap::default();
5550 map.insert(buffer_id, linked_ranges);
5551 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5552 Some(())
5553 }
5554
5555 fn trigger_completion_on_input(
5556 &mut self,
5557 text: &str,
5558 trigger_in_words: bool,
5559 window: &mut Window,
5560 cx: &mut Context<Self>,
5561 ) {
5562 let completions_source = self
5563 .context_menu
5564 .borrow()
5565 .as_ref()
5566 .and_then(|menu| match menu {
5567 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5568 CodeContextMenu::CodeActions(_) => None,
5569 });
5570
5571 match completions_source {
5572 Some(CompletionsMenuSource::Words { .. }) => {
5573 self.open_or_update_completions_menu(
5574 Some(CompletionsMenuSource::Words {
5575 ignore_threshold: false,
5576 }),
5577 None,
5578 trigger_in_words,
5579 window,
5580 cx,
5581 );
5582 }
5583 _ => self.open_or_update_completions_menu(
5584 None,
5585 Some(text.to_owned()).filter(|x| !x.is_empty()),
5586 true,
5587 window,
5588 cx,
5589 ),
5590 }
5591 }
5592
5593 /// If any empty selections is touching the start of its innermost containing autoclose
5594 /// region, expand it to select the brackets.
5595 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5596 let selections = self
5597 .selections
5598 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5599 let buffer = self.buffer.read(cx).read(cx);
5600 let new_selections = self
5601 .selections_with_autoclose_regions(selections, &buffer)
5602 .map(|(mut selection, region)| {
5603 if !selection.is_empty() {
5604 return selection;
5605 }
5606
5607 if let Some(region) = region {
5608 let mut range = region.range.to_offset(&buffer);
5609 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5610 range.start -= region.pair.start.len();
5611 if buffer.contains_str_at(range.start, ®ion.pair.start)
5612 && buffer.contains_str_at(range.end, ®ion.pair.end)
5613 {
5614 range.end += region.pair.end.len();
5615 selection.start = range.start;
5616 selection.end = range.end;
5617
5618 return selection;
5619 }
5620 }
5621 }
5622
5623 let always_treat_brackets_as_autoclosed = buffer
5624 .language_settings_at(selection.start, cx)
5625 .always_treat_brackets_as_autoclosed;
5626
5627 if !always_treat_brackets_as_autoclosed {
5628 return selection;
5629 }
5630
5631 if let Some(scope) = buffer.language_scope_at(selection.start) {
5632 for (pair, enabled) in scope.brackets() {
5633 if !enabled || !pair.close {
5634 continue;
5635 }
5636
5637 if buffer.contains_str_at(selection.start, &pair.end) {
5638 let pair_start_len = pair.start.len();
5639 if buffer.contains_str_at(
5640 selection.start.saturating_sub_usize(pair_start_len),
5641 &pair.start,
5642 ) {
5643 selection.start -= pair_start_len;
5644 selection.end += pair.end.len();
5645
5646 return selection;
5647 }
5648 }
5649 }
5650 }
5651
5652 selection
5653 })
5654 .collect();
5655
5656 drop(buffer);
5657 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5658 selections.select(new_selections)
5659 });
5660 }
5661
5662 /// Iterate the given selections, and for each one, find the smallest surrounding
5663 /// autoclose region. This uses the ordering of the selections and the autoclose
5664 /// regions to avoid repeated comparisons.
5665 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5666 &'a self,
5667 selections: impl IntoIterator<Item = Selection<D>>,
5668 buffer: &'a MultiBufferSnapshot,
5669 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5670 let mut i = 0;
5671 let mut regions = self.autoclose_regions.as_slice();
5672 selections.into_iter().map(move |selection| {
5673 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5674
5675 let mut enclosing = None;
5676 while let Some(pair_state) = regions.get(i) {
5677 if pair_state.range.end.to_offset(buffer) < range.start {
5678 regions = ®ions[i + 1..];
5679 i = 0;
5680 } else if pair_state.range.start.to_offset(buffer) > range.end {
5681 break;
5682 } else {
5683 if pair_state.selection_id == selection.id {
5684 enclosing = Some(pair_state);
5685 }
5686 i += 1;
5687 }
5688 }
5689
5690 (selection, enclosing)
5691 })
5692 }
5693
5694 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5695 fn invalidate_autoclose_regions(
5696 &mut self,
5697 mut selections: &[Selection<Anchor>],
5698 buffer: &MultiBufferSnapshot,
5699 ) {
5700 self.autoclose_regions.retain(|state| {
5701 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5702 return false;
5703 }
5704
5705 let mut i = 0;
5706 while let Some(selection) = selections.get(i) {
5707 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5708 selections = &selections[1..];
5709 continue;
5710 }
5711 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5712 break;
5713 }
5714 if selection.id == state.selection_id {
5715 return true;
5716 } else {
5717 i += 1;
5718 }
5719 }
5720 false
5721 });
5722 }
5723
5724 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5725 let offset = position.to_offset(buffer);
5726 let (word_range, kind) =
5727 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5728 if offset > word_range.start && kind == Some(CharKind::Word) {
5729 Some(
5730 buffer
5731 .text_for_range(word_range.start..offset)
5732 .collect::<String>(),
5733 )
5734 } else {
5735 None
5736 }
5737 }
5738
5739 pub fn visible_excerpts(
5740 &self,
5741 lsp_related_only: bool,
5742 cx: &mut Context<Editor>,
5743 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5744 let project = self.project().cloned();
5745 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5746 let multi_buffer = self.buffer().read(cx);
5747 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5748 let multi_buffer_visible_start = self
5749 .scroll_manager
5750 .native_anchor(&display_snapshot, cx)
5751 .anchor
5752 .to_point(&multi_buffer_snapshot);
5753 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5754 multi_buffer_visible_start
5755 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5756 Bias::Left,
5757 );
5758 multi_buffer_snapshot
5759 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5760 .into_iter()
5761 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5762 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5763 if !lsp_related_only {
5764 return Some((
5765 excerpt_id,
5766 (
5767 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5768 buffer.version().clone(),
5769 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5770 ),
5771 ));
5772 }
5773
5774 let project = project.as_ref()?.read(cx);
5775 let buffer_file = project::File::from_dyn(buffer.file())?;
5776 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5777 let worktree_entry = buffer_worktree
5778 .read(cx)
5779 .entry_for_id(buffer_file.project_entry_id()?)?;
5780 if worktree_entry.is_ignored {
5781 None
5782 } else {
5783 Some((
5784 excerpt_id,
5785 (
5786 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5787 buffer.version().clone(),
5788 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5789 ),
5790 ))
5791 }
5792 })
5793 .collect()
5794 }
5795
5796 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5797 TextLayoutDetails {
5798 text_system: window.text_system().clone(),
5799 editor_style: self.style.clone().unwrap(),
5800 rem_size: window.rem_size(),
5801 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5802 visible_rows: self.visible_line_count(),
5803 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5804 }
5805 }
5806
5807 fn trigger_on_type_formatting(
5808 &self,
5809 input: String,
5810 window: &mut Window,
5811 cx: &mut Context<Self>,
5812 ) -> Option<Task<Result<()>>> {
5813 if input.chars().count() != 1 {
5814 return None;
5815 }
5816
5817 let project = self.project()?;
5818 let position = self.selections.newest_anchor().head();
5819 let (buffer, buffer_position) = self
5820 .buffer
5821 .read(cx)
5822 .text_anchor_for_position(position, cx)?;
5823
5824 let settings = language_settings::language_settings(
5825 buffer
5826 .read(cx)
5827 .language_at(buffer_position)
5828 .map(|l| l.name()),
5829 buffer.read(cx).file(),
5830 cx,
5831 );
5832 if !settings.use_on_type_format {
5833 return None;
5834 }
5835
5836 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5837 // hence we do LSP request & edit on host side only — add formats to host's history.
5838 let push_to_lsp_host_history = true;
5839 // If this is not the host, append its history with new edits.
5840 let push_to_client_history = project.read(cx).is_via_collab();
5841
5842 let on_type_formatting = project.update(cx, |project, cx| {
5843 project.on_type_format(
5844 buffer.clone(),
5845 buffer_position,
5846 input,
5847 push_to_lsp_host_history,
5848 cx,
5849 )
5850 });
5851 Some(cx.spawn_in(window, async move |editor, cx| {
5852 if let Some(transaction) = on_type_formatting.await? {
5853 if push_to_client_history {
5854 buffer.update(cx, |buffer, _| {
5855 buffer.push_transaction(transaction, Instant::now());
5856 buffer.finalize_last_transaction();
5857 });
5858 }
5859 editor.update(cx, |editor, cx| {
5860 editor.refresh_document_highlights(cx);
5861 })?;
5862 }
5863 Ok(())
5864 }))
5865 }
5866
5867 pub fn show_word_completions(
5868 &mut self,
5869 _: &ShowWordCompletions,
5870 window: &mut Window,
5871 cx: &mut Context<Self>,
5872 ) {
5873 self.open_or_update_completions_menu(
5874 Some(CompletionsMenuSource::Words {
5875 ignore_threshold: true,
5876 }),
5877 None,
5878 false,
5879 window,
5880 cx,
5881 );
5882 }
5883
5884 pub fn show_completions(
5885 &mut self,
5886 _: &ShowCompletions,
5887 window: &mut Window,
5888 cx: &mut Context<Self>,
5889 ) {
5890 self.open_or_update_completions_menu(None, None, false, window, cx);
5891 }
5892
5893 fn open_or_update_completions_menu(
5894 &mut self,
5895 requested_source: Option<CompletionsMenuSource>,
5896 trigger: Option<String>,
5897 trigger_in_words: bool,
5898 window: &mut Window,
5899 cx: &mut Context<Self>,
5900 ) {
5901 if self.pending_rename.is_some() {
5902 return;
5903 }
5904
5905 let completions_source = self
5906 .context_menu
5907 .borrow()
5908 .as_ref()
5909 .and_then(|menu| match menu {
5910 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5911 CodeContextMenu::CodeActions(_) => None,
5912 });
5913
5914 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5915
5916 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5917 // inserted and selected. To handle that case, the start of the selection is used so that
5918 // the menu starts with all choices.
5919 let position = self
5920 .selections
5921 .newest_anchor()
5922 .start
5923 .bias_right(&multibuffer_snapshot);
5924 if position.diff_base_anchor.is_some() {
5925 return;
5926 }
5927 let buffer_position = multibuffer_snapshot.anchor_before(position);
5928 let Some(buffer) = buffer_position
5929 .text_anchor
5930 .buffer_id
5931 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5932 else {
5933 return;
5934 };
5935 let buffer_snapshot = buffer.read(cx).snapshot();
5936
5937 let menu_is_open = matches!(
5938 self.context_menu.borrow().as_ref(),
5939 Some(CodeContextMenu::Completions(_))
5940 );
5941
5942 let language = buffer_snapshot
5943 .language_at(buffer_position.text_anchor)
5944 .map(|language| language.name());
5945
5946 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5947 let completion_settings = language_settings.completions.clone();
5948
5949 let show_completions_on_input = self
5950 .show_completions_on_input_override
5951 .unwrap_or(language_settings.show_completions_on_input);
5952 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5953 return;
5954 }
5955
5956 let query: Option<Arc<String>> =
5957 Self::completion_query(&multibuffer_snapshot, buffer_position)
5958 .map(|query| query.into());
5959
5960 drop(multibuffer_snapshot);
5961
5962 // Hide the current completions menu when query is empty. Without this, cached
5963 // completions from before the trigger char may be reused (#32774).
5964 if query.is_none() && menu_is_open {
5965 self.hide_context_menu(window, cx);
5966 }
5967
5968 let mut ignore_word_threshold = false;
5969 let provider = match requested_source {
5970 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5971 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5972 ignore_word_threshold = ignore_threshold;
5973 None
5974 }
5975 Some(CompletionsMenuSource::SnippetChoices)
5976 | Some(CompletionsMenuSource::SnippetsOnly) => {
5977 log::error!("bug: SnippetChoices requested_source is not handled");
5978 None
5979 }
5980 };
5981
5982 let sort_completions = provider
5983 .as_ref()
5984 .is_some_and(|provider| provider.sort_completions());
5985
5986 let filter_completions = provider
5987 .as_ref()
5988 .is_none_or(|provider| provider.filter_completions());
5989
5990 let was_snippets_only = matches!(
5991 completions_source,
5992 Some(CompletionsMenuSource::SnippetsOnly)
5993 );
5994
5995 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5996 if filter_completions {
5997 menu.filter(
5998 query.clone().unwrap_or_default(),
5999 buffer_position.text_anchor,
6000 &buffer,
6001 provider.clone(),
6002 window,
6003 cx,
6004 );
6005 }
6006 // When `is_incomplete` is false, no need to re-query completions when the current query
6007 // is a suffix of the initial query.
6008 let was_complete = !menu.is_incomplete;
6009 if was_complete && !was_snippets_only {
6010 // If the new query is a suffix of the old query (typing more characters) and
6011 // the previous result was complete, the existing completions can be filtered.
6012 //
6013 // Note that snippet completions are always complete.
6014 let query_matches = match (&menu.initial_query, &query) {
6015 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6016 (None, _) => true,
6017 _ => false,
6018 };
6019 if query_matches {
6020 let position_matches = if menu.initial_position == position {
6021 true
6022 } else {
6023 let snapshot = self.buffer.read(cx).read(cx);
6024 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6025 };
6026 if position_matches {
6027 return;
6028 }
6029 }
6030 }
6031 };
6032
6033 let Anchor {
6034 excerpt_id: buffer_excerpt_id,
6035 text_anchor: buffer_position,
6036 ..
6037 } = buffer_position;
6038
6039 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6040 buffer_snapshot.surrounding_word(buffer_position, None)
6041 {
6042 let word_to_exclude = buffer_snapshot
6043 .text_for_range(word_range.clone())
6044 .collect::<String>();
6045 (
6046 buffer_snapshot.anchor_before(word_range.start)
6047 ..buffer_snapshot.anchor_after(buffer_position),
6048 Some(word_to_exclude),
6049 )
6050 } else {
6051 (buffer_position..buffer_position, None)
6052 };
6053
6054 let show_completion_documentation = buffer_snapshot
6055 .settings_at(buffer_position, cx)
6056 .show_completion_documentation;
6057
6058 // The document can be large, so stay in reasonable bounds when searching for words,
6059 // otherwise completion pop-up might be slow to appear.
6060 const WORD_LOOKUP_ROWS: u32 = 5_000;
6061 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6062 let min_word_search = buffer_snapshot.clip_point(
6063 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6064 Bias::Left,
6065 );
6066 let max_word_search = buffer_snapshot.clip_point(
6067 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6068 Bias::Right,
6069 );
6070 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6071 ..buffer_snapshot.point_to_offset(max_word_search);
6072
6073 let skip_digits = query
6074 .as_ref()
6075 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6076
6077 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6078 trigger.as_ref().is_none_or(|trigger| {
6079 provider.is_completion_trigger(
6080 &buffer,
6081 position.text_anchor,
6082 trigger,
6083 trigger_in_words,
6084 cx,
6085 )
6086 })
6087 });
6088
6089 let provider_responses = if let Some(provider) = &provider
6090 && load_provider_completions
6091 {
6092 let trigger_character =
6093 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6094 let completion_context = CompletionContext {
6095 trigger_kind: match &trigger_character {
6096 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6097 None => CompletionTriggerKind::INVOKED,
6098 },
6099 trigger_character,
6100 };
6101
6102 provider.completions(
6103 buffer_excerpt_id,
6104 &buffer,
6105 buffer_position,
6106 completion_context,
6107 window,
6108 cx,
6109 )
6110 } else {
6111 Task::ready(Ok(Vec::new()))
6112 };
6113
6114 let load_word_completions = if !self.word_completions_enabled {
6115 false
6116 } else if requested_source
6117 == Some(CompletionsMenuSource::Words {
6118 ignore_threshold: true,
6119 })
6120 {
6121 true
6122 } else {
6123 load_provider_completions
6124 && completion_settings.words != WordsCompletionMode::Disabled
6125 && (ignore_word_threshold || {
6126 let words_min_length = completion_settings.words_min_length;
6127 // check whether word has at least `words_min_length` characters
6128 let query_chars = query.iter().flat_map(|q| q.chars());
6129 query_chars.take(words_min_length).count() == words_min_length
6130 })
6131 };
6132
6133 let mut words = if load_word_completions {
6134 cx.background_spawn({
6135 let buffer_snapshot = buffer_snapshot.clone();
6136 async move {
6137 buffer_snapshot.words_in_range(WordsQuery {
6138 fuzzy_contents: None,
6139 range: word_search_range,
6140 skip_digits,
6141 })
6142 }
6143 })
6144 } else {
6145 Task::ready(BTreeMap::default())
6146 };
6147
6148 let snippets = if let Some(provider) = &provider
6149 && provider.show_snippets()
6150 && let Some(project) = self.project()
6151 {
6152 let char_classifier = buffer_snapshot
6153 .char_classifier_at(buffer_position)
6154 .scope_context(Some(CharScopeContext::Completion));
6155 project.update(cx, |project, cx| {
6156 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6157 })
6158 } else {
6159 Task::ready(Ok(CompletionResponse {
6160 completions: Vec::new(),
6161 display_options: Default::default(),
6162 is_incomplete: false,
6163 }))
6164 };
6165
6166 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6167
6168 let id = post_inc(&mut self.next_completion_id);
6169 let task = cx.spawn_in(window, async move |editor, cx| {
6170 let Ok(()) = editor.update(cx, |this, _| {
6171 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6172 }) else {
6173 return;
6174 };
6175
6176 // TODO: Ideally completions from different sources would be selectively re-queried, so
6177 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6178 let mut completions = Vec::new();
6179 let mut is_incomplete = false;
6180 let mut display_options: Option<CompletionDisplayOptions> = None;
6181 if let Some(provider_responses) = provider_responses.await.log_err()
6182 && !provider_responses.is_empty()
6183 {
6184 for response in provider_responses {
6185 completions.extend(response.completions);
6186 is_incomplete = is_incomplete || response.is_incomplete;
6187 match display_options.as_mut() {
6188 None => {
6189 display_options = Some(response.display_options);
6190 }
6191 Some(options) => options.merge(&response.display_options),
6192 }
6193 }
6194 if completion_settings.words == WordsCompletionMode::Fallback {
6195 words = Task::ready(BTreeMap::default());
6196 }
6197 }
6198 let display_options = display_options.unwrap_or_default();
6199
6200 let mut words = words.await;
6201 if let Some(word_to_exclude) = &word_to_exclude {
6202 words.remove(word_to_exclude);
6203 }
6204 for lsp_completion in &completions {
6205 words.remove(&lsp_completion.new_text);
6206 }
6207 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6208 replace_range: word_replace_range.clone(),
6209 new_text: word.clone(),
6210 label: CodeLabel::plain(word, None),
6211 match_start: None,
6212 snippet_deduplication_key: None,
6213 icon_path: None,
6214 documentation: None,
6215 source: CompletionSource::BufferWord {
6216 word_range,
6217 resolved: false,
6218 },
6219 insert_text_mode: Some(InsertTextMode::AS_IS),
6220 confirm: None,
6221 }));
6222
6223 completions.extend(
6224 snippets
6225 .await
6226 .into_iter()
6227 .flat_map(|response| response.completions),
6228 );
6229
6230 let menu = if completions.is_empty() {
6231 None
6232 } else {
6233 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6234 let languages = editor
6235 .workspace
6236 .as_ref()
6237 .and_then(|(workspace, _)| workspace.upgrade())
6238 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6239 let menu = CompletionsMenu::new(
6240 id,
6241 requested_source.unwrap_or(if load_provider_completions {
6242 CompletionsMenuSource::Normal
6243 } else {
6244 CompletionsMenuSource::SnippetsOnly
6245 }),
6246 sort_completions,
6247 show_completion_documentation,
6248 position,
6249 query.clone(),
6250 is_incomplete,
6251 buffer.clone(),
6252 completions.into(),
6253 editor
6254 .context_menu()
6255 .borrow_mut()
6256 .as_ref()
6257 .map(|menu| menu.primary_scroll_handle()),
6258 display_options,
6259 snippet_sort_order,
6260 languages,
6261 language,
6262 cx,
6263 );
6264
6265 let query = if filter_completions { query } else { None };
6266 let matches_task = menu.do_async_filtering(
6267 query.unwrap_or_default(),
6268 buffer_position,
6269 &buffer,
6270 cx,
6271 );
6272 (menu, matches_task)
6273 }) else {
6274 return;
6275 };
6276
6277 let matches = matches_task.await;
6278
6279 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6280 // Newer menu already set, so exit.
6281 if let Some(CodeContextMenu::Completions(prev_menu)) =
6282 editor.context_menu.borrow().as_ref()
6283 && prev_menu.id > id
6284 {
6285 return;
6286 };
6287
6288 // Only valid to take prev_menu because either the new menu is immediately set
6289 // below, or the menu is hidden.
6290 if let Some(CodeContextMenu::Completions(prev_menu)) =
6291 editor.context_menu.borrow_mut().take()
6292 {
6293 let position_matches =
6294 if prev_menu.initial_position == menu.initial_position {
6295 true
6296 } else {
6297 let snapshot = editor.buffer.read(cx).read(cx);
6298 prev_menu.initial_position.to_offset(&snapshot)
6299 == menu.initial_position.to_offset(&snapshot)
6300 };
6301 if position_matches {
6302 // Preserve markdown cache before `set_filter_results` because it will
6303 // try to populate the documentation cache.
6304 menu.preserve_markdown_cache(prev_menu);
6305 }
6306 };
6307
6308 menu.set_filter_results(matches, provider, window, cx);
6309 }) else {
6310 return;
6311 };
6312
6313 menu.visible().then_some(menu)
6314 };
6315
6316 editor
6317 .update_in(cx, |editor, window, cx| {
6318 if editor.focus_handle.is_focused(window)
6319 && let Some(menu) = menu
6320 {
6321 *editor.context_menu.borrow_mut() =
6322 Some(CodeContextMenu::Completions(menu));
6323
6324 crate::hover_popover::hide_hover(editor, cx);
6325 if editor.show_edit_predictions_in_menu() {
6326 editor.update_visible_edit_prediction(window, cx);
6327 } else {
6328 editor
6329 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6330 }
6331
6332 cx.notify();
6333 return;
6334 }
6335
6336 if editor.completion_tasks.len() <= 1 {
6337 // If there are no more completion tasks and the last menu was empty, we should hide it.
6338 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6339 // If it was already hidden and we don't show edit predictions in the menu,
6340 // we should also show the edit prediction when available.
6341 if was_hidden && editor.show_edit_predictions_in_menu() {
6342 editor.update_visible_edit_prediction(window, cx);
6343 }
6344 }
6345 })
6346 .ok();
6347 });
6348
6349 self.completion_tasks.push((id, task));
6350 }
6351
6352 #[cfg(any(test, feature = "test-support"))]
6353 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6354 let menu = self.context_menu.borrow();
6355 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6356 let completions = menu.completions.borrow();
6357 Some(completions.to_vec())
6358 } else {
6359 None
6360 }
6361 }
6362
6363 pub fn with_completions_menu_matching_id<R>(
6364 &self,
6365 id: CompletionId,
6366 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6367 ) -> R {
6368 let mut context_menu = self.context_menu.borrow_mut();
6369 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6370 return f(None);
6371 };
6372 if completions_menu.id != id {
6373 return f(None);
6374 }
6375 f(Some(completions_menu))
6376 }
6377
6378 pub fn confirm_completion(
6379 &mut self,
6380 action: &ConfirmCompletion,
6381 window: &mut Window,
6382 cx: &mut Context<Self>,
6383 ) -> Option<Task<Result<()>>> {
6384 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6385 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6386 }
6387
6388 pub fn confirm_completion_insert(
6389 &mut self,
6390 _: &ConfirmCompletionInsert,
6391 window: &mut Window,
6392 cx: &mut Context<Self>,
6393 ) -> Option<Task<Result<()>>> {
6394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6395 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6396 }
6397
6398 pub fn confirm_completion_replace(
6399 &mut self,
6400 _: &ConfirmCompletionReplace,
6401 window: &mut Window,
6402 cx: &mut Context<Self>,
6403 ) -> Option<Task<Result<()>>> {
6404 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6405 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6406 }
6407
6408 pub fn compose_completion(
6409 &mut self,
6410 action: &ComposeCompletion,
6411 window: &mut Window,
6412 cx: &mut Context<Self>,
6413 ) -> Option<Task<Result<()>>> {
6414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6415 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6416 }
6417
6418 fn do_completion(
6419 &mut self,
6420 item_ix: Option<usize>,
6421 intent: CompletionIntent,
6422 window: &mut Window,
6423 cx: &mut Context<Editor>,
6424 ) -> Option<Task<Result<()>>> {
6425 use language::ToOffset as _;
6426
6427 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6428 else {
6429 return None;
6430 };
6431
6432 let candidate_id = {
6433 let entries = completions_menu.entries.borrow();
6434 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6435 if self.show_edit_predictions_in_menu() {
6436 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6437 }
6438 mat.candidate_id
6439 };
6440
6441 let completion = completions_menu
6442 .completions
6443 .borrow()
6444 .get(candidate_id)?
6445 .clone();
6446 cx.stop_propagation();
6447
6448 let buffer_handle = completions_menu.buffer.clone();
6449
6450 let CompletionEdit {
6451 new_text,
6452 snippet,
6453 replace_range,
6454 } = process_completion_for_edit(
6455 &completion,
6456 intent,
6457 &buffer_handle,
6458 &completions_menu.initial_position.text_anchor,
6459 cx,
6460 );
6461
6462 let buffer = buffer_handle.read(cx);
6463 let snapshot = self.buffer.read(cx).snapshot(cx);
6464 let newest_anchor = self.selections.newest_anchor();
6465 let replace_range_multibuffer = {
6466 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6467 excerpt.map_range_from_buffer(replace_range.clone())
6468 };
6469 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6470 return None;
6471 }
6472
6473 let old_text = buffer
6474 .text_for_range(replace_range.clone())
6475 .collect::<String>();
6476 let lookbehind = newest_anchor
6477 .start
6478 .text_anchor
6479 .to_offset(buffer)
6480 .saturating_sub(replace_range.start.0);
6481 let lookahead = replace_range
6482 .end
6483 .0
6484 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6485 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6486 let suffix = &old_text[lookbehind.min(old_text.len())..];
6487
6488 let selections = self
6489 .selections
6490 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6491 let mut ranges = Vec::new();
6492 let mut linked_edits = LinkedEdits::new();
6493
6494 let text: Arc<str> = new_text.clone().into();
6495 for selection in &selections {
6496 let range = if selection.id == newest_anchor.id {
6497 replace_range_multibuffer.clone()
6498 } else {
6499 let mut range = selection.range();
6500
6501 // if prefix is present, don't duplicate it
6502 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6503 range.start = range.start.saturating_sub_usize(lookbehind);
6504
6505 // if suffix is also present, mimic the newest cursor and replace it
6506 if selection.id != newest_anchor.id
6507 && snapshot.contains_str_at(range.end, suffix)
6508 {
6509 range.end += lookahead;
6510 }
6511 }
6512 range
6513 };
6514
6515 ranges.push(range.clone());
6516
6517 if !self.linked_edit_ranges.is_empty() {
6518 let start_anchor = snapshot.anchor_before(range.start);
6519 let end_anchor = snapshot.anchor_after(range.end);
6520 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6521 linked_edits.push(&self, anchor_range, text.clone(), cx);
6522 }
6523 }
6524
6525 let common_prefix_len = old_text
6526 .chars()
6527 .zip(new_text.chars())
6528 .take_while(|(a, b)| a == b)
6529 .map(|(a, _)| a.len_utf8())
6530 .sum::<usize>();
6531
6532 cx.emit(EditorEvent::InputHandled {
6533 utf16_range_to_replace: None,
6534 text: new_text[common_prefix_len..].into(),
6535 });
6536
6537 self.transact(window, cx, |editor, window, cx| {
6538 if let Some(mut snippet) = snippet {
6539 snippet.text = new_text.to_string();
6540 editor
6541 .insert_snippet(&ranges, snippet, window, cx)
6542 .log_err();
6543 } else {
6544 editor.buffer.update(cx, |multi_buffer, cx| {
6545 let auto_indent = match completion.insert_text_mode {
6546 Some(InsertTextMode::AS_IS) => None,
6547 _ => editor.autoindent_mode.clone(),
6548 };
6549 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6550 multi_buffer.edit(edits, auto_indent, cx);
6551 });
6552 }
6553 linked_edits.apply(cx);
6554 editor.refresh_edit_prediction(true, false, window, cx);
6555 });
6556 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6557
6558 let show_new_completions_on_confirm = completion
6559 .confirm
6560 .as_ref()
6561 .is_some_and(|confirm| confirm(intent, window, cx));
6562 if show_new_completions_on_confirm {
6563 self.open_or_update_completions_menu(None, None, false, window, cx);
6564 }
6565
6566 let provider = self.completion_provider.as_ref()?;
6567
6568 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6569 let command = lsp_store.as_ref().and_then(|lsp_store| {
6570 let CompletionSource::Lsp {
6571 lsp_completion,
6572 server_id,
6573 ..
6574 } = &completion.source
6575 else {
6576 return None;
6577 };
6578 let lsp_command = lsp_completion.command.as_ref()?;
6579 let available_commands = lsp_store
6580 .read(cx)
6581 .lsp_server_capabilities
6582 .get(server_id)
6583 .and_then(|server_capabilities| {
6584 server_capabilities
6585 .execute_command_provider
6586 .as_ref()
6587 .map(|options| options.commands.as_slice())
6588 })?;
6589 if available_commands.contains(&lsp_command.command) {
6590 Some(CodeAction {
6591 server_id: *server_id,
6592 range: language::Anchor::MIN..language::Anchor::MIN,
6593 lsp_action: LspAction::Command(lsp_command.clone()),
6594 resolved: false,
6595 })
6596 } else {
6597 None
6598 }
6599 });
6600
6601 drop(completion);
6602 let apply_edits = provider.apply_additional_edits_for_completion(
6603 buffer_handle.clone(),
6604 completions_menu.completions.clone(),
6605 candidate_id,
6606 true,
6607 cx,
6608 );
6609
6610 let editor_settings = EditorSettings::get_global(cx);
6611 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6612 // After the code completion is finished, users often want to know what signatures are needed.
6613 // so we should automatically call signature_help
6614 self.show_signature_help(&ShowSignatureHelp, window, cx);
6615 }
6616
6617 Some(cx.spawn_in(window, async move |editor, cx| {
6618 apply_edits.await?;
6619
6620 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6621 let title = command.lsp_action.title().to_owned();
6622 let project_transaction = lsp_store
6623 .update(cx, |lsp_store, cx| {
6624 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6625 })
6626 .await
6627 .context("applying post-completion command")?;
6628 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6629 Self::open_project_transaction(
6630 &editor,
6631 workspace.downgrade(),
6632 project_transaction,
6633 title,
6634 cx,
6635 )
6636 .await?;
6637 }
6638 }
6639
6640 Ok(())
6641 }))
6642 }
6643
6644 pub fn toggle_code_actions(
6645 &mut self,
6646 action: &ToggleCodeActions,
6647 window: &mut Window,
6648 cx: &mut Context<Self>,
6649 ) {
6650 let quick_launch = action.quick_launch;
6651 let mut context_menu = self.context_menu.borrow_mut();
6652 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6653 if code_actions.deployed_from == action.deployed_from {
6654 // Toggle if we're selecting the same one
6655 *context_menu = None;
6656 cx.notify();
6657 return;
6658 } else {
6659 // Otherwise, clear it and start a new one
6660 *context_menu = None;
6661 cx.notify();
6662 }
6663 }
6664 drop(context_menu);
6665 let snapshot = self.snapshot(window, cx);
6666 let deployed_from = action.deployed_from.clone();
6667 let action = action.clone();
6668 self.completion_tasks.clear();
6669 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6670
6671 let multibuffer_point = match &action.deployed_from {
6672 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6673 DisplayPoint::new(*row, 0).to_point(&snapshot)
6674 }
6675 _ => self
6676 .selections
6677 .newest::<Point>(&snapshot.display_snapshot)
6678 .head(),
6679 };
6680 let Some((buffer, buffer_row)) = snapshot
6681 .buffer_snapshot()
6682 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6683 .and_then(|(buffer_snapshot, range)| {
6684 self.buffer()
6685 .read(cx)
6686 .buffer(buffer_snapshot.remote_id())
6687 .map(|buffer| (buffer, range.start.row))
6688 })
6689 else {
6690 return;
6691 };
6692 let buffer_id = buffer.read(cx).remote_id();
6693 let tasks = self
6694 .tasks
6695 .get(&(buffer_id, buffer_row))
6696 .map(|t| Arc::new(t.to_owned()));
6697
6698 if !self.focus_handle.is_focused(window) {
6699 return;
6700 }
6701 let project = self.project.clone();
6702
6703 let code_actions_task = match deployed_from {
6704 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6705 _ => self.code_actions(buffer_row, window, cx),
6706 };
6707
6708 let runnable_task = match deployed_from {
6709 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6710 _ => {
6711 let mut task_context_task = Task::ready(None);
6712 if let Some(tasks) = &tasks
6713 && let Some(project) = project
6714 {
6715 task_context_task =
6716 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6717 }
6718
6719 cx.spawn_in(window, {
6720 let buffer = buffer.clone();
6721 async move |editor, cx| {
6722 let task_context = task_context_task.await;
6723
6724 let resolved_tasks =
6725 tasks
6726 .zip(task_context.clone())
6727 .map(|(tasks, task_context)| ResolvedTasks {
6728 templates: tasks.resolve(&task_context).collect(),
6729 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6730 multibuffer_point.row,
6731 tasks.column,
6732 )),
6733 });
6734 let debug_scenarios = editor
6735 .update(cx, |editor, cx| {
6736 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6737 })?
6738 .await;
6739 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6740 }
6741 })
6742 }
6743 };
6744
6745 cx.spawn_in(window, async move |editor, cx| {
6746 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6747 let code_actions = code_actions_task.await;
6748 let spawn_straight_away = quick_launch
6749 && resolved_tasks
6750 .as_ref()
6751 .is_some_and(|tasks| tasks.templates.len() == 1)
6752 && code_actions
6753 .as_ref()
6754 .is_none_or(|actions| actions.is_empty())
6755 && debug_scenarios.is_empty();
6756
6757 editor.update_in(cx, |editor, window, cx| {
6758 crate::hover_popover::hide_hover(editor, cx);
6759 let actions = CodeActionContents::new(
6760 resolved_tasks,
6761 code_actions,
6762 debug_scenarios,
6763 task_context.unwrap_or_default(),
6764 );
6765
6766 // Don't show the menu if there are no actions available
6767 if actions.is_empty() {
6768 cx.notify();
6769 return Task::ready(Ok(()));
6770 }
6771
6772 *editor.context_menu.borrow_mut() =
6773 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6774 buffer,
6775 actions,
6776 selected_item: Default::default(),
6777 scroll_handle: UniformListScrollHandle::default(),
6778 deployed_from,
6779 }));
6780 cx.notify();
6781 if spawn_straight_away
6782 && let Some(task) = editor.confirm_code_action(
6783 &ConfirmCodeAction { item_ix: Some(0) },
6784 window,
6785 cx,
6786 )
6787 {
6788 return task;
6789 }
6790
6791 Task::ready(Ok(()))
6792 })
6793 })
6794 .detach_and_log_err(cx);
6795 }
6796
6797 fn debug_scenarios(
6798 &mut self,
6799 resolved_tasks: &Option<ResolvedTasks>,
6800 buffer: &Entity<Buffer>,
6801 cx: &mut App,
6802 ) -> Task<Vec<task::DebugScenario>> {
6803 maybe!({
6804 let project = self.project()?;
6805 let dap_store = project.read(cx).dap_store();
6806 let mut scenarios = vec![];
6807 let resolved_tasks = resolved_tasks.as_ref()?;
6808 let buffer = buffer.read(cx);
6809 let language = buffer.language()?;
6810 let file = buffer.file();
6811 let debug_adapter = language_settings(language.name().into(), file, cx)
6812 .debuggers
6813 .first()
6814 .map(SharedString::from)
6815 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6816
6817 dap_store.update(cx, |dap_store, cx| {
6818 for (_, task) in &resolved_tasks.templates {
6819 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6820 task.original_task().clone(),
6821 debug_adapter.clone().into(),
6822 task.display_label().to_owned().into(),
6823 cx,
6824 );
6825 scenarios.push(maybe_scenario);
6826 }
6827 });
6828 Some(cx.background_spawn(async move {
6829 futures::future::join_all(scenarios)
6830 .await
6831 .into_iter()
6832 .flatten()
6833 .collect::<Vec<_>>()
6834 }))
6835 })
6836 .unwrap_or_else(|| Task::ready(vec![]))
6837 }
6838
6839 fn code_actions(
6840 &mut self,
6841 buffer_row: u32,
6842 window: &mut Window,
6843 cx: &mut Context<Self>,
6844 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6845 let mut task = self.code_actions_task.take();
6846 cx.spawn_in(window, async move |editor, cx| {
6847 while let Some(prev_task) = task {
6848 prev_task.await.log_err();
6849 task = editor
6850 .update(cx, |this, _| this.code_actions_task.take())
6851 .ok()?;
6852 }
6853
6854 editor
6855 .update(cx, |editor, cx| {
6856 editor
6857 .available_code_actions
6858 .clone()
6859 .and_then(|(location, code_actions)| {
6860 let snapshot = location.buffer.read(cx).snapshot();
6861 let point_range = location.range.to_point(&snapshot);
6862 let point_range = point_range.start.row..=point_range.end.row;
6863 if point_range.contains(&buffer_row) {
6864 Some(code_actions)
6865 } else {
6866 None
6867 }
6868 })
6869 })
6870 .ok()
6871 .flatten()
6872 })
6873 }
6874
6875 pub fn confirm_code_action(
6876 &mut self,
6877 action: &ConfirmCodeAction,
6878 window: &mut Window,
6879 cx: &mut Context<Self>,
6880 ) -> Option<Task<Result<()>>> {
6881 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6882
6883 let actions_menu =
6884 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6885 menu
6886 } else {
6887 return None;
6888 };
6889
6890 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6891 let action = actions_menu.actions.get(action_ix)?;
6892 let title = action.label();
6893 let buffer = actions_menu.buffer;
6894 let workspace = self.workspace()?;
6895
6896 match action {
6897 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6898 workspace.update(cx, |workspace, cx| {
6899 workspace.schedule_resolved_task(
6900 task_source_kind,
6901 resolved_task,
6902 false,
6903 window,
6904 cx,
6905 );
6906
6907 Some(Task::ready(Ok(())))
6908 })
6909 }
6910 CodeActionsItem::CodeAction {
6911 excerpt_id,
6912 action,
6913 provider,
6914 } => {
6915 let apply_code_action =
6916 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6917 let workspace = workspace.downgrade();
6918 Some(cx.spawn_in(window, async move |editor, cx| {
6919 let project_transaction = apply_code_action.await?;
6920 Self::open_project_transaction(
6921 &editor,
6922 workspace,
6923 project_transaction,
6924 title,
6925 cx,
6926 )
6927 .await
6928 }))
6929 }
6930 CodeActionsItem::DebugScenario(scenario) => {
6931 let context = actions_menu.actions.context.into();
6932
6933 workspace.update(cx, |workspace, cx| {
6934 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6935 workspace.start_debug_session(
6936 scenario,
6937 context,
6938 Some(buffer),
6939 None,
6940 window,
6941 cx,
6942 );
6943 });
6944 Some(Task::ready(Ok(())))
6945 }
6946 }
6947 }
6948
6949 fn open_transaction_for_hidden_buffers(
6950 workspace: Entity<Workspace>,
6951 transaction: ProjectTransaction,
6952 title: String,
6953 window: &mut Window,
6954 cx: &mut Context<Self>,
6955 ) {
6956 if transaction.0.is_empty() {
6957 return;
6958 }
6959
6960 let edited_buffers_already_open = {
6961 let other_editors: Vec<Entity<Editor>> = workspace
6962 .read(cx)
6963 .panes()
6964 .iter()
6965 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6966 .filter(|editor| editor.entity_id() != cx.entity_id())
6967 .collect();
6968
6969 transaction.0.keys().all(|buffer| {
6970 other_editors.iter().any(|editor| {
6971 let multi_buffer = editor.read(cx).buffer();
6972 multi_buffer.read(cx).is_singleton()
6973 && multi_buffer
6974 .read(cx)
6975 .as_singleton()
6976 .map_or(false, |singleton| {
6977 singleton.entity_id() == buffer.entity_id()
6978 })
6979 })
6980 })
6981 };
6982 if !edited_buffers_already_open {
6983 let workspace = workspace.downgrade();
6984 cx.defer_in(window, move |_, window, cx| {
6985 cx.spawn_in(window, async move |editor, cx| {
6986 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6987 .await
6988 .ok()
6989 })
6990 .detach();
6991 });
6992 }
6993 }
6994
6995 pub async fn open_project_transaction(
6996 editor: &WeakEntity<Editor>,
6997 workspace: WeakEntity<Workspace>,
6998 transaction: ProjectTransaction,
6999 title: String,
7000 cx: &mut AsyncWindowContext,
7001 ) -> Result<()> {
7002 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7003 cx.update(|_, cx| {
7004 entries.sort_unstable_by_key(|(buffer, _)| {
7005 buffer.read(cx).file().map(|f| f.path().clone())
7006 });
7007 })?;
7008 if entries.is_empty() {
7009 return Ok(());
7010 }
7011
7012 // If the project transaction's edits are all contained within this editor, then
7013 // avoid opening a new editor to display them.
7014
7015 if let [(buffer, transaction)] = &*entries {
7016 let excerpt = editor.update(cx, |editor, cx| {
7017 editor
7018 .buffer()
7019 .read(cx)
7020 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7021 })?;
7022 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7023 && excerpted_buffer == *buffer
7024 {
7025 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7026 let excerpt_range = excerpt_range.to_offset(buffer);
7027 buffer
7028 .edited_ranges_for_transaction::<usize>(transaction)
7029 .all(|range| {
7030 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7031 })
7032 });
7033
7034 if all_edits_within_excerpt {
7035 return Ok(());
7036 }
7037 }
7038 }
7039
7040 let mut ranges_to_highlight = Vec::new();
7041 let excerpt_buffer = cx.new(|cx| {
7042 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7043 for (buffer_handle, transaction) in &entries {
7044 let edited_ranges = buffer_handle
7045 .read(cx)
7046 .edited_ranges_for_transaction::<Point>(transaction)
7047 .collect::<Vec<_>>();
7048 let (ranges, _) = multibuffer.set_excerpts_for_path(
7049 PathKey::for_buffer(buffer_handle, cx),
7050 buffer_handle.clone(),
7051 edited_ranges,
7052 multibuffer_context_lines(cx),
7053 cx,
7054 );
7055
7056 ranges_to_highlight.extend(ranges);
7057 }
7058 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7059 multibuffer
7060 });
7061
7062 workspace.update_in(cx, |workspace, window, cx| {
7063 let project = workspace.project().clone();
7064 let editor =
7065 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7066 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7067 editor.update(cx, |editor, cx| {
7068 editor.highlight_background(
7069 HighlightKey::Editor,
7070 &ranges_to_highlight,
7071 |_, theme| theme.colors().editor_highlighted_line_background,
7072 cx,
7073 );
7074 });
7075 })?;
7076
7077 Ok(())
7078 }
7079
7080 pub fn clear_code_action_providers(&mut self) {
7081 self.code_action_providers.clear();
7082 self.available_code_actions.take();
7083 }
7084
7085 pub fn add_code_action_provider(
7086 &mut self,
7087 provider: Rc<dyn CodeActionProvider>,
7088 window: &mut Window,
7089 cx: &mut Context<Self>,
7090 ) {
7091 if self
7092 .code_action_providers
7093 .iter()
7094 .any(|existing_provider| existing_provider.id() == provider.id())
7095 {
7096 return;
7097 }
7098
7099 self.code_action_providers.push(provider);
7100 self.refresh_code_actions(window, cx);
7101 }
7102
7103 pub fn remove_code_action_provider(
7104 &mut self,
7105 id: Arc<str>,
7106 window: &mut Window,
7107 cx: &mut Context<Self>,
7108 ) {
7109 self.code_action_providers
7110 .retain(|provider| provider.id() != id);
7111 self.refresh_code_actions(window, cx);
7112 }
7113
7114 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7115 !self.code_action_providers.is_empty()
7116 && EditorSettings::get_global(cx).toolbar.code_actions
7117 }
7118
7119 pub fn has_available_code_actions(&self) -> bool {
7120 self.available_code_actions
7121 .as_ref()
7122 .is_some_and(|(_, actions)| !actions.is_empty())
7123 }
7124
7125 fn render_inline_code_actions(
7126 &self,
7127 icon_size: ui::IconSize,
7128 display_row: DisplayRow,
7129 is_active: bool,
7130 cx: &mut Context<Self>,
7131 ) -> AnyElement {
7132 let show_tooltip = !self.context_menu_visible();
7133 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7134 .icon_size(icon_size)
7135 .shape(ui::IconButtonShape::Square)
7136 .icon_color(ui::Color::Hidden)
7137 .toggle_state(is_active)
7138 .when(show_tooltip, |this| {
7139 this.tooltip({
7140 let focus_handle = self.focus_handle.clone();
7141 move |_window, cx| {
7142 Tooltip::for_action_in(
7143 "Toggle Code Actions",
7144 &ToggleCodeActions {
7145 deployed_from: None,
7146 quick_launch: false,
7147 },
7148 &focus_handle,
7149 cx,
7150 )
7151 }
7152 })
7153 })
7154 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7155 window.focus(&editor.focus_handle(cx), cx);
7156 editor.toggle_code_actions(
7157 &crate::actions::ToggleCodeActions {
7158 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7159 display_row,
7160 )),
7161 quick_launch: false,
7162 },
7163 window,
7164 cx,
7165 );
7166 }))
7167 .into_any_element()
7168 }
7169
7170 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7171 &self.context_menu
7172 }
7173
7174 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7175 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7176 cx.background_executor()
7177 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7178 .await;
7179
7180 let (start_buffer, start, _, end, newest_selection) = this
7181 .update(cx, |this, cx| {
7182 let newest_selection = this.selections.newest_anchor().clone();
7183 if newest_selection.head().diff_base_anchor.is_some() {
7184 return None;
7185 }
7186 let display_snapshot = this.display_snapshot(cx);
7187 let newest_selection_adjusted =
7188 this.selections.newest_adjusted(&display_snapshot);
7189 let buffer = this.buffer.read(cx);
7190
7191 let (start_buffer, start) =
7192 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7193 let (end_buffer, end) =
7194 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7195
7196 Some((start_buffer, start, end_buffer, end, newest_selection))
7197 })?
7198 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7199 .context(
7200 "Expected selection to lie in a single buffer when refreshing code actions",
7201 )?;
7202 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7203 let providers = this.code_action_providers.clone();
7204 let tasks = this
7205 .code_action_providers
7206 .iter()
7207 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7208 .collect::<Vec<_>>();
7209 (providers, tasks)
7210 })?;
7211
7212 let mut actions = Vec::new();
7213 for (provider, provider_actions) in
7214 providers.into_iter().zip(future::join_all(tasks).await)
7215 {
7216 if let Some(provider_actions) = provider_actions.log_err() {
7217 actions.extend(provider_actions.into_iter().map(|action| {
7218 AvailableCodeAction {
7219 excerpt_id: newest_selection.start.excerpt_id,
7220 action,
7221 provider: provider.clone(),
7222 }
7223 }));
7224 }
7225 }
7226
7227 this.update(cx, |this, cx| {
7228 this.available_code_actions = if actions.is_empty() {
7229 None
7230 } else {
7231 Some((
7232 Location {
7233 buffer: start_buffer,
7234 range: start..end,
7235 },
7236 actions.into(),
7237 ))
7238 };
7239 cx.notify();
7240 })
7241 }));
7242 }
7243
7244 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7245 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7246 self.show_git_blame_inline = false;
7247
7248 self.show_git_blame_inline_delay_task =
7249 Some(cx.spawn_in(window, async move |this, cx| {
7250 cx.background_executor().timer(delay).await;
7251
7252 this.update(cx, |this, cx| {
7253 this.show_git_blame_inline = true;
7254 cx.notify();
7255 })
7256 .log_err();
7257 }));
7258 }
7259 }
7260
7261 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7262 let snapshot = self.snapshot(window, cx);
7263 let cursor = self
7264 .selections
7265 .newest::<Point>(&snapshot.display_snapshot)
7266 .head();
7267 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7268 else {
7269 return;
7270 };
7271
7272 if self.blame.is_none() {
7273 self.start_git_blame(true, window, cx);
7274 }
7275 let Some(blame) = self.blame.as_ref() else {
7276 return;
7277 };
7278
7279 let row_info = RowInfo {
7280 buffer_id: Some(buffer.remote_id()),
7281 buffer_row: Some(point.row),
7282 ..Default::default()
7283 };
7284 let Some((buffer, blame_entry)) = blame
7285 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7286 .flatten()
7287 else {
7288 return;
7289 };
7290
7291 let anchor = self.selections.newest_anchor().head();
7292 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7293 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7294 self.show_blame_popover(
7295 buffer,
7296 &blame_entry,
7297 position + last_bounds.origin,
7298 true,
7299 cx,
7300 );
7301 };
7302 }
7303
7304 fn show_blame_popover(
7305 &mut self,
7306 buffer: BufferId,
7307 blame_entry: &BlameEntry,
7308 position: gpui::Point<Pixels>,
7309 ignore_timeout: bool,
7310 cx: &mut Context<Self>,
7311 ) {
7312 if let Some(state) = &mut self.inline_blame_popover {
7313 state.hide_task.take();
7314 } else {
7315 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7316 let blame_entry = blame_entry.clone();
7317 let show_task = cx.spawn(async move |editor, cx| {
7318 if !ignore_timeout {
7319 cx.background_executor()
7320 .timer(std::time::Duration::from_millis(blame_popover_delay))
7321 .await;
7322 }
7323 editor
7324 .update(cx, |editor, cx| {
7325 editor.inline_blame_popover_show_task.take();
7326 let Some(blame) = editor.blame.as_ref() else {
7327 return;
7328 };
7329 let blame = blame.read(cx);
7330 let details = blame.details_for_entry(buffer, &blame_entry);
7331 let markdown = cx.new(|cx| {
7332 Markdown::new(
7333 details
7334 .as_ref()
7335 .map(|message| message.message.clone())
7336 .unwrap_or_default(),
7337 None,
7338 None,
7339 cx,
7340 )
7341 });
7342 editor.inline_blame_popover = Some(InlineBlamePopover {
7343 position,
7344 hide_task: None,
7345 popover_bounds: None,
7346 popover_state: InlineBlamePopoverState {
7347 scroll_handle: ScrollHandle::new(),
7348 commit_message: details,
7349 markdown,
7350 },
7351 keyboard_grace: ignore_timeout,
7352 });
7353 cx.notify();
7354 })
7355 .ok();
7356 });
7357 self.inline_blame_popover_show_task = Some(show_task);
7358 }
7359 }
7360
7361 pub fn has_mouse_context_menu(&self) -> bool {
7362 self.mouse_context_menu.is_some()
7363 }
7364
7365 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7366 self.inline_blame_popover_show_task.take();
7367 if let Some(state) = &mut self.inline_blame_popover {
7368 let hide_task = cx.spawn(async move |editor, cx| {
7369 if !ignore_timeout {
7370 cx.background_executor()
7371 .timer(std::time::Duration::from_millis(100))
7372 .await;
7373 }
7374 editor
7375 .update(cx, |editor, cx| {
7376 editor.inline_blame_popover.take();
7377 cx.notify();
7378 })
7379 .ok();
7380 });
7381 state.hide_task = Some(hide_task);
7382 true
7383 } else {
7384 false
7385 }
7386 }
7387
7388 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7389 if self.pending_rename.is_some() {
7390 return None;
7391 }
7392
7393 let provider = self.semantics_provider.clone()?;
7394 let buffer = self.buffer.read(cx);
7395 let newest_selection = self.selections.newest_anchor().clone();
7396 let cursor_position = newest_selection.head();
7397 let (cursor_buffer, cursor_buffer_position) =
7398 buffer.text_anchor_for_position(cursor_position, cx)?;
7399 let (tail_buffer, tail_buffer_position) =
7400 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7401 if cursor_buffer != tail_buffer {
7402 return None;
7403 }
7404
7405 let snapshot = cursor_buffer.read(cx).snapshot();
7406 let word_ranges = cx.background_spawn(async move {
7407 // this might look odd to put on the background thread, but
7408 // `surrounding_word` can be quite expensive as it calls into
7409 // tree-sitter language scopes
7410 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7411 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7412 (start_word_range, end_word_range)
7413 });
7414
7415 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7416 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7417 let (start_word_range, end_word_range) = word_ranges.await;
7418 if start_word_range != end_word_range {
7419 this.update(cx, |this, cx| {
7420 this.document_highlights_task.take();
7421 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7422 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7423 })
7424 .ok();
7425 return;
7426 }
7427 cx.background_executor()
7428 .timer(Duration::from_millis(debounce))
7429 .await;
7430
7431 let highlights = if let Some(highlights) = cx.update(|cx| {
7432 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7433 }) {
7434 highlights.await.log_err()
7435 } else {
7436 None
7437 };
7438
7439 if let Some(highlights) = highlights {
7440 this.update(cx, |this, cx| {
7441 if this.pending_rename.is_some() {
7442 return;
7443 }
7444
7445 let buffer = this.buffer.read(cx);
7446 if buffer
7447 .text_anchor_for_position(cursor_position, cx)
7448 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7449 {
7450 return;
7451 }
7452
7453 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7454 let mut write_ranges = Vec::new();
7455 let mut read_ranges = Vec::new();
7456 for highlight in highlights {
7457 let buffer_id = cursor_buffer.read(cx).remote_id();
7458 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7459 {
7460 let start = highlight
7461 .range
7462 .start
7463 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7464 let end = highlight
7465 .range
7466 .end
7467 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7468 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7469 continue;
7470 }
7471
7472 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7473 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7474 write_ranges.push(range);
7475 } else {
7476 read_ranges.push(range);
7477 }
7478 }
7479 }
7480
7481 this.highlight_background(
7482 HighlightKey::DocumentHighlightRead,
7483 &read_ranges,
7484 |_, theme| theme.colors().editor_document_highlight_read_background,
7485 cx,
7486 );
7487 this.highlight_background(
7488 HighlightKey::DocumentHighlightWrite,
7489 &write_ranges,
7490 |_, theme| theme.colors().editor_document_highlight_write_background,
7491 cx,
7492 );
7493 cx.notify();
7494 })
7495 .log_err();
7496 }
7497 }));
7498 None
7499 }
7500
7501 fn prepare_highlight_query_from_selection(
7502 &mut self,
7503 window: &Window,
7504 cx: &mut Context<Editor>,
7505 ) -> Option<(String, Range<Anchor>)> {
7506 if matches!(self.mode, EditorMode::SingleLine) {
7507 return None;
7508 }
7509 if !EditorSettings::get_global(cx).selection_highlight {
7510 return None;
7511 }
7512 if self.selections.count() != 1 || self.selections.line_mode() {
7513 return None;
7514 }
7515 let snapshot = self.snapshot(window, cx);
7516 let selection = self.selections.newest::<Point>(&snapshot);
7517 // If the selection spans multiple rows OR it is empty
7518 if selection.start.row != selection.end.row
7519 || selection.start.column == selection.end.column
7520 {
7521 return None;
7522 }
7523 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7524 let query = snapshot
7525 .buffer_snapshot()
7526 .text_for_range(selection_anchor_range.clone())
7527 .collect::<String>();
7528 if query.trim().is_empty() {
7529 return None;
7530 }
7531 Some((query, selection_anchor_range))
7532 }
7533
7534 #[ztracing::instrument(skip_all)]
7535 fn update_selection_occurrence_highlights(
7536 &mut self,
7537 query_text: String,
7538 query_range: Range<Anchor>,
7539 multi_buffer_range_to_query: Range<Point>,
7540 use_debounce: bool,
7541 window: &mut Window,
7542 cx: &mut Context<Editor>,
7543 ) -> Task<()> {
7544 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7545 cx.spawn_in(window, async move |editor, cx| {
7546 if use_debounce {
7547 cx.background_executor()
7548 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7549 .await;
7550 }
7551 let match_task = cx.background_spawn(async move {
7552 let buffer_ranges = multi_buffer_snapshot
7553 .range_to_buffer_ranges(
7554 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7555 )
7556 .into_iter()
7557 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7558 let mut match_ranges = Vec::new();
7559 let Ok(regex) = project::search::SearchQuery::text(
7560 query_text.clone(),
7561 false,
7562 false,
7563 false,
7564 Default::default(),
7565 Default::default(),
7566 false,
7567 None,
7568 ) else {
7569 return Vec::default();
7570 };
7571 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7572 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7573 match_ranges.extend(
7574 regex
7575 .search(
7576 buffer_snapshot,
7577 Some(search_range.start.0..search_range.end.0),
7578 )
7579 .await
7580 .into_iter()
7581 .filter_map(|match_range| {
7582 let match_start = buffer_snapshot
7583 .anchor_after(search_range.start + match_range.start);
7584 let match_end = buffer_snapshot
7585 .anchor_before(search_range.start + match_range.end);
7586 let match_anchor_range =
7587 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7588 (match_anchor_range != query_range).then_some(match_anchor_range)
7589 }),
7590 );
7591 }
7592 match_ranges
7593 });
7594 let match_ranges = match_task.await;
7595 editor
7596 .update_in(cx, |editor, _, cx| {
7597 if use_debounce {
7598 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7599 editor.debounced_selection_highlight_complete = true;
7600 } else if editor.debounced_selection_highlight_complete {
7601 return;
7602 }
7603 if !match_ranges.is_empty() {
7604 editor.highlight_background(
7605 HighlightKey::SelectedTextHighlight,
7606 &match_ranges,
7607 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7608 cx,
7609 )
7610 }
7611 })
7612 .log_err();
7613 })
7614 }
7615
7616 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7617 struct NewlineFold;
7618 let type_id = std::any::TypeId::of::<NewlineFold>();
7619 if !self.mode.is_single_line() {
7620 return;
7621 }
7622 let snapshot = self.snapshot(window, cx);
7623 if snapshot.buffer_snapshot().max_point().row == 0 {
7624 return;
7625 }
7626 let task = cx.background_spawn(async move {
7627 let new_newlines = snapshot
7628 .buffer_chars_at(MultiBufferOffset(0))
7629 .filter_map(|(c, i)| {
7630 if c == '\n' {
7631 Some(
7632 snapshot.buffer_snapshot().anchor_after(i)
7633 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7634 )
7635 } else {
7636 None
7637 }
7638 })
7639 .collect::<Vec<_>>();
7640 let existing_newlines = snapshot
7641 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7642 .filter_map(|fold| {
7643 if fold.placeholder.type_tag == Some(type_id) {
7644 Some(fold.range.start..fold.range.end)
7645 } else {
7646 None
7647 }
7648 })
7649 .collect::<Vec<_>>();
7650
7651 (new_newlines, existing_newlines)
7652 });
7653 self.folding_newlines = cx.spawn(async move |this, cx| {
7654 let (new_newlines, existing_newlines) = task.await;
7655 if new_newlines == existing_newlines {
7656 return;
7657 }
7658 let placeholder = FoldPlaceholder {
7659 render: Arc::new(move |_, _, cx| {
7660 div()
7661 .bg(cx.theme().status().hint_background)
7662 .border_b_1()
7663 .size_full()
7664 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7665 .border_color(cx.theme().status().hint)
7666 .child("\\n")
7667 .into_any()
7668 }),
7669 constrain_width: false,
7670 merge_adjacent: false,
7671 type_tag: Some(type_id),
7672 collapsed_text: None,
7673 };
7674 let creases = new_newlines
7675 .into_iter()
7676 .map(|range| Crease::simple(range, placeholder.clone()))
7677 .collect();
7678 this.update(cx, |this, cx| {
7679 this.display_map.update(cx, |display_map, cx| {
7680 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7681 display_map.fold(creases, cx);
7682 });
7683 })
7684 .ok();
7685 });
7686 }
7687
7688 #[ztracing::instrument(skip_all)]
7689 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7690 if !self.mode.is_full() {
7691 return;
7692 }
7693 let cursor = self.selections.newest_anchor().head();
7694 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7695
7696 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7697 self.outline_symbols_at_cursor =
7698 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7699 cx.emit(EditorEvent::OutlineSymbolsChanged);
7700 cx.notify();
7701 } else {
7702 let syntax = cx.theme().syntax().clone();
7703 let background_task = cx.background_spawn(async move {
7704 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7705 });
7706 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7707 cx.spawn(async move |this, cx| {
7708 let symbols = background_task.await;
7709 this.update(cx, |this, cx| {
7710 this.outline_symbols_at_cursor = symbols;
7711 cx.emit(EditorEvent::OutlineSymbolsChanged);
7712 cx.notify();
7713 })
7714 .ok();
7715 });
7716 }
7717 }
7718
7719 #[ztracing::instrument(skip_all)]
7720 fn refresh_selected_text_highlights(
7721 &mut self,
7722 on_buffer_edit: bool,
7723 window: &mut Window,
7724 cx: &mut Context<Editor>,
7725 ) {
7726 let Some((query_text, query_range)) =
7727 self.prepare_highlight_query_from_selection(window, cx)
7728 else {
7729 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7730 self.quick_selection_highlight_task.take();
7731 self.debounced_selection_highlight_task.take();
7732 self.debounced_selection_highlight_complete = false;
7733 return;
7734 };
7735 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7736 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7737 let query_changed = self
7738 .quick_selection_highlight_task
7739 .as_ref()
7740 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7741 if query_changed {
7742 self.debounced_selection_highlight_complete = false;
7743 }
7744 if on_buffer_edit || query_changed {
7745 let multi_buffer_visible_start = self
7746 .scroll_manager
7747 .native_anchor(&display_snapshot, cx)
7748 .anchor
7749 .to_point(&multi_buffer_snapshot);
7750 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7751 multi_buffer_visible_start
7752 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7753 Bias::Left,
7754 );
7755 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7756 self.quick_selection_highlight_task = Some((
7757 query_range.clone(),
7758 self.update_selection_occurrence_highlights(
7759 query_text.clone(),
7760 query_range.clone(),
7761 multi_buffer_visible_range,
7762 false,
7763 window,
7764 cx,
7765 ),
7766 ));
7767 }
7768 if on_buffer_edit
7769 || self
7770 .debounced_selection_highlight_task
7771 .as_ref()
7772 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7773 {
7774 let multi_buffer_start = multi_buffer_snapshot
7775 .anchor_before(MultiBufferOffset(0))
7776 .to_point(&multi_buffer_snapshot);
7777 let multi_buffer_end = multi_buffer_snapshot
7778 .anchor_after(multi_buffer_snapshot.len())
7779 .to_point(&multi_buffer_snapshot);
7780 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7781 self.debounced_selection_highlight_task = Some((
7782 query_range.clone(),
7783 self.update_selection_occurrence_highlights(
7784 query_text,
7785 query_range,
7786 multi_buffer_full_range,
7787 true,
7788 window,
7789 cx,
7790 ),
7791 ));
7792 }
7793 }
7794
7795 pub fn refresh_edit_prediction(
7796 &mut self,
7797 debounce: bool,
7798 user_requested: bool,
7799 window: &mut Window,
7800 cx: &mut Context<Self>,
7801 ) -> Option<()> {
7802 let provider = self.edit_prediction_provider()?;
7803 let cursor = self.selections.newest_anchor().head();
7804 let (buffer, cursor_buffer_position) =
7805 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7806
7807 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7808 return None;
7809 }
7810
7811 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7812 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7813 return None;
7814 }
7815
7816 self.update_visible_edit_prediction(window, cx);
7817
7818 if !user_requested
7819 && (!self.should_show_edit_predictions()
7820 || !self.is_focused(window)
7821 || buffer.read(cx).is_empty())
7822 {
7823 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7824 return None;
7825 }
7826
7827 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7828 Some(())
7829 }
7830
7831 fn show_edit_predictions_in_menu(&self) -> bool {
7832 match self.edit_prediction_settings {
7833 EditPredictionSettings::Disabled => false,
7834 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7835 }
7836 }
7837
7838 pub fn edit_predictions_enabled(&self) -> bool {
7839 match self.edit_prediction_settings {
7840 EditPredictionSettings::Disabled => false,
7841 EditPredictionSettings::Enabled { .. } => true,
7842 }
7843 }
7844
7845 fn edit_prediction_requires_modifier(&self) -> bool {
7846 match self.edit_prediction_settings {
7847 EditPredictionSettings::Disabled => false,
7848 EditPredictionSettings::Enabled {
7849 preview_requires_modifier,
7850 ..
7851 } => preview_requires_modifier,
7852 }
7853 }
7854
7855 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7856 if self.edit_prediction_provider.is_none() {
7857 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7858 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7859 return;
7860 }
7861
7862 let selection = self.selections.newest_anchor();
7863 let cursor = selection.head();
7864
7865 if let Some((buffer, cursor_buffer_position)) =
7866 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7867 {
7868 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7869 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7870 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7871 return;
7872 }
7873 self.edit_prediction_settings =
7874 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7875 }
7876 }
7877
7878 fn edit_prediction_settings_at_position(
7879 &self,
7880 buffer: &Entity<Buffer>,
7881 buffer_position: language::Anchor,
7882 cx: &App,
7883 ) -> EditPredictionSettings {
7884 if !self.mode.is_full()
7885 || !self.show_edit_predictions_override.unwrap_or(true)
7886 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7887 {
7888 return EditPredictionSettings::Disabled;
7889 }
7890
7891 let buffer = buffer.read(cx);
7892
7893 let file = buffer.file();
7894
7895 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7896 return EditPredictionSettings::Disabled;
7897 };
7898
7899 let by_provider = matches!(
7900 self.menu_edit_predictions_policy,
7901 MenuEditPredictionsPolicy::ByProvider
7902 );
7903
7904 let show_in_menu = by_provider
7905 && self
7906 .edit_prediction_provider
7907 .as_ref()
7908 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7909
7910 let preview_requires_modifier =
7911 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7912
7913 EditPredictionSettings::Enabled {
7914 show_in_menu,
7915 preview_requires_modifier,
7916 }
7917 }
7918
7919 fn should_show_edit_predictions(&self) -> bool {
7920 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7921 }
7922
7923 pub fn edit_prediction_preview_is_active(&self) -> bool {
7924 matches!(
7925 self.edit_prediction_preview,
7926 EditPredictionPreview::Active { .. }
7927 )
7928 }
7929
7930 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7931 let cursor = self.selections.newest_anchor().head();
7932 if let Some((buffer, cursor_position)) =
7933 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7934 {
7935 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7936 } else {
7937 false
7938 }
7939 }
7940
7941 pub fn supports_minimap(&self, cx: &App) -> bool {
7942 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7943 }
7944
7945 fn edit_predictions_enabled_in_buffer(
7946 &self,
7947 buffer: &Entity<Buffer>,
7948 buffer_position: language::Anchor,
7949 cx: &App,
7950 ) -> bool {
7951 maybe!({
7952 if self.read_only(cx) {
7953 return Some(false);
7954 }
7955 let provider = self.edit_prediction_provider()?;
7956 if !provider.is_enabled(buffer, buffer_position, cx) {
7957 return Some(false);
7958 }
7959 let buffer = buffer.read(cx);
7960 let Some(file) = buffer.file() else {
7961 return Some(true);
7962 };
7963 let settings = all_language_settings(Some(file), cx);
7964 Some(settings.edit_predictions_enabled_for_file(file, cx))
7965 })
7966 .unwrap_or(false)
7967 }
7968
7969 pub fn show_edit_prediction(
7970 &mut self,
7971 _: &ShowEditPrediction,
7972 window: &mut Window,
7973 cx: &mut Context<Self>,
7974 ) {
7975 if !self.has_active_edit_prediction() {
7976 self.refresh_edit_prediction(false, true, window, cx);
7977 return;
7978 }
7979
7980 self.update_visible_edit_prediction(window, cx);
7981 }
7982
7983 pub fn display_cursor_names(
7984 &mut self,
7985 _: &DisplayCursorNames,
7986 window: &mut Window,
7987 cx: &mut Context<Self>,
7988 ) {
7989 self.show_cursor_names(window, cx);
7990 }
7991
7992 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7993 self.show_cursor_names = true;
7994 cx.notify();
7995 cx.spawn_in(window, async move |this, cx| {
7996 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7997 this.update(cx, |this, cx| {
7998 this.show_cursor_names = false;
7999 cx.notify()
8000 })
8001 .ok()
8002 })
8003 .detach();
8004 }
8005
8006 pub fn accept_partial_edit_prediction(
8007 &mut self,
8008 granularity: EditPredictionGranularity,
8009 window: &mut Window,
8010 cx: &mut Context<Self>,
8011 ) {
8012 if self.show_edit_predictions_in_menu() {
8013 self.hide_context_menu(window, cx);
8014 }
8015
8016 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8017 return;
8018 };
8019
8020 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8021 return;
8022 }
8023
8024 match &active_edit_prediction.completion {
8025 EditPrediction::MoveWithin { target, .. } => {
8026 let target = *target;
8027
8028 if matches!(granularity, EditPredictionGranularity::Full) {
8029 if let Some(position_map) = &self.last_position_map {
8030 let target_row = target.to_display_point(&position_map.snapshot).row();
8031 let is_visible = position_map.visible_row_range.contains(&target_row);
8032
8033 if is_visible || !self.edit_prediction_requires_modifier() {
8034 self.unfold_ranges(&[target..target], true, false, cx);
8035 self.change_selections(
8036 SelectionEffects::scroll(Autoscroll::newest()),
8037 window,
8038 cx,
8039 |selections| {
8040 selections.select_anchor_ranges([target..target]);
8041 },
8042 );
8043 self.clear_row_highlights::<EditPredictionPreview>();
8044 self.edit_prediction_preview
8045 .set_previous_scroll_position(None);
8046 } else {
8047 // Highlight and request scroll
8048 self.edit_prediction_preview
8049 .set_previous_scroll_position(Some(
8050 position_map.snapshot.scroll_anchor,
8051 ));
8052 self.highlight_rows::<EditPredictionPreview>(
8053 target..target,
8054 cx.theme().colors().editor_highlighted_line_background,
8055 RowHighlightOptions {
8056 autoscroll: true,
8057 ..Default::default()
8058 },
8059 cx,
8060 );
8061 self.request_autoscroll(Autoscroll::fit(), cx);
8062 }
8063 }
8064 } else {
8065 self.change_selections(
8066 SelectionEffects::scroll(Autoscroll::newest()),
8067 window,
8068 cx,
8069 |selections| {
8070 selections.select_anchor_ranges([target..target]);
8071 },
8072 );
8073 }
8074 }
8075 EditPrediction::MoveOutside { snapshot, target } => {
8076 if let Some(workspace) = self.workspace() {
8077 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8078 .detach_and_log_err(cx);
8079 }
8080 }
8081 EditPrediction::Edit {
8082 edits,
8083 cursor_position,
8084 ..
8085 } => {
8086 self.report_edit_prediction_event(
8087 active_edit_prediction.completion_id.clone(),
8088 true,
8089 cx,
8090 );
8091
8092 match granularity {
8093 EditPredictionGranularity::Full => {
8094 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8095
8096 // Compute fallback cursor position BEFORE applying the edit,
8097 // so the anchor tracks through the edit correctly
8098 let fallback_cursor_target = {
8099 let snapshot = self.buffer.read(cx).snapshot(cx);
8100 edits.last().unwrap().0.end.bias_right(&snapshot)
8101 };
8102
8103 self.buffer.update(cx, |buffer, cx| {
8104 buffer.edit(edits.iter().cloned(), None, cx)
8105 });
8106
8107 if let Some(provider) = self.edit_prediction_provider() {
8108 provider.accept(cx);
8109 }
8110
8111 // Resolve cursor position after the edit is applied
8112 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8113 // The anchor tracks through the edit, then we add the offset
8114 let snapshot = self.buffer.read(cx).snapshot(cx);
8115 let base_offset = anchor.to_offset(&snapshot).0;
8116 let target_offset =
8117 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8118 snapshot.anchor_after(target_offset)
8119 } else {
8120 fallback_cursor_target
8121 };
8122
8123 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8124 s.select_anchor_ranges([cursor_target..cursor_target]);
8125 });
8126
8127 let selections = self.selections.disjoint_anchors_arc();
8128 if let Some(transaction_id_now) =
8129 self.buffer.read(cx).last_transaction_id(cx)
8130 {
8131 if transaction_id_prev != Some(transaction_id_now) {
8132 self.selection_history
8133 .insert_transaction(transaction_id_now, selections);
8134 }
8135 }
8136
8137 self.update_visible_edit_prediction(window, cx);
8138 if self.active_edit_prediction.is_none() {
8139 self.refresh_edit_prediction(true, true, window, cx);
8140 }
8141 cx.notify();
8142 }
8143 _ => {
8144 let snapshot = self.buffer.read(cx).snapshot(cx);
8145 let cursor_offset = self
8146 .selections
8147 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8148 .head();
8149
8150 let insertion = edits.iter().find_map(|(range, text)| {
8151 let range = range.to_offset(&snapshot);
8152 if range.is_empty() && range.start == cursor_offset {
8153 Some(text)
8154 } else {
8155 None
8156 }
8157 });
8158
8159 if let Some(text) = insertion {
8160 let text_to_insert = match granularity {
8161 EditPredictionGranularity::Word => {
8162 let mut partial = text
8163 .chars()
8164 .by_ref()
8165 .take_while(|c| c.is_alphabetic())
8166 .collect::<String>();
8167 if partial.is_empty() {
8168 partial = text
8169 .chars()
8170 .by_ref()
8171 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8172 .collect::<String>();
8173 }
8174 partial
8175 }
8176 EditPredictionGranularity::Line => {
8177 if let Some(line) = text.split_inclusive('\n').next() {
8178 line.to_string()
8179 } else {
8180 text.to_string()
8181 }
8182 }
8183 EditPredictionGranularity::Full => unreachable!(),
8184 };
8185
8186 cx.emit(EditorEvent::InputHandled {
8187 utf16_range_to_replace: None,
8188 text: text_to_insert.clone().into(),
8189 });
8190
8191 self.replace_selections(&text_to_insert, None, window, cx, false);
8192 self.refresh_edit_prediction(true, true, window, cx);
8193 cx.notify();
8194 } else {
8195 self.accept_partial_edit_prediction(
8196 EditPredictionGranularity::Full,
8197 window,
8198 cx,
8199 );
8200 }
8201 }
8202 }
8203 }
8204 }
8205
8206 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8207 }
8208
8209 pub fn accept_next_word_edit_prediction(
8210 &mut self,
8211 _: &AcceptNextWordEditPrediction,
8212 window: &mut Window,
8213 cx: &mut Context<Self>,
8214 ) {
8215 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8216 }
8217
8218 pub fn accept_next_line_edit_prediction(
8219 &mut self,
8220 _: &AcceptNextLineEditPrediction,
8221 window: &mut Window,
8222 cx: &mut Context<Self>,
8223 ) {
8224 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8225 }
8226
8227 pub fn accept_edit_prediction(
8228 &mut self,
8229 _: &AcceptEditPrediction,
8230 window: &mut Window,
8231 cx: &mut Context<Self>,
8232 ) {
8233 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8234 }
8235
8236 fn discard_edit_prediction(
8237 &mut self,
8238 reason: EditPredictionDiscardReason,
8239 cx: &mut Context<Self>,
8240 ) -> bool {
8241 if reason == EditPredictionDiscardReason::Rejected {
8242 let completion_id = self
8243 .active_edit_prediction
8244 .as_ref()
8245 .and_then(|active_completion| active_completion.completion_id.clone());
8246
8247 self.report_edit_prediction_event(completion_id, false, cx);
8248 }
8249
8250 if let Some(provider) = self.edit_prediction_provider() {
8251 provider.discard(reason, cx);
8252 }
8253
8254 self.take_active_edit_prediction(cx)
8255 }
8256
8257 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8258 let Some(provider) = self.edit_prediction_provider() else {
8259 return;
8260 };
8261
8262 let Some((_, buffer, _)) = self
8263 .buffer
8264 .read(cx)
8265 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8266 else {
8267 return;
8268 };
8269
8270 let extension = buffer
8271 .read(cx)
8272 .file()
8273 .and_then(|file| Some(file.path().extension()?.to_string()));
8274
8275 let event_type = match accepted {
8276 true => "Edit Prediction Accepted",
8277 false => "Edit Prediction Discarded",
8278 };
8279 telemetry::event!(
8280 event_type,
8281 provider = provider.name(),
8282 prediction_id = id,
8283 suggestion_accepted = accepted,
8284 file_extension = extension,
8285 );
8286 }
8287
8288 fn open_editor_at_anchor(
8289 snapshot: &language::BufferSnapshot,
8290 target: language::Anchor,
8291 workspace: &Entity<Workspace>,
8292 window: &mut Window,
8293 cx: &mut App,
8294 ) -> Task<Result<()>> {
8295 workspace.update(cx, |workspace, cx| {
8296 let path = snapshot.file().map(|file| file.full_path(cx));
8297 let Some(path) =
8298 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8299 else {
8300 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8301 };
8302 let target = text::ToPoint::to_point(&target, snapshot);
8303 let item = workspace.open_path(path, None, true, window, cx);
8304 window.spawn(cx, async move |cx| {
8305 let Some(editor) = item.await?.downcast::<Editor>() else {
8306 return Ok(());
8307 };
8308 editor
8309 .update_in(cx, |editor, window, cx| {
8310 editor.go_to_singleton_buffer_point(target, window, cx);
8311 })
8312 .ok();
8313 anyhow::Ok(())
8314 })
8315 })
8316 }
8317
8318 pub fn has_active_edit_prediction(&self) -> bool {
8319 self.active_edit_prediction.is_some()
8320 }
8321
8322 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8323 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8324 return false;
8325 };
8326
8327 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8328 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8329 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8330 true
8331 }
8332
8333 /// Returns true when we're displaying the edit prediction popover below the cursor
8334 /// like we are not previewing and the LSP autocomplete menu is visible
8335 /// or we are in `when_holding_modifier` mode.
8336 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8337 if self.edit_prediction_preview_is_active()
8338 || !self.show_edit_predictions_in_menu()
8339 || !self.edit_predictions_enabled()
8340 {
8341 return false;
8342 }
8343
8344 if self.has_visible_completions_menu() {
8345 return true;
8346 }
8347
8348 has_completion && self.edit_prediction_requires_modifier()
8349 }
8350
8351 fn handle_modifiers_changed(
8352 &mut self,
8353 modifiers: Modifiers,
8354 position_map: &PositionMap,
8355 window: &mut Window,
8356 cx: &mut Context<Self>,
8357 ) {
8358 // Ensure that the edit prediction preview is updated, even when not
8359 // enabled, if there's an active edit prediction preview.
8360 if self.show_edit_predictions_in_menu()
8361 || matches!(
8362 self.edit_prediction_preview,
8363 EditPredictionPreview::Active { .. }
8364 )
8365 {
8366 self.update_edit_prediction_preview(&modifiers, window, cx);
8367 }
8368
8369 self.update_selection_mode(&modifiers, position_map, window, cx);
8370
8371 let mouse_position = window.mouse_position();
8372 if !position_map.text_hitbox.is_hovered(window) {
8373 return;
8374 }
8375
8376 self.update_hovered_link(
8377 position_map.point_for_position(mouse_position),
8378 &position_map.snapshot,
8379 modifiers,
8380 window,
8381 cx,
8382 )
8383 }
8384
8385 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8386 match EditorSettings::get_global(cx).multi_cursor_modifier {
8387 MultiCursorModifier::Alt => modifiers.secondary(),
8388 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8389 }
8390 }
8391
8392 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8393 match EditorSettings::get_global(cx).multi_cursor_modifier {
8394 MultiCursorModifier::Alt => modifiers.alt,
8395 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8396 }
8397 }
8398
8399 fn columnar_selection_mode(
8400 modifiers: &Modifiers,
8401 cx: &mut Context<Self>,
8402 ) -> Option<ColumnarMode> {
8403 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8404 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8405 Some(ColumnarMode::FromMouse)
8406 } else if Self::is_alt_pressed(modifiers, cx) {
8407 Some(ColumnarMode::FromSelection)
8408 } else {
8409 None
8410 }
8411 } else {
8412 None
8413 }
8414 }
8415
8416 fn update_selection_mode(
8417 &mut self,
8418 modifiers: &Modifiers,
8419 position_map: &PositionMap,
8420 window: &mut Window,
8421 cx: &mut Context<Self>,
8422 ) {
8423 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8424 return;
8425 };
8426 if self.selections.pending_anchor().is_none() {
8427 return;
8428 }
8429
8430 let mouse_position = window.mouse_position();
8431 let point_for_position = position_map.point_for_position(mouse_position);
8432 let position = point_for_position.previous_valid;
8433
8434 self.select(
8435 SelectPhase::BeginColumnar {
8436 position,
8437 reset: false,
8438 mode,
8439 goal_column: point_for_position.exact_unclipped.column(),
8440 },
8441 window,
8442 cx,
8443 );
8444 }
8445
8446 fn update_edit_prediction_preview(
8447 &mut self,
8448 modifiers: &Modifiers,
8449 window: &mut Window,
8450 cx: &mut Context<Self>,
8451 ) {
8452 let mut modifiers_held = false;
8453
8454 // Check bindings for all granularities.
8455 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8456 let granularities = [
8457 EditPredictionGranularity::Full,
8458 EditPredictionGranularity::Line,
8459 EditPredictionGranularity::Word,
8460 ];
8461
8462 for granularity in granularities {
8463 if let Some(keystroke) = self
8464 .accept_edit_prediction_keybind(granularity, window, cx)
8465 .keystroke()
8466 {
8467 modifiers_held = modifiers_held
8468 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8469 }
8470 }
8471
8472 if modifiers_held {
8473 if matches!(
8474 self.edit_prediction_preview,
8475 EditPredictionPreview::Inactive { .. }
8476 ) {
8477 self.edit_prediction_preview = EditPredictionPreview::Active {
8478 previous_scroll_position: None,
8479 since: Instant::now(),
8480 };
8481
8482 self.update_visible_edit_prediction(window, cx);
8483 cx.notify();
8484 }
8485 } else if let EditPredictionPreview::Active {
8486 previous_scroll_position,
8487 since,
8488 } = self.edit_prediction_preview
8489 {
8490 if let (Some(previous_scroll_position), Some(position_map)) =
8491 (previous_scroll_position, self.last_position_map.as_ref())
8492 {
8493 self.set_scroll_position(
8494 previous_scroll_position
8495 .scroll_position(&position_map.snapshot.display_snapshot),
8496 window,
8497 cx,
8498 );
8499 }
8500
8501 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8502 released_too_fast: since.elapsed() < Duration::from_millis(200),
8503 };
8504 self.clear_row_highlights::<EditPredictionPreview>();
8505 self.update_visible_edit_prediction(window, cx);
8506 cx.notify();
8507 }
8508 }
8509
8510 fn update_visible_edit_prediction(
8511 &mut self,
8512 _window: &mut Window,
8513 cx: &mut Context<Self>,
8514 ) -> Option<()> {
8515 if self.ime_transaction.is_some() {
8516 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8517 return None;
8518 }
8519
8520 let selection = self.selections.newest_anchor();
8521 let cursor = selection.head();
8522 let multibuffer = self.buffer.read(cx).snapshot(cx);
8523
8524 // Check project-level disable_ai setting for the current buffer
8525 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8526 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8527 return None;
8528 }
8529 }
8530 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8531 let excerpt_id = cursor.excerpt_id;
8532
8533 let show_in_menu = self.show_edit_predictions_in_menu();
8534 let completions_menu_has_precedence = !show_in_menu
8535 && (self.context_menu.borrow().is_some()
8536 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8537
8538 if completions_menu_has_precedence
8539 || !offset_selection.is_empty()
8540 || self
8541 .active_edit_prediction
8542 .as_ref()
8543 .is_some_and(|completion| {
8544 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8545 return false;
8546 };
8547 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8548 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8549 !invalidation_range.contains(&offset_selection.head())
8550 })
8551 {
8552 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8553 return None;
8554 }
8555
8556 self.take_active_edit_prediction(cx);
8557 let Some(provider) = self.edit_prediction_provider() else {
8558 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8559 return None;
8560 };
8561
8562 let (buffer, cursor_buffer_position) =
8563 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8564
8565 self.edit_prediction_settings =
8566 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8567
8568 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8569
8570 if self.edit_prediction_indent_conflict {
8571 let cursor_point = cursor.to_point(&multibuffer);
8572 let mut suggested_indent = None;
8573 multibuffer.suggested_indents_callback(
8574 cursor_point.row..cursor_point.row + 1,
8575 &mut |_, indent| {
8576 suggested_indent = Some(indent);
8577 ControlFlow::Break(())
8578 },
8579 cx,
8580 );
8581
8582 if let Some(indent) = suggested_indent
8583 && indent.len == cursor_point.column
8584 {
8585 self.edit_prediction_indent_conflict = false;
8586 }
8587 }
8588
8589 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8590
8591 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8592 {
8593 edit_prediction_types::EditPrediction::Local {
8594 id,
8595 edits,
8596 cursor_position,
8597 edit_preview,
8598 } => (id, edits, cursor_position, edit_preview),
8599 edit_prediction_types::EditPrediction::Jump {
8600 id,
8601 snapshot,
8602 target,
8603 } => {
8604 if let Some(provider) = &self.edit_prediction_provider {
8605 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8606 }
8607 self.stale_edit_prediction_in_menu = None;
8608 self.active_edit_prediction = Some(EditPredictionState {
8609 inlay_ids: vec![],
8610 completion: EditPrediction::MoveOutside { snapshot, target },
8611 completion_id: id,
8612 invalidation_range: None,
8613 });
8614 cx.notify();
8615 return Some(());
8616 }
8617 };
8618
8619 let edits = edits
8620 .into_iter()
8621 .flat_map(|(range, new_text)| {
8622 Some((
8623 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8624 new_text,
8625 ))
8626 })
8627 .collect::<Vec<_>>();
8628 if edits.is_empty() {
8629 return None;
8630 }
8631
8632 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8633 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8634 Some((anchor, predicted.offset))
8635 });
8636
8637 let first_edit_start = edits.first().unwrap().0.start;
8638 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8639 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8640
8641 let last_edit_end = edits.last().unwrap().0.end;
8642 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8643 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8644
8645 let cursor_row = cursor.to_point(&multibuffer).row;
8646
8647 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8648
8649 let mut inlay_ids = Vec::new();
8650 let invalidation_row_range;
8651 let move_invalidation_row_range = if cursor_row < edit_start_row {
8652 Some(cursor_row..edit_end_row)
8653 } else if cursor_row > edit_end_row {
8654 Some(edit_start_row..cursor_row)
8655 } else {
8656 None
8657 };
8658 let supports_jump = self
8659 .edit_prediction_provider
8660 .as_ref()
8661 .map(|provider| provider.provider.supports_jump_to_edit())
8662 .unwrap_or(true);
8663
8664 let is_move = supports_jump
8665 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8666 let completion = if is_move {
8667 if let Some(provider) = &self.edit_prediction_provider {
8668 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8669 }
8670 invalidation_row_range =
8671 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8672 let target = first_edit_start;
8673 EditPrediction::MoveWithin { target, snapshot }
8674 } else {
8675 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8676 && !self.edit_predictions_hidden_for_vim_mode;
8677
8678 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8679 if provider.show_tab_accept_marker() {
8680 EditDisplayMode::TabAccept
8681 } else {
8682 EditDisplayMode::Inline
8683 }
8684 } else {
8685 EditDisplayMode::DiffPopover
8686 };
8687
8688 if show_completions_in_buffer {
8689 if let Some(provider) = &self.edit_prediction_provider {
8690 let suggestion_display_type = match display_mode {
8691 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8692 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8693 SuggestionDisplayType::GhostText
8694 }
8695 };
8696 provider.provider.did_show(suggestion_display_type, cx);
8697 }
8698 if edits
8699 .iter()
8700 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8701 {
8702 let mut inlays = Vec::new();
8703 for (range, new_text) in &edits {
8704 let inlay = Inlay::edit_prediction(
8705 post_inc(&mut self.next_inlay_id),
8706 range.start,
8707 new_text.as_ref(),
8708 );
8709 inlay_ids.push(inlay.id);
8710 inlays.push(inlay);
8711 }
8712
8713 self.splice_inlays(&[], inlays, cx);
8714 } else {
8715 let background_color = cx.theme().status().deleted_background;
8716 self.highlight_text(
8717 HighlightKey::EditPredictionHighlight,
8718 edits.iter().map(|(range, _)| range.clone()).collect(),
8719 HighlightStyle {
8720 background_color: Some(background_color),
8721 ..Default::default()
8722 },
8723 cx,
8724 );
8725 }
8726 }
8727
8728 invalidation_row_range = edit_start_row..edit_end_row;
8729
8730 EditPrediction::Edit {
8731 edits,
8732 cursor_position,
8733 edit_preview,
8734 display_mode,
8735 snapshot,
8736 }
8737 };
8738
8739 let invalidation_range = multibuffer
8740 .anchor_before(Point::new(invalidation_row_range.start, 0))
8741 ..multibuffer.anchor_after(Point::new(
8742 invalidation_row_range.end,
8743 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8744 ));
8745
8746 self.stale_edit_prediction_in_menu = None;
8747 self.active_edit_prediction = Some(EditPredictionState {
8748 inlay_ids,
8749 completion,
8750 completion_id,
8751 invalidation_range: Some(invalidation_range),
8752 });
8753
8754 cx.notify();
8755
8756 Some(())
8757 }
8758
8759 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8760 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8761 }
8762
8763 fn clear_tasks(&mut self) {
8764 self.tasks.clear()
8765 }
8766
8767 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8768 if self.tasks.insert(key, value).is_some() {
8769 // This case should hopefully be rare, but just in case...
8770 log::error!(
8771 "multiple different run targets found on a single line, only the last target will be rendered"
8772 )
8773 }
8774 }
8775
8776 /// Get all display points of breakpoints that will be rendered within editor
8777 ///
8778 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8779 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8780 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8781 fn active_breakpoints(
8782 &self,
8783 range: Range<DisplayRow>,
8784 window: &mut Window,
8785 cx: &mut Context<Self>,
8786 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8787 let mut breakpoint_display_points = HashMap::default();
8788
8789 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8790 return breakpoint_display_points;
8791 };
8792
8793 let snapshot = self.snapshot(window, cx);
8794
8795 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8796 let Some(project) = self.project() else {
8797 return breakpoint_display_points;
8798 };
8799
8800 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8801 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8802
8803 for (buffer_snapshot, range, excerpt_id) in
8804 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8805 {
8806 let Some(buffer) = project
8807 .read(cx)
8808 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8809 else {
8810 continue;
8811 };
8812 let breakpoints = breakpoint_store.read(cx).breakpoints(
8813 &buffer,
8814 Some(
8815 buffer_snapshot.anchor_before(range.start)
8816 ..buffer_snapshot.anchor_after(range.end),
8817 ),
8818 buffer_snapshot,
8819 cx,
8820 );
8821 for (breakpoint, state) in breakpoints {
8822 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8823 let position = multi_buffer_anchor
8824 .to_point(&multi_buffer_snapshot)
8825 .to_display_point(&snapshot);
8826
8827 breakpoint_display_points.insert(
8828 position.row(),
8829 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8830 );
8831 }
8832 }
8833
8834 breakpoint_display_points
8835 }
8836
8837 fn breakpoint_context_menu(
8838 &self,
8839 anchor: Anchor,
8840 window: &mut Window,
8841 cx: &mut Context<Self>,
8842 ) -> Entity<ui::ContextMenu> {
8843 let weak_editor = cx.weak_entity();
8844 let focus_handle = self.focus_handle(cx);
8845
8846 let row = self
8847 .buffer
8848 .read(cx)
8849 .snapshot(cx)
8850 .summary_for_anchor::<Point>(&anchor)
8851 .row;
8852
8853 let breakpoint = self
8854 .breakpoint_at_row(row, window, cx)
8855 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8856
8857 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8858 "Edit Log Breakpoint"
8859 } else {
8860 "Set Log Breakpoint"
8861 };
8862
8863 let condition_breakpoint_msg = if breakpoint
8864 .as_ref()
8865 .is_some_and(|bp| bp.1.condition.is_some())
8866 {
8867 "Edit Condition Breakpoint"
8868 } else {
8869 "Set Condition Breakpoint"
8870 };
8871
8872 let hit_condition_breakpoint_msg = if breakpoint
8873 .as_ref()
8874 .is_some_and(|bp| bp.1.hit_condition.is_some())
8875 {
8876 "Edit Hit Condition Breakpoint"
8877 } else {
8878 "Set Hit Condition Breakpoint"
8879 };
8880
8881 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8882 "Unset Breakpoint"
8883 } else {
8884 "Set Breakpoint"
8885 };
8886
8887 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8888
8889 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8890 BreakpointState::Enabled => Some("Disable"),
8891 BreakpointState::Disabled => Some("Enable"),
8892 });
8893
8894 let (anchor, breakpoint) =
8895 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8896
8897 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8898 menu.on_blur_subscription(Subscription::new(|| {}))
8899 .context(focus_handle)
8900 .when(run_to_cursor, |this| {
8901 let weak_editor = weak_editor.clone();
8902 this.entry("Run to Cursor", None, move |window, cx| {
8903 weak_editor
8904 .update(cx, |editor, cx| {
8905 editor.change_selections(
8906 SelectionEffects::no_scroll(),
8907 window,
8908 cx,
8909 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8910 );
8911 })
8912 .ok();
8913
8914 window.dispatch_action(Box::new(RunToCursor), cx);
8915 })
8916 .separator()
8917 })
8918 .when_some(toggle_state_msg, |this, msg| {
8919 this.entry(msg, None, {
8920 let weak_editor = weak_editor.clone();
8921 let breakpoint = breakpoint.clone();
8922 move |_window, cx| {
8923 weak_editor
8924 .update(cx, |this, cx| {
8925 this.edit_breakpoint_at_anchor(
8926 anchor,
8927 breakpoint.as_ref().clone(),
8928 BreakpointEditAction::InvertState,
8929 cx,
8930 );
8931 })
8932 .log_err();
8933 }
8934 })
8935 })
8936 .entry(set_breakpoint_msg, None, {
8937 let weak_editor = weak_editor.clone();
8938 let breakpoint = breakpoint.clone();
8939 move |_window, cx| {
8940 weak_editor
8941 .update(cx, |this, cx| {
8942 this.edit_breakpoint_at_anchor(
8943 anchor,
8944 breakpoint.as_ref().clone(),
8945 BreakpointEditAction::Toggle,
8946 cx,
8947 );
8948 })
8949 .log_err();
8950 }
8951 })
8952 .entry(log_breakpoint_msg, None, {
8953 let breakpoint = breakpoint.clone();
8954 let weak_editor = weak_editor.clone();
8955 move |window, cx| {
8956 weak_editor
8957 .update(cx, |this, cx| {
8958 this.add_edit_breakpoint_block(
8959 anchor,
8960 breakpoint.as_ref(),
8961 BreakpointPromptEditAction::Log,
8962 window,
8963 cx,
8964 );
8965 })
8966 .log_err();
8967 }
8968 })
8969 .entry(condition_breakpoint_msg, None, {
8970 let breakpoint = breakpoint.clone();
8971 let weak_editor = weak_editor.clone();
8972 move |window, cx| {
8973 weak_editor
8974 .update(cx, |this, cx| {
8975 this.add_edit_breakpoint_block(
8976 anchor,
8977 breakpoint.as_ref(),
8978 BreakpointPromptEditAction::Condition,
8979 window,
8980 cx,
8981 );
8982 })
8983 .log_err();
8984 }
8985 })
8986 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8987 weak_editor
8988 .update(cx, |this, cx| {
8989 this.add_edit_breakpoint_block(
8990 anchor,
8991 breakpoint.as_ref(),
8992 BreakpointPromptEditAction::HitCondition,
8993 window,
8994 cx,
8995 );
8996 })
8997 .log_err();
8998 })
8999 })
9000 }
9001
9002 fn render_breakpoint(
9003 &self,
9004 position: Anchor,
9005 row: DisplayRow,
9006 breakpoint: &Breakpoint,
9007 state: Option<BreakpointSessionState>,
9008 cx: &mut Context<Self>,
9009 ) -> IconButton {
9010 let is_rejected = state.is_some_and(|s| !s.verified);
9011 // Is it a breakpoint that shows up when hovering over gutter?
9012 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9013 (false, false),
9014 |PhantomBreakpointIndicator {
9015 is_active,
9016 display_row,
9017 collides_with_existing_breakpoint,
9018 }| {
9019 (
9020 is_active && display_row == row,
9021 collides_with_existing_breakpoint,
9022 )
9023 },
9024 );
9025
9026 let (color, icon) = {
9027 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9028 (false, false) => ui::IconName::DebugBreakpoint,
9029 (true, false) => ui::IconName::DebugLogBreakpoint,
9030 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9031 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9032 };
9033
9034 let theme_colors = cx.theme().colors();
9035
9036 let color = if is_phantom {
9037 if collides_with_existing {
9038 Color::Custom(
9039 theme_colors
9040 .debugger_accent
9041 .blend(theme_colors.text.opacity(0.6)),
9042 )
9043 } else {
9044 Color::Hint
9045 }
9046 } else if is_rejected {
9047 Color::Disabled
9048 } else {
9049 Color::Debugger
9050 };
9051
9052 (color, icon)
9053 };
9054
9055 let breakpoint = Arc::from(breakpoint.clone());
9056
9057 let alt_as_text = gpui::Keystroke {
9058 modifiers: Modifiers::secondary_key(),
9059 ..Default::default()
9060 };
9061 let primary_action_text = if breakpoint.is_disabled() {
9062 "Enable breakpoint"
9063 } else if is_phantom && !collides_with_existing {
9064 "Set breakpoint"
9065 } else {
9066 "Unset breakpoint"
9067 };
9068 let focus_handle = self.focus_handle.clone();
9069
9070 let meta = if is_rejected {
9071 SharedString::from("No executable code is associated with this line.")
9072 } else if collides_with_existing && !breakpoint.is_disabled() {
9073 SharedString::from(format!(
9074 "{alt_as_text}-click to disable,\nright-click for more options."
9075 ))
9076 } else {
9077 SharedString::from("Right-click for more options.")
9078 };
9079 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9080 .icon_size(IconSize::XSmall)
9081 .size(ui::ButtonSize::None)
9082 .when(is_rejected, |this| {
9083 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9084 })
9085 .icon_color(color)
9086 .style(ButtonStyle::Transparent)
9087 .on_click(cx.listener({
9088 move |editor, event: &ClickEvent, window, cx| {
9089 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9090 BreakpointEditAction::InvertState
9091 } else {
9092 BreakpointEditAction::Toggle
9093 };
9094
9095 window.focus(&editor.focus_handle(cx), cx);
9096 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9097 editor.edit_breakpoint_at_anchor(
9098 position,
9099 breakpoint.as_ref().clone(),
9100 edit_action,
9101 cx,
9102 );
9103 }
9104 }))
9105 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9106 editor.set_breakpoint_context_menu(
9107 row,
9108 Some(position),
9109 event.position(),
9110 window,
9111 cx,
9112 );
9113 }))
9114 .tooltip(move |_window, cx| {
9115 Tooltip::with_meta_in(
9116 primary_action_text,
9117 Some(&ToggleBreakpoint),
9118 meta.clone(),
9119 &focus_handle,
9120 cx,
9121 )
9122 })
9123 }
9124
9125 fn build_tasks_context(
9126 project: &Entity<Project>,
9127 buffer: &Entity<Buffer>,
9128 buffer_row: u32,
9129 tasks: &Arc<RunnableTasks>,
9130 cx: &mut Context<Self>,
9131 ) -> Task<Option<task::TaskContext>> {
9132 let position = Point::new(buffer_row, tasks.column);
9133 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9134 let location = Location {
9135 buffer: buffer.clone(),
9136 range: range_start..range_start,
9137 };
9138 // Fill in the environmental variables from the tree-sitter captures
9139 let mut captured_task_variables = TaskVariables::default();
9140 for (capture_name, value) in tasks.extra_variables.clone() {
9141 captured_task_variables.insert(
9142 task::VariableName::Custom(capture_name.into()),
9143 value.clone(),
9144 );
9145 }
9146 project.update(cx, |project, cx| {
9147 project.task_store().update(cx, |task_store, cx| {
9148 task_store.task_context_for_location(captured_task_variables, location, cx)
9149 })
9150 })
9151 }
9152
9153 pub fn spawn_nearest_task(
9154 &mut self,
9155 action: &SpawnNearestTask,
9156 window: &mut Window,
9157 cx: &mut Context<Self>,
9158 ) {
9159 let Some((workspace, _)) = self.workspace.clone() else {
9160 return;
9161 };
9162 let Some(project) = self.project.clone() else {
9163 return;
9164 };
9165
9166 // Try to find a closest, enclosing node using tree-sitter that has a task
9167 let Some((buffer, buffer_row, tasks)) = self
9168 .find_enclosing_node_task(cx)
9169 // Or find the task that's closest in row-distance.
9170 .or_else(|| self.find_closest_task(cx))
9171 else {
9172 return;
9173 };
9174
9175 let reveal_strategy = action.reveal;
9176 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
9177 cx.spawn_in(window, async move |_, cx| {
9178 let context = task_context.await?;
9179 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
9180
9181 let resolved = &mut resolved_task.resolved;
9182 resolved.reveal = reveal_strategy;
9183
9184 workspace
9185 .update_in(cx, |workspace, window, cx| {
9186 workspace.schedule_resolved_task(
9187 task_source_kind,
9188 resolved_task,
9189 false,
9190 window,
9191 cx,
9192 );
9193 })
9194 .ok()
9195 })
9196 .detach();
9197 }
9198
9199 fn find_closest_task(
9200 &mut self,
9201 cx: &mut Context<Self>,
9202 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9203 let cursor_row = self
9204 .selections
9205 .newest_adjusted(&self.display_snapshot(cx))
9206 .head()
9207 .row;
9208
9209 let ((buffer_id, row), tasks) = self
9210 .tasks
9211 .iter()
9212 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
9213
9214 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
9215 let tasks = Arc::new(tasks.to_owned());
9216 Some((buffer, *row, tasks))
9217 }
9218
9219 fn find_enclosing_node_task(
9220 &mut self,
9221 cx: &mut Context<Self>,
9222 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9223 let snapshot = self.buffer.read(cx).snapshot(cx);
9224 let offset = self
9225 .selections
9226 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
9227 .head();
9228 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
9229 let offset = excerpt.map_offset_to_buffer(offset);
9230 let buffer_id = excerpt.buffer().remote_id();
9231
9232 let layer = excerpt.buffer().syntax_layer_at(offset)?;
9233 let mut cursor = layer.node().walk();
9234
9235 while cursor.goto_first_child_for_byte(offset.0).is_some() {
9236 if cursor.node().end_byte() == offset.0 {
9237 cursor.goto_next_sibling();
9238 }
9239 }
9240
9241 // Ascend to the smallest ancestor that contains the range and has a task.
9242 loop {
9243 let node = cursor.node();
9244 let node_range = node.byte_range();
9245 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9246
9247 // Check if this node contains our offset
9248 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9249 // If it contains offset, check for task
9250 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9251 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9252 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9253 }
9254 }
9255
9256 if !cursor.goto_parent() {
9257 break;
9258 }
9259 }
9260 None
9261 }
9262
9263 fn render_run_indicator(
9264 &self,
9265 _style: &EditorStyle,
9266 is_active: bool,
9267 row: DisplayRow,
9268 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9269 cx: &mut Context<Self>,
9270 ) -> IconButton {
9271 let color = Color::Muted;
9272 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9273
9274 IconButton::new(
9275 ("run_indicator", row.0 as usize),
9276 ui::IconName::PlayOutlined,
9277 )
9278 .shape(ui::IconButtonShape::Square)
9279 .icon_size(IconSize::XSmall)
9280 .icon_color(color)
9281 .toggle_state(is_active)
9282 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9283 let quick_launch = match e {
9284 ClickEvent::Keyboard(_) => true,
9285 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9286 };
9287
9288 window.focus(&editor.focus_handle(cx), cx);
9289 editor.toggle_code_actions(
9290 &ToggleCodeActions {
9291 deployed_from: Some(CodeActionSource::RunMenu(row)),
9292 quick_launch,
9293 },
9294 window,
9295 cx,
9296 );
9297 }))
9298 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9299 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9300 }))
9301 }
9302
9303 pub fn context_menu_visible(&self) -> bool {
9304 !self.edit_prediction_preview_is_active()
9305 && self
9306 .context_menu
9307 .borrow()
9308 .as_ref()
9309 .is_some_and(|menu| menu.visible())
9310 }
9311
9312 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9313 self.context_menu
9314 .borrow()
9315 .as_ref()
9316 .map(|menu| menu.origin())
9317 }
9318
9319 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9320 self.context_menu_options = Some(options);
9321 }
9322
9323 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9324 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9325
9326 fn render_edit_prediction_popover(
9327 &mut self,
9328 text_bounds: &Bounds<Pixels>,
9329 content_origin: gpui::Point<Pixels>,
9330 right_margin: Pixels,
9331 editor_snapshot: &EditorSnapshot,
9332 visible_row_range: Range<DisplayRow>,
9333 scroll_top: ScrollOffset,
9334 scroll_bottom: ScrollOffset,
9335 line_layouts: &[LineWithInvisibles],
9336 line_height: Pixels,
9337 scroll_position: gpui::Point<ScrollOffset>,
9338 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9339 newest_selection_head: Option<DisplayPoint>,
9340 editor_width: Pixels,
9341 style: &EditorStyle,
9342 window: &mut Window,
9343 cx: &mut App,
9344 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9345 if self.mode().is_minimap() {
9346 return None;
9347 }
9348 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9349
9350 if self.edit_prediction_visible_in_cursor_popover(true) {
9351 return None;
9352 }
9353
9354 match &active_edit_prediction.completion {
9355 EditPrediction::MoveWithin { target, .. } => {
9356 let target_display_point = target.to_display_point(editor_snapshot);
9357
9358 if self.edit_prediction_requires_modifier() {
9359 if !self.edit_prediction_preview_is_active() {
9360 return None;
9361 }
9362
9363 self.render_edit_prediction_modifier_jump_popover(
9364 text_bounds,
9365 content_origin,
9366 visible_row_range,
9367 line_layouts,
9368 line_height,
9369 scroll_pixel_position,
9370 newest_selection_head,
9371 target_display_point,
9372 window,
9373 cx,
9374 )
9375 } else {
9376 self.render_edit_prediction_eager_jump_popover(
9377 text_bounds,
9378 content_origin,
9379 editor_snapshot,
9380 visible_row_range,
9381 scroll_top,
9382 scroll_bottom,
9383 line_height,
9384 scroll_pixel_position,
9385 target_display_point,
9386 editor_width,
9387 window,
9388 cx,
9389 )
9390 }
9391 }
9392 EditPrediction::Edit {
9393 display_mode: EditDisplayMode::Inline,
9394 ..
9395 } => None,
9396 EditPrediction::Edit {
9397 display_mode: EditDisplayMode::TabAccept,
9398 edits,
9399 ..
9400 } => {
9401 let range = &edits.first()?.0;
9402 let target_display_point = range.end.to_display_point(editor_snapshot);
9403
9404 self.render_edit_prediction_end_of_line_popover(
9405 "Accept",
9406 editor_snapshot,
9407 visible_row_range,
9408 target_display_point,
9409 line_height,
9410 scroll_pixel_position,
9411 content_origin,
9412 editor_width,
9413 window,
9414 cx,
9415 )
9416 }
9417 EditPrediction::Edit {
9418 edits,
9419 edit_preview,
9420 display_mode: EditDisplayMode::DiffPopover,
9421 snapshot,
9422 ..
9423 } => self.render_edit_prediction_diff_popover(
9424 text_bounds,
9425 content_origin,
9426 right_margin,
9427 editor_snapshot,
9428 visible_row_range,
9429 line_layouts,
9430 line_height,
9431 scroll_position,
9432 scroll_pixel_position,
9433 newest_selection_head,
9434 editor_width,
9435 style,
9436 edits,
9437 edit_preview,
9438 snapshot,
9439 window,
9440 cx,
9441 ),
9442 EditPrediction::MoveOutside { snapshot, .. } => {
9443 let mut element = self
9444 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9445 .into_any();
9446
9447 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9448 let origin_x = text_bounds.size.width - size.width - px(30.);
9449 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9450 element.prepaint_at(origin, window, cx);
9451
9452 Some((element, origin))
9453 }
9454 }
9455 }
9456
9457 fn render_edit_prediction_modifier_jump_popover(
9458 &mut self,
9459 text_bounds: &Bounds<Pixels>,
9460 content_origin: gpui::Point<Pixels>,
9461 visible_row_range: Range<DisplayRow>,
9462 line_layouts: &[LineWithInvisibles],
9463 line_height: Pixels,
9464 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9465 newest_selection_head: Option<DisplayPoint>,
9466 target_display_point: DisplayPoint,
9467 window: &mut Window,
9468 cx: &mut App,
9469 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9470 let scrolled_content_origin =
9471 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9472
9473 const SCROLL_PADDING_Y: Pixels = px(12.);
9474
9475 if target_display_point.row() < visible_row_range.start {
9476 return self.render_edit_prediction_scroll_popover(
9477 &|_| SCROLL_PADDING_Y,
9478 IconName::ArrowUp,
9479 visible_row_range,
9480 line_layouts,
9481 newest_selection_head,
9482 scrolled_content_origin,
9483 window,
9484 cx,
9485 );
9486 } else if target_display_point.row() >= visible_row_range.end {
9487 return self.render_edit_prediction_scroll_popover(
9488 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9489 IconName::ArrowDown,
9490 visible_row_range,
9491 line_layouts,
9492 newest_selection_head,
9493 scrolled_content_origin,
9494 window,
9495 cx,
9496 );
9497 }
9498
9499 const POLE_WIDTH: Pixels = px(2.);
9500
9501 let line_layout =
9502 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9503 let target_column = target_display_point.column() as usize;
9504
9505 let target_x = line_layout.x_for_index(target_column);
9506 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9507 - scroll_pixel_position.y;
9508
9509 let flag_on_right = target_x < text_bounds.size.width / 2.;
9510
9511 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9512 border_color.l += 0.001;
9513
9514 let mut element = v_flex()
9515 .items_end()
9516 .when(flag_on_right, |el| el.items_start())
9517 .child(if flag_on_right {
9518 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9519 .rounded_bl(px(0.))
9520 .rounded_tl(px(0.))
9521 .border_l_2()
9522 .border_color(border_color)
9523 } else {
9524 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9525 .rounded_br(px(0.))
9526 .rounded_tr(px(0.))
9527 .border_r_2()
9528 .border_color(border_color)
9529 })
9530 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9531 .into_any();
9532
9533 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9534
9535 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9536 - point(
9537 if flag_on_right {
9538 POLE_WIDTH
9539 } else {
9540 size.width - POLE_WIDTH
9541 },
9542 size.height - line_height,
9543 );
9544
9545 origin.x = origin.x.max(content_origin.x);
9546
9547 element.prepaint_at(origin, window, cx);
9548
9549 Some((element, origin))
9550 }
9551
9552 fn render_edit_prediction_scroll_popover(
9553 &mut self,
9554 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9555 scroll_icon: IconName,
9556 visible_row_range: Range<DisplayRow>,
9557 line_layouts: &[LineWithInvisibles],
9558 newest_selection_head: Option<DisplayPoint>,
9559 scrolled_content_origin: gpui::Point<Pixels>,
9560 window: &mut Window,
9561 cx: &mut App,
9562 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9563 let mut element = self
9564 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9565 .into_any();
9566
9567 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9568
9569 let cursor = newest_selection_head?;
9570 let cursor_row_layout =
9571 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9572 let cursor_column = cursor.column() as usize;
9573
9574 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9575
9576 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9577
9578 element.prepaint_at(origin, window, cx);
9579 Some((element, origin))
9580 }
9581
9582 fn render_edit_prediction_eager_jump_popover(
9583 &mut self,
9584 text_bounds: &Bounds<Pixels>,
9585 content_origin: gpui::Point<Pixels>,
9586 editor_snapshot: &EditorSnapshot,
9587 visible_row_range: Range<DisplayRow>,
9588 scroll_top: ScrollOffset,
9589 scroll_bottom: ScrollOffset,
9590 line_height: Pixels,
9591 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9592 target_display_point: DisplayPoint,
9593 editor_width: Pixels,
9594 window: &mut Window,
9595 cx: &mut App,
9596 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9597 if target_display_point.row().as_f64() < scroll_top {
9598 let mut element = self
9599 .render_edit_prediction_line_popover(
9600 "Jump to Edit",
9601 Some(IconName::ArrowUp),
9602 window,
9603 cx,
9604 )
9605 .into_any();
9606
9607 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9608 let offset = point(
9609 (text_bounds.size.width - size.width) / 2.,
9610 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9611 );
9612
9613 let origin = text_bounds.origin + offset;
9614 element.prepaint_at(origin, window, cx);
9615 Some((element, origin))
9616 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9617 let mut element = self
9618 .render_edit_prediction_line_popover(
9619 "Jump to Edit",
9620 Some(IconName::ArrowDown),
9621 window,
9622 cx,
9623 )
9624 .into_any();
9625
9626 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9627 let offset = point(
9628 (text_bounds.size.width - size.width) / 2.,
9629 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9630 );
9631
9632 let origin = text_bounds.origin + offset;
9633 element.prepaint_at(origin, window, cx);
9634 Some((element, origin))
9635 } else {
9636 self.render_edit_prediction_end_of_line_popover(
9637 "Jump to Edit",
9638 editor_snapshot,
9639 visible_row_range,
9640 target_display_point,
9641 line_height,
9642 scroll_pixel_position,
9643 content_origin,
9644 editor_width,
9645 window,
9646 cx,
9647 )
9648 }
9649 }
9650
9651 fn render_edit_prediction_end_of_line_popover(
9652 self: &mut Editor,
9653 label: &'static str,
9654 editor_snapshot: &EditorSnapshot,
9655 visible_row_range: Range<DisplayRow>,
9656 target_display_point: DisplayPoint,
9657 line_height: Pixels,
9658 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9659 content_origin: gpui::Point<Pixels>,
9660 editor_width: Pixels,
9661 window: &mut Window,
9662 cx: &mut App,
9663 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9664 let target_line_end = DisplayPoint::new(
9665 target_display_point.row(),
9666 editor_snapshot.line_len(target_display_point.row()),
9667 );
9668
9669 let mut element = self
9670 .render_edit_prediction_line_popover(label, None, window, cx)
9671 .into_any();
9672
9673 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9674
9675 let line_origin =
9676 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9677
9678 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9679 let mut origin = start_point
9680 + line_origin
9681 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9682 origin.x = origin.x.max(content_origin.x);
9683
9684 let max_x = content_origin.x + editor_width - size.width;
9685
9686 if origin.x > max_x {
9687 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9688
9689 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9690 origin.y += offset;
9691 IconName::ArrowUp
9692 } else {
9693 origin.y -= offset;
9694 IconName::ArrowDown
9695 };
9696
9697 element = self
9698 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9699 .into_any();
9700
9701 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9702
9703 origin.x = content_origin.x + editor_width - size.width - px(2.);
9704 }
9705
9706 element.prepaint_at(origin, window, cx);
9707 Some((element, origin))
9708 }
9709
9710 fn render_edit_prediction_diff_popover(
9711 self: &Editor,
9712 text_bounds: &Bounds<Pixels>,
9713 content_origin: gpui::Point<Pixels>,
9714 right_margin: Pixels,
9715 editor_snapshot: &EditorSnapshot,
9716 visible_row_range: Range<DisplayRow>,
9717 line_layouts: &[LineWithInvisibles],
9718 line_height: Pixels,
9719 scroll_position: gpui::Point<ScrollOffset>,
9720 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9721 newest_selection_head: Option<DisplayPoint>,
9722 editor_width: Pixels,
9723 style: &EditorStyle,
9724 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9725 edit_preview: &Option<language::EditPreview>,
9726 snapshot: &language::BufferSnapshot,
9727 window: &mut Window,
9728 cx: &mut App,
9729 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9730 let edit_start = edits
9731 .first()
9732 .unwrap()
9733 .0
9734 .start
9735 .to_display_point(editor_snapshot);
9736 let edit_end = edits
9737 .last()
9738 .unwrap()
9739 .0
9740 .end
9741 .to_display_point(editor_snapshot);
9742
9743 let is_visible = visible_row_range.contains(&edit_start.row())
9744 || visible_row_range.contains(&edit_end.row());
9745 if !is_visible {
9746 return None;
9747 }
9748
9749 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9750 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9751 } else {
9752 // Fallback for providers without edit_preview
9753 crate::edit_prediction_fallback_text(edits, cx)
9754 };
9755
9756 let styled_text = highlighted_edits.to_styled_text(&style.text);
9757 let line_count = highlighted_edits.text.lines().count();
9758
9759 const BORDER_WIDTH: Pixels = px(1.);
9760
9761 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9762 let has_keybind = keybind.is_some();
9763
9764 let mut element = h_flex()
9765 .items_start()
9766 .child(
9767 h_flex()
9768 .bg(cx.theme().colors().editor_background)
9769 .border(BORDER_WIDTH)
9770 .shadow_xs()
9771 .border_color(cx.theme().colors().border)
9772 .rounded_l_lg()
9773 .when(line_count > 1, |el| el.rounded_br_lg())
9774 .pr_1()
9775 .child(styled_text),
9776 )
9777 .child(
9778 h_flex()
9779 .h(line_height + BORDER_WIDTH * 2.)
9780 .px_1p5()
9781 .gap_1()
9782 // Workaround: For some reason, there's a gap if we don't do this
9783 .ml(-BORDER_WIDTH)
9784 .shadow(vec![gpui::BoxShadow {
9785 color: gpui::black().opacity(0.05),
9786 offset: point(px(1.), px(1.)),
9787 blur_radius: px(2.),
9788 spread_radius: px(0.),
9789 }])
9790 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9791 .border(BORDER_WIDTH)
9792 .border_color(cx.theme().colors().border)
9793 .rounded_r_lg()
9794 .id("edit_prediction_diff_popover_keybind")
9795 .when(!has_keybind, |el| {
9796 let status_colors = cx.theme().status();
9797
9798 el.bg(status_colors.error_background)
9799 .border_color(status_colors.error.opacity(0.6))
9800 .child(Icon::new(IconName::Info).color(Color::Error))
9801 .cursor_default()
9802 .hoverable_tooltip(move |_window, cx| {
9803 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9804 })
9805 })
9806 .children(keybind),
9807 )
9808 .into_any();
9809
9810 let longest_row =
9811 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9812 let longest_line_width = if visible_row_range.contains(&longest_row) {
9813 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9814 } else {
9815 layout_line(
9816 longest_row,
9817 editor_snapshot,
9818 style,
9819 editor_width,
9820 |_| false,
9821 window,
9822 cx,
9823 )
9824 .width
9825 };
9826
9827 let viewport_bounds =
9828 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9829 right: -right_margin,
9830 ..Default::default()
9831 });
9832
9833 let x_after_longest = Pixels::from(
9834 ScrollPixelOffset::from(
9835 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9836 ) - scroll_pixel_position.x,
9837 );
9838
9839 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9840
9841 // Fully visible if it can be displayed within the window (allow overlapping other
9842 // panes). However, this is only allowed if the popover starts within text_bounds.
9843 let can_position_to_the_right = x_after_longest < text_bounds.right()
9844 && x_after_longest + element_bounds.width < viewport_bounds.right();
9845
9846 let mut origin = if can_position_to_the_right {
9847 point(
9848 x_after_longest,
9849 text_bounds.origin.y
9850 + Pixels::from(
9851 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9852 - scroll_pixel_position.y,
9853 ),
9854 )
9855 } else {
9856 let cursor_row = newest_selection_head.map(|head| head.row());
9857 let above_edit = edit_start
9858 .row()
9859 .0
9860 .checked_sub(line_count as u32)
9861 .map(DisplayRow);
9862 let below_edit = Some(edit_end.row() + 1);
9863 let above_cursor =
9864 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9865 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9866
9867 // Place the edit popover adjacent to the edit if there is a location
9868 // available that is onscreen and does not obscure the cursor. Otherwise,
9869 // place it adjacent to the cursor.
9870 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9871 .into_iter()
9872 .flatten()
9873 .find(|&start_row| {
9874 let end_row = start_row + line_count as u32;
9875 visible_row_range.contains(&start_row)
9876 && visible_row_range.contains(&end_row)
9877 && cursor_row
9878 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9879 })?;
9880
9881 content_origin
9882 + point(
9883 Pixels::from(-scroll_pixel_position.x),
9884 Pixels::from(
9885 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9886 ),
9887 )
9888 };
9889
9890 origin.x -= BORDER_WIDTH;
9891
9892 window.defer_draw(element, origin, 1);
9893
9894 // Do not return an element, since it will already be drawn due to defer_draw.
9895 None
9896 }
9897
9898 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9899 px(30.)
9900 }
9901
9902 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9903 if self.read_only(cx) {
9904 cx.theme().players().read_only()
9905 } else {
9906 self.style.as_ref().unwrap().local_player
9907 }
9908 }
9909
9910 fn render_edit_prediction_accept_keybind(
9911 &self,
9912 window: &mut Window,
9913 cx: &mut App,
9914 ) -> Option<AnyElement> {
9915 let accept_binding =
9916 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9917 let accept_keystroke = accept_binding.keystroke()?;
9918
9919 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9920
9921 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9922 Color::Accent
9923 } else {
9924 Color::Muted
9925 };
9926
9927 h_flex()
9928 .px_0p5()
9929 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9930 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9931 .text_size(TextSize::XSmall.rems(cx))
9932 .child(h_flex().children(ui::render_modifiers(
9933 accept_keystroke.modifiers(),
9934 PlatformStyle::platform(),
9935 Some(modifiers_color),
9936 Some(IconSize::XSmall.rems().into()),
9937 true,
9938 )))
9939 .when(is_platform_style_mac, |parent| {
9940 parent.child(accept_keystroke.key().to_string())
9941 })
9942 .when(!is_platform_style_mac, |parent| {
9943 parent.child(
9944 Key::new(
9945 util::capitalize(accept_keystroke.key()),
9946 Some(Color::Default),
9947 )
9948 .size(Some(IconSize::XSmall.rems().into())),
9949 )
9950 })
9951 .into_any()
9952 .into()
9953 }
9954
9955 fn render_edit_prediction_line_popover(
9956 &self,
9957 label: impl Into<SharedString>,
9958 icon: Option<IconName>,
9959 window: &mut Window,
9960 cx: &mut App,
9961 ) -> Stateful<Div> {
9962 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9963
9964 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9965 let has_keybind = keybind.is_some();
9966 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9967
9968 h_flex()
9969 .id("ep-line-popover")
9970 .py_0p5()
9971 .pl_1()
9972 .pr(padding_right)
9973 .gap_1()
9974 .rounded_md()
9975 .border_1()
9976 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9977 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9978 .shadow_xs()
9979 .when(!has_keybind, |el| {
9980 let status_colors = cx.theme().status();
9981
9982 el.bg(status_colors.error_background)
9983 .border_color(status_colors.error.opacity(0.6))
9984 .pl_2()
9985 .child(Icon::new(icons.error).color(Color::Error))
9986 .cursor_default()
9987 .hoverable_tooltip(move |_window, cx| {
9988 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9989 })
9990 })
9991 .children(keybind)
9992 .child(
9993 Label::new(label)
9994 .size(LabelSize::Small)
9995 .when(!has_keybind, |el| {
9996 el.color(cx.theme().status().error.into()).strikethrough()
9997 }),
9998 )
9999 .when(!has_keybind, |el| {
10000 el.child(
10001 h_flex().ml_1().child(
10002 Icon::new(IconName::Info)
10003 .size(IconSize::Small)
10004 .color(cx.theme().status().error.into()),
10005 ),
10006 )
10007 })
10008 .when_some(icon, |element, icon| {
10009 element.child(
10010 div()
10011 .mt(px(1.5))
10012 .child(Icon::new(icon).size(IconSize::Small)),
10013 )
10014 })
10015 }
10016
10017 fn render_edit_prediction_jump_outside_popover(
10018 &self,
10019 snapshot: &BufferSnapshot,
10020 window: &mut Window,
10021 cx: &mut App,
10022 ) -> Stateful<Div> {
10023 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
10024 let has_keybind = keybind.is_some();
10025 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10026
10027 let file_name = snapshot
10028 .file()
10029 .map(|file| SharedString::new(file.file_name(cx)))
10030 .unwrap_or(SharedString::new_static("untitled"));
10031
10032 h_flex()
10033 .id("ep-jump-outside-popover")
10034 .py_1()
10035 .px_2()
10036 .gap_1()
10037 .rounded_md()
10038 .border_1()
10039 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10040 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10041 .shadow_xs()
10042 .when(!has_keybind, |el| {
10043 let status_colors = cx.theme().status();
10044
10045 el.bg(status_colors.error_background)
10046 .border_color(status_colors.error.opacity(0.6))
10047 .pl_2()
10048 .child(Icon::new(icons.error).color(Color::Error))
10049 .cursor_default()
10050 .hoverable_tooltip(move |_window, cx| {
10051 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10052 })
10053 })
10054 .children(keybind)
10055 .child(
10056 Label::new(file_name)
10057 .size(LabelSize::Small)
10058 .buffer_font(cx)
10059 .when(!has_keybind, |el| {
10060 el.color(cx.theme().status().error.into()).strikethrough()
10061 }),
10062 )
10063 .when(!has_keybind, |el| {
10064 el.child(
10065 h_flex().ml_1().child(
10066 Icon::new(IconName::Info)
10067 .size(IconSize::Small)
10068 .color(cx.theme().status().error.into()),
10069 ),
10070 )
10071 })
10072 .child(
10073 div()
10074 .mt(px(1.5))
10075 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10076 )
10077 }
10078
10079 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10080 let accent_color = cx.theme().colors().text_accent;
10081 let editor_bg_color = cx.theme().colors().editor_background;
10082 editor_bg_color.blend(accent_color.opacity(0.1))
10083 }
10084
10085 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10086 let accent_color = cx.theme().colors().text_accent;
10087 let editor_bg_color = cx.theme().colors().editor_background;
10088 editor_bg_color.blend(accent_color.opacity(0.6))
10089 }
10090 fn get_prediction_provider_icons(
10091 provider: &Option<RegisteredEditPredictionDelegate>,
10092 cx: &App,
10093 ) -> edit_prediction_types::EditPredictionIconSet {
10094 match provider {
10095 Some(provider) => provider.provider.icons(cx),
10096 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10097 }
10098 }
10099
10100 fn render_edit_prediction_cursor_popover(
10101 &self,
10102 min_width: Pixels,
10103 max_width: Pixels,
10104 cursor_point: Point,
10105 style: &EditorStyle,
10106 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
10107 _window: &Window,
10108 cx: &mut Context<Editor>,
10109 ) -> Option<AnyElement> {
10110 let provider = self.edit_prediction_provider.as_ref()?;
10111 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10112
10113 let is_refreshing = provider.provider.is_refreshing(cx);
10114
10115 fn pending_completion_container(icon: IconName) -> Div {
10116 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10117 }
10118
10119 let completion = match &self.active_edit_prediction {
10120 Some(prediction) => {
10121 if !self.has_visible_completions_menu() {
10122 const RADIUS: Pixels = px(6.);
10123 const BORDER_WIDTH: Pixels = px(1.);
10124
10125 return Some(
10126 h_flex()
10127 .elevation_2(cx)
10128 .border(BORDER_WIDTH)
10129 .border_color(cx.theme().colors().border)
10130 .when(accept_keystroke.is_none(), |el| {
10131 el.border_color(cx.theme().status().error)
10132 })
10133 .rounded(RADIUS)
10134 .rounded_tl(px(0.))
10135 .overflow_hidden()
10136 .child(div().px_1p5().child(match &prediction.completion {
10137 EditPrediction::MoveWithin { target, snapshot } => {
10138 use text::ToPoint as _;
10139 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10140 {
10141 Icon::new(icons.down)
10142 } else {
10143 Icon::new(icons.up)
10144 }
10145 }
10146 EditPrediction::MoveOutside { .. } => {
10147 // TODO [zeta2] custom icon for external jump?
10148 Icon::new(icons.base)
10149 }
10150 EditPrediction::Edit { .. } => Icon::new(icons.base),
10151 }))
10152 .child(
10153 h_flex()
10154 .gap_1()
10155 .py_1()
10156 .px_2()
10157 .rounded_r(RADIUS - BORDER_WIDTH)
10158 .border_l_1()
10159 .border_color(cx.theme().colors().border)
10160 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10161 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10162 el.child(
10163 Label::new("Hold")
10164 .size(LabelSize::Small)
10165 .when(accept_keystroke.is_none(), |el| {
10166 el.strikethrough()
10167 })
10168 .line_height_style(LineHeightStyle::UiLabel),
10169 )
10170 })
10171 .id("edit_prediction_cursor_popover_keybind")
10172 .when(accept_keystroke.is_none(), |el| {
10173 let status_colors = cx.theme().status();
10174
10175 el.bg(status_colors.error_background)
10176 .border_color(status_colors.error.opacity(0.6))
10177 .child(Icon::new(IconName::Info).color(Color::Error))
10178 .cursor_default()
10179 .hoverable_tooltip(move |_window, cx| {
10180 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10181 .into()
10182 })
10183 })
10184 .when_some(
10185 accept_keystroke.as_ref(),
10186 |el, accept_keystroke| {
10187 el.child(h_flex().children(ui::render_modifiers(
10188 accept_keystroke.modifiers(),
10189 PlatformStyle::platform(),
10190 Some(Color::Default),
10191 Some(IconSize::XSmall.rems().into()),
10192 false,
10193 )))
10194 },
10195 ),
10196 )
10197 .into_any(),
10198 );
10199 }
10200
10201 self.render_edit_prediction_cursor_popover_preview(
10202 prediction,
10203 cursor_point,
10204 style,
10205 cx,
10206 )?
10207 }
10208
10209 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10210 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10211 stale_completion,
10212 cursor_point,
10213 style,
10214 cx,
10215 )?,
10216
10217 None => pending_completion_container(icons.base)
10218 .child(Label::new("...").size(LabelSize::Small)),
10219 },
10220
10221 None => pending_completion_container(icons.base)
10222 .child(Label::new("...").size(LabelSize::Small)),
10223 };
10224
10225 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10226 completion
10227 .with_animation(
10228 "loading-completion",
10229 Animation::new(Duration::from_secs(2))
10230 .repeat()
10231 .with_easing(pulsating_between(0.4, 0.8)),
10232 |label, delta| label.opacity(delta),
10233 )
10234 .into_any_element()
10235 } else {
10236 completion.into_any_element()
10237 };
10238
10239 let has_completion = self.active_edit_prediction.is_some();
10240
10241 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10242 Some(
10243 h_flex()
10244 .min_w(min_width)
10245 .max_w(max_width)
10246 .flex_1()
10247 .elevation_2(cx)
10248 .border_color(cx.theme().colors().border)
10249 .child(
10250 div()
10251 .flex_1()
10252 .py_1()
10253 .px_2()
10254 .overflow_hidden()
10255 .child(completion),
10256 )
10257 .when_some(accept_keystroke, |el, accept_keystroke| {
10258 if !accept_keystroke.modifiers().modified() {
10259 return el;
10260 }
10261
10262 el.child(
10263 h_flex()
10264 .h_full()
10265 .border_l_1()
10266 .rounded_r_lg()
10267 .border_color(cx.theme().colors().border)
10268 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10269 .gap_1()
10270 .py_1()
10271 .px_2()
10272 .child(
10273 h_flex()
10274 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10275 .when(is_platform_style_mac, |parent| parent.gap_1())
10276 .child(h_flex().children(ui::render_modifiers(
10277 accept_keystroke.modifiers(),
10278 PlatformStyle::platform(),
10279 Some(if !has_completion {
10280 Color::Muted
10281 } else {
10282 Color::Default
10283 }),
10284 None,
10285 false,
10286 ))),
10287 )
10288 .child(Label::new("Preview").into_any_element())
10289 .opacity(if has_completion { 1.0 } else { 0.4 }),
10290 )
10291 })
10292 .into_any(),
10293 )
10294 }
10295
10296 fn render_edit_prediction_cursor_popover_preview(
10297 &self,
10298 completion: &EditPredictionState,
10299 cursor_point: Point,
10300 style: &EditorStyle,
10301 cx: &mut Context<Editor>,
10302 ) -> Option<Div> {
10303 use text::ToPoint as _;
10304
10305 fn render_relative_row_jump(
10306 prefix: impl Into<String>,
10307 current_row: u32,
10308 target_row: u32,
10309 ) -> Div {
10310 let (row_diff, arrow) = if target_row < current_row {
10311 (current_row - target_row, IconName::ArrowUp)
10312 } else {
10313 (target_row - current_row, IconName::ArrowDown)
10314 };
10315
10316 h_flex()
10317 .child(
10318 Label::new(format!("{}{}", prefix.into(), row_diff))
10319 .color(Color::Muted)
10320 .size(LabelSize::Small),
10321 )
10322 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10323 }
10324
10325 let supports_jump = self
10326 .edit_prediction_provider
10327 .as_ref()
10328 .map(|provider| provider.provider.supports_jump_to_edit())
10329 .unwrap_or(true);
10330
10331 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10332
10333 match &completion.completion {
10334 EditPrediction::MoveWithin {
10335 target, snapshot, ..
10336 } => {
10337 if !supports_jump {
10338 return None;
10339 }
10340
10341 Some(
10342 h_flex()
10343 .px_2()
10344 .gap_2()
10345 .flex_1()
10346 .child(
10347 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10348 Icon::new(icons.down)
10349 } else {
10350 Icon::new(icons.up)
10351 },
10352 )
10353 .child(Label::new("Jump to Edit")),
10354 )
10355 }
10356 EditPrediction::MoveOutside { snapshot, .. } => {
10357 let file_name = snapshot
10358 .file()
10359 .map(|file| file.file_name(cx))
10360 .unwrap_or("untitled");
10361 Some(
10362 h_flex()
10363 .px_2()
10364 .gap_2()
10365 .flex_1()
10366 .child(Icon::new(icons.base))
10367 .child(Label::new(format!("Jump to {file_name}"))),
10368 )
10369 }
10370 EditPrediction::Edit {
10371 edits,
10372 edit_preview,
10373 snapshot,
10374 ..
10375 } => {
10376 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10377
10378 let (highlighted_edits, has_more_lines) =
10379 if let Some(edit_preview) = edit_preview.as_ref() {
10380 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10381 .first_line_preview()
10382 } else {
10383 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10384 };
10385
10386 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10387 .with_default_highlights(&style.text, highlighted_edits.highlights);
10388
10389 let preview = h_flex()
10390 .gap_1()
10391 .min_w_16()
10392 .child(styled_text)
10393 .when(has_more_lines, |parent| parent.child("…"));
10394
10395 let left = if supports_jump && first_edit_row != cursor_point.row {
10396 render_relative_row_jump("", cursor_point.row, first_edit_row)
10397 .into_any_element()
10398 } else {
10399 Icon::new(icons.base).into_any_element()
10400 };
10401
10402 Some(
10403 h_flex()
10404 .h_full()
10405 .flex_1()
10406 .gap_2()
10407 .pr_1()
10408 .overflow_x_hidden()
10409 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10410 .child(left)
10411 .child(preview),
10412 )
10413 }
10414 }
10415 }
10416
10417 pub fn render_context_menu(
10418 &mut self,
10419 max_height_in_lines: u32,
10420 window: &mut Window,
10421 cx: &mut Context<Editor>,
10422 ) -> Option<AnyElement> {
10423 let menu = self.context_menu.borrow();
10424 let menu = menu.as_ref()?;
10425 if !menu.visible() {
10426 return None;
10427 };
10428 self.style
10429 .as_ref()
10430 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10431 }
10432
10433 fn render_context_menu_aside(
10434 &mut self,
10435 max_size: Size<Pixels>,
10436 window: &mut Window,
10437 cx: &mut Context<Editor>,
10438 ) -> Option<AnyElement> {
10439 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10440 if menu.visible() {
10441 menu.render_aside(max_size, window, cx)
10442 } else {
10443 None
10444 }
10445 })
10446 }
10447
10448 fn hide_context_menu(
10449 &mut self,
10450 window: &mut Window,
10451 cx: &mut Context<Self>,
10452 ) -> Option<CodeContextMenu> {
10453 cx.notify();
10454 self.completion_tasks.clear();
10455 let context_menu = self.context_menu.borrow_mut().take();
10456 self.stale_edit_prediction_in_menu.take();
10457 self.update_visible_edit_prediction(window, cx);
10458 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10459 && let Some(completion_provider) = &self.completion_provider
10460 {
10461 completion_provider.selection_changed(None, window, cx);
10462 }
10463 context_menu
10464 }
10465
10466 fn show_snippet_choices(
10467 &mut self,
10468 choices: &Vec<String>,
10469 selection: Range<Anchor>,
10470 cx: &mut Context<Self>,
10471 ) {
10472 let Some((_, buffer, _)) = self
10473 .buffer()
10474 .read(cx)
10475 .excerpt_containing(selection.start, cx)
10476 else {
10477 return;
10478 };
10479 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10480 else {
10481 return;
10482 };
10483 if buffer != end_buffer {
10484 log::error!("expected anchor range to have matching buffer IDs");
10485 return;
10486 }
10487
10488 let id = post_inc(&mut self.next_completion_id);
10489 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10490 let mut context_menu = self.context_menu.borrow_mut();
10491 let old_menu = context_menu.take();
10492 *context_menu = Some(CodeContextMenu::Completions(
10493 CompletionsMenu::new_snippet_choices(
10494 id,
10495 true,
10496 choices,
10497 selection,
10498 buffer,
10499 old_menu.map(|menu| menu.primary_scroll_handle()),
10500 snippet_sort_order,
10501 ),
10502 ));
10503 }
10504
10505 pub fn insert_snippet(
10506 &mut self,
10507 insertion_ranges: &[Range<MultiBufferOffset>],
10508 snippet: Snippet,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) -> Result<()> {
10512 struct Tabstop<T> {
10513 is_end_tabstop: bool,
10514 ranges: Vec<Range<T>>,
10515 choices: Option<Vec<String>>,
10516 }
10517
10518 let tabstops = self.buffer.update(cx, |buffer, cx| {
10519 let snippet_text: Arc<str> = snippet.text.clone().into();
10520 let edits = insertion_ranges
10521 .iter()
10522 .cloned()
10523 .map(|range| (range, snippet_text.clone()));
10524 let autoindent_mode = AutoindentMode::Block {
10525 original_indent_columns: Vec::new(),
10526 };
10527 buffer.edit(edits, Some(autoindent_mode), cx);
10528
10529 let snapshot = &*buffer.read(cx);
10530 let snippet = &snippet;
10531 snippet
10532 .tabstops
10533 .iter()
10534 .map(|tabstop| {
10535 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10536 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10537 });
10538 let mut tabstop_ranges = tabstop
10539 .ranges
10540 .iter()
10541 .flat_map(|tabstop_range| {
10542 let mut delta = 0_isize;
10543 insertion_ranges.iter().map(move |insertion_range| {
10544 let insertion_start = insertion_range.start + delta;
10545 delta += snippet.text.len() as isize
10546 - (insertion_range.end - insertion_range.start) as isize;
10547
10548 let start =
10549 (insertion_start + tabstop_range.start).min(snapshot.len());
10550 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10551 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10552 })
10553 })
10554 .collect::<Vec<_>>();
10555 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10556
10557 Tabstop {
10558 is_end_tabstop,
10559 ranges: tabstop_ranges,
10560 choices: tabstop.choices.clone(),
10561 }
10562 })
10563 .collect::<Vec<_>>()
10564 });
10565 if let Some(tabstop) = tabstops.first() {
10566 self.change_selections(Default::default(), window, cx, |s| {
10567 // Reverse order so that the first range is the newest created selection.
10568 // Completions will use it and autoscroll will prioritize it.
10569 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10570 });
10571
10572 if let Some(choices) = &tabstop.choices
10573 && let Some(selection) = tabstop.ranges.first()
10574 {
10575 self.show_snippet_choices(choices, selection.clone(), cx)
10576 }
10577
10578 // If we're already at the last tabstop and it's at the end of the snippet,
10579 // we're done, we don't need to keep the state around.
10580 if !tabstop.is_end_tabstop {
10581 let choices = tabstops
10582 .iter()
10583 .map(|tabstop| tabstop.choices.clone())
10584 .collect();
10585
10586 let ranges = tabstops
10587 .into_iter()
10588 .map(|tabstop| tabstop.ranges)
10589 .collect::<Vec<_>>();
10590
10591 self.snippet_stack.push(SnippetState {
10592 active_index: 0,
10593 ranges,
10594 choices,
10595 });
10596 }
10597
10598 // Check whether the just-entered snippet ends with an auto-closable bracket.
10599 if self.autoclose_regions.is_empty() {
10600 let snapshot = self.buffer.read(cx).snapshot(cx);
10601 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10602 let selection_head = selection.head();
10603 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10604 continue;
10605 };
10606
10607 let mut bracket_pair = None;
10608 let max_lookup_length = scope
10609 .brackets()
10610 .map(|(pair, _)| {
10611 pair.start
10612 .as_str()
10613 .chars()
10614 .count()
10615 .max(pair.end.as_str().chars().count())
10616 })
10617 .max();
10618 if let Some(max_lookup_length) = max_lookup_length {
10619 let next_text = snapshot
10620 .chars_at(selection_head)
10621 .take(max_lookup_length)
10622 .collect::<String>();
10623 let prev_text = snapshot
10624 .reversed_chars_at(selection_head)
10625 .take(max_lookup_length)
10626 .collect::<String>();
10627
10628 for (pair, enabled) in scope.brackets() {
10629 if enabled
10630 && pair.close
10631 && prev_text.starts_with(pair.start.as_str())
10632 && next_text.starts_with(pair.end.as_str())
10633 {
10634 bracket_pair = Some(pair.clone());
10635 break;
10636 }
10637 }
10638 }
10639
10640 if let Some(pair) = bracket_pair {
10641 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10642 let autoclose_enabled =
10643 self.use_autoclose && snapshot_settings.use_autoclose;
10644 if autoclose_enabled {
10645 let start = snapshot.anchor_after(selection_head);
10646 let end = snapshot.anchor_after(selection_head);
10647 self.autoclose_regions.push(AutocloseRegion {
10648 selection_id: selection.id,
10649 range: start..end,
10650 pair,
10651 });
10652 }
10653 }
10654 }
10655 }
10656 }
10657 Ok(())
10658 }
10659
10660 pub fn move_to_next_snippet_tabstop(
10661 &mut self,
10662 window: &mut Window,
10663 cx: &mut Context<Self>,
10664 ) -> bool {
10665 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10666 }
10667
10668 pub fn move_to_prev_snippet_tabstop(
10669 &mut self,
10670 window: &mut Window,
10671 cx: &mut Context<Self>,
10672 ) -> bool {
10673 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10674 }
10675
10676 pub fn move_to_snippet_tabstop(
10677 &mut self,
10678 bias: Bias,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) -> bool {
10682 if let Some(mut snippet) = self.snippet_stack.pop() {
10683 match bias {
10684 Bias::Left => {
10685 if snippet.active_index > 0 {
10686 snippet.active_index -= 1;
10687 } else {
10688 self.snippet_stack.push(snippet);
10689 return false;
10690 }
10691 }
10692 Bias::Right => {
10693 if snippet.active_index + 1 < snippet.ranges.len() {
10694 snippet.active_index += 1;
10695 } else {
10696 self.snippet_stack.push(snippet);
10697 return false;
10698 }
10699 }
10700 }
10701 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10702 self.change_selections(Default::default(), window, cx, |s| {
10703 // Reverse order so that the first range is the newest created selection.
10704 // Completions will use it and autoscroll will prioritize it.
10705 s.select_ranges(current_ranges.iter().rev().cloned())
10706 });
10707
10708 if let Some(choices) = &snippet.choices[snippet.active_index]
10709 && let Some(selection) = current_ranges.first()
10710 {
10711 self.show_snippet_choices(choices, selection.clone(), cx);
10712 }
10713
10714 // If snippet state is not at the last tabstop, push it back on the stack
10715 if snippet.active_index + 1 < snippet.ranges.len() {
10716 self.snippet_stack.push(snippet);
10717 }
10718 return true;
10719 }
10720 }
10721
10722 false
10723 }
10724
10725 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10726 self.transact(window, cx, |this, window, cx| {
10727 this.select_all(&SelectAll, window, cx);
10728 this.insert("", window, cx);
10729 });
10730 }
10731
10732 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10733 if self.read_only(cx) {
10734 return;
10735 }
10736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10737 self.transact(window, cx, |this, window, cx| {
10738 this.select_autoclose_pair(window, cx);
10739
10740 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10741
10742 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10743 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10744 for selection in &mut selections {
10745 if selection.is_empty() {
10746 let old_head = selection.head();
10747 let mut new_head =
10748 movement::left(&display_map, old_head.to_display_point(&display_map))
10749 .to_point(&display_map);
10750 if let Some((buffer, line_buffer_range)) = display_map
10751 .buffer_snapshot()
10752 .buffer_line_for_row(MultiBufferRow(old_head.row))
10753 {
10754 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10755 let indent_len = match indent_size.kind {
10756 IndentKind::Space => {
10757 buffer.settings_at(line_buffer_range.start, cx).tab_size
10758 }
10759 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10760 };
10761 if old_head.column <= indent_size.len && old_head.column > 0 {
10762 let indent_len = indent_len.get();
10763 new_head = cmp::min(
10764 new_head,
10765 MultiBufferPoint::new(
10766 old_head.row,
10767 ((old_head.column - 1) / indent_len) * indent_len,
10768 ),
10769 );
10770 }
10771 }
10772
10773 selection.set_head(new_head, SelectionGoal::None);
10774 }
10775 }
10776
10777 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10778 this.insert("", window, cx);
10779 linked_edits.apply_with_left_expansion(cx);
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 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10802 this.insert("", window, cx);
10803 linked_edits.apply(cx);
10804 this.refresh_edit_prediction(true, false, window, cx);
10805 refresh_linked_ranges(this, window, cx);
10806 });
10807 }
10808
10809 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10810 if self.mode.is_single_line() {
10811 cx.propagate();
10812 return;
10813 }
10814
10815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10816 if self.move_to_prev_snippet_tabstop(window, cx) {
10817 return;
10818 }
10819 self.outdent(&Outdent, window, cx);
10820 }
10821
10822 pub fn next_snippet_tabstop(
10823 &mut self,
10824 _: &NextSnippetTabstop,
10825 window: &mut Window,
10826 cx: &mut Context<Self>,
10827 ) {
10828 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10829 cx.propagate();
10830 return;
10831 }
10832
10833 if self.move_to_next_snippet_tabstop(window, cx) {
10834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10835 return;
10836 }
10837 cx.propagate();
10838 }
10839
10840 pub fn previous_snippet_tabstop(
10841 &mut self,
10842 _: &PreviousSnippetTabstop,
10843 window: &mut Window,
10844 cx: &mut Context<Self>,
10845 ) {
10846 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10847 cx.propagate();
10848 return;
10849 }
10850
10851 if self.move_to_prev_snippet_tabstop(window, cx) {
10852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10853 return;
10854 }
10855 cx.propagate();
10856 }
10857
10858 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10859 if self.mode.is_single_line() {
10860 cx.propagate();
10861 return;
10862 }
10863
10864 if self.move_to_next_snippet_tabstop(window, cx) {
10865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10866 return;
10867 }
10868 if self.read_only(cx) {
10869 return;
10870 }
10871 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10872 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10873 let buffer = self.buffer.read(cx);
10874 let snapshot = buffer.snapshot(cx);
10875 let rows_iter = selections.iter().map(|s| s.head().row);
10876 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10877
10878 let has_some_cursor_in_whitespace = selections
10879 .iter()
10880 .filter(|selection| selection.is_empty())
10881 .any(|selection| {
10882 let cursor = selection.head();
10883 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10884 cursor.column < current_indent.len
10885 });
10886
10887 let mut edits = Vec::new();
10888 let mut prev_edited_row = 0;
10889 let mut row_delta = 0;
10890 for selection in &mut selections {
10891 if selection.start.row != prev_edited_row {
10892 row_delta = 0;
10893 }
10894 prev_edited_row = selection.end.row;
10895
10896 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10897 if selection.is_empty() {
10898 let cursor = selection.head();
10899 let settings = buffer.language_settings_at(cursor, cx);
10900 if settings.indent_list_on_tab {
10901 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10902 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10903 row_delta = Self::indent_selection(
10904 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10905 );
10906 continue;
10907 }
10908 }
10909 }
10910 }
10911
10912 // If the selection is non-empty, then increase the indentation of the selected lines.
10913 if !selection.is_empty() {
10914 row_delta =
10915 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10916 continue;
10917 }
10918
10919 let cursor = selection.head();
10920 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10921 if let Some(suggested_indent) =
10922 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10923 {
10924 // Don't do anything if already at suggested indent
10925 // and there is any other cursor which is not
10926 if has_some_cursor_in_whitespace
10927 && cursor.column == current_indent.len
10928 && current_indent.len == suggested_indent.len
10929 {
10930 continue;
10931 }
10932
10933 // Adjust line and move cursor to suggested indent
10934 // if cursor is not at suggested indent
10935 if cursor.column < suggested_indent.len
10936 && cursor.column <= current_indent.len
10937 && current_indent.len <= suggested_indent.len
10938 {
10939 selection.start = Point::new(cursor.row, suggested_indent.len);
10940 selection.end = selection.start;
10941 if row_delta == 0 {
10942 edits.extend(Buffer::edit_for_indent_size_adjustment(
10943 cursor.row,
10944 current_indent,
10945 suggested_indent,
10946 ));
10947 row_delta = suggested_indent.len - current_indent.len;
10948 }
10949 continue;
10950 }
10951
10952 // If current indent is more than suggested indent
10953 // only move cursor to current indent and skip indent
10954 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10955 selection.start = Point::new(cursor.row, current_indent.len);
10956 selection.end = selection.start;
10957 continue;
10958 }
10959 }
10960
10961 // Otherwise, insert a hard or soft tab.
10962 let settings = buffer.language_settings_at(cursor, cx);
10963 let tab_size = if settings.hard_tabs {
10964 IndentSize::tab()
10965 } else {
10966 let tab_size = settings.tab_size.get();
10967 let indent_remainder = snapshot
10968 .text_for_range(Point::new(cursor.row, 0)..cursor)
10969 .flat_map(str::chars)
10970 .fold(row_delta % tab_size, |counter: u32, c| {
10971 if c == '\t' {
10972 0
10973 } else {
10974 (counter + 1) % tab_size
10975 }
10976 });
10977
10978 let chars_to_next_tab_stop = tab_size - indent_remainder;
10979 IndentSize::spaces(chars_to_next_tab_stop)
10980 };
10981 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10982 selection.end = selection.start;
10983 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10984 row_delta += tab_size.len;
10985 }
10986
10987 self.transact(window, cx, |this, window, cx| {
10988 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10989 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10990 this.refresh_edit_prediction(true, false, window, cx);
10991 });
10992 }
10993
10994 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10995 if self.read_only(cx) {
10996 return;
10997 }
10998 if self.mode.is_single_line() {
10999 cx.propagate();
11000 return;
11001 }
11002
11003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11004 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11005 let mut prev_edited_row = 0;
11006 let mut row_delta = 0;
11007 let mut edits = Vec::new();
11008 let buffer = self.buffer.read(cx);
11009 let snapshot = buffer.snapshot(cx);
11010 for selection in &mut selections {
11011 if selection.start.row != prev_edited_row {
11012 row_delta = 0;
11013 }
11014 prev_edited_row = selection.end.row;
11015
11016 row_delta =
11017 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11018 }
11019
11020 self.transact(window, cx, |this, window, cx| {
11021 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11022 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11023 });
11024 }
11025
11026 fn indent_selection(
11027 buffer: &MultiBuffer,
11028 snapshot: &MultiBufferSnapshot,
11029 selection: &mut Selection<Point>,
11030 edits: &mut Vec<(Range<Point>, String)>,
11031 delta_for_start_row: u32,
11032 cx: &App,
11033 ) -> u32 {
11034 let settings = buffer.language_settings_at(selection.start, cx);
11035 let tab_size = settings.tab_size.get();
11036 let indent_kind = if settings.hard_tabs {
11037 IndentKind::Tab
11038 } else {
11039 IndentKind::Space
11040 };
11041 let mut start_row = selection.start.row;
11042 let mut end_row = selection.end.row + 1;
11043
11044 // If a selection ends at the beginning of a line, don't indent
11045 // that last line.
11046 if selection.end.column == 0 && selection.end.row > selection.start.row {
11047 end_row -= 1;
11048 }
11049
11050 // Avoid re-indenting a row that has already been indented by a
11051 // previous selection, but still update this selection's column
11052 // to reflect that indentation.
11053 if delta_for_start_row > 0 {
11054 start_row += 1;
11055 selection.start.column += delta_for_start_row;
11056 if selection.end.row == selection.start.row {
11057 selection.end.column += delta_for_start_row;
11058 }
11059 }
11060
11061 let mut delta_for_end_row = 0;
11062 let has_multiple_rows = start_row + 1 != end_row;
11063 for row in start_row..end_row {
11064 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11065 let indent_delta = match (current_indent.kind, indent_kind) {
11066 (IndentKind::Space, IndentKind::Space) => {
11067 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11068 IndentSize::spaces(columns_to_next_tab_stop)
11069 }
11070 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11071 (_, IndentKind::Tab) => IndentSize::tab(),
11072 };
11073
11074 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11075 0
11076 } else {
11077 selection.start.column
11078 };
11079 let row_start = Point::new(row, start);
11080 edits.push((
11081 row_start..row_start,
11082 indent_delta.chars().collect::<String>(),
11083 ));
11084
11085 // Update this selection's endpoints to reflect the indentation.
11086 if row == selection.start.row {
11087 selection.start.column += indent_delta.len;
11088 }
11089 if row == selection.end.row {
11090 selection.end.column += indent_delta.len;
11091 delta_for_end_row = indent_delta.len;
11092 }
11093 }
11094
11095 if selection.start.row == selection.end.row {
11096 delta_for_start_row + delta_for_end_row
11097 } else {
11098 delta_for_end_row
11099 }
11100 }
11101
11102 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11103 if self.read_only(cx) {
11104 return;
11105 }
11106 if self.mode.is_single_line() {
11107 cx.propagate();
11108 return;
11109 }
11110
11111 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11112 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11113 let selections = self.selections.all::<Point>(&display_map);
11114 let mut deletion_ranges = Vec::new();
11115 let mut last_outdent = None;
11116 {
11117 let buffer = self.buffer.read(cx);
11118 let snapshot = buffer.snapshot(cx);
11119 for selection in &selections {
11120 let settings = buffer.language_settings_at(selection.start, cx);
11121 let tab_size = settings.tab_size.get();
11122 let mut rows = selection.spanned_rows(false, &display_map);
11123
11124 // Avoid re-outdenting a row that has already been outdented by a
11125 // previous selection.
11126 if let Some(last_row) = last_outdent
11127 && last_row == rows.start
11128 {
11129 rows.start = rows.start.next_row();
11130 }
11131 let has_multiple_rows = rows.len() > 1;
11132 for row in rows.iter_rows() {
11133 let indent_size = snapshot.indent_size_for_line(row);
11134 if indent_size.len > 0 {
11135 let deletion_len = match indent_size.kind {
11136 IndentKind::Space => {
11137 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11138 if columns_to_prev_tab_stop == 0 {
11139 tab_size
11140 } else {
11141 columns_to_prev_tab_stop
11142 }
11143 }
11144 IndentKind::Tab => 1,
11145 };
11146 let start = if has_multiple_rows
11147 || deletion_len > selection.start.column
11148 || indent_size.len < selection.start.column
11149 {
11150 0
11151 } else {
11152 selection.start.column - deletion_len
11153 };
11154 deletion_ranges.push(
11155 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11156 );
11157 last_outdent = Some(row);
11158 }
11159 }
11160 }
11161 }
11162
11163 self.transact(window, cx, |this, window, cx| {
11164 this.buffer.update(cx, |buffer, cx| {
11165 let empty_str: Arc<str> = Arc::default();
11166 buffer.edit(
11167 deletion_ranges
11168 .into_iter()
11169 .map(|range| (range, empty_str.clone())),
11170 None,
11171 cx,
11172 );
11173 });
11174 let selections = this
11175 .selections
11176 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11177 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11178 });
11179 }
11180
11181 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11182 if self.read_only(cx) {
11183 return;
11184 }
11185 if self.mode.is_single_line() {
11186 cx.propagate();
11187 return;
11188 }
11189
11190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11191 let selections = self
11192 .selections
11193 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11194 .into_iter()
11195 .map(|s| s.range());
11196
11197 self.transact(window, cx, |this, window, cx| {
11198 this.buffer.update(cx, |buffer, cx| {
11199 buffer.autoindent_ranges(selections, cx);
11200 });
11201 let selections = this
11202 .selections
11203 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11204 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11205 });
11206 }
11207
11208 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11209 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11211 let selections = self.selections.all::<Point>(&display_map);
11212
11213 let mut new_cursors = Vec::new();
11214 let mut edit_ranges = Vec::new();
11215 let mut selections = selections.iter().peekable();
11216 while let Some(selection) = selections.next() {
11217 let mut rows = selection.spanned_rows(false, &display_map);
11218
11219 // Accumulate contiguous regions of rows that we want to delete.
11220 while let Some(next_selection) = selections.peek() {
11221 let next_rows = next_selection.spanned_rows(false, &display_map);
11222 if next_rows.start <= rows.end {
11223 rows.end = next_rows.end;
11224 selections.next().unwrap();
11225 } else {
11226 break;
11227 }
11228 }
11229
11230 let buffer = display_map.buffer_snapshot();
11231 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11232 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11233 // If there's a line after the range, delete the \n from the end of the row range
11234 (
11235 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11236 rows.end,
11237 )
11238 } else {
11239 // If there isn't a line after the range, delete the \n from the line before the
11240 // start of the row range
11241 edit_start = edit_start.saturating_sub_usize(1);
11242 (buffer.len(), rows.start.previous_row())
11243 };
11244
11245 let text_layout_details = self.text_layout_details(window, cx);
11246 let x = display_map.x_for_display_point(
11247 selection.head().to_display_point(&display_map),
11248 &text_layout_details,
11249 );
11250 let row = Point::new(target_row.0, 0)
11251 .to_display_point(&display_map)
11252 .row();
11253 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11254
11255 new_cursors.push((
11256 selection.id,
11257 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11258 SelectionGoal::None,
11259 ));
11260 edit_ranges.push(edit_start..edit_end);
11261 }
11262
11263 self.transact(window, cx, |this, window, cx| {
11264 let buffer = this.buffer.update(cx, |buffer, cx| {
11265 let empty_str: Arc<str> = Arc::default();
11266 buffer.edit(
11267 edit_ranges
11268 .into_iter()
11269 .map(|range| (range, empty_str.clone())),
11270 None,
11271 cx,
11272 );
11273 buffer.snapshot(cx)
11274 });
11275 let new_selections = new_cursors
11276 .into_iter()
11277 .map(|(id, cursor, goal)| {
11278 let cursor = cursor.to_point(&buffer);
11279 Selection {
11280 id,
11281 start: cursor,
11282 end: cursor,
11283 reversed: false,
11284 goal,
11285 }
11286 })
11287 .collect();
11288
11289 this.change_selections(Default::default(), window, cx, |s| {
11290 s.select(new_selections);
11291 });
11292 });
11293 }
11294
11295 pub fn join_lines_impl(
11296 &mut self,
11297 insert_whitespace: bool,
11298 window: &mut Window,
11299 cx: &mut Context<Self>,
11300 ) {
11301 if self.read_only(cx) {
11302 return;
11303 }
11304 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11305 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11306 let start = MultiBufferRow(selection.start.row);
11307 // Treat single line selections as if they include the next line. Otherwise this action
11308 // would do nothing for single line selections individual cursors.
11309 let end = if selection.start.row == selection.end.row {
11310 MultiBufferRow(selection.start.row + 1)
11311 } else {
11312 MultiBufferRow(selection.end.row)
11313 };
11314
11315 if let Some(last_row_range) = row_ranges.last_mut()
11316 && start <= last_row_range.end
11317 {
11318 last_row_range.end = end;
11319 continue;
11320 }
11321 row_ranges.push(start..end);
11322 }
11323
11324 let snapshot = self.buffer.read(cx).snapshot(cx);
11325 let mut cursor_positions = Vec::new();
11326 for row_range in &row_ranges {
11327 let anchor = snapshot.anchor_before(Point::new(
11328 row_range.end.previous_row().0,
11329 snapshot.line_len(row_range.end.previous_row()),
11330 ));
11331 cursor_positions.push(anchor..anchor);
11332 }
11333
11334 self.transact(window, cx, |this, window, cx| {
11335 for row_range in row_ranges.into_iter().rev() {
11336 for row in row_range.iter_rows().rev() {
11337 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11338 let next_line_row = row.next_row();
11339 let indent = snapshot.indent_size_for_line(next_line_row);
11340 let mut join_start_column = indent.len;
11341
11342 if let Some(language_scope) =
11343 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11344 {
11345 let line_end =
11346 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11347 let line_text_after_indent = snapshot
11348 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11349 .collect::<String>();
11350
11351 if !line_text_after_indent.is_empty() {
11352 let block_prefix = language_scope
11353 .block_comment()
11354 .map(|c| c.prefix.as_ref())
11355 .filter(|p| !p.is_empty());
11356 let doc_prefix = language_scope
11357 .documentation_comment()
11358 .map(|c| c.prefix.as_ref())
11359 .filter(|p| !p.is_empty());
11360 let all_prefixes = language_scope
11361 .line_comment_prefixes()
11362 .iter()
11363 .map(|p| p.as_ref())
11364 .chain(block_prefix)
11365 .chain(doc_prefix)
11366 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11367
11368 let mut longest_prefix_len = None;
11369 for prefix in all_prefixes {
11370 let trimmed = prefix.trim_end();
11371 if line_text_after_indent.starts_with(trimmed) {
11372 let candidate_len =
11373 if line_text_after_indent.starts_with(prefix) {
11374 prefix.len()
11375 } else {
11376 trimmed.len()
11377 };
11378 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11379 longest_prefix_len = Some(candidate_len);
11380 }
11381 }
11382 }
11383
11384 if let Some(prefix_len) = longest_prefix_len {
11385 join_start_column =
11386 join_start_column.saturating_add(prefix_len as u32);
11387 }
11388 }
11389 }
11390
11391 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11392
11393 let replace = if snapshot.line_len(next_line_row) > join_start_column
11394 && insert_whitespace
11395 {
11396 " "
11397 } else {
11398 ""
11399 };
11400
11401 this.buffer.update(cx, |buffer, cx| {
11402 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11403 });
11404 }
11405 }
11406
11407 this.change_selections(Default::default(), window, cx, |s| {
11408 s.select_anchor_ranges(cursor_positions)
11409 });
11410 });
11411 }
11412
11413 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11415 self.join_lines_impl(true, window, cx);
11416 }
11417
11418 pub fn sort_lines_case_sensitive(
11419 &mut self,
11420 _: &SortLinesCaseSensitive,
11421 window: &mut Window,
11422 cx: &mut Context<Self>,
11423 ) {
11424 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11425 }
11426
11427 pub fn sort_lines_by_length(
11428 &mut self,
11429 _: &SortLinesByLength,
11430 window: &mut Window,
11431 cx: &mut Context<Self>,
11432 ) {
11433 self.manipulate_immutable_lines(window, cx, |lines| {
11434 lines.sort_by_key(|&line| line.chars().count())
11435 })
11436 }
11437
11438 pub fn sort_lines_case_insensitive(
11439 &mut self,
11440 _: &SortLinesCaseInsensitive,
11441 window: &mut Window,
11442 cx: &mut Context<Self>,
11443 ) {
11444 self.manipulate_immutable_lines(window, cx, |lines| {
11445 lines.sort_by_key(|line| line.to_lowercase())
11446 })
11447 }
11448
11449 pub fn unique_lines_case_insensitive(
11450 &mut self,
11451 _: &UniqueLinesCaseInsensitive,
11452 window: &mut Window,
11453 cx: &mut Context<Self>,
11454 ) {
11455 self.manipulate_immutable_lines(window, cx, |lines| {
11456 let mut seen = HashSet::default();
11457 lines.retain(|line| seen.insert(line.to_lowercase()));
11458 })
11459 }
11460
11461 pub fn unique_lines_case_sensitive(
11462 &mut self,
11463 _: &UniqueLinesCaseSensitive,
11464 window: &mut Window,
11465 cx: &mut Context<Self>,
11466 ) {
11467 self.manipulate_immutable_lines(window, cx, |lines| {
11468 let mut seen = HashSet::default();
11469 lines.retain(|line| seen.insert(*line));
11470 })
11471 }
11472
11473 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11474 let snapshot = self.buffer.read(cx).snapshot(cx);
11475 for selection in self.selections.disjoint_anchors_arc().iter() {
11476 if snapshot
11477 .language_at(selection.start)
11478 .and_then(|lang| lang.config().wrap_characters.as_ref())
11479 .is_some()
11480 {
11481 return true;
11482 }
11483 }
11484 false
11485 }
11486
11487 fn wrap_selections_in_tag(
11488 &mut self,
11489 _: &WrapSelectionsInTag,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11494
11495 let snapshot = self.buffer.read(cx).snapshot(cx);
11496
11497 let mut edits = Vec::new();
11498 let mut boundaries = Vec::new();
11499
11500 for selection in self
11501 .selections
11502 .all_adjusted(&self.display_snapshot(cx))
11503 .iter()
11504 {
11505 let Some(wrap_config) = snapshot
11506 .language_at(selection.start)
11507 .and_then(|lang| lang.config().wrap_characters.clone())
11508 else {
11509 continue;
11510 };
11511
11512 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11513 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11514
11515 let start_before = snapshot.anchor_before(selection.start);
11516 let end_after = snapshot.anchor_after(selection.end);
11517
11518 edits.push((start_before..start_before, open_tag));
11519 edits.push((end_after..end_after, close_tag));
11520
11521 boundaries.push((
11522 start_before,
11523 end_after,
11524 wrap_config.start_prefix.len(),
11525 wrap_config.end_suffix.len(),
11526 ));
11527 }
11528
11529 if edits.is_empty() {
11530 return;
11531 }
11532
11533 self.transact(window, cx, |this, window, cx| {
11534 let buffer = this.buffer.update(cx, |buffer, cx| {
11535 buffer.edit(edits, None, cx);
11536 buffer.snapshot(cx)
11537 });
11538
11539 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11540 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11541 boundaries.into_iter()
11542 {
11543 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11544 let close_offset = end_after
11545 .to_offset(&buffer)
11546 .saturating_sub_usize(end_suffix_len);
11547 new_selections.push(open_offset..open_offset);
11548 new_selections.push(close_offset..close_offset);
11549 }
11550
11551 this.change_selections(Default::default(), window, cx, |s| {
11552 s.select_ranges(new_selections);
11553 });
11554
11555 this.request_autoscroll(Autoscroll::fit(), cx);
11556 });
11557 }
11558
11559 pub fn toggle_read_only(
11560 &mut self,
11561 _: &workspace::ToggleReadOnlyFile,
11562 _: &mut Window,
11563 cx: &mut Context<Self>,
11564 ) {
11565 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11566 buffer.update(cx, |buffer, cx| {
11567 buffer.set_capability(
11568 match buffer.capability() {
11569 Capability::ReadWrite => Capability::Read,
11570 Capability::Read => Capability::ReadWrite,
11571 Capability::ReadOnly => Capability::ReadOnly,
11572 },
11573 cx,
11574 );
11575 })
11576 }
11577 }
11578
11579 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11580 let Some(project) = self.project.clone() else {
11581 return;
11582 };
11583 let task = self.reload(project, window, cx);
11584 self.detach_and_notify_err(task, window, cx);
11585 }
11586
11587 pub fn restore_file(
11588 &mut self,
11589 _: &::git::RestoreFile,
11590 window: &mut Window,
11591 cx: &mut Context<Self>,
11592 ) {
11593 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11594 let mut buffer_ids = HashSet::default();
11595 let snapshot = self.buffer().read(cx).snapshot(cx);
11596 for selection in self
11597 .selections
11598 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11599 {
11600 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11601 }
11602
11603 let buffer = self.buffer().read(cx);
11604 let ranges = buffer_ids
11605 .into_iter()
11606 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11607 .collect::<Vec<_>>();
11608
11609 self.restore_hunks_in_ranges(ranges, window, cx);
11610 }
11611
11612 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11613 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11614 let selections = self
11615 .selections
11616 .all(&self.display_snapshot(cx))
11617 .into_iter()
11618 .map(|s| s.range())
11619 .collect();
11620 self.restore_hunks_in_ranges(selections, window, cx);
11621 }
11622
11623 pub fn restore_hunks_in_ranges(
11624 &mut self,
11625 ranges: Vec<Range<Point>>,
11626 window: &mut Window,
11627 cx: &mut Context<Editor>,
11628 ) {
11629 if self.delegate_stage_and_restore {
11630 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11631 if !hunks.is_empty() {
11632 cx.emit(EditorEvent::RestoreRequested { hunks });
11633 }
11634 return;
11635 }
11636 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11637 self.transact(window, cx, |editor, window, cx| {
11638 editor.restore_diff_hunks(hunks, cx);
11639 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11640 selections.refresh()
11641 });
11642 });
11643 }
11644
11645 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11646 let mut revert_changes = HashMap::default();
11647 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11648 for (buffer_id, hunks) in &chunk_by {
11649 let hunks = hunks.collect::<Vec<_>>();
11650 for hunk in &hunks {
11651 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11652 }
11653 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11654 }
11655 if !revert_changes.is_empty() {
11656 self.buffer().update(cx, |multi_buffer, cx| {
11657 for (buffer_id, changes) in revert_changes {
11658 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11659 buffer.update(cx, |buffer, cx| {
11660 buffer.edit(
11661 changes
11662 .into_iter()
11663 .map(|(range, text)| (range, text.to_string())),
11664 None,
11665 cx,
11666 );
11667 });
11668 }
11669 }
11670 });
11671 }
11672 }
11673
11674 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11675 if let Some(status) = self
11676 .addons
11677 .iter()
11678 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11679 {
11680 return Some(status);
11681 }
11682 self.project
11683 .as_ref()?
11684 .read(cx)
11685 .status_for_buffer_id(buffer_id, cx)
11686 }
11687
11688 pub fn open_active_item_in_terminal(
11689 &mut self,
11690 _: &OpenInTerminal,
11691 window: &mut Window,
11692 cx: &mut Context<Self>,
11693 ) {
11694 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11695 let project_path = buffer.read(cx).project_path(cx)?;
11696 let project = self.project()?.read(cx);
11697 let entry = project.entry_for_path(&project_path, cx)?;
11698 let parent = match &entry.canonical_path {
11699 Some(canonical_path) => canonical_path.to_path_buf(),
11700 None => project.absolute_path(&project_path, cx)?,
11701 }
11702 .parent()?
11703 .to_path_buf();
11704 Some(parent)
11705 }) {
11706 window.dispatch_action(
11707 OpenTerminal {
11708 working_directory,
11709 local: false,
11710 }
11711 .boxed_clone(),
11712 cx,
11713 );
11714 }
11715 }
11716
11717 fn set_breakpoint_context_menu(
11718 &mut self,
11719 display_row: DisplayRow,
11720 position: Option<Anchor>,
11721 clicked_point: gpui::Point<Pixels>,
11722 window: &mut Window,
11723 cx: &mut Context<Self>,
11724 ) {
11725 let source = self
11726 .buffer
11727 .read(cx)
11728 .snapshot(cx)
11729 .anchor_before(Point::new(display_row.0, 0u32));
11730
11731 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11732
11733 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11734 self,
11735 source,
11736 clicked_point,
11737 context_menu,
11738 window,
11739 cx,
11740 );
11741 }
11742
11743 fn add_edit_breakpoint_block(
11744 &mut self,
11745 anchor: Anchor,
11746 breakpoint: &Breakpoint,
11747 edit_action: BreakpointPromptEditAction,
11748 window: &mut Window,
11749 cx: &mut Context<Self>,
11750 ) {
11751 let weak_editor = cx.weak_entity();
11752 let bp_prompt = cx.new(|cx| {
11753 BreakpointPromptEditor::new(
11754 weak_editor,
11755 anchor,
11756 breakpoint.clone(),
11757 edit_action,
11758 window,
11759 cx,
11760 )
11761 });
11762
11763 let height = bp_prompt.update(cx, |this, cx| {
11764 this.prompt
11765 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11766 });
11767 let cloned_prompt = bp_prompt.clone();
11768 let blocks = vec![BlockProperties {
11769 style: BlockStyle::Sticky,
11770 placement: BlockPlacement::Above(anchor),
11771 height: Some(height),
11772 render: Arc::new(move |cx| {
11773 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11774 cloned_prompt.clone().into_any_element()
11775 }),
11776 priority: 0,
11777 }];
11778
11779 let focus_handle = bp_prompt.focus_handle(cx);
11780 window.focus(&focus_handle, cx);
11781
11782 let block_ids = self.insert_blocks(blocks, None, cx);
11783 bp_prompt.update(cx, |prompt, _| {
11784 prompt.add_block_ids(block_ids);
11785 });
11786 }
11787
11788 pub(crate) fn breakpoint_at_row(
11789 &self,
11790 row: u32,
11791 window: &mut Window,
11792 cx: &mut Context<Self>,
11793 ) -> Option<(Anchor, Breakpoint)> {
11794 let snapshot = self.snapshot(window, cx);
11795 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11796
11797 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11798 }
11799
11800 pub(crate) fn breakpoint_at_anchor(
11801 &self,
11802 breakpoint_position: Anchor,
11803 snapshot: &EditorSnapshot,
11804 cx: &mut Context<Self>,
11805 ) -> Option<(Anchor, Breakpoint)> {
11806 let buffer = self
11807 .buffer
11808 .read(cx)
11809 .buffer_for_anchor(breakpoint_position, cx)?;
11810
11811 let enclosing_excerpt = breakpoint_position.excerpt_id;
11812 let buffer_snapshot = buffer.read(cx).snapshot();
11813
11814 let row = buffer_snapshot
11815 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11816 .row;
11817
11818 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11819 let anchor_end = snapshot
11820 .buffer_snapshot()
11821 .anchor_after(Point::new(row, line_len));
11822
11823 self.breakpoint_store
11824 .as_ref()?
11825 .read_with(cx, |breakpoint_store, cx| {
11826 breakpoint_store
11827 .breakpoints(
11828 &buffer,
11829 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11830 &buffer_snapshot,
11831 cx,
11832 )
11833 .next()
11834 .and_then(|(bp, _)| {
11835 let breakpoint_row = buffer_snapshot
11836 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11837 .row;
11838
11839 if breakpoint_row == row {
11840 snapshot
11841 .buffer_snapshot()
11842 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11843 .map(|position| (position, bp.bp.clone()))
11844 } else {
11845 None
11846 }
11847 })
11848 })
11849 }
11850
11851 pub fn edit_log_breakpoint(
11852 &mut self,
11853 _: &EditLogBreakpoint,
11854 window: &mut Window,
11855 cx: &mut Context<Self>,
11856 ) {
11857 if self.breakpoint_store.is_none() {
11858 return;
11859 }
11860
11861 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11862 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11863 message: None,
11864 state: BreakpointState::Enabled,
11865 condition: None,
11866 hit_condition: None,
11867 });
11868
11869 self.add_edit_breakpoint_block(
11870 anchor,
11871 &breakpoint,
11872 BreakpointPromptEditAction::Log,
11873 window,
11874 cx,
11875 );
11876 }
11877 }
11878
11879 fn breakpoints_at_cursors(
11880 &self,
11881 window: &mut Window,
11882 cx: &mut Context<Self>,
11883 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11884 let snapshot = self.snapshot(window, cx);
11885 let cursors = self
11886 .selections
11887 .disjoint_anchors_arc()
11888 .iter()
11889 .map(|selection| {
11890 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11891
11892 let breakpoint_position = self
11893 .breakpoint_at_row(cursor_position.row, window, cx)
11894 .map(|bp| bp.0)
11895 .unwrap_or_else(|| {
11896 snapshot
11897 .display_snapshot
11898 .buffer_snapshot()
11899 .anchor_after(Point::new(cursor_position.row, 0))
11900 });
11901
11902 let breakpoint = self
11903 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11904 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11905
11906 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11907 })
11908 // 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.
11909 .collect::<HashMap<Anchor, _>>();
11910
11911 cursors.into_iter().collect()
11912 }
11913
11914 pub fn enable_breakpoint(
11915 &mut self,
11916 _: &crate::actions::EnableBreakpoint,
11917 window: &mut Window,
11918 cx: &mut Context<Self>,
11919 ) {
11920 if self.breakpoint_store.is_none() {
11921 return;
11922 }
11923
11924 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11925 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11926 continue;
11927 };
11928 self.edit_breakpoint_at_anchor(
11929 anchor,
11930 breakpoint,
11931 BreakpointEditAction::InvertState,
11932 cx,
11933 );
11934 }
11935 }
11936
11937 pub fn disable_breakpoint(
11938 &mut self,
11939 _: &crate::actions::DisableBreakpoint,
11940 window: &mut Window,
11941 cx: &mut Context<Self>,
11942 ) {
11943 if self.breakpoint_store.is_none() {
11944 return;
11945 }
11946
11947 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11948 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11949 continue;
11950 };
11951 self.edit_breakpoint_at_anchor(
11952 anchor,
11953 breakpoint,
11954 BreakpointEditAction::InvertState,
11955 cx,
11956 );
11957 }
11958 }
11959
11960 pub fn toggle_breakpoint(
11961 &mut self,
11962 _: &crate::actions::ToggleBreakpoint,
11963 window: &mut Window,
11964 cx: &mut Context<Self>,
11965 ) {
11966 if self.breakpoint_store.is_none() {
11967 return;
11968 }
11969
11970 let snapshot = self.snapshot(window, cx);
11971 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11972 if self.gutter_breakpoint_indicator.0.is_some() {
11973 let display_row = anchor
11974 .to_point(snapshot.buffer_snapshot())
11975 .to_display_point(&snapshot.display_snapshot)
11976 .row();
11977 self.update_breakpoint_collision_on_toggle(
11978 display_row,
11979 &BreakpointEditAction::Toggle,
11980 );
11981 }
11982
11983 if let Some(breakpoint) = breakpoint {
11984 self.edit_breakpoint_at_anchor(
11985 anchor,
11986 breakpoint,
11987 BreakpointEditAction::Toggle,
11988 cx,
11989 );
11990 } else {
11991 self.edit_breakpoint_at_anchor(
11992 anchor,
11993 Breakpoint::new_standard(),
11994 BreakpointEditAction::Toggle,
11995 cx,
11996 );
11997 }
11998 }
11999 }
12000
12001 fn update_breakpoint_collision_on_toggle(
12002 &mut self,
12003 display_row: DisplayRow,
12004 edit_action: &BreakpointEditAction,
12005 ) {
12006 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12007 if breakpoint_indicator.display_row == display_row
12008 && matches!(edit_action, BreakpointEditAction::Toggle)
12009 {
12010 breakpoint_indicator.collides_with_existing_breakpoint =
12011 !breakpoint_indicator.collides_with_existing_breakpoint;
12012 }
12013 }
12014 }
12015
12016 pub fn edit_breakpoint_at_anchor(
12017 &mut self,
12018 breakpoint_position: Anchor,
12019 breakpoint: Breakpoint,
12020 edit_action: BreakpointEditAction,
12021 cx: &mut Context<Self>,
12022 ) {
12023 let Some(breakpoint_store) = &self.breakpoint_store else {
12024 return;
12025 };
12026
12027 let Some(buffer) = self
12028 .buffer
12029 .read(cx)
12030 .buffer_for_anchor(breakpoint_position, cx)
12031 else {
12032 return;
12033 };
12034
12035 breakpoint_store.update(cx, |breakpoint_store, cx| {
12036 breakpoint_store.toggle_breakpoint(
12037 buffer,
12038 BreakpointWithPosition {
12039 position: breakpoint_position.text_anchor,
12040 bp: breakpoint,
12041 },
12042 edit_action,
12043 cx,
12044 );
12045 });
12046
12047 cx.notify();
12048 }
12049
12050 #[cfg(any(test, feature = "test-support"))]
12051 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12052 self.breakpoint_store.clone()
12053 }
12054
12055 pub fn prepare_restore_change(
12056 &self,
12057 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12058 hunk: &MultiBufferDiffHunk,
12059 cx: &mut App,
12060 ) -> Option<()> {
12061 if hunk.is_created_file() {
12062 return None;
12063 }
12064 let buffer = self.buffer.read(cx);
12065 let diff = buffer.diff_for(hunk.buffer_id)?;
12066 let buffer = buffer.buffer(hunk.buffer_id)?;
12067 let buffer = buffer.read(cx);
12068 let original_text = diff
12069 .read(cx)
12070 .base_text(cx)
12071 .as_rope()
12072 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12073 let buffer_snapshot = buffer.snapshot();
12074 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12075 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12076 probe
12077 .0
12078 .start
12079 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12080 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12081 }) {
12082 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12083 Some(())
12084 } else {
12085 None
12086 }
12087 }
12088
12089 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12090 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12091 }
12092
12093 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12094 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12095 }
12096
12097 pub fn rotate_selections_forward(
12098 &mut self,
12099 _: &RotateSelectionsForward,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 self.rotate_selections(window, cx, false)
12104 }
12105
12106 pub fn rotate_selections_backward(
12107 &mut self,
12108 _: &RotateSelectionsBackward,
12109 window: &mut Window,
12110 cx: &mut Context<Self>,
12111 ) {
12112 self.rotate_selections(window, cx, true)
12113 }
12114
12115 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12117 let display_snapshot = self.display_snapshot(cx);
12118 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12119
12120 if selections.len() < 2 {
12121 return;
12122 }
12123
12124 let (edits, new_selections) = {
12125 let buffer = self.buffer.read(cx).read(cx);
12126 let has_selections = selections.iter().any(|s| !s.is_empty());
12127 if has_selections {
12128 let mut selected_texts: Vec<String> = selections
12129 .iter()
12130 .map(|selection| {
12131 buffer
12132 .text_for_range(selection.start..selection.end)
12133 .collect()
12134 })
12135 .collect();
12136
12137 if reverse {
12138 selected_texts.rotate_left(1);
12139 } else {
12140 selected_texts.rotate_right(1);
12141 }
12142
12143 let mut offset_delta: i64 = 0;
12144 let mut new_selections = Vec::new();
12145 let edits: Vec<_> = selections
12146 .iter()
12147 .zip(selected_texts.iter())
12148 .map(|(selection, new_text)| {
12149 let old_len = (selection.end.0 - selection.start.0) as i64;
12150 let new_len = new_text.len() as i64;
12151 let adjusted_start =
12152 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12153 let adjusted_end =
12154 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12155
12156 new_selections.push(Selection {
12157 id: selection.id,
12158 start: adjusted_start,
12159 end: adjusted_end,
12160 reversed: selection.reversed,
12161 goal: selection.goal,
12162 });
12163
12164 offset_delta += new_len - old_len;
12165 (selection.start..selection.end, new_text.clone())
12166 })
12167 .collect();
12168 (edits, new_selections)
12169 } else {
12170 let mut all_rows: Vec<u32> = selections
12171 .iter()
12172 .map(|selection| buffer.offset_to_point(selection.start).row)
12173 .collect();
12174 all_rows.sort_unstable();
12175 all_rows.dedup();
12176
12177 if all_rows.len() < 2 {
12178 return;
12179 }
12180
12181 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12182 .iter()
12183 .map(|&row| {
12184 let start = Point::new(row, 0);
12185 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12186 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12187 })
12188 .collect();
12189
12190 let mut line_texts: Vec<String> = line_ranges
12191 .iter()
12192 .map(|range| buffer.text_for_range(range.clone()).collect())
12193 .collect();
12194
12195 if reverse {
12196 line_texts.rotate_left(1);
12197 } else {
12198 line_texts.rotate_right(1);
12199 }
12200
12201 let edits = line_ranges
12202 .iter()
12203 .zip(line_texts.iter())
12204 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12205 .collect();
12206
12207 let num_rows = all_rows.len();
12208 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12209 .iter()
12210 .enumerate()
12211 .map(|(i, &row)| (row, i))
12212 .collect();
12213
12214 // Compute new line start offsets after rotation (handles CRLF)
12215 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12216 let first_line_start = line_ranges[0].start.0;
12217 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12218 for text in line_texts.iter().take(num_rows - 1) {
12219 let prev_start = *new_line_starts.last().unwrap();
12220 new_line_starts.push(prev_start + text.len() + newline_len);
12221 }
12222
12223 let new_selections = selections
12224 .iter()
12225 .map(|selection| {
12226 let point = buffer.offset_to_point(selection.start);
12227 let old_index = row_to_index[&point.row];
12228 let new_index = if reverse {
12229 (old_index + num_rows - 1) % num_rows
12230 } else {
12231 (old_index + 1) % num_rows
12232 };
12233 let new_offset =
12234 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12235 Selection {
12236 id: selection.id,
12237 start: new_offset,
12238 end: new_offset,
12239 reversed: selection.reversed,
12240 goal: selection.goal,
12241 }
12242 })
12243 .collect();
12244
12245 (edits, new_selections)
12246 }
12247 };
12248
12249 self.transact(window, cx, |this, window, cx| {
12250 this.buffer.update(cx, |buffer, cx| {
12251 buffer.edit(edits, None, cx);
12252 });
12253 this.change_selections(Default::default(), window, cx, |s| {
12254 s.select(new_selections);
12255 });
12256 });
12257 }
12258
12259 fn manipulate_lines<M>(
12260 &mut self,
12261 window: &mut Window,
12262 cx: &mut Context<Self>,
12263 mut manipulate: M,
12264 ) where
12265 M: FnMut(&str) -> LineManipulationResult,
12266 {
12267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12268
12269 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12270 let buffer = self.buffer.read(cx).snapshot(cx);
12271
12272 let mut edits = Vec::new();
12273
12274 let selections = self.selections.all::<Point>(&display_map);
12275 let mut selections = selections.iter().peekable();
12276 let mut contiguous_row_selections = Vec::new();
12277 let mut new_selections = Vec::new();
12278 let mut added_lines = 0;
12279 let mut removed_lines = 0;
12280
12281 while let Some(selection) = selections.next() {
12282 let (start_row, end_row) = consume_contiguous_rows(
12283 &mut contiguous_row_selections,
12284 selection,
12285 &display_map,
12286 &mut selections,
12287 );
12288
12289 let start_point = Point::new(start_row.0, 0);
12290 let end_point = Point::new(
12291 end_row.previous_row().0,
12292 buffer.line_len(end_row.previous_row()),
12293 );
12294 let text = buffer
12295 .text_for_range(start_point..end_point)
12296 .collect::<String>();
12297
12298 let LineManipulationResult {
12299 new_text,
12300 line_count_before,
12301 line_count_after,
12302 } = manipulate(&text);
12303
12304 edits.push((start_point..end_point, new_text));
12305
12306 // Selections must change based on added and removed line count
12307 let start_row =
12308 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12309 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12310 new_selections.push(Selection {
12311 id: selection.id,
12312 start: start_row,
12313 end: end_row,
12314 goal: SelectionGoal::None,
12315 reversed: selection.reversed,
12316 });
12317
12318 if line_count_after > line_count_before {
12319 added_lines += line_count_after - line_count_before;
12320 } else if line_count_before > line_count_after {
12321 removed_lines += line_count_before - line_count_after;
12322 }
12323 }
12324
12325 self.transact(window, cx, |this, window, cx| {
12326 let buffer = this.buffer.update(cx, |buffer, cx| {
12327 buffer.edit(edits, None, cx);
12328 buffer.snapshot(cx)
12329 });
12330
12331 // Recalculate offsets on newly edited buffer
12332 let new_selections = new_selections
12333 .iter()
12334 .map(|s| {
12335 let start_point = Point::new(s.start.0, 0);
12336 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12337 Selection {
12338 id: s.id,
12339 start: buffer.point_to_offset(start_point),
12340 end: buffer.point_to_offset(end_point),
12341 goal: s.goal,
12342 reversed: s.reversed,
12343 }
12344 })
12345 .collect();
12346
12347 this.change_selections(Default::default(), window, cx, |s| {
12348 s.select(new_selections);
12349 });
12350
12351 this.request_autoscroll(Autoscroll::fit(), cx);
12352 });
12353 }
12354
12355 fn manipulate_immutable_lines<Fn>(
12356 &mut self,
12357 window: &mut Window,
12358 cx: &mut Context<Self>,
12359 mut callback: Fn,
12360 ) where
12361 Fn: FnMut(&mut Vec<&str>),
12362 {
12363 self.manipulate_lines(window, cx, |text| {
12364 let mut lines: Vec<&str> = text.split('\n').collect();
12365 let line_count_before = lines.len();
12366
12367 callback(&mut lines);
12368
12369 LineManipulationResult {
12370 new_text: lines.join("\n"),
12371 line_count_before,
12372 line_count_after: lines.len(),
12373 }
12374 });
12375 }
12376
12377 fn manipulate_mutable_lines<Fn>(
12378 &mut self,
12379 window: &mut Window,
12380 cx: &mut Context<Self>,
12381 mut callback: Fn,
12382 ) where
12383 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12384 {
12385 self.manipulate_lines(window, cx, |text| {
12386 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12387 let line_count_before = lines.len();
12388
12389 callback(&mut lines);
12390
12391 LineManipulationResult {
12392 new_text: lines.join("\n"),
12393 line_count_before,
12394 line_count_after: lines.len(),
12395 }
12396 });
12397 }
12398
12399 pub fn convert_indentation_to_spaces(
12400 &mut self,
12401 _: &ConvertIndentationToSpaces,
12402 window: &mut Window,
12403 cx: &mut Context<Self>,
12404 ) {
12405 let settings = self.buffer.read(cx).language_settings(cx);
12406 let tab_size = settings.tab_size.get() as usize;
12407
12408 self.manipulate_mutable_lines(window, cx, |lines| {
12409 // Allocates a reasonably sized scratch buffer once for the whole loop
12410 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12411 // Avoids recomputing spaces that could be inserted many times
12412 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12413 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12414 .collect();
12415
12416 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12417 let mut chars = line.as_ref().chars();
12418 let mut col = 0;
12419 let mut changed = false;
12420
12421 for ch in chars.by_ref() {
12422 match ch {
12423 ' ' => {
12424 reindented_line.push(' ');
12425 col += 1;
12426 }
12427 '\t' => {
12428 // \t are converted to spaces depending on the current column
12429 let spaces_len = tab_size - (col % tab_size);
12430 reindented_line.extend(&space_cache[spaces_len - 1]);
12431 col += spaces_len;
12432 changed = true;
12433 }
12434 _ => {
12435 // If we dont append before break, the character is consumed
12436 reindented_line.push(ch);
12437 break;
12438 }
12439 }
12440 }
12441
12442 if !changed {
12443 reindented_line.clear();
12444 continue;
12445 }
12446 // Append the rest of the line and replace old reference with new one
12447 reindented_line.extend(chars);
12448 *line = Cow::Owned(reindented_line.clone());
12449 reindented_line.clear();
12450 }
12451 });
12452 }
12453
12454 pub fn convert_indentation_to_tabs(
12455 &mut self,
12456 _: &ConvertIndentationToTabs,
12457 window: &mut Window,
12458 cx: &mut Context<Self>,
12459 ) {
12460 let settings = self.buffer.read(cx).language_settings(cx);
12461 let tab_size = settings.tab_size.get() as usize;
12462
12463 self.manipulate_mutable_lines(window, cx, |lines| {
12464 // Allocates a reasonably sized buffer once for the whole loop
12465 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12466 // Avoids recomputing spaces that could be inserted many times
12467 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12468 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12469 .collect();
12470
12471 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12472 let mut chars = line.chars();
12473 let mut spaces_count = 0;
12474 let mut first_non_indent_char = None;
12475 let mut changed = false;
12476
12477 for ch in chars.by_ref() {
12478 match ch {
12479 ' ' => {
12480 // Keep track of spaces. Append \t when we reach tab_size
12481 spaces_count += 1;
12482 changed = true;
12483 if spaces_count == tab_size {
12484 reindented_line.push('\t');
12485 spaces_count = 0;
12486 }
12487 }
12488 '\t' => {
12489 reindented_line.push('\t');
12490 spaces_count = 0;
12491 }
12492 _ => {
12493 // Dont append it yet, we might have remaining spaces
12494 first_non_indent_char = Some(ch);
12495 break;
12496 }
12497 }
12498 }
12499
12500 if !changed {
12501 reindented_line.clear();
12502 continue;
12503 }
12504 // Remaining spaces that didn't make a full tab stop
12505 if spaces_count > 0 {
12506 reindented_line.extend(&space_cache[spaces_count - 1]);
12507 }
12508 // If we consume an extra character that was not indentation, add it back
12509 if let Some(extra_char) = first_non_indent_char {
12510 reindented_line.push(extra_char);
12511 }
12512 // Append the rest of the line and replace old reference with new one
12513 reindented_line.extend(chars);
12514 *line = Cow::Owned(reindented_line.clone());
12515 reindented_line.clear();
12516 }
12517 });
12518 }
12519
12520 pub fn convert_to_upper_case(
12521 &mut self,
12522 _: &ConvertToUpperCase,
12523 window: &mut Window,
12524 cx: &mut Context<Self>,
12525 ) {
12526 self.manipulate_text(window, cx, |text| text.to_uppercase())
12527 }
12528
12529 pub fn convert_to_lower_case(
12530 &mut self,
12531 _: &ConvertToLowerCase,
12532 window: &mut Window,
12533 cx: &mut Context<Self>,
12534 ) {
12535 self.manipulate_text(window, cx, |text| text.to_lowercase())
12536 }
12537
12538 pub fn convert_to_title_case(
12539 &mut self,
12540 _: &ConvertToTitleCase,
12541 window: &mut Window,
12542 cx: &mut Context<Self>,
12543 ) {
12544 self.manipulate_text(window, cx, |text| {
12545 text.split('\n')
12546 .map(|line| line.to_case(Case::Title))
12547 .join("\n")
12548 })
12549 }
12550
12551 pub fn convert_to_snake_case(
12552 &mut self,
12553 _: &ConvertToSnakeCase,
12554 window: &mut Window,
12555 cx: &mut Context<Self>,
12556 ) {
12557 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12558 }
12559
12560 pub fn convert_to_kebab_case(
12561 &mut self,
12562 _: &ConvertToKebabCase,
12563 window: &mut Window,
12564 cx: &mut Context<Self>,
12565 ) {
12566 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12567 }
12568
12569 pub fn convert_to_upper_camel_case(
12570 &mut self,
12571 _: &ConvertToUpperCamelCase,
12572 window: &mut Window,
12573 cx: &mut Context<Self>,
12574 ) {
12575 self.manipulate_text(window, cx, |text| {
12576 text.split('\n')
12577 .map(|line| line.to_case(Case::UpperCamel))
12578 .join("\n")
12579 })
12580 }
12581
12582 pub fn convert_to_lower_camel_case(
12583 &mut self,
12584 _: &ConvertToLowerCamelCase,
12585 window: &mut Window,
12586 cx: &mut Context<Self>,
12587 ) {
12588 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12589 }
12590
12591 pub fn convert_to_opposite_case(
12592 &mut self,
12593 _: &ConvertToOppositeCase,
12594 window: &mut Window,
12595 cx: &mut Context<Self>,
12596 ) {
12597 self.manipulate_text(window, cx, |text| {
12598 text.chars()
12599 .fold(String::with_capacity(text.len()), |mut t, c| {
12600 if c.is_uppercase() {
12601 t.extend(c.to_lowercase());
12602 } else {
12603 t.extend(c.to_uppercase());
12604 }
12605 t
12606 })
12607 })
12608 }
12609
12610 pub fn convert_to_sentence_case(
12611 &mut self,
12612 _: &ConvertToSentenceCase,
12613 window: &mut Window,
12614 cx: &mut Context<Self>,
12615 ) {
12616 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12617 }
12618
12619 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12620 self.manipulate_text(window, cx, |text| {
12621 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12622 if has_upper_case_characters {
12623 text.to_lowercase()
12624 } else {
12625 text.to_uppercase()
12626 }
12627 })
12628 }
12629
12630 pub fn convert_to_rot13(
12631 &mut self,
12632 _: &ConvertToRot13,
12633 window: &mut Window,
12634 cx: &mut Context<Self>,
12635 ) {
12636 self.manipulate_text(window, cx, |text| {
12637 text.chars()
12638 .map(|c| match c {
12639 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12640 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12641 _ => c,
12642 })
12643 .collect()
12644 })
12645 }
12646
12647 pub fn convert_to_rot47(
12648 &mut self,
12649 _: &ConvertToRot47,
12650 window: &mut Window,
12651 cx: &mut Context<Self>,
12652 ) {
12653 self.manipulate_text(window, cx, |text| {
12654 text.chars()
12655 .map(|c| {
12656 let code_point = c as u32;
12657 if code_point >= 33 && code_point <= 126 {
12658 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12659 }
12660 c
12661 })
12662 .collect()
12663 })
12664 }
12665
12666 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12667 where
12668 Fn: FnMut(&str) -> String,
12669 {
12670 let buffer = self.buffer.read(cx).snapshot(cx);
12671
12672 let mut new_selections = Vec::new();
12673 let mut edits = Vec::new();
12674 let mut selection_adjustment = 0isize;
12675
12676 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12677 let selection_is_empty = selection.is_empty();
12678
12679 let (start, end) = if selection_is_empty {
12680 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12681 (word_range.start, word_range.end)
12682 } else {
12683 (
12684 buffer.point_to_offset(selection.start),
12685 buffer.point_to_offset(selection.end),
12686 )
12687 };
12688
12689 let text = buffer.text_for_range(start..end).collect::<String>();
12690 let old_length = text.len() as isize;
12691 let text = callback(&text);
12692
12693 new_selections.push(Selection {
12694 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12695 end: MultiBufferOffset(
12696 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12697 ),
12698 goal: SelectionGoal::None,
12699 id: selection.id,
12700 reversed: selection.reversed,
12701 });
12702
12703 selection_adjustment += old_length - text.len() as isize;
12704
12705 edits.push((start..end, text));
12706 }
12707
12708 self.transact(window, cx, |this, window, cx| {
12709 this.buffer.update(cx, |buffer, cx| {
12710 buffer.edit(edits, None, cx);
12711 });
12712
12713 this.change_selections(Default::default(), window, cx, |s| {
12714 s.select(new_selections);
12715 });
12716
12717 this.request_autoscroll(Autoscroll::fit(), cx);
12718 });
12719 }
12720
12721 pub fn move_selection_on_drop(
12722 &mut self,
12723 selection: &Selection<Anchor>,
12724 target: DisplayPoint,
12725 is_cut: bool,
12726 window: &mut Window,
12727 cx: &mut Context<Self>,
12728 ) {
12729 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12730 let buffer = display_map.buffer_snapshot();
12731 let mut edits = Vec::new();
12732 let insert_point = display_map
12733 .clip_point(target, Bias::Left)
12734 .to_point(&display_map);
12735 let text = buffer
12736 .text_for_range(selection.start..selection.end)
12737 .collect::<String>();
12738 if is_cut {
12739 edits.push(((selection.start..selection.end), String::new()));
12740 }
12741 let insert_anchor = buffer.anchor_before(insert_point);
12742 edits.push(((insert_anchor..insert_anchor), text));
12743 let last_edit_start = insert_anchor.bias_left(buffer);
12744 let last_edit_end = insert_anchor.bias_right(buffer);
12745 self.transact(window, cx, |this, window, cx| {
12746 this.buffer.update(cx, |buffer, cx| {
12747 buffer.edit(edits, None, cx);
12748 });
12749 this.change_selections(Default::default(), window, cx, |s| {
12750 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12751 });
12752 });
12753 }
12754
12755 pub fn clear_selection_drag_state(&mut self) {
12756 self.selection_drag_state = SelectionDragState::None;
12757 }
12758
12759 pub fn duplicate(
12760 &mut self,
12761 upwards: bool,
12762 whole_lines: bool,
12763 window: &mut Window,
12764 cx: &mut Context<Self>,
12765 ) {
12766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12767
12768 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12769 let buffer = display_map.buffer_snapshot();
12770 let selections = self.selections.all::<Point>(&display_map);
12771
12772 let mut edits = Vec::new();
12773 let mut selections_iter = selections.iter().peekable();
12774 while let Some(selection) = selections_iter.next() {
12775 let mut rows = selection.spanned_rows(false, &display_map);
12776 // duplicate line-wise
12777 if whole_lines || selection.start == selection.end {
12778 // Avoid duplicating the same lines twice.
12779 while let Some(next_selection) = selections_iter.peek() {
12780 let next_rows = next_selection.spanned_rows(false, &display_map);
12781 if next_rows.start < rows.end {
12782 rows.end = next_rows.end;
12783 selections_iter.next().unwrap();
12784 } else {
12785 break;
12786 }
12787 }
12788
12789 // Copy the text from the selected row region and splice it either at the start
12790 // or end of the region.
12791 let start = Point::new(rows.start.0, 0);
12792 let end = Point::new(
12793 rows.end.previous_row().0,
12794 buffer.line_len(rows.end.previous_row()),
12795 );
12796
12797 let mut text = buffer.text_for_range(start..end).collect::<String>();
12798
12799 let insert_location = if upwards {
12800 // When duplicating upward, we need to insert before the current line.
12801 // If we're on the last line and it doesn't end with a newline,
12802 // we need to add a newline before the duplicated content.
12803 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12804 && buffer.max_point().column > 0
12805 && !text.ends_with('\n');
12806
12807 if needs_leading_newline {
12808 text.insert(0, '\n');
12809 end
12810 } else {
12811 text.push('\n');
12812 Point::new(rows.start.0, 0)
12813 }
12814 } else {
12815 text.push('\n');
12816 start
12817 };
12818 edits.push((insert_location..insert_location, text));
12819 } else {
12820 // duplicate character-wise
12821 let start = selection.start;
12822 let end = selection.end;
12823 let text = buffer.text_for_range(start..end).collect::<String>();
12824 edits.push((selection.end..selection.end, text));
12825 }
12826 }
12827
12828 self.transact(window, cx, |this, window, cx| {
12829 this.buffer.update(cx, |buffer, cx| {
12830 buffer.edit(edits, None, cx);
12831 });
12832
12833 // When duplicating upward with whole lines, move the cursor to the duplicated line
12834 if upwards && whole_lines {
12835 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12836
12837 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12838 let mut new_ranges = Vec::new();
12839 let selections = s.all::<Point>(&display_map);
12840 let mut selections_iter = selections.iter().peekable();
12841
12842 while let Some(first_selection) = selections_iter.next() {
12843 // Group contiguous selections together to find the total row span
12844 let mut group_selections = vec![first_selection];
12845 let mut rows = first_selection.spanned_rows(false, &display_map);
12846
12847 while let Some(next_selection) = selections_iter.peek() {
12848 let next_rows = next_selection.spanned_rows(false, &display_map);
12849 if next_rows.start < rows.end {
12850 rows.end = next_rows.end;
12851 group_selections.push(selections_iter.next().unwrap());
12852 } else {
12853 break;
12854 }
12855 }
12856
12857 let row_count = rows.end.0 - rows.start.0;
12858
12859 // Move all selections in this group up by the total number of duplicated rows
12860 for selection in group_selections {
12861 let new_start = Point::new(
12862 selection.start.row.saturating_sub(row_count),
12863 selection.start.column,
12864 );
12865
12866 let new_end = Point::new(
12867 selection.end.row.saturating_sub(row_count),
12868 selection.end.column,
12869 );
12870
12871 new_ranges.push(new_start..new_end);
12872 }
12873 }
12874
12875 s.select_ranges(new_ranges);
12876 });
12877 }
12878
12879 this.request_autoscroll(Autoscroll::fit(), cx);
12880 });
12881 }
12882
12883 pub fn duplicate_line_up(
12884 &mut self,
12885 _: &DuplicateLineUp,
12886 window: &mut Window,
12887 cx: &mut Context<Self>,
12888 ) {
12889 self.duplicate(true, true, window, cx);
12890 }
12891
12892 pub fn duplicate_line_down(
12893 &mut self,
12894 _: &DuplicateLineDown,
12895 window: &mut Window,
12896 cx: &mut Context<Self>,
12897 ) {
12898 self.duplicate(false, true, window, cx);
12899 }
12900
12901 pub fn duplicate_selection(
12902 &mut self,
12903 _: &DuplicateSelection,
12904 window: &mut Window,
12905 cx: &mut Context<Self>,
12906 ) {
12907 self.duplicate(false, false, window, cx);
12908 }
12909
12910 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12912 if self.mode.is_single_line() {
12913 cx.propagate();
12914 return;
12915 }
12916
12917 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12918 let buffer = self.buffer.read(cx).snapshot(cx);
12919
12920 let mut edits = Vec::new();
12921 let mut unfold_ranges = Vec::new();
12922 let mut refold_creases = Vec::new();
12923
12924 let selections = self.selections.all::<Point>(&display_map);
12925 let mut selections = selections.iter().peekable();
12926 let mut contiguous_row_selections = Vec::new();
12927 let mut new_selections = Vec::new();
12928
12929 while let Some(selection) = selections.next() {
12930 // Find all the selections that span a contiguous row range
12931 let (start_row, end_row) = consume_contiguous_rows(
12932 &mut contiguous_row_selections,
12933 selection,
12934 &display_map,
12935 &mut selections,
12936 );
12937
12938 // Move the text spanned by the row range to be before the line preceding the row range
12939 if start_row.0 > 0 {
12940 let range_to_move = Point::new(
12941 start_row.previous_row().0,
12942 buffer.line_len(start_row.previous_row()),
12943 )
12944 ..Point::new(
12945 end_row.previous_row().0,
12946 buffer.line_len(end_row.previous_row()),
12947 );
12948 let insertion_point = display_map
12949 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12950 .0;
12951
12952 // Don't move lines across excerpts
12953 if buffer
12954 .excerpt_containing(insertion_point..range_to_move.end)
12955 .is_some()
12956 {
12957 let text = buffer
12958 .text_for_range(range_to_move.clone())
12959 .flat_map(|s| s.chars())
12960 .skip(1)
12961 .chain(['\n'])
12962 .collect::<String>();
12963
12964 edits.push((
12965 buffer.anchor_after(range_to_move.start)
12966 ..buffer.anchor_before(range_to_move.end),
12967 String::new(),
12968 ));
12969 let insertion_anchor = buffer.anchor_after(insertion_point);
12970 edits.push((insertion_anchor..insertion_anchor, text));
12971
12972 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12973
12974 // Move selections up
12975 new_selections.extend(contiguous_row_selections.drain(..).map(
12976 |mut selection| {
12977 selection.start.row -= row_delta;
12978 selection.end.row -= row_delta;
12979 selection
12980 },
12981 ));
12982
12983 // Move folds up
12984 unfold_ranges.push(range_to_move.clone());
12985 for fold in display_map.folds_in_range(
12986 buffer.anchor_before(range_to_move.start)
12987 ..buffer.anchor_after(range_to_move.end),
12988 ) {
12989 let mut start = fold.range.start.to_point(&buffer);
12990 let mut end = fold.range.end.to_point(&buffer);
12991 start.row -= row_delta;
12992 end.row -= row_delta;
12993 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12994 }
12995 }
12996 }
12997
12998 // If we didn't move line(s), preserve the existing selections
12999 new_selections.append(&mut contiguous_row_selections);
13000 }
13001
13002 self.transact(window, cx, |this, window, cx| {
13003 this.unfold_ranges(&unfold_ranges, true, true, cx);
13004 this.buffer.update(cx, |buffer, cx| {
13005 for (range, text) in edits {
13006 buffer.edit([(range, text)], None, cx);
13007 }
13008 });
13009 this.fold_creases(refold_creases, true, window, cx);
13010 this.change_selections(Default::default(), window, cx, |s| {
13011 s.select(new_selections);
13012 })
13013 });
13014 }
13015
13016 pub fn move_line_down(
13017 &mut self,
13018 _: &MoveLineDown,
13019 window: &mut Window,
13020 cx: &mut Context<Self>,
13021 ) {
13022 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13023 if self.mode.is_single_line() {
13024 cx.propagate();
13025 return;
13026 }
13027
13028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13029 let buffer = self.buffer.read(cx).snapshot(cx);
13030
13031 let mut edits = Vec::new();
13032 let mut unfold_ranges = Vec::new();
13033 let mut refold_creases = Vec::new();
13034
13035 let selections = self.selections.all::<Point>(&display_map);
13036 let mut selections = selections.iter().peekable();
13037 let mut contiguous_row_selections = Vec::new();
13038 let mut new_selections = Vec::new();
13039
13040 while let Some(selection) = selections.next() {
13041 // Find all the selections that span a contiguous row range
13042 let (start_row, end_row) = consume_contiguous_rows(
13043 &mut contiguous_row_selections,
13044 selection,
13045 &display_map,
13046 &mut selections,
13047 );
13048
13049 // Move the text spanned by the row range to be after the last line of the row range
13050 if end_row.0 <= buffer.max_point().row {
13051 let range_to_move =
13052 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13053 let insertion_point = display_map
13054 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13055 .0;
13056
13057 // Don't move lines across excerpt boundaries
13058 if buffer
13059 .excerpt_containing(range_to_move.start..insertion_point)
13060 .is_some()
13061 {
13062 let mut text = String::from("\n");
13063 text.extend(buffer.text_for_range(range_to_move.clone()));
13064 text.pop(); // Drop trailing newline
13065 edits.push((
13066 buffer.anchor_after(range_to_move.start)
13067 ..buffer.anchor_before(range_to_move.end),
13068 String::new(),
13069 ));
13070 let insertion_anchor = buffer.anchor_after(insertion_point);
13071 edits.push((insertion_anchor..insertion_anchor, text));
13072
13073 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13074
13075 // Move selections down
13076 new_selections.extend(contiguous_row_selections.drain(..).map(
13077 |mut selection| {
13078 selection.start.row += row_delta;
13079 selection.end.row += row_delta;
13080 selection
13081 },
13082 ));
13083
13084 // Move folds down
13085 unfold_ranges.push(range_to_move.clone());
13086 for fold in display_map.folds_in_range(
13087 buffer.anchor_before(range_to_move.start)
13088 ..buffer.anchor_after(range_to_move.end),
13089 ) {
13090 let mut start = fold.range.start.to_point(&buffer);
13091 let mut end = fold.range.end.to_point(&buffer);
13092 start.row += row_delta;
13093 end.row += row_delta;
13094 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13095 }
13096 }
13097 }
13098
13099 // If we didn't move line(s), preserve the existing selections
13100 new_selections.append(&mut contiguous_row_selections);
13101 }
13102
13103 self.transact(window, cx, |this, window, cx| {
13104 this.unfold_ranges(&unfold_ranges, true, true, cx);
13105 this.buffer.update(cx, |buffer, cx| {
13106 for (range, text) in edits {
13107 buffer.edit([(range, text)], None, cx);
13108 }
13109 });
13110 this.fold_creases(refold_creases, true, window, cx);
13111 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13112 });
13113 }
13114
13115 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13117 let text_layout_details = &self.text_layout_details(window, cx);
13118 self.transact(window, cx, |this, window, cx| {
13119 let edits = this.change_selections(Default::default(), window, cx, |s| {
13120 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13121 s.move_with(&mut |display_map, selection| {
13122 if !selection.is_empty() {
13123 return;
13124 }
13125
13126 let mut head = selection.head();
13127 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13128 if head.column() == display_map.line_len(head.row()) {
13129 transpose_offset = display_map
13130 .buffer_snapshot()
13131 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13132 }
13133
13134 if transpose_offset == MultiBufferOffset(0) {
13135 return;
13136 }
13137
13138 *head.column_mut() += 1;
13139 head = display_map.clip_point(head, Bias::Right);
13140 let goal = SelectionGoal::HorizontalPosition(
13141 display_map
13142 .x_for_display_point(head, text_layout_details)
13143 .into(),
13144 );
13145 selection.collapse_to(head, goal);
13146
13147 let transpose_start = display_map
13148 .buffer_snapshot()
13149 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13150 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13151 let transpose_end = display_map
13152 .buffer_snapshot()
13153 .clip_offset(transpose_offset + 1usize, Bias::Right);
13154 if let Some(ch) = display_map
13155 .buffer_snapshot()
13156 .chars_at(transpose_start)
13157 .next()
13158 {
13159 edits.push((transpose_start..transpose_offset, String::new()));
13160 edits.push((transpose_end..transpose_end, ch.to_string()));
13161 }
13162 }
13163 });
13164 edits
13165 });
13166 this.buffer
13167 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13168 let selections = this
13169 .selections
13170 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13171 this.change_selections(Default::default(), window, cx, |s| {
13172 s.select(selections);
13173 });
13174 });
13175 }
13176
13177 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13178 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13179 if self.mode.is_single_line() {
13180 cx.propagate();
13181 return;
13182 }
13183
13184 self.rewrap_impl(RewrapOptions::default(), cx)
13185 }
13186
13187 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13188 let buffer = self.buffer.read(cx).snapshot(cx);
13189 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13190
13191 #[derive(Clone, Debug, PartialEq)]
13192 enum CommentFormat {
13193 /// single line comment, with prefix for line
13194 Line(String),
13195 /// single line within a block comment, with prefix for line
13196 BlockLine(String),
13197 /// a single line of a block comment that includes the initial delimiter
13198 BlockCommentWithStart(BlockCommentConfig),
13199 /// a single line of a block comment that includes the ending delimiter
13200 BlockCommentWithEnd(BlockCommentConfig),
13201 }
13202
13203 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13204 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13205 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13206 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13207 .peekable();
13208
13209 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13210 row
13211 } else {
13212 return Vec::new();
13213 };
13214
13215 let language_settings = buffer.language_settings_at(selection.head(), cx);
13216 let language_scope = buffer.language_scope_at(selection.head());
13217
13218 let indent_and_prefix_for_row =
13219 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13220 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13221 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13222 &language_scope
13223 {
13224 let indent_end = Point::new(row, indent.len);
13225 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13226 let line_text_after_indent = buffer
13227 .text_for_range(indent_end..line_end)
13228 .collect::<String>();
13229
13230 let is_within_comment_override = buffer
13231 .language_scope_at(indent_end)
13232 .is_some_and(|scope| scope.override_name() == Some("comment"));
13233 let comment_delimiters = if is_within_comment_override {
13234 // we are within a comment syntax node, but we don't
13235 // yet know what kind of comment: block, doc or line
13236 match (
13237 language_scope.documentation_comment(),
13238 language_scope.block_comment(),
13239 ) {
13240 (Some(config), _) | (_, Some(config))
13241 if buffer.contains_str_at(indent_end, &config.start) =>
13242 {
13243 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13244 }
13245 (Some(config), _) | (_, Some(config))
13246 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13247 {
13248 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13249 }
13250 (Some(config), _) | (_, Some(config))
13251 if buffer.contains_str_at(indent_end, &config.prefix) =>
13252 {
13253 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13254 }
13255 (_, _) => language_scope
13256 .line_comment_prefixes()
13257 .iter()
13258 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13259 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13260 }
13261 } else {
13262 // we not in an overridden comment node, but we may
13263 // be within a non-overridden line comment node
13264 language_scope
13265 .line_comment_prefixes()
13266 .iter()
13267 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13268 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13269 };
13270
13271 let rewrap_prefix = language_scope
13272 .rewrap_prefixes()
13273 .iter()
13274 .find_map(|prefix_regex| {
13275 prefix_regex.find(&line_text_after_indent).map(|mat| {
13276 if mat.start() == 0 {
13277 Some(mat.as_str().to_string())
13278 } else {
13279 None
13280 }
13281 })
13282 })
13283 .flatten();
13284 (comment_delimiters, rewrap_prefix)
13285 } else {
13286 (None, None)
13287 };
13288 (indent, comment_prefix, rewrap_prefix)
13289 };
13290
13291 let mut ranges = Vec::new();
13292 let from_empty_selection = selection.is_empty();
13293
13294 let mut current_range_start = first_row;
13295 let mut prev_row = first_row;
13296 let (
13297 mut current_range_indent,
13298 mut current_range_comment_delimiters,
13299 mut current_range_rewrap_prefix,
13300 ) = indent_and_prefix_for_row(first_row);
13301
13302 for row in non_blank_rows_iter.skip(1) {
13303 let has_paragraph_break = row > prev_row + 1;
13304
13305 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13306 indent_and_prefix_for_row(row);
13307
13308 let has_indent_change = row_indent != current_range_indent;
13309 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13310
13311 let has_boundary_change = has_comment_change
13312 || row_rewrap_prefix.is_some()
13313 || (has_indent_change && current_range_comment_delimiters.is_some());
13314
13315 if has_paragraph_break || has_boundary_change {
13316 ranges.push((
13317 language_settings.clone(),
13318 Point::new(current_range_start, 0)
13319 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13320 current_range_indent,
13321 current_range_comment_delimiters.clone(),
13322 current_range_rewrap_prefix.clone(),
13323 from_empty_selection,
13324 ));
13325 current_range_start = row;
13326 current_range_indent = row_indent;
13327 current_range_comment_delimiters = row_comment_delimiters;
13328 current_range_rewrap_prefix = row_rewrap_prefix;
13329 }
13330 prev_row = row;
13331 }
13332
13333 ranges.push((
13334 language_settings.clone(),
13335 Point::new(current_range_start, 0)
13336 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13337 current_range_indent,
13338 current_range_comment_delimiters,
13339 current_range_rewrap_prefix,
13340 from_empty_selection,
13341 ));
13342
13343 ranges
13344 });
13345
13346 let mut edits = Vec::new();
13347 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13348
13349 for (
13350 language_settings,
13351 wrap_range,
13352 mut indent_size,
13353 comment_prefix,
13354 rewrap_prefix,
13355 from_empty_selection,
13356 ) in wrap_ranges
13357 {
13358 let mut start_row = wrap_range.start.row;
13359 let mut end_row = wrap_range.end.row;
13360
13361 // Skip selections that overlap with a range that has already been rewrapped.
13362 let selection_range = start_row..end_row;
13363 if rewrapped_row_ranges
13364 .iter()
13365 .any(|range| range.overlaps(&selection_range))
13366 {
13367 continue;
13368 }
13369
13370 let tab_size = language_settings.tab_size;
13371
13372 let (line_prefix, inside_comment) = match &comment_prefix {
13373 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13374 (Some(prefix.as_str()), true)
13375 }
13376 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13377 (Some(prefix.as_ref()), true)
13378 }
13379 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13380 start: _,
13381 end: _,
13382 prefix,
13383 tab_size,
13384 })) => {
13385 indent_size.len += tab_size;
13386 (Some(prefix.as_ref()), true)
13387 }
13388 None => (None, false),
13389 };
13390 let indent_prefix = indent_size.chars().collect::<String>();
13391 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13392
13393 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13394 RewrapBehavior::InComments => inside_comment,
13395 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13396 RewrapBehavior::Anywhere => true,
13397 };
13398
13399 let should_rewrap = options.override_language_settings
13400 || allow_rewrap_based_on_language
13401 || self.hard_wrap.is_some();
13402 if !should_rewrap {
13403 continue;
13404 }
13405
13406 if from_empty_selection {
13407 'expand_upwards: while start_row > 0 {
13408 let prev_row = start_row - 1;
13409 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13410 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13411 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13412 {
13413 start_row = prev_row;
13414 } else {
13415 break 'expand_upwards;
13416 }
13417 }
13418
13419 'expand_downwards: while end_row < buffer.max_point().row {
13420 let next_row = end_row + 1;
13421 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13422 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13423 && !buffer.is_line_blank(MultiBufferRow(next_row))
13424 {
13425 end_row = next_row;
13426 } else {
13427 break 'expand_downwards;
13428 }
13429 }
13430 }
13431
13432 let start = Point::new(start_row, 0);
13433 let start_offset = ToOffset::to_offset(&start, &buffer);
13434 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13435 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13436 let mut first_line_delimiter = None;
13437 let mut last_line_delimiter = None;
13438 let Some(lines_without_prefixes) = selection_text
13439 .lines()
13440 .enumerate()
13441 .map(|(ix, line)| {
13442 let line_trimmed = line.trim_start();
13443 if rewrap_prefix.is_some() && ix > 0 {
13444 Ok(line_trimmed)
13445 } else if let Some(
13446 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13447 start,
13448 prefix,
13449 end,
13450 tab_size,
13451 })
13452 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13453 start,
13454 prefix,
13455 end,
13456 tab_size,
13457 }),
13458 ) = &comment_prefix
13459 {
13460 let line_trimmed = line_trimmed
13461 .strip_prefix(start.as_ref())
13462 .map(|s| {
13463 let mut indent_size = indent_size;
13464 indent_size.len -= tab_size;
13465 let indent_prefix: String = indent_size.chars().collect();
13466 first_line_delimiter = Some((indent_prefix, start));
13467 s.trim_start()
13468 })
13469 .unwrap_or(line_trimmed);
13470 let line_trimmed = line_trimmed
13471 .strip_suffix(end.as_ref())
13472 .map(|s| {
13473 last_line_delimiter = Some(end);
13474 s.trim_end()
13475 })
13476 .unwrap_or(line_trimmed);
13477 let line_trimmed = line_trimmed
13478 .strip_prefix(prefix.as_ref())
13479 .unwrap_or(line_trimmed);
13480 Ok(line_trimmed)
13481 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13482 line_trimmed.strip_prefix(prefix).with_context(|| {
13483 format!("line did not start with prefix {prefix:?}: {line:?}")
13484 })
13485 } else {
13486 line_trimmed
13487 .strip_prefix(&line_prefix.trim_start())
13488 .with_context(|| {
13489 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13490 })
13491 }
13492 })
13493 .collect::<Result<Vec<_>, _>>()
13494 .log_err()
13495 else {
13496 continue;
13497 };
13498
13499 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13500 buffer
13501 .language_settings_at(Point::new(start_row, 0), cx)
13502 .preferred_line_length as usize
13503 });
13504
13505 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13506 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13507 } else {
13508 line_prefix.clone()
13509 };
13510
13511 let wrapped_text = {
13512 let mut wrapped_text = wrap_with_prefix(
13513 line_prefix,
13514 subsequent_lines_prefix,
13515 lines_without_prefixes.join("\n"),
13516 wrap_column,
13517 tab_size,
13518 options.preserve_existing_whitespace,
13519 );
13520
13521 if let Some((indent, delimiter)) = first_line_delimiter {
13522 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13523 }
13524 if let Some(last_line) = last_line_delimiter {
13525 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13526 }
13527
13528 wrapped_text
13529 };
13530
13531 // TODO: should always use char-based diff while still supporting cursor behavior that
13532 // matches vim.
13533 let mut diff_options = DiffOptions::default();
13534 if options.override_language_settings {
13535 diff_options.max_word_diff_len = 0;
13536 diff_options.max_word_diff_line_count = 0;
13537 } else {
13538 diff_options.max_word_diff_len = usize::MAX;
13539 diff_options.max_word_diff_line_count = usize::MAX;
13540 }
13541
13542 for (old_range, new_text) in
13543 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13544 {
13545 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13546 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13547 edits.push((edit_start..edit_end, new_text));
13548 }
13549
13550 rewrapped_row_ranges.push(start_row..=end_row);
13551 }
13552
13553 self.buffer
13554 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13555 }
13556
13557 pub fn cut_common(
13558 &mut self,
13559 cut_no_selection_line: bool,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) -> ClipboardItem {
13563 let mut text = String::new();
13564 let buffer = self.buffer.read(cx).snapshot(cx);
13565 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13566 let mut clipboard_selections = Vec::with_capacity(selections.len());
13567 {
13568 let max_point = buffer.max_point();
13569 let mut is_first = true;
13570 let mut prev_selection_was_entire_line = false;
13571 for selection in &mut selections {
13572 let is_entire_line =
13573 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13574 if is_entire_line {
13575 selection.start = Point::new(selection.start.row, 0);
13576 if !selection.is_empty() && selection.end.column == 0 {
13577 selection.end = cmp::min(max_point, selection.end);
13578 } else {
13579 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13580 }
13581 selection.goal = SelectionGoal::None;
13582 }
13583 if is_first {
13584 is_first = false;
13585 } else if !prev_selection_was_entire_line {
13586 text += "\n";
13587 }
13588 prev_selection_was_entire_line = is_entire_line;
13589 let mut len = 0;
13590 for chunk in buffer.text_for_range(selection.start..selection.end) {
13591 text.push_str(chunk);
13592 len += chunk.len();
13593 }
13594
13595 clipboard_selections.push(ClipboardSelection::for_buffer(
13596 len,
13597 is_entire_line,
13598 selection.range(),
13599 &buffer,
13600 self.project.as_ref(),
13601 cx,
13602 ));
13603 }
13604 }
13605
13606 self.transact(window, cx, |this, window, cx| {
13607 this.change_selections(Default::default(), window, cx, |s| {
13608 s.select(selections);
13609 });
13610 this.insert("", window, cx);
13611 });
13612 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13613 }
13614
13615 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13616 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13617 let item = self.cut_common(true, window, cx);
13618 cx.write_to_clipboard(item);
13619 }
13620
13621 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13623 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13624 s.move_with(&mut |snapshot, sel| {
13625 if sel.is_empty() {
13626 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13627 }
13628 if sel.is_empty() {
13629 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13630 }
13631 });
13632 });
13633 let item = self.cut_common(false, window, cx);
13634 cx.set_global(KillRing(item))
13635 }
13636
13637 pub fn kill_ring_yank(
13638 &mut self,
13639 _: &KillRingYank,
13640 window: &mut Window,
13641 cx: &mut Context<Self>,
13642 ) {
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13644 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13645 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13646 (kill_ring.text().to_string(), kill_ring.metadata_json())
13647 } else {
13648 return;
13649 }
13650 } else {
13651 return;
13652 };
13653 self.do_paste(&text, metadata, false, window, cx);
13654 }
13655
13656 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13657 self.do_copy(true, cx);
13658 }
13659
13660 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13661 self.do_copy(false, cx);
13662 }
13663
13664 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13665 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13666 let buffer = self.buffer.read(cx).read(cx);
13667 let mut text = String::new();
13668 let mut clipboard_selections = Vec::with_capacity(selections.len());
13669
13670 let max_point = buffer.max_point();
13671 let mut is_first = true;
13672 for selection in &selections {
13673 let mut start = selection.start;
13674 let mut end = selection.end;
13675 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13676 let mut add_trailing_newline = false;
13677 if is_entire_line {
13678 start = Point::new(start.row, 0);
13679 let next_line_start = Point::new(end.row + 1, 0);
13680 if next_line_start <= max_point {
13681 end = next_line_start;
13682 } else {
13683 // We're on the last line without a trailing newline.
13684 // Copy to the end of the line and add a newline afterwards.
13685 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13686 add_trailing_newline = true;
13687 }
13688 }
13689
13690 let mut trimmed_selections = Vec::new();
13691 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13692 let row = MultiBufferRow(start.row);
13693 let first_indent = buffer.indent_size_for_line(row);
13694 if first_indent.len == 0 || start.column > first_indent.len {
13695 trimmed_selections.push(start..end);
13696 } else {
13697 trimmed_selections.push(
13698 Point::new(row.0, first_indent.len)
13699 ..Point::new(row.0, buffer.line_len(row)),
13700 );
13701 for row in start.row + 1..=end.row {
13702 let mut line_len = buffer.line_len(MultiBufferRow(row));
13703 if row == end.row {
13704 line_len = end.column;
13705 }
13706 if line_len == 0 {
13707 trimmed_selections.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
13713 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13714 } else {
13715 trimmed_selections.clear();
13716 trimmed_selections.push(start..end);
13717 break;
13718 }
13719 }
13720 }
13721 } else {
13722 trimmed_selections.push(start..end);
13723 }
13724
13725 let is_multiline_trim = trimmed_selections.len() > 1;
13726 let mut selection_len: usize = 0;
13727 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13728
13729 for trimmed_range in trimmed_selections {
13730 if is_first {
13731 is_first = false;
13732 } else if is_multiline_trim || !prev_selection_was_entire_line {
13733 text.push('\n');
13734 if is_multiline_trim {
13735 selection_len += 1;
13736 }
13737 }
13738 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13739 text.push_str(chunk);
13740 selection_len += chunk.len();
13741 }
13742 if add_trailing_newline {
13743 text.push('\n');
13744 selection_len += 1;
13745 }
13746 }
13747
13748 clipboard_selections.push(ClipboardSelection::for_buffer(
13749 selection_len,
13750 is_entire_line,
13751 start..end,
13752 &buffer,
13753 self.project.as_ref(),
13754 cx,
13755 ));
13756 }
13757
13758 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13759 text,
13760 clipboard_selections,
13761 ));
13762 }
13763
13764 pub fn do_paste(
13765 &mut self,
13766 text: &String,
13767 clipboard_selections: Option<Vec<ClipboardSelection>>,
13768 handle_entire_lines: bool,
13769 window: &mut Window,
13770 cx: &mut Context<Self>,
13771 ) {
13772 if self.read_only(cx) {
13773 return;
13774 }
13775
13776 let clipboard_text = Cow::Borrowed(text.as_str());
13777
13778 self.transact(window, cx, |this, window, cx| {
13779 let had_active_edit_prediction = this.has_active_edit_prediction();
13780 let display_map = this.display_snapshot(cx);
13781 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13782 let cursor_offset = this
13783 .selections
13784 .last::<MultiBufferOffset>(&display_map)
13785 .head();
13786
13787 if let Some(mut clipboard_selections) = clipboard_selections {
13788 let all_selections_were_entire_line =
13789 clipboard_selections.iter().all(|s| s.is_entire_line);
13790 let first_selection_indent_column =
13791 clipboard_selections.first().map(|s| s.first_line_indent);
13792 if clipboard_selections.len() != old_selections.len() {
13793 clipboard_selections.drain(..);
13794 }
13795 let mut auto_indent_on_paste = true;
13796
13797 this.buffer.update(cx, |buffer, cx| {
13798 let snapshot = buffer.read(cx);
13799 auto_indent_on_paste = snapshot
13800 .language_settings_at(cursor_offset, cx)
13801 .auto_indent_on_paste;
13802
13803 let mut start_offset = 0;
13804 let mut edits = Vec::new();
13805 let mut original_indent_columns = Vec::new();
13806 for (ix, selection) in old_selections.iter().enumerate() {
13807 let to_insert;
13808 let entire_line;
13809 let original_indent_column;
13810 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13811 let end_offset = start_offset + clipboard_selection.len;
13812 to_insert = &clipboard_text[start_offset..end_offset];
13813 entire_line = clipboard_selection.is_entire_line;
13814 start_offset = if entire_line {
13815 end_offset
13816 } else {
13817 end_offset + 1
13818 };
13819 original_indent_column = Some(clipboard_selection.first_line_indent);
13820 } else {
13821 to_insert = &*clipboard_text;
13822 entire_line = all_selections_were_entire_line;
13823 original_indent_column = first_selection_indent_column
13824 }
13825
13826 let (range, to_insert) =
13827 if selection.is_empty() && handle_entire_lines && entire_line {
13828 // If the corresponding selection was empty when this slice of the
13829 // clipboard text was written, then the entire line containing the
13830 // selection was copied. If this selection is also currently empty,
13831 // then paste the line before the current line of the buffer.
13832 let column = selection.start.to_point(&snapshot).column as usize;
13833 let line_start = selection.start - column;
13834 (line_start..line_start, Cow::Borrowed(to_insert))
13835 } else {
13836 let language = snapshot.language_at(selection.head());
13837 let range = selection.range();
13838 if let Some(language) = language
13839 && language.name() == "Markdown"
13840 {
13841 edit_for_markdown_paste(
13842 &snapshot,
13843 range,
13844 to_insert,
13845 url::Url::parse(to_insert).ok(),
13846 )
13847 } else {
13848 (range, Cow::Borrowed(to_insert))
13849 }
13850 };
13851
13852 edits.push((range, to_insert));
13853 original_indent_columns.push(original_indent_column);
13854 }
13855 drop(snapshot);
13856
13857 buffer.edit(
13858 edits,
13859 if auto_indent_on_paste {
13860 Some(AutoindentMode::Block {
13861 original_indent_columns,
13862 })
13863 } else {
13864 None
13865 },
13866 cx,
13867 );
13868 });
13869
13870 let selections = this
13871 .selections
13872 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13873 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13874 } else {
13875 let url = url::Url::parse(&clipboard_text).ok();
13876
13877 let auto_indent_mode = if !clipboard_text.is_empty() {
13878 Some(AutoindentMode::Block {
13879 original_indent_columns: Vec::new(),
13880 })
13881 } else {
13882 None
13883 };
13884
13885 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13886 let snapshot = buffer.snapshot(cx);
13887
13888 let anchors = old_selections
13889 .iter()
13890 .map(|s| {
13891 let anchor = snapshot.anchor_after(s.head());
13892 s.map(|_| anchor)
13893 })
13894 .collect::<Vec<_>>();
13895
13896 let mut edits = Vec::new();
13897
13898 // When pasting text without metadata (e.g. copied from an
13899 // external editor using multiple cursors) and the number of
13900 // lines matches the number of selections, distribute one
13901 // line per cursor instead of pasting the whole text at each.
13902 let lines: Vec<&str> = clipboard_text.split('\n').collect();
13903 let distribute_lines =
13904 old_selections.len() > 1 && lines.len() == old_selections.len();
13905
13906 for (ix, selection) in old_selections.iter().enumerate() {
13907 let language = snapshot.language_at(selection.head());
13908 let range = selection.range();
13909
13910 let text_for_cursor: &str = if distribute_lines {
13911 lines[ix]
13912 } else {
13913 &clipboard_text
13914 };
13915
13916 let (edit_range, edit_text) = if let Some(language) = language
13917 && language.name() == "Markdown"
13918 {
13919 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
13920 } else {
13921 (range, Cow::Borrowed(text_for_cursor))
13922 };
13923
13924 edits.push((edit_range, edit_text));
13925 }
13926
13927 drop(snapshot);
13928 buffer.edit(edits, auto_indent_mode, cx);
13929
13930 anchors
13931 });
13932
13933 this.change_selections(Default::default(), window, cx, |s| {
13934 s.select_anchors(selection_anchors);
13935 });
13936 }
13937
13938 // 🤔 | .. | show_in_menu |
13939 // | .. | true true
13940 // | had_edit_prediction | false true
13941
13942 let trigger_in_words =
13943 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13944
13945 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13946 });
13947 }
13948
13949 pub fn diff_clipboard_with_selection(
13950 &mut self,
13951 _: &DiffClipboardWithSelection,
13952 window: &mut Window,
13953 cx: &mut Context<Self>,
13954 ) {
13955 let selections = self
13956 .selections
13957 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13958
13959 if selections.is_empty() {
13960 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13961 return;
13962 };
13963
13964 let clipboard_text = match cx.read_from_clipboard() {
13965 Some(item) => match item.entries().first() {
13966 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13967 _ => None,
13968 },
13969 None => None,
13970 };
13971
13972 let Some(clipboard_text) = clipboard_text else {
13973 log::warn!("Clipboard doesn't contain text.");
13974 return;
13975 };
13976
13977 window.dispatch_action(
13978 Box::new(DiffClipboardWithSelectionData {
13979 clipboard_text,
13980 editor: cx.entity(),
13981 }),
13982 cx,
13983 );
13984 }
13985
13986 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13987 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13988 if let Some(item) = cx.read_from_clipboard() {
13989 let entries = item.entries();
13990
13991 match entries.first() {
13992 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13993 // of all the pasted entries.
13994 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13995 .do_paste(
13996 clipboard_string.text(),
13997 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13998 true,
13999 window,
14000 cx,
14001 ),
14002 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14003 }
14004 }
14005 }
14006
14007 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14008 if self.read_only(cx) {
14009 return;
14010 }
14011
14012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14013
14014 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14015 if let Some((selections, _)) =
14016 self.selection_history.transaction(transaction_id).cloned()
14017 {
14018 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14019 s.select_anchors(selections.to_vec());
14020 });
14021 } else {
14022 log::error!(
14023 "No entry in selection_history found for undo. \
14024 This may correspond to a bug where undo does not update the selection. \
14025 If this is occurring, please add details to \
14026 https://github.com/zed-industries/zed/issues/22692"
14027 );
14028 }
14029 self.request_autoscroll(Autoscroll::fit(), cx);
14030 self.unmark_text(window, cx);
14031 self.refresh_edit_prediction(true, false, window, cx);
14032 cx.emit(EditorEvent::Edited { transaction_id });
14033 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14034 }
14035 }
14036
14037 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14038 if self.read_only(cx) {
14039 return;
14040 }
14041
14042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14043
14044 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14045 if let Some((_, Some(selections))) =
14046 self.selection_history.transaction(transaction_id).cloned()
14047 {
14048 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14049 s.select_anchors(selections.to_vec());
14050 });
14051 } else {
14052 log::error!(
14053 "No entry in selection_history found for redo. \
14054 This may correspond to a bug where undo does not update the selection. \
14055 If this is occurring, please add details to \
14056 https://github.com/zed-industries/zed/issues/22692"
14057 );
14058 }
14059 self.request_autoscroll(Autoscroll::fit(), cx);
14060 self.unmark_text(window, cx);
14061 self.refresh_edit_prediction(true, false, window, cx);
14062 cx.emit(EditorEvent::Edited { transaction_id });
14063 }
14064 }
14065
14066 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14067 self.buffer
14068 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14069 }
14070
14071 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14072 self.buffer
14073 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14074 }
14075
14076 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14078 self.change_selections(Default::default(), window, cx, |s| {
14079 s.move_with(&mut |map, selection| {
14080 let cursor = if selection.is_empty() {
14081 movement::left(map, selection.start)
14082 } else {
14083 selection.start
14084 };
14085 selection.collapse_to(cursor, SelectionGoal::None);
14086 });
14087 })
14088 }
14089
14090 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14092 self.change_selections(Default::default(), window, cx, |s| {
14093 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14094 })
14095 }
14096
14097 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14099 self.change_selections(Default::default(), window, cx, |s| {
14100 s.move_with(&mut |map, selection| {
14101 let cursor = if selection.is_empty() {
14102 movement::right(map, selection.end)
14103 } else {
14104 selection.end
14105 };
14106 selection.collapse_to(cursor, SelectionGoal::None)
14107 });
14108 })
14109 }
14110
14111 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14113 self.change_selections(Default::default(), window, cx, |s| {
14114 s.move_heads_with(&mut |map, head, _| {
14115 (movement::right(map, head), SelectionGoal::None)
14116 });
14117 });
14118 }
14119
14120 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14121 if self.take_rename(true, window, cx).is_some() {
14122 return;
14123 }
14124
14125 if self.mode.is_single_line() {
14126 cx.propagate();
14127 return;
14128 }
14129
14130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14131
14132 let text_layout_details = &self.text_layout_details(window, cx);
14133 let selection_count = self.selections.count();
14134 let first_selection = self.selections.first_anchor();
14135
14136 self.change_selections(Default::default(), window, cx, |s| {
14137 s.move_with(&mut |map, selection| {
14138 if !selection.is_empty() {
14139 selection.goal = SelectionGoal::None;
14140 }
14141 let (cursor, goal) = movement::up(
14142 map,
14143 selection.start,
14144 selection.goal,
14145 false,
14146 text_layout_details,
14147 );
14148 selection.collapse_to(cursor, goal);
14149 });
14150 });
14151
14152 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14153 {
14154 cx.propagate();
14155 }
14156 }
14157
14158 pub fn move_up_by_lines(
14159 &mut self,
14160 action: &MoveUpByLines,
14161 window: &mut Window,
14162 cx: &mut Context<Self>,
14163 ) {
14164 if self.take_rename(true, window, cx).is_some() {
14165 return;
14166 }
14167
14168 if self.mode.is_single_line() {
14169 cx.propagate();
14170 return;
14171 }
14172
14173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14174
14175 let text_layout_details = &self.text_layout_details(window, cx);
14176
14177 self.change_selections(Default::default(), window, cx, |s| {
14178 s.move_with(&mut |map, selection| {
14179 if !selection.is_empty() {
14180 selection.goal = SelectionGoal::None;
14181 }
14182 let (cursor, goal) = movement::up_by_rows(
14183 map,
14184 selection.start,
14185 action.lines,
14186 selection.goal,
14187 false,
14188 text_layout_details,
14189 );
14190 selection.collapse_to(cursor, goal);
14191 });
14192 })
14193 }
14194
14195 pub fn move_down_by_lines(
14196 &mut self,
14197 action: &MoveDownByLines,
14198 window: &mut Window,
14199 cx: &mut Context<Self>,
14200 ) {
14201 if self.take_rename(true, window, cx).is_some() {
14202 return;
14203 }
14204
14205 if self.mode.is_single_line() {
14206 cx.propagate();
14207 return;
14208 }
14209
14210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14211
14212 let text_layout_details = &self.text_layout_details(window, cx);
14213
14214 self.change_selections(Default::default(), window, cx, |s| {
14215 s.move_with(&mut |map, selection| {
14216 if !selection.is_empty() {
14217 selection.goal = SelectionGoal::None;
14218 }
14219 let (cursor, goal) = movement::down_by_rows(
14220 map,
14221 selection.start,
14222 action.lines,
14223 selection.goal,
14224 false,
14225 text_layout_details,
14226 );
14227 selection.collapse_to(cursor, goal);
14228 });
14229 })
14230 }
14231
14232 pub fn select_down_by_lines(
14233 &mut self,
14234 action: &SelectDownByLines,
14235 window: &mut Window,
14236 cx: &mut Context<Self>,
14237 ) {
14238 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14239 let text_layout_details = &self.text_layout_details(window, cx);
14240 self.change_selections(Default::default(), window, cx, |s| {
14241 s.move_heads_with(&mut |map, head, goal| {
14242 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14243 })
14244 })
14245 }
14246
14247 pub fn select_up_by_lines(
14248 &mut self,
14249 action: &SelectUpByLines,
14250 window: &mut Window,
14251 cx: &mut Context<Self>,
14252 ) {
14253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14254 let text_layout_details = &self.text_layout_details(window, cx);
14255 self.change_selections(Default::default(), window, cx, |s| {
14256 s.move_heads_with(&mut |map, head, goal| {
14257 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14258 })
14259 })
14260 }
14261
14262 pub fn select_page_up(
14263 &mut self,
14264 _: &SelectPageUp,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) {
14268 let Some(row_count) = self.visible_row_count() else {
14269 return;
14270 };
14271
14272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14273
14274 let text_layout_details = &self.text_layout_details(window, cx);
14275
14276 self.change_selections(Default::default(), window, cx, |s| {
14277 s.move_heads_with(&mut |map, head, goal| {
14278 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14279 })
14280 })
14281 }
14282
14283 pub fn move_page_up(
14284 &mut self,
14285 action: &MovePageUp,
14286 window: &mut Window,
14287 cx: &mut Context<Self>,
14288 ) {
14289 if self.take_rename(true, window, cx).is_some() {
14290 return;
14291 }
14292
14293 if self
14294 .context_menu
14295 .borrow_mut()
14296 .as_mut()
14297 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14298 .unwrap_or(false)
14299 {
14300 return;
14301 }
14302
14303 if matches!(self.mode, EditorMode::SingleLine) {
14304 cx.propagate();
14305 return;
14306 }
14307
14308 let Some(row_count) = self.visible_row_count() else {
14309 return;
14310 };
14311
14312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14313
14314 let effects = if action.center_cursor {
14315 SelectionEffects::scroll(Autoscroll::center())
14316 } else {
14317 SelectionEffects::default()
14318 };
14319
14320 let text_layout_details = &self.text_layout_details(window, cx);
14321
14322 self.change_selections(effects, window, cx, |s| {
14323 s.move_with(&mut |map, selection| {
14324 if !selection.is_empty() {
14325 selection.goal = SelectionGoal::None;
14326 }
14327 let (cursor, goal) = movement::up_by_rows(
14328 map,
14329 selection.end,
14330 row_count,
14331 selection.goal,
14332 false,
14333 text_layout_details,
14334 );
14335 selection.collapse_to(cursor, goal);
14336 });
14337 });
14338 }
14339
14340 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14341 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14342 let text_layout_details = &self.text_layout_details(window, cx);
14343 self.change_selections(Default::default(), window, cx, |s| {
14344 s.move_heads_with(&mut |map, head, goal| {
14345 movement::up(map, head, goal, false, text_layout_details)
14346 })
14347 })
14348 }
14349
14350 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14351 self.take_rename(true, window, cx);
14352
14353 if self.mode.is_single_line() {
14354 cx.propagate();
14355 return;
14356 }
14357
14358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14359
14360 let text_layout_details = &self.text_layout_details(window, cx);
14361 let selection_count = self.selections.count();
14362 let first_selection = self.selections.first_anchor();
14363
14364 self.change_selections(Default::default(), window, cx, |s| {
14365 s.move_with(&mut |map, selection| {
14366 if !selection.is_empty() {
14367 selection.goal = SelectionGoal::None;
14368 }
14369 let (cursor, goal) = movement::down(
14370 map,
14371 selection.end,
14372 selection.goal,
14373 false,
14374 text_layout_details,
14375 );
14376 selection.collapse_to(cursor, goal);
14377 });
14378 });
14379
14380 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14381 {
14382 cx.propagate();
14383 }
14384 }
14385
14386 pub fn select_page_down(
14387 &mut self,
14388 _: &SelectPageDown,
14389 window: &mut Window,
14390 cx: &mut Context<Self>,
14391 ) {
14392 let Some(row_count) = self.visible_row_count() else {
14393 return;
14394 };
14395
14396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14397
14398 let text_layout_details = &self.text_layout_details(window, cx);
14399
14400 self.change_selections(Default::default(), window, cx, |s| {
14401 s.move_heads_with(&mut |map, head, goal| {
14402 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14403 })
14404 })
14405 }
14406
14407 pub fn move_page_down(
14408 &mut self,
14409 action: &MovePageDown,
14410 window: &mut Window,
14411 cx: &mut Context<Self>,
14412 ) {
14413 if self.take_rename(true, window, cx).is_some() {
14414 return;
14415 }
14416
14417 if self
14418 .context_menu
14419 .borrow_mut()
14420 .as_mut()
14421 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14422 .unwrap_or(false)
14423 {
14424 return;
14425 }
14426
14427 if matches!(self.mode, EditorMode::SingleLine) {
14428 cx.propagate();
14429 return;
14430 }
14431
14432 let Some(row_count) = self.visible_row_count() else {
14433 return;
14434 };
14435
14436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14437
14438 let effects = if action.center_cursor {
14439 SelectionEffects::scroll(Autoscroll::center())
14440 } else {
14441 SelectionEffects::default()
14442 };
14443
14444 let text_layout_details = &self.text_layout_details(window, cx);
14445 self.change_selections(effects, window, cx, |s| {
14446 s.move_with(&mut |map, selection| {
14447 if !selection.is_empty() {
14448 selection.goal = SelectionGoal::None;
14449 }
14450 let (cursor, goal) = movement::down_by_rows(
14451 map,
14452 selection.end,
14453 row_count,
14454 selection.goal,
14455 false,
14456 text_layout_details,
14457 );
14458 selection.collapse_to(cursor, goal);
14459 });
14460 });
14461 }
14462
14463 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14465 let text_layout_details = &self.text_layout_details(window, cx);
14466 self.change_selections(Default::default(), window, cx, |s| {
14467 s.move_heads_with(&mut |map, head, goal| {
14468 movement::down(map, head, goal, false, text_layout_details)
14469 })
14470 });
14471 }
14472
14473 pub fn context_menu_first(
14474 &mut self,
14475 _: &ContextMenuFirst,
14476 window: &mut Window,
14477 cx: &mut Context<Self>,
14478 ) {
14479 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14480 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14481 }
14482 }
14483
14484 pub fn context_menu_prev(
14485 &mut self,
14486 _: &ContextMenuPrevious,
14487 window: &mut Window,
14488 cx: &mut Context<Self>,
14489 ) {
14490 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14491 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14492 }
14493 }
14494
14495 pub fn context_menu_next(
14496 &mut self,
14497 _: &ContextMenuNext,
14498 window: &mut Window,
14499 cx: &mut Context<Self>,
14500 ) {
14501 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14502 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14503 }
14504 }
14505
14506 pub fn context_menu_last(
14507 &mut self,
14508 _: &ContextMenuLast,
14509 window: &mut Window,
14510 cx: &mut Context<Self>,
14511 ) {
14512 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14513 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14514 }
14515 }
14516
14517 pub fn signature_help_prev(
14518 &mut self,
14519 _: &SignatureHelpPrevious,
14520 _: &mut Window,
14521 cx: &mut Context<Self>,
14522 ) {
14523 if let Some(popover) = self.signature_help_state.popover_mut() {
14524 if popover.current_signature == 0 {
14525 popover.current_signature = popover.signatures.len() - 1;
14526 } else {
14527 popover.current_signature -= 1;
14528 }
14529 cx.notify();
14530 }
14531 }
14532
14533 pub fn signature_help_next(
14534 &mut self,
14535 _: &SignatureHelpNext,
14536 _: &mut Window,
14537 cx: &mut Context<Self>,
14538 ) {
14539 if let Some(popover) = self.signature_help_state.popover_mut() {
14540 if popover.current_signature + 1 == popover.signatures.len() {
14541 popover.current_signature = 0;
14542 } else {
14543 popover.current_signature += 1;
14544 }
14545 cx.notify();
14546 }
14547 }
14548
14549 pub fn move_to_previous_word_start(
14550 &mut self,
14551 _: &MoveToPreviousWordStart,
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_word_start(map, head),
14560 SelectionGoal::None,
14561 )
14562 });
14563 })
14564 }
14565
14566 pub fn move_to_previous_subword_start(
14567 &mut self,
14568 _: &MoveToPreviousSubwordStart,
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_cursors_with(&mut |map, head, _| {
14575 (
14576 movement::previous_subword_start(map, head),
14577 SelectionGoal::None,
14578 )
14579 });
14580 })
14581 }
14582
14583 pub fn select_to_previous_word_start(
14584 &mut self,
14585 _: &SelectToPreviousWordStart,
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_word_start(map, head),
14594 SelectionGoal::None,
14595 )
14596 });
14597 })
14598 }
14599
14600 pub fn select_to_previous_subword_start(
14601 &mut self,
14602 _: &SelectToPreviousSubwordStart,
14603 window: &mut Window,
14604 cx: &mut Context<Self>,
14605 ) {
14606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14607 self.change_selections(Default::default(), window, cx, |s| {
14608 s.move_heads_with(&mut |map, head, _| {
14609 (
14610 movement::previous_subword_start(map, head),
14611 SelectionGoal::None,
14612 )
14613 });
14614 })
14615 }
14616
14617 pub fn delete_to_previous_word_start(
14618 &mut self,
14619 action: &DeleteToPreviousWordStart,
14620 window: &mut Window,
14621 cx: &mut Context<Self>,
14622 ) {
14623 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14624 self.transact(window, cx, |this, window, cx| {
14625 this.select_autoclose_pair(window, cx);
14626 this.change_selections(Default::default(), window, cx, |s| {
14627 s.move_with(&mut |map, selection| {
14628 if selection.is_empty() {
14629 let mut cursor = if action.ignore_newlines {
14630 movement::previous_word_start(map, selection.head())
14631 } else {
14632 movement::previous_word_start_or_newline(map, selection.head())
14633 };
14634 cursor = movement::adjust_greedy_deletion(
14635 map,
14636 selection.head(),
14637 cursor,
14638 action.ignore_brackets,
14639 );
14640 selection.set_head(cursor, SelectionGoal::None);
14641 }
14642 });
14643 });
14644 this.insert("", window, cx);
14645 });
14646 }
14647
14648 pub fn delete_to_previous_subword_start(
14649 &mut self,
14650 action: &DeleteToPreviousSubwordStart,
14651 window: &mut Window,
14652 cx: &mut Context<Self>,
14653 ) {
14654 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14655 self.transact(window, cx, |this, window, cx| {
14656 this.select_autoclose_pair(window, cx);
14657 this.change_selections(Default::default(), window, cx, |s| {
14658 s.move_with(&mut |map, selection| {
14659 if selection.is_empty() {
14660 let mut cursor = if action.ignore_newlines {
14661 movement::previous_subword_start(map, selection.head())
14662 } else {
14663 movement::previous_subword_start_or_newline(map, selection.head())
14664 };
14665 cursor = movement::adjust_greedy_deletion(
14666 map,
14667 selection.head(),
14668 cursor,
14669 action.ignore_brackets,
14670 );
14671 selection.set_head(cursor, SelectionGoal::None);
14672 }
14673 });
14674 });
14675 this.insert("", window, cx);
14676 });
14677 }
14678
14679 pub fn move_to_next_word_end(
14680 &mut self,
14681 _: &MoveToNextWordEnd,
14682 window: &mut Window,
14683 cx: &mut Context<Self>,
14684 ) {
14685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14686 self.change_selections(Default::default(), window, cx, |s| {
14687 s.move_cursors_with(&mut |map, head, _| {
14688 (movement::next_word_end(map, head), SelectionGoal::None)
14689 });
14690 })
14691 }
14692
14693 pub fn move_to_next_subword_end(
14694 &mut self,
14695 _: &MoveToNextSubwordEnd,
14696 window: &mut Window,
14697 cx: &mut Context<Self>,
14698 ) {
14699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14700 self.change_selections(Default::default(), window, cx, |s| {
14701 s.move_cursors_with(&mut |map, head, _| {
14702 (movement::next_subword_end(map, head), SelectionGoal::None)
14703 });
14704 })
14705 }
14706
14707 pub fn select_to_next_word_end(
14708 &mut self,
14709 _: &SelectToNextWordEnd,
14710 window: &mut Window,
14711 cx: &mut Context<Self>,
14712 ) {
14713 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14714 self.change_selections(Default::default(), window, cx, |s| {
14715 s.move_heads_with(&mut |map, head, _| {
14716 (movement::next_word_end(map, head), SelectionGoal::None)
14717 });
14718 })
14719 }
14720
14721 pub fn select_to_next_subword_end(
14722 &mut self,
14723 _: &SelectToNextSubwordEnd,
14724 window: &mut Window,
14725 cx: &mut Context<Self>,
14726 ) {
14727 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14728 self.change_selections(Default::default(), window, cx, |s| {
14729 s.move_heads_with(&mut |map, head, _| {
14730 (movement::next_subword_end(map, head), SelectionGoal::None)
14731 });
14732 })
14733 }
14734
14735 pub fn delete_to_next_word_end(
14736 &mut self,
14737 action: &DeleteToNextWordEnd,
14738 window: &mut Window,
14739 cx: &mut Context<Self>,
14740 ) {
14741 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14742 self.transact(window, cx, |this, window, cx| {
14743 this.change_selections(Default::default(), window, cx, |s| {
14744 s.move_with(&mut |map, selection| {
14745 if selection.is_empty() {
14746 let mut cursor = if action.ignore_newlines {
14747 movement::next_word_end(map, selection.head())
14748 } else {
14749 movement::next_word_end_or_newline(map, selection.head())
14750 };
14751 cursor = movement::adjust_greedy_deletion(
14752 map,
14753 selection.head(),
14754 cursor,
14755 action.ignore_brackets,
14756 );
14757 selection.set_head(cursor, SelectionGoal::None);
14758 }
14759 });
14760 });
14761 this.insert("", window, cx);
14762 });
14763 }
14764
14765 pub fn delete_to_next_subword_end(
14766 &mut self,
14767 action: &DeleteToNextSubwordEnd,
14768 window: &mut Window,
14769 cx: &mut Context<Self>,
14770 ) {
14771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14772 self.transact(window, cx, |this, window, cx| {
14773 this.change_selections(Default::default(), window, cx, |s| {
14774 s.move_with(&mut |map, selection| {
14775 if selection.is_empty() {
14776 let mut cursor = if action.ignore_newlines {
14777 movement::next_subword_end(map, selection.head())
14778 } else {
14779 movement::next_subword_end_or_newline(map, selection.head())
14780 };
14781 cursor = movement::adjust_greedy_deletion(
14782 map,
14783 selection.head(),
14784 cursor,
14785 action.ignore_brackets,
14786 );
14787 selection.set_head(cursor, SelectionGoal::None);
14788 }
14789 });
14790 });
14791 this.insert("", window, cx);
14792 });
14793 }
14794
14795 pub fn move_to_beginning_of_line(
14796 &mut self,
14797 action: &MoveToBeginningOfLine,
14798 window: &mut Window,
14799 cx: &mut Context<Self>,
14800 ) {
14801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14802 self.change_selections(Default::default(), window, cx, |s| {
14803 s.move_cursors_with(&mut |map, head, _| {
14804 (
14805 movement::indented_line_beginning(
14806 map,
14807 head,
14808 action.stop_at_soft_wraps,
14809 action.stop_at_indent,
14810 ),
14811 SelectionGoal::None,
14812 )
14813 });
14814 })
14815 }
14816
14817 pub fn select_to_beginning_of_line(
14818 &mut self,
14819 action: &SelectToBeginningOfLine,
14820 window: &mut Window,
14821 cx: &mut Context<Self>,
14822 ) {
14823 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14824 self.change_selections(Default::default(), window, cx, |s| {
14825 s.move_heads_with(&mut |map, head, _| {
14826 (
14827 movement::indented_line_beginning(
14828 map,
14829 head,
14830 action.stop_at_soft_wraps,
14831 action.stop_at_indent,
14832 ),
14833 SelectionGoal::None,
14834 )
14835 });
14836 });
14837 }
14838
14839 pub fn delete_to_beginning_of_line(
14840 &mut self,
14841 action: &DeleteToBeginningOfLine,
14842 window: &mut Window,
14843 cx: &mut Context<Self>,
14844 ) {
14845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14846 self.transact(window, cx, |this, window, cx| {
14847 this.change_selections(Default::default(), window, cx, |s| {
14848 s.move_with(&mut |_, selection| {
14849 selection.reversed = true;
14850 });
14851 });
14852
14853 this.select_to_beginning_of_line(
14854 &SelectToBeginningOfLine {
14855 stop_at_soft_wraps: false,
14856 stop_at_indent: action.stop_at_indent,
14857 },
14858 window,
14859 cx,
14860 );
14861 this.backspace(&Backspace, window, cx);
14862 });
14863 }
14864
14865 pub fn move_to_end_of_line(
14866 &mut self,
14867 action: &MoveToEndOfLine,
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_cursors_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 select_to_end_of_line(
14883 &mut self,
14884 action: &SelectToEndOfLine,
14885 window: &mut Window,
14886 cx: &mut Context<Self>,
14887 ) {
14888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14889 self.change_selections(Default::default(), window, cx, |s| {
14890 s.move_heads_with(&mut |map, head, _| {
14891 (
14892 movement::line_end(map, head, action.stop_at_soft_wraps),
14893 SelectionGoal::None,
14894 )
14895 });
14896 })
14897 }
14898
14899 pub fn delete_to_end_of_line(
14900 &mut self,
14901 _: &DeleteToEndOfLine,
14902 window: &mut Window,
14903 cx: &mut Context<Self>,
14904 ) {
14905 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14906 self.transact(window, cx, |this, window, cx| {
14907 this.select_to_end_of_line(
14908 &SelectToEndOfLine {
14909 stop_at_soft_wraps: false,
14910 },
14911 window,
14912 cx,
14913 );
14914 this.delete(&Delete, window, cx);
14915 });
14916 }
14917
14918 pub fn cut_to_end_of_line(
14919 &mut self,
14920 action: &CutToEndOfLine,
14921 window: &mut Window,
14922 cx: &mut Context<Self>,
14923 ) {
14924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14925 self.transact(window, cx, |this, window, cx| {
14926 this.select_to_end_of_line(
14927 &SelectToEndOfLine {
14928 stop_at_soft_wraps: false,
14929 },
14930 window,
14931 cx,
14932 );
14933 if !action.stop_at_newlines {
14934 this.change_selections(Default::default(), window, cx, |s| {
14935 s.move_with(&mut |_, sel| {
14936 if sel.is_empty() {
14937 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14938 }
14939 });
14940 });
14941 }
14942 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14943 let item = this.cut_common(false, window, cx);
14944 cx.write_to_clipboard(item);
14945 });
14946 }
14947
14948 pub fn move_to_start_of_paragraph(
14949 &mut self,
14950 _: &MoveToStartOfParagraph,
14951 window: &mut Window,
14952 cx: &mut Context<Self>,
14953 ) {
14954 if matches!(self.mode, EditorMode::SingleLine) {
14955 cx.propagate();
14956 return;
14957 }
14958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14959 self.change_selections(Default::default(), window, cx, |s| {
14960 s.move_with(&mut |map, selection| {
14961 selection.collapse_to(
14962 movement::start_of_paragraph(map, selection.head(), 1),
14963 SelectionGoal::None,
14964 )
14965 });
14966 })
14967 }
14968
14969 pub fn move_to_end_of_paragraph(
14970 &mut self,
14971 _: &MoveToEndOfParagraph,
14972 window: &mut Window,
14973 cx: &mut Context<Self>,
14974 ) {
14975 if matches!(self.mode, EditorMode::SingleLine) {
14976 cx.propagate();
14977 return;
14978 }
14979 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14980 self.change_selections(Default::default(), window, cx, |s| {
14981 s.move_with(&mut |map, selection| {
14982 selection.collapse_to(
14983 movement::end_of_paragraph(map, selection.head(), 1),
14984 SelectionGoal::None,
14985 )
14986 });
14987 })
14988 }
14989
14990 pub fn select_to_start_of_paragraph(
14991 &mut self,
14992 _: &SelectToStartOfParagraph,
14993 window: &mut Window,
14994 cx: &mut Context<Self>,
14995 ) {
14996 if matches!(self.mode, EditorMode::SingleLine) {
14997 cx.propagate();
14998 return;
14999 }
15000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15001 self.change_selections(Default::default(), window, cx, |s| {
15002 s.move_heads_with(&mut |map, head, _| {
15003 (
15004 movement::start_of_paragraph(map, head, 1),
15005 SelectionGoal::None,
15006 )
15007 });
15008 })
15009 }
15010
15011 pub fn select_to_end_of_paragraph(
15012 &mut self,
15013 _: &SelectToEndOfParagraph,
15014 window: &mut Window,
15015 cx: &mut Context<Self>,
15016 ) {
15017 if matches!(self.mode, EditorMode::SingleLine) {
15018 cx.propagate();
15019 return;
15020 }
15021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15022 self.change_selections(Default::default(), window, cx, |s| {
15023 s.move_heads_with(&mut |map, head, _| {
15024 (
15025 movement::end_of_paragraph(map, head, 1),
15026 SelectionGoal::None,
15027 )
15028 });
15029 })
15030 }
15031
15032 pub fn move_to_start_of_excerpt(
15033 &mut self,
15034 _: &MoveToStartOfExcerpt,
15035 window: &mut Window,
15036 cx: &mut Context<Self>,
15037 ) {
15038 if matches!(self.mode, EditorMode::SingleLine) {
15039 cx.propagate();
15040 return;
15041 }
15042 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15043 self.change_selections(Default::default(), window, cx, |s| {
15044 s.move_with(&mut |map, selection| {
15045 selection.collapse_to(
15046 movement::start_of_excerpt(
15047 map,
15048 selection.head(),
15049 workspace::searchable::Direction::Prev,
15050 ),
15051 SelectionGoal::None,
15052 )
15053 });
15054 })
15055 }
15056
15057 pub fn move_to_start_of_next_excerpt(
15058 &mut self,
15059 _: &MoveToStartOfNextExcerpt,
15060 window: &mut Window,
15061 cx: &mut Context<Self>,
15062 ) {
15063 if matches!(self.mode, EditorMode::SingleLine) {
15064 cx.propagate();
15065 return;
15066 }
15067
15068 self.change_selections(Default::default(), window, cx, |s| {
15069 s.move_with(&mut |map, selection| {
15070 selection.collapse_to(
15071 movement::start_of_excerpt(
15072 map,
15073 selection.head(),
15074 workspace::searchable::Direction::Next,
15075 ),
15076 SelectionGoal::None,
15077 )
15078 });
15079 })
15080 }
15081
15082 pub fn move_to_end_of_excerpt(
15083 &mut self,
15084 _: &MoveToEndOfExcerpt,
15085 window: &mut Window,
15086 cx: &mut Context<Self>,
15087 ) {
15088 if matches!(self.mode, EditorMode::SingleLine) {
15089 cx.propagate();
15090 return;
15091 }
15092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15093 self.change_selections(Default::default(), window, cx, |s| {
15094 s.move_with(&mut |map, selection| {
15095 selection.collapse_to(
15096 movement::end_of_excerpt(
15097 map,
15098 selection.head(),
15099 workspace::searchable::Direction::Next,
15100 ),
15101 SelectionGoal::None,
15102 )
15103 });
15104 })
15105 }
15106
15107 pub fn move_to_end_of_previous_excerpt(
15108 &mut self,
15109 _: &MoveToEndOfPreviousExcerpt,
15110 window: &mut Window,
15111 cx: &mut Context<Self>,
15112 ) {
15113 if matches!(self.mode, EditorMode::SingleLine) {
15114 cx.propagate();
15115 return;
15116 }
15117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15118 self.change_selections(Default::default(), window, cx, |s| {
15119 s.move_with(&mut |map, selection| {
15120 selection.collapse_to(
15121 movement::end_of_excerpt(
15122 map,
15123 selection.head(),
15124 workspace::searchable::Direction::Prev,
15125 ),
15126 SelectionGoal::None,
15127 )
15128 });
15129 })
15130 }
15131
15132 pub fn select_to_start_of_excerpt(
15133 &mut self,
15134 _: &SelectToStartOfExcerpt,
15135 window: &mut Window,
15136 cx: &mut Context<Self>,
15137 ) {
15138 if matches!(self.mode, EditorMode::SingleLine) {
15139 cx.propagate();
15140 return;
15141 }
15142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15143 self.change_selections(Default::default(), window, cx, |s| {
15144 s.move_heads_with(&mut |map, head, _| {
15145 (
15146 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15147 SelectionGoal::None,
15148 )
15149 });
15150 })
15151 }
15152
15153 pub fn select_to_start_of_next_excerpt(
15154 &mut self,
15155 _: &SelectToStartOfNextExcerpt,
15156 window: &mut Window,
15157 cx: &mut Context<Self>,
15158 ) {
15159 if matches!(self.mode, EditorMode::SingleLine) {
15160 cx.propagate();
15161 return;
15162 }
15163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15164 self.change_selections(Default::default(), window, cx, |s| {
15165 s.move_heads_with(&mut |map, head, _| {
15166 (
15167 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15168 SelectionGoal::None,
15169 )
15170 });
15171 })
15172 }
15173
15174 pub fn select_to_end_of_excerpt(
15175 &mut self,
15176 _: &SelectToEndOfExcerpt,
15177 window: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) {
15180 if matches!(self.mode, EditorMode::SingleLine) {
15181 cx.propagate();
15182 return;
15183 }
15184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15185 self.change_selections(Default::default(), window, cx, |s| {
15186 s.move_heads_with(&mut |map, head, _| {
15187 (
15188 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15189 SelectionGoal::None,
15190 )
15191 });
15192 })
15193 }
15194
15195 pub fn select_to_end_of_previous_excerpt(
15196 &mut self,
15197 _: &SelectToEndOfPreviousExcerpt,
15198 window: &mut Window,
15199 cx: &mut Context<Self>,
15200 ) {
15201 if matches!(self.mode, EditorMode::SingleLine) {
15202 cx.propagate();
15203 return;
15204 }
15205 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15206 self.change_selections(Default::default(), window, cx, |s| {
15207 s.move_heads_with(&mut |map, head, _| {
15208 (
15209 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15210 SelectionGoal::None,
15211 )
15212 });
15213 })
15214 }
15215
15216 pub fn move_to_beginning(
15217 &mut self,
15218 _: &MoveToBeginning,
15219 window: &mut Window,
15220 cx: &mut Context<Self>,
15221 ) {
15222 if matches!(self.mode, EditorMode::SingleLine) {
15223 cx.propagate();
15224 return;
15225 }
15226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15227 self.change_selections(Default::default(), window, cx, |s| {
15228 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15229 });
15230 }
15231
15232 pub fn select_to_beginning(
15233 &mut self,
15234 _: &SelectToBeginning,
15235 window: &mut Window,
15236 cx: &mut Context<Self>,
15237 ) {
15238 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15239 selection.set_head(Point::zero(), SelectionGoal::None);
15240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15241 self.change_selections(Default::default(), window, cx, |s| {
15242 s.select(vec![selection]);
15243 });
15244 }
15245
15246 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15247 if matches!(self.mode, EditorMode::SingleLine) {
15248 cx.propagate();
15249 return;
15250 }
15251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15252 let cursor = self.buffer.read(cx).read(cx).len();
15253 self.change_selections(Default::default(), window, cx, |s| {
15254 s.select_ranges(vec![cursor..cursor])
15255 });
15256 }
15257
15258 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15259 self.nav_history = nav_history;
15260 }
15261
15262 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15263 self.nav_history.as_ref()
15264 }
15265
15266 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15267 self.push_to_nav_history(
15268 self.selections.newest_anchor().head(),
15269 None,
15270 false,
15271 true,
15272 cx,
15273 );
15274 }
15275
15276 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15277 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15278 let buffer = self.buffer.read(cx).read(cx);
15279 let cursor_position = cursor_anchor.to_point(&buffer);
15280 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15281 let scroll_top_row = scroll_anchor.top_row(&buffer);
15282 drop(buffer);
15283
15284 NavigationData {
15285 cursor_anchor,
15286 cursor_position,
15287 scroll_anchor,
15288 scroll_top_row,
15289 }
15290 }
15291
15292 fn navigation_entry(
15293 &self,
15294 cursor_anchor: Anchor,
15295 cx: &mut Context<Self>,
15296 ) -> Option<NavigationEntry> {
15297 let Some(history) = self.nav_history.clone() else {
15298 return None;
15299 };
15300 let data = self.navigation_data(cursor_anchor, cx);
15301 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15302 }
15303
15304 fn push_to_nav_history(
15305 &mut self,
15306 cursor_anchor: Anchor,
15307 new_position: Option<Point>,
15308 is_deactivate: bool,
15309 always: bool,
15310 cx: &mut Context<Self>,
15311 ) {
15312 let data = self.navigation_data(cursor_anchor, cx);
15313 if let Some(nav_history) = self.nav_history.as_mut() {
15314 if let Some(new_position) = new_position {
15315 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15316 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15317 return;
15318 }
15319 }
15320
15321 nav_history.push(Some(data), cx);
15322 cx.emit(EditorEvent::PushedToNavHistory {
15323 anchor: cursor_anchor,
15324 is_deactivate,
15325 })
15326 }
15327 }
15328
15329 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15331 let buffer = self.buffer.read(cx).snapshot(cx);
15332 let mut selection = self
15333 .selections
15334 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15335 selection.set_head(buffer.len(), SelectionGoal::None);
15336 self.change_selections(Default::default(), window, cx, |s| {
15337 s.select(vec![selection]);
15338 });
15339 }
15340
15341 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15343 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15344 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
15345 });
15346 }
15347
15348 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15351 let mut selections = self.selections.all::<Point>(&display_map);
15352 let max_point = display_map.buffer_snapshot().max_point();
15353 for selection in &mut selections {
15354 let rows = selection.spanned_rows(true, &display_map);
15355 selection.start = Point::new(rows.start.0, 0);
15356 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15357 selection.reversed = false;
15358 }
15359 self.change_selections(Default::default(), window, cx, |s| {
15360 s.select(selections);
15361 });
15362 }
15363
15364 pub fn split_selection_into_lines(
15365 &mut self,
15366 action: &SplitSelectionIntoLines,
15367 window: &mut Window,
15368 cx: &mut Context<Self>,
15369 ) {
15370 let selections = self
15371 .selections
15372 .all::<Point>(&self.display_snapshot(cx))
15373 .into_iter()
15374 .map(|selection| selection.start..selection.end)
15375 .collect::<Vec<_>>();
15376 self.unfold_ranges(&selections, true, true, cx);
15377
15378 let mut new_selection_ranges = Vec::new();
15379 {
15380 let buffer = self.buffer.read(cx).read(cx);
15381 for selection in selections {
15382 for row in selection.start.row..selection.end.row {
15383 let line_start = Point::new(row, 0);
15384 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15385
15386 if action.keep_selections {
15387 // Keep the selection range for each line
15388 let selection_start = if row == selection.start.row {
15389 selection.start
15390 } else {
15391 line_start
15392 };
15393 new_selection_ranges.push(selection_start..line_end);
15394 } else {
15395 // Collapse to cursor at end of line
15396 new_selection_ranges.push(line_end..line_end);
15397 }
15398 }
15399
15400 let is_multiline_selection = selection.start.row != selection.end.row;
15401 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15402 // so this action feels more ergonomic when paired with other selection operations
15403 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15404 if !should_skip_last {
15405 if action.keep_selections {
15406 if is_multiline_selection {
15407 let line_start = Point::new(selection.end.row, 0);
15408 new_selection_ranges.push(line_start..selection.end);
15409 } else {
15410 new_selection_ranges.push(selection.start..selection.end);
15411 }
15412 } else {
15413 new_selection_ranges.push(selection.end..selection.end);
15414 }
15415 }
15416 }
15417 }
15418 self.change_selections(Default::default(), window, cx, |s| {
15419 s.select_ranges(new_selection_ranges);
15420 });
15421 }
15422
15423 pub fn add_selection_above(
15424 &mut self,
15425 action: &AddSelectionAbove,
15426 window: &mut Window,
15427 cx: &mut Context<Self>,
15428 ) {
15429 self.add_selection(true, action.skip_soft_wrap, window, cx);
15430 }
15431
15432 pub fn add_selection_below(
15433 &mut self,
15434 action: &AddSelectionBelow,
15435 window: &mut Window,
15436 cx: &mut Context<Self>,
15437 ) {
15438 self.add_selection(false, action.skip_soft_wrap, window, cx);
15439 }
15440
15441 fn add_selection(
15442 &mut self,
15443 above: bool,
15444 skip_soft_wrap: bool,
15445 window: &mut Window,
15446 cx: &mut Context<Self>,
15447 ) {
15448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15449
15450 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15451 let all_selections = self.selections.all::<Point>(&display_map);
15452 let text_layout_details = self.text_layout_details(window, cx);
15453
15454 let (mut columnar_selections, new_selections_to_columnarize) = {
15455 if let Some(state) = self.add_selections_state.as_ref() {
15456 let columnar_selection_ids: HashSet<_> = state
15457 .groups
15458 .iter()
15459 .flat_map(|group| group.stack.iter())
15460 .copied()
15461 .collect();
15462
15463 all_selections
15464 .into_iter()
15465 .partition(|s| columnar_selection_ids.contains(&s.id))
15466 } else {
15467 (Vec::new(), all_selections)
15468 }
15469 };
15470
15471 let mut state = self
15472 .add_selections_state
15473 .take()
15474 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15475
15476 for selection in new_selections_to_columnarize {
15477 let range = selection.display_range(&display_map).sorted();
15478 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15479 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15480 let positions = start_x.min(end_x)..start_x.max(end_x);
15481 let mut stack = Vec::new();
15482 for row in range.start.row().0..=range.end.row().0 {
15483 if let Some(selection) = self.selections.build_columnar_selection(
15484 &display_map,
15485 DisplayRow(row),
15486 &positions,
15487 selection.reversed,
15488 &text_layout_details,
15489 ) {
15490 stack.push(selection.id);
15491 columnar_selections.push(selection);
15492 }
15493 }
15494 if !stack.is_empty() {
15495 if above {
15496 stack.reverse();
15497 }
15498 state.groups.push(AddSelectionsGroup { above, stack });
15499 }
15500 }
15501
15502 let mut final_selections = Vec::new();
15503 let end_row = if above {
15504 DisplayRow(0)
15505 } else {
15506 display_map.max_point().row()
15507 };
15508
15509 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15510 // positions to place new selections, so we need to keep track of the
15511 // column range of the oldest selection in each group, because
15512 // intermediate selections may have been clamped to shorter lines.
15513 // selections may have been clamped to shorter lines.
15514 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15515 let mut map = HashMap::default();
15516 for group in state.groups.iter() {
15517 if let Some(oldest_id) = group.stack.first() {
15518 if let Some(oldest_selection) =
15519 columnar_selections.iter().find(|s| s.id == *oldest_id)
15520 {
15521 let start_col = oldest_selection.start.column;
15522 let end_col = oldest_selection.end.column;
15523 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15524 for id in &group.stack {
15525 map.insert(*id, goal_columns.clone());
15526 }
15527 }
15528 }
15529 }
15530 map
15531 } else {
15532 HashMap::default()
15533 };
15534
15535 let mut last_added_item_per_group = HashMap::default();
15536 for group in state.groups.iter_mut() {
15537 if let Some(last_id) = group.stack.last() {
15538 last_added_item_per_group.insert(*last_id, group);
15539 }
15540 }
15541
15542 for selection in columnar_selections {
15543 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15544 if above == group.above {
15545 let range = selection.display_range(&display_map).sorted();
15546 debug_assert_eq!(range.start.row(), range.end.row());
15547 let row = range.start.row();
15548 let positions =
15549 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15550 Pixels::from(start)..Pixels::from(end)
15551 } else {
15552 let start_x =
15553 display_map.x_for_display_point(range.start, &text_layout_details);
15554 let end_x =
15555 display_map.x_for_display_point(range.end, &text_layout_details);
15556 start_x.min(end_x)..start_x.max(end_x)
15557 };
15558
15559 let maybe_new_selection = if skip_soft_wrap {
15560 let goal_columns = goal_columns_by_selection_id
15561 .remove(&selection.id)
15562 .unwrap_or_else(|| {
15563 let start_col = selection.start.column;
15564 let end_col = selection.end.column;
15565 start_col.min(end_col)..start_col.max(end_col)
15566 });
15567 self.selections.find_next_columnar_selection_by_buffer_row(
15568 &display_map,
15569 row,
15570 end_row,
15571 above,
15572 &goal_columns,
15573 selection.reversed,
15574 &text_layout_details,
15575 )
15576 } else {
15577 self.selections.find_next_columnar_selection_by_display_row(
15578 &display_map,
15579 row,
15580 end_row,
15581 above,
15582 &positions,
15583 selection.reversed,
15584 &text_layout_details,
15585 )
15586 };
15587
15588 if let Some(new_selection) = maybe_new_selection {
15589 group.stack.push(new_selection.id);
15590 if above {
15591 final_selections.push(new_selection);
15592 final_selections.push(selection);
15593 } else {
15594 final_selections.push(selection);
15595 final_selections.push(new_selection);
15596 }
15597 } else {
15598 final_selections.push(selection);
15599 }
15600 } else {
15601 group.stack.pop();
15602 }
15603 } else {
15604 final_selections.push(selection);
15605 }
15606 }
15607
15608 self.change_selections(Default::default(), window, cx, |s| {
15609 s.select(final_selections);
15610 });
15611
15612 let final_selection_ids: HashSet<_> = self
15613 .selections
15614 .all::<Point>(&display_map)
15615 .iter()
15616 .map(|s| s.id)
15617 .collect();
15618 state.groups.retain_mut(|group| {
15619 // selections might get merged above so we remove invalid items from stacks
15620 group.stack.retain(|id| final_selection_ids.contains(id));
15621
15622 // single selection in stack can be treated as initial state
15623 group.stack.len() > 1
15624 });
15625
15626 if !state.groups.is_empty() {
15627 self.add_selections_state = Some(state);
15628 }
15629 }
15630
15631 pub fn insert_snippet_at_selections(
15632 &mut self,
15633 action: &InsertSnippet,
15634 window: &mut Window,
15635 cx: &mut Context<Self>,
15636 ) {
15637 self.try_insert_snippet_at_selections(action, window, cx)
15638 .log_err();
15639 }
15640
15641 fn try_insert_snippet_at_selections(
15642 &mut self,
15643 action: &InsertSnippet,
15644 window: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) -> Result<()> {
15647 let insertion_ranges = self
15648 .selections
15649 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15650 .into_iter()
15651 .map(|selection| selection.range())
15652 .collect_vec();
15653
15654 let snippet = if let Some(snippet_body) = &action.snippet {
15655 if action.language.is_none() && action.name.is_none() {
15656 Snippet::parse(snippet_body)?
15657 } else {
15658 bail!("`snippet` is mutually exclusive with `language` and `name`")
15659 }
15660 } else if let Some(name) = &action.name {
15661 let project = self.project().context("no project")?;
15662 let snippet_store = project.read(cx).snippets().read(cx);
15663 let snippet = snippet_store
15664 .snippets_for(action.language.clone(), cx)
15665 .into_iter()
15666 .find(|snippet| snippet.name == *name)
15667 .context("snippet not found")?;
15668 Snippet::parse(&snippet.body)?
15669 } else {
15670 // todo(andrew): open modal to select snippet
15671 bail!("`name` or `snippet` is required")
15672 };
15673
15674 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15675 }
15676
15677 fn select_match_ranges(
15678 &mut self,
15679 range: Range<MultiBufferOffset>,
15680 reversed: bool,
15681 replace_newest: bool,
15682 auto_scroll: Option<Autoscroll>,
15683 window: &mut Window,
15684 cx: &mut Context<Editor>,
15685 ) {
15686 self.unfold_ranges(
15687 std::slice::from_ref(&range),
15688 false,
15689 auto_scroll.is_some(),
15690 cx,
15691 );
15692 let effects = if let Some(scroll) = auto_scroll {
15693 SelectionEffects::scroll(scroll)
15694 } else {
15695 SelectionEffects::no_scroll()
15696 };
15697 self.change_selections(effects, window, cx, |s| {
15698 if replace_newest {
15699 s.delete(s.newest_anchor().id);
15700 }
15701 if reversed {
15702 s.insert_range(range.end..range.start);
15703 } else {
15704 s.insert_range(range);
15705 }
15706 });
15707 }
15708
15709 pub fn select_next_match_internal(
15710 &mut self,
15711 display_map: &DisplaySnapshot,
15712 replace_newest: bool,
15713 autoscroll: Option<Autoscroll>,
15714 window: &mut Window,
15715 cx: &mut Context<Self>,
15716 ) -> Result<()> {
15717 let buffer = display_map.buffer_snapshot();
15718 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15719 if let Some(mut select_next_state) = self.select_next_state.take() {
15720 let query = &select_next_state.query;
15721 if !select_next_state.done {
15722 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15723 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15724 let mut next_selected_range = None;
15725
15726 let bytes_after_last_selection =
15727 buffer.bytes_in_range(last_selection.end..buffer.len());
15728 let bytes_before_first_selection =
15729 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15730 let query_matches = query
15731 .stream_find_iter(bytes_after_last_selection)
15732 .map(|result| (last_selection.end, result))
15733 .chain(
15734 query
15735 .stream_find_iter(bytes_before_first_selection)
15736 .map(|result| (MultiBufferOffset(0), result)),
15737 );
15738
15739 for (start_offset, query_match) in query_matches {
15740 let query_match = query_match.unwrap(); // can only fail due to I/O
15741 let offset_range =
15742 start_offset + query_match.start()..start_offset + query_match.end();
15743
15744 if !select_next_state.wordwise
15745 || (!buffer.is_inside_word(offset_range.start, None)
15746 && !buffer.is_inside_word(offset_range.end, None))
15747 {
15748 let idx = selections
15749 .partition_point(|selection| selection.end <= offset_range.start);
15750 let overlaps = selections
15751 .get(idx)
15752 .map_or(false, |selection| selection.start < offset_range.end);
15753
15754 if !overlaps {
15755 next_selected_range = Some(offset_range);
15756 break;
15757 }
15758 }
15759 }
15760
15761 if let Some(next_selected_range) = next_selected_range {
15762 self.select_match_ranges(
15763 next_selected_range,
15764 last_selection.reversed,
15765 replace_newest,
15766 autoscroll,
15767 window,
15768 cx,
15769 );
15770 } else {
15771 select_next_state.done = true;
15772 }
15773 }
15774
15775 self.select_next_state = Some(select_next_state);
15776 } else {
15777 let mut only_carets = true;
15778 let mut same_text_selected = true;
15779 let mut selected_text = None;
15780
15781 let mut selections_iter = selections.iter().peekable();
15782 while let Some(selection) = selections_iter.next() {
15783 if selection.start != selection.end {
15784 only_carets = false;
15785 }
15786
15787 if same_text_selected {
15788 if selected_text.is_none() {
15789 selected_text =
15790 Some(buffer.text_for_range(selection.range()).collect::<String>());
15791 }
15792
15793 if let Some(next_selection) = selections_iter.peek() {
15794 if next_selection.len() == selection.len() {
15795 let next_selected_text = buffer
15796 .text_for_range(next_selection.range())
15797 .collect::<String>();
15798 if Some(next_selected_text) != selected_text {
15799 same_text_selected = false;
15800 selected_text = None;
15801 }
15802 } else {
15803 same_text_selected = false;
15804 selected_text = None;
15805 }
15806 }
15807 }
15808 }
15809
15810 if only_carets {
15811 for selection in &mut selections {
15812 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15813 selection.start = word_range.start;
15814 selection.end = word_range.end;
15815 selection.goal = SelectionGoal::None;
15816 selection.reversed = false;
15817 self.select_match_ranges(
15818 selection.start..selection.end,
15819 selection.reversed,
15820 replace_newest,
15821 autoscroll,
15822 window,
15823 cx,
15824 );
15825 }
15826
15827 if selections.len() == 1 {
15828 let selection = selections
15829 .last()
15830 .expect("ensured that there's only one selection");
15831 let query = buffer
15832 .text_for_range(selection.start..selection.end)
15833 .collect::<String>();
15834 let is_empty = query.is_empty();
15835 let select_state = SelectNextState {
15836 query: self.build_query(&[query], cx)?,
15837 wordwise: true,
15838 done: is_empty,
15839 };
15840 self.select_next_state = Some(select_state);
15841 } else {
15842 self.select_next_state = None;
15843 }
15844 } else if let Some(selected_text) = selected_text {
15845 self.select_next_state = Some(SelectNextState {
15846 query: self.build_query(&[selected_text], cx)?,
15847 wordwise: false,
15848 done: false,
15849 });
15850 self.select_next_match_internal(
15851 display_map,
15852 replace_newest,
15853 autoscroll,
15854 window,
15855 cx,
15856 )?;
15857 }
15858 }
15859 Ok(())
15860 }
15861
15862 pub fn select_all_matches(
15863 &mut self,
15864 _action: &SelectAllMatches,
15865 window: &mut Window,
15866 cx: &mut Context<Self>,
15867 ) -> Result<()> {
15868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15869
15870 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15871
15872 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15873 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15874 else {
15875 return Ok(());
15876 };
15877
15878 let mut new_selections = Vec::new();
15879
15880 let reversed = self
15881 .selections
15882 .oldest::<MultiBufferOffset>(&display_map)
15883 .reversed;
15884 let buffer = display_map.buffer_snapshot();
15885 let query_matches = select_next_state
15886 .query
15887 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15888
15889 for query_match in query_matches.into_iter() {
15890 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15891 let offset_range = if reversed {
15892 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15893 } else {
15894 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15895 };
15896
15897 if !select_next_state.wordwise
15898 || (!buffer.is_inside_word(offset_range.start, None)
15899 && !buffer.is_inside_word(offset_range.end, None))
15900 {
15901 new_selections.push(offset_range.start..offset_range.end);
15902 }
15903 }
15904
15905 select_next_state.done = true;
15906
15907 if new_selections.is_empty() {
15908 log::error!("bug: new_selections is empty in select_all_matches");
15909 return Ok(());
15910 }
15911
15912 self.unfold_ranges(&new_selections, false, false, cx);
15913 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15914 selections.select_ranges(new_selections)
15915 });
15916
15917 Ok(())
15918 }
15919
15920 pub fn select_next(
15921 &mut self,
15922 action: &SelectNext,
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 self.select_next_match_internal(
15929 &display_map,
15930 action.replace_newest,
15931 Some(Autoscroll::newest()),
15932 window,
15933 cx,
15934 )
15935 }
15936
15937 pub fn select_previous(
15938 &mut self,
15939 action: &SelectPrevious,
15940 window: &mut Window,
15941 cx: &mut Context<Self>,
15942 ) -> Result<()> {
15943 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15944 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15945 let buffer = display_map.buffer_snapshot();
15946 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15947 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15948 let query = &select_prev_state.query;
15949 if !select_prev_state.done {
15950 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15951 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15952 let mut next_selected_range = None;
15953 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15954 let bytes_before_last_selection =
15955 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15956 let bytes_after_first_selection =
15957 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15958 let query_matches = query
15959 .stream_find_iter(bytes_before_last_selection)
15960 .map(|result| (last_selection.start, result))
15961 .chain(
15962 query
15963 .stream_find_iter(bytes_after_first_selection)
15964 .map(|result| (buffer.len(), result)),
15965 );
15966 for (end_offset, query_match) in query_matches {
15967 let query_match = query_match.unwrap(); // can only fail due to I/O
15968 let offset_range =
15969 end_offset - query_match.end()..end_offset - query_match.start();
15970
15971 if !select_prev_state.wordwise
15972 || (!buffer.is_inside_word(offset_range.start, None)
15973 && !buffer.is_inside_word(offset_range.end, None))
15974 {
15975 next_selected_range = Some(offset_range);
15976 break;
15977 }
15978 }
15979
15980 if let Some(next_selected_range) = next_selected_range {
15981 self.select_match_ranges(
15982 next_selected_range,
15983 last_selection.reversed,
15984 action.replace_newest,
15985 Some(Autoscroll::newest()),
15986 window,
15987 cx,
15988 );
15989 } else {
15990 select_prev_state.done = true;
15991 }
15992 }
15993
15994 self.select_prev_state = Some(select_prev_state);
15995 } else {
15996 let mut only_carets = true;
15997 let mut same_text_selected = true;
15998 let mut selected_text = None;
15999
16000 let mut selections_iter = selections.iter().peekable();
16001 while let Some(selection) = selections_iter.next() {
16002 if selection.start != selection.end {
16003 only_carets = false;
16004 }
16005
16006 if same_text_selected {
16007 if selected_text.is_none() {
16008 selected_text =
16009 Some(buffer.text_for_range(selection.range()).collect::<String>());
16010 }
16011
16012 if let Some(next_selection) = selections_iter.peek() {
16013 if next_selection.len() == selection.len() {
16014 let next_selected_text = buffer
16015 .text_for_range(next_selection.range())
16016 .collect::<String>();
16017 if Some(next_selected_text) != selected_text {
16018 same_text_selected = false;
16019 selected_text = None;
16020 }
16021 } else {
16022 same_text_selected = false;
16023 selected_text = None;
16024 }
16025 }
16026 }
16027 }
16028
16029 if only_carets {
16030 for selection in &mut selections {
16031 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16032 selection.start = word_range.start;
16033 selection.end = word_range.end;
16034 selection.goal = SelectionGoal::None;
16035 selection.reversed = false;
16036 self.select_match_ranges(
16037 selection.start..selection.end,
16038 selection.reversed,
16039 action.replace_newest,
16040 Some(Autoscroll::newest()),
16041 window,
16042 cx,
16043 );
16044 }
16045 if selections.len() == 1 {
16046 let selection = selections
16047 .last()
16048 .expect("ensured that there's only one selection");
16049 let query = buffer
16050 .text_for_range(selection.start..selection.end)
16051 .collect::<String>();
16052 let is_empty = query.is_empty();
16053 let select_state = SelectNextState {
16054 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16055 wordwise: true,
16056 done: is_empty,
16057 };
16058 self.select_prev_state = Some(select_state);
16059 } else {
16060 self.select_prev_state = None;
16061 }
16062 } else if let Some(selected_text) = selected_text {
16063 self.select_prev_state = Some(SelectNextState {
16064 query: self
16065 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16066 wordwise: false,
16067 done: false,
16068 });
16069 self.select_previous(action, window, cx)?;
16070 }
16071 }
16072 Ok(())
16073 }
16074
16075 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16076 /// setting the case sensitivity based on the global
16077 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16078 /// editor's settings.
16079 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16080 where
16081 I: IntoIterator<Item = P>,
16082 P: AsRef<[u8]>,
16083 {
16084 let case_sensitive = self
16085 .select_next_is_case_sensitive
16086 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16087
16088 let mut builder = AhoCorasickBuilder::new();
16089 builder.ascii_case_insensitive(!case_sensitive);
16090 builder.build(patterns)
16091 }
16092
16093 pub fn find_next_match(
16094 &mut self,
16095 _: &FindNextMatch,
16096 window: &mut Window,
16097 cx: &mut Context<Self>,
16098 ) -> Result<()> {
16099 let selections = self.selections.disjoint_anchors_arc();
16100 match selections.first() {
16101 Some(first) if selections.len() >= 2 => {
16102 self.change_selections(Default::default(), window, cx, |s| {
16103 s.select_ranges([first.range()]);
16104 });
16105 }
16106 _ => self.select_next(
16107 &SelectNext {
16108 replace_newest: true,
16109 },
16110 window,
16111 cx,
16112 )?,
16113 }
16114 Ok(())
16115 }
16116
16117 pub fn find_previous_match(
16118 &mut self,
16119 _: &FindPreviousMatch,
16120 window: &mut Window,
16121 cx: &mut Context<Self>,
16122 ) -> Result<()> {
16123 let selections = self.selections.disjoint_anchors_arc();
16124 match selections.last() {
16125 Some(last) if selections.len() >= 2 => {
16126 self.change_selections(Default::default(), window, cx, |s| {
16127 s.select_ranges([last.range()]);
16128 });
16129 }
16130 _ => self.select_previous(
16131 &SelectPrevious {
16132 replace_newest: true,
16133 },
16134 window,
16135 cx,
16136 )?,
16137 }
16138 Ok(())
16139 }
16140
16141 pub fn toggle_comments(
16142 &mut self,
16143 action: &ToggleComments,
16144 window: &mut Window,
16145 cx: &mut Context<Self>,
16146 ) {
16147 if self.read_only(cx) {
16148 return;
16149 }
16150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16151 let text_layout_details = &self.text_layout_details(window, cx);
16152 self.transact(window, cx, |this, window, cx| {
16153 let mut selections = this
16154 .selections
16155 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16156 let mut edits = Vec::new();
16157 let mut selection_edit_ranges = Vec::new();
16158 let mut last_toggled_row = None;
16159 let snapshot = this.buffer.read(cx).read(cx);
16160 let empty_str: Arc<str> = Arc::default();
16161 let mut suffixes_inserted = Vec::new();
16162 let ignore_indent = action.ignore_indent;
16163
16164 fn comment_prefix_range(
16165 snapshot: &MultiBufferSnapshot,
16166 row: MultiBufferRow,
16167 comment_prefix: &str,
16168 comment_prefix_whitespace: &str,
16169 ignore_indent: bool,
16170 ) -> Range<Point> {
16171 let indent_size = if ignore_indent {
16172 0
16173 } else {
16174 snapshot.indent_size_for_line(row).len
16175 };
16176
16177 let start = Point::new(row.0, indent_size);
16178
16179 let mut line_bytes = snapshot
16180 .bytes_in_range(start..snapshot.max_point())
16181 .flatten()
16182 .copied();
16183
16184 // If this line currently begins with the line comment prefix, then record
16185 // the range containing the prefix.
16186 if line_bytes
16187 .by_ref()
16188 .take(comment_prefix.len())
16189 .eq(comment_prefix.bytes())
16190 {
16191 // Include any whitespace that matches the comment prefix.
16192 let matching_whitespace_len = line_bytes
16193 .zip(comment_prefix_whitespace.bytes())
16194 .take_while(|(a, b)| a == b)
16195 .count() as u32;
16196 let end = Point::new(
16197 start.row,
16198 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16199 );
16200 start..end
16201 } else {
16202 start..start
16203 }
16204 }
16205
16206 fn comment_suffix_range(
16207 snapshot: &MultiBufferSnapshot,
16208 row: MultiBufferRow,
16209 comment_suffix: &str,
16210 comment_suffix_has_leading_space: bool,
16211 ) -> Range<Point> {
16212 let end = Point::new(row.0, snapshot.line_len(row));
16213 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16214
16215 let mut line_end_bytes = snapshot
16216 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16217 .flatten()
16218 .copied();
16219
16220 let leading_space_len = if suffix_start_column > 0
16221 && line_end_bytes.next() == Some(b' ')
16222 && comment_suffix_has_leading_space
16223 {
16224 1
16225 } else {
16226 0
16227 };
16228
16229 // If this line currently begins with the line comment prefix, then record
16230 // the range containing the prefix.
16231 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16232 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16233 start..end
16234 } else {
16235 end..end
16236 }
16237 }
16238
16239 // TODO: Handle selections that cross excerpts
16240 for selection in &mut selections {
16241 let start_column = snapshot
16242 .indent_size_for_line(MultiBufferRow(selection.start.row))
16243 .len;
16244 let language = if let Some(language) =
16245 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16246 {
16247 language
16248 } else {
16249 continue;
16250 };
16251
16252 selection_edit_ranges.clear();
16253
16254 // If multiple selections contain a given row, avoid processing that
16255 // row more than once.
16256 let mut start_row = MultiBufferRow(selection.start.row);
16257 if last_toggled_row == Some(start_row) {
16258 start_row = start_row.next_row();
16259 }
16260 let end_row =
16261 if selection.end.row > selection.start.row && selection.end.column == 0 {
16262 MultiBufferRow(selection.end.row - 1)
16263 } else {
16264 MultiBufferRow(selection.end.row)
16265 };
16266 last_toggled_row = Some(end_row);
16267
16268 if start_row > end_row {
16269 continue;
16270 }
16271
16272 // If the language has line comments, toggle those.
16273 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16274
16275 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16276 if ignore_indent {
16277 full_comment_prefixes = full_comment_prefixes
16278 .into_iter()
16279 .map(|s| Arc::from(s.trim_end()))
16280 .collect();
16281 }
16282
16283 if !full_comment_prefixes.is_empty() {
16284 let first_prefix = full_comment_prefixes
16285 .first()
16286 .expect("prefixes is non-empty");
16287 let prefix_trimmed_lengths = full_comment_prefixes
16288 .iter()
16289 .map(|p| p.trim_end_matches(' ').len())
16290 .collect::<SmallVec<[usize; 4]>>();
16291
16292 let mut all_selection_lines_are_comments = true;
16293
16294 for row in start_row.0..=end_row.0 {
16295 let row = MultiBufferRow(row);
16296 if start_row < end_row && snapshot.is_line_blank(row) {
16297 continue;
16298 }
16299
16300 let prefix_range = full_comment_prefixes
16301 .iter()
16302 .zip(prefix_trimmed_lengths.iter().copied())
16303 .map(|(prefix, trimmed_prefix_len)| {
16304 comment_prefix_range(
16305 snapshot.deref(),
16306 row,
16307 &prefix[..trimmed_prefix_len],
16308 &prefix[trimmed_prefix_len..],
16309 ignore_indent,
16310 )
16311 })
16312 .max_by_key(|range| range.end.column - range.start.column)
16313 .expect("prefixes is non-empty");
16314
16315 if prefix_range.is_empty() {
16316 all_selection_lines_are_comments = false;
16317 }
16318
16319 selection_edit_ranges.push(prefix_range);
16320 }
16321
16322 if all_selection_lines_are_comments {
16323 edits.extend(
16324 selection_edit_ranges
16325 .iter()
16326 .cloned()
16327 .map(|range| (range, empty_str.clone())),
16328 );
16329 } else {
16330 let min_column = selection_edit_ranges
16331 .iter()
16332 .map(|range| range.start.column)
16333 .min()
16334 .unwrap_or(0);
16335 edits.extend(selection_edit_ranges.iter().map(|range| {
16336 let position = Point::new(range.start.row, min_column);
16337 (position..position, first_prefix.clone())
16338 }));
16339 }
16340 } else if let Some(BlockCommentConfig {
16341 start: full_comment_prefix,
16342 end: comment_suffix,
16343 ..
16344 }) = language.block_comment()
16345 {
16346 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16347 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16348 let prefix_range = comment_prefix_range(
16349 snapshot.deref(),
16350 start_row,
16351 comment_prefix,
16352 comment_prefix_whitespace,
16353 ignore_indent,
16354 );
16355 let suffix_range = comment_suffix_range(
16356 snapshot.deref(),
16357 end_row,
16358 comment_suffix.trim_start_matches(' '),
16359 comment_suffix.starts_with(' '),
16360 );
16361
16362 if prefix_range.is_empty() || suffix_range.is_empty() {
16363 edits.push((
16364 prefix_range.start..prefix_range.start,
16365 full_comment_prefix.clone(),
16366 ));
16367 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16368 suffixes_inserted.push((end_row, comment_suffix.len()));
16369 } else {
16370 edits.push((prefix_range, empty_str.clone()));
16371 edits.push((suffix_range, empty_str.clone()));
16372 }
16373 } else {
16374 continue;
16375 }
16376 }
16377
16378 drop(snapshot);
16379 this.buffer.update(cx, |buffer, cx| {
16380 buffer.edit(edits, None, cx);
16381 });
16382
16383 // Adjust selections so that they end before any comment suffixes that
16384 // were inserted.
16385 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16386 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16387 let snapshot = this.buffer.read(cx).read(cx);
16388 for selection in &mut selections {
16389 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16390 match row.cmp(&MultiBufferRow(selection.end.row)) {
16391 Ordering::Less => {
16392 suffixes_inserted.next();
16393 continue;
16394 }
16395 Ordering::Greater => break,
16396 Ordering::Equal => {
16397 if selection.end.column == snapshot.line_len(row) {
16398 if selection.is_empty() {
16399 selection.start.column -= suffix_len as u32;
16400 }
16401 selection.end.column -= suffix_len as u32;
16402 }
16403 break;
16404 }
16405 }
16406 }
16407 }
16408
16409 drop(snapshot);
16410 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16411
16412 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16413 let selections_on_single_row = selections.windows(2).all(|selections| {
16414 selections[0].start.row == selections[1].start.row
16415 && selections[0].end.row == selections[1].end.row
16416 && selections[0].start.row == selections[0].end.row
16417 });
16418 let selections_selecting = selections
16419 .iter()
16420 .any(|selection| selection.start != selection.end);
16421 let advance_downwards = action.advance_downwards
16422 && selections_on_single_row
16423 && !selections_selecting
16424 && !matches!(this.mode, EditorMode::SingleLine);
16425
16426 if advance_downwards {
16427 let snapshot = this.buffer.read(cx).snapshot(cx);
16428
16429 this.change_selections(Default::default(), window, cx, |s| {
16430 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16431 let mut point = display_point.to_point(display_snapshot);
16432 point.row += 1;
16433 point = snapshot.clip_point(point, Bias::Left);
16434 let display_point = point.to_display_point(display_snapshot);
16435 let goal = SelectionGoal::HorizontalPosition(
16436 display_snapshot
16437 .x_for_display_point(display_point, text_layout_details)
16438 .into(),
16439 );
16440 (display_point, goal)
16441 })
16442 });
16443 }
16444 });
16445 }
16446
16447 pub fn select_enclosing_symbol(
16448 &mut self,
16449 _: &SelectEnclosingSymbol,
16450 window: &mut Window,
16451 cx: &mut Context<Self>,
16452 ) {
16453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16454
16455 let buffer = self.buffer.read(cx).snapshot(cx);
16456 let old_selections = self
16457 .selections
16458 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16459 .into_boxed_slice();
16460
16461 fn update_selection(
16462 selection: &Selection<MultiBufferOffset>,
16463 buffer_snap: &MultiBufferSnapshot,
16464 ) -> Option<Selection<MultiBufferOffset>> {
16465 let cursor = selection.head();
16466 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16467 for symbol in symbols.iter().rev() {
16468 let start = symbol.range.start.to_offset(buffer_snap);
16469 let end = symbol.range.end.to_offset(buffer_snap);
16470 let new_range = start..end;
16471 if start < selection.start || end > selection.end {
16472 return Some(Selection {
16473 id: selection.id,
16474 start: new_range.start,
16475 end: new_range.end,
16476 goal: SelectionGoal::None,
16477 reversed: selection.reversed,
16478 });
16479 }
16480 }
16481 None
16482 }
16483
16484 let mut selected_larger_symbol = false;
16485 let new_selections = old_selections
16486 .iter()
16487 .map(|selection| match update_selection(selection, &buffer) {
16488 Some(new_selection) => {
16489 if new_selection.range() != selection.range() {
16490 selected_larger_symbol = true;
16491 }
16492 new_selection
16493 }
16494 None => selection.clone(),
16495 })
16496 .collect::<Vec<_>>();
16497
16498 if selected_larger_symbol {
16499 self.change_selections(Default::default(), window, cx, |s| {
16500 s.select(new_selections);
16501 });
16502 }
16503 }
16504
16505 pub fn select_larger_syntax_node(
16506 &mut self,
16507 _: &SelectLargerSyntaxNode,
16508 window: &mut Window,
16509 cx: &mut Context<Self>,
16510 ) {
16511 let Some(visible_row_count) = self.visible_row_count() else {
16512 return;
16513 };
16514 let old_selections: Box<[_]> = self
16515 .selections
16516 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16517 .into();
16518 if old_selections.is_empty() {
16519 return;
16520 }
16521
16522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16523
16524 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16525 let buffer = self.buffer.read(cx).snapshot(cx);
16526
16527 let mut selected_larger_node = false;
16528 let mut new_selections = old_selections
16529 .iter()
16530 .map(|selection| {
16531 let old_range = selection.start..selection.end;
16532
16533 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16534 // manually select word at selection
16535 if ["string_content", "inline"].contains(&node.kind()) {
16536 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16537 // ignore if word is already selected
16538 if !word_range.is_empty() && old_range != word_range {
16539 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16540 // only select word if start and end point belongs to same word
16541 if word_range == last_word_range {
16542 selected_larger_node = true;
16543 return Selection {
16544 id: selection.id,
16545 start: word_range.start,
16546 end: word_range.end,
16547 goal: SelectionGoal::None,
16548 reversed: selection.reversed,
16549 };
16550 }
16551 }
16552 }
16553 }
16554
16555 let mut new_range = old_range.clone();
16556 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16557 new_range = range;
16558 if !node.is_named() {
16559 continue;
16560 }
16561 if !display_map.intersects_fold(new_range.start)
16562 && !display_map.intersects_fold(new_range.end)
16563 {
16564 break;
16565 }
16566 }
16567
16568 selected_larger_node |= new_range != old_range;
16569 Selection {
16570 id: selection.id,
16571 start: new_range.start,
16572 end: new_range.end,
16573 goal: SelectionGoal::None,
16574 reversed: selection.reversed,
16575 }
16576 })
16577 .collect::<Vec<_>>();
16578
16579 if !selected_larger_node {
16580 return; // don't put this call in the history
16581 }
16582
16583 // scroll based on transformation done to the last selection created by the user
16584 let (last_old, last_new) = old_selections
16585 .last()
16586 .zip(new_selections.last().cloned())
16587 .expect("old_selections isn't empty");
16588
16589 // revert selection
16590 let is_selection_reversed = {
16591 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16592 new_selections.last_mut().expect("checked above").reversed =
16593 should_newest_selection_be_reversed;
16594 should_newest_selection_be_reversed
16595 };
16596
16597 if selected_larger_node {
16598 self.select_syntax_node_history.disable_clearing = true;
16599 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16600 s.select(new_selections.clone());
16601 });
16602 self.select_syntax_node_history.disable_clearing = false;
16603 }
16604
16605 let start_row = last_new.start.to_display_point(&display_map).row().0;
16606 let end_row = last_new.end.to_display_point(&display_map).row().0;
16607 let selection_height = end_row - start_row + 1;
16608 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16609
16610 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16611 let scroll_behavior = if fits_on_the_screen {
16612 self.request_autoscroll(Autoscroll::fit(), cx);
16613 SelectSyntaxNodeScrollBehavior::FitSelection
16614 } else if is_selection_reversed {
16615 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16616 SelectSyntaxNodeScrollBehavior::CursorTop
16617 } else {
16618 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16619 SelectSyntaxNodeScrollBehavior::CursorBottom
16620 };
16621
16622 let old_selections: Box<[Selection<Anchor>]> = old_selections
16623 .iter()
16624 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16625 .collect();
16626 self.select_syntax_node_history.push((
16627 old_selections,
16628 scroll_behavior,
16629 is_selection_reversed,
16630 ));
16631 }
16632
16633 pub fn select_smaller_syntax_node(
16634 &mut self,
16635 _: &SelectSmallerSyntaxNode,
16636 window: &mut Window,
16637 cx: &mut Context<Self>,
16638 ) {
16639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16640
16641 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16642 self.select_syntax_node_history.pop()
16643 {
16644 if let Some(selection) = selections.last_mut() {
16645 selection.reversed = is_selection_reversed;
16646 }
16647
16648 let snapshot = self.buffer.read(cx).snapshot(cx);
16649 let selections: Vec<Selection<MultiBufferOffset>> = selections
16650 .iter()
16651 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16652 .collect();
16653
16654 self.select_syntax_node_history.disable_clearing = true;
16655 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16656 s.select(selections);
16657 });
16658 self.select_syntax_node_history.disable_clearing = false;
16659
16660 match scroll_behavior {
16661 SelectSyntaxNodeScrollBehavior::CursorTop => {
16662 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16663 }
16664 SelectSyntaxNodeScrollBehavior::FitSelection => {
16665 self.request_autoscroll(Autoscroll::fit(), cx);
16666 }
16667 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16668 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16669 }
16670 }
16671 }
16672 }
16673
16674 pub fn unwrap_syntax_node(
16675 &mut self,
16676 _: &UnwrapSyntaxNode,
16677 window: &mut Window,
16678 cx: &mut Context<Self>,
16679 ) {
16680 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16681
16682 let buffer = self.buffer.read(cx).snapshot(cx);
16683 let selections = self
16684 .selections
16685 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16686 .into_iter()
16687 // subtracting the offset requires sorting
16688 .sorted_by_key(|i| i.start);
16689
16690 let full_edits = selections
16691 .into_iter()
16692 .filter_map(|selection| {
16693 let child = if selection.is_empty()
16694 && let Some((_, ancestor_range)) =
16695 buffer.syntax_ancestor(selection.start..selection.end)
16696 {
16697 ancestor_range
16698 } else {
16699 selection.range()
16700 };
16701
16702 let mut parent = child.clone();
16703 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16704 parent = ancestor_range;
16705 if parent.start < child.start || parent.end > child.end {
16706 break;
16707 }
16708 }
16709
16710 if parent == child {
16711 return None;
16712 }
16713 let text = buffer.text_for_range(child).collect::<String>();
16714 Some((selection.id, parent, text))
16715 })
16716 .collect::<Vec<_>>();
16717 if full_edits.is_empty() {
16718 return;
16719 }
16720
16721 self.transact(window, cx, |this, window, cx| {
16722 this.buffer.update(cx, |buffer, cx| {
16723 buffer.edit(
16724 full_edits
16725 .iter()
16726 .map(|(_, p, t)| (p.clone(), t.clone()))
16727 .collect::<Vec<_>>(),
16728 None,
16729 cx,
16730 );
16731 });
16732 this.change_selections(Default::default(), window, cx, |s| {
16733 let mut offset = 0;
16734 let mut selections = vec![];
16735 for (id, parent, text) in full_edits {
16736 let start = parent.start - offset;
16737 offset += (parent.end - parent.start) - text.len();
16738 selections.push(Selection {
16739 id,
16740 start,
16741 end: start + text.len(),
16742 reversed: false,
16743 goal: Default::default(),
16744 });
16745 }
16746 s.select(selections);
16747 });
16748 });
16749 }
16750
16751 pub fn select_next_syntax_node(
16752 &mut self,
16753 _: &SelectNextSyntaxNode,
16754 window: &mut Window,
16755 cx: &mut Context<Self>,
16756 ) {
16757 let old_selections: Box<[_]> = self
16758 .selections
16759 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16760 .into();
16761 if old_selections.is_empty() {
16762 return;
16763 }
16764
16765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16766
16767 let buffer = self.buffer.read(cx).snapshot(cx);
16768 let mut selected_sibling = false;
16769
16770 let new_selections = old_selections
16771 .iter()
16772 .map(|selection| {
16773 let old_range = selection.start..selection.end;
16774
16775 let old_range =
16776 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16777 let excerpt = buffer.excerpt_containing(old_range.clone());
16778
16779 if let Some(mut excerpt) = excerpt
16780 && let Some(node) = excerpt
16781 .buffer()
16782 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16783 {
16784 let new_range = excerpt.map_range_from_buffer(
16785 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16786 );
16787 selected_sibling = true;
16788 Selection {
16789 id: selection.id,
16790 start: new_range.start,
16791 end: new_range.end,
16792 goal: SelectionGoal::None,
16793 reversed: selection.reversed,
16794 }
16795 } else {
16796 selection.clone()
16797 }
16798 })
16799 .collect::<Vec<_>>();
16800
16801 if selected_sibling {
16802 self.change_selections(
16803 SelectionEffects::scroll(Autoscroll::fit()),
16804 window,
16805 cx,
16806 |s| {
16807 s.select(new_selections);
16808 },
16809 );
16810 }
16811 }
16812
16813 pub fn select_prev_syntax_node(
16814 &mut self,
16815 _: &SelectPreviousSyntaxNode,
16816 window: &mut Window,
16817 cx: &mut Context<Self>,
16818 ) {
16819 let old_selections: Box<[_]> = self
16820 .selections
16821 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16822 .into();
16823 if old_selections.is_empty() {
16824 return;
16825 }
16826
16827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16828
16829 let buffer = self.buffer.read(cx).snapshot(cx);
16830 let mut selected_sibling = false;
16831
16832 let new_selections = old_selections
16833 .iter()
16834 .map(|selection| {
16835 let old_range = selection.start..selection.end;
16836 let old_range =
16837 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16838 let excerpt = buffer.excerpt_containing(old_range.clone());
16839
16840 if let Some(mut excerpt) = excerpt
16841 && let Some(node) = excerpt
16842 .buffer()
16843 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16844 {
16845 let new_range = excerpt.map_range_from_buffer(
16846 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16847 );
16848 selected_sibling = true;
16849 Selection {
16850 id: selection.id,
16851 start: new_range.start,
16852 end: new_range.end,
16853 goal: SelectionGoal::None,
16854 reversed: selection.reversed,
16855 }
16856 } else {
16857 selection.clone()
16858 }
16859 })
16860 .collect::<Vec<_>>();
16861
16862 if selected_sibling {
16863 self.change_selections(
16864 SelectionEffects::scroll(Autoscroll::fit()),
16865 window,
16866 cx,
16867 |s| {
16868 s.select(new_selections);
16869 },
16870 );
16871 }
16872 }
16873
16874 pub fn move_to_start_of_larger_syntax_node(
16875 &mut self,
16876 _: &MoveToStartOfLargerSyntaxNode,
16877 window: &mut Window,
16878 cx: &mut Context<Self>,
16879 ) {
16880 self.move_cursors_to_syntax_nodes(window, cx, false);
16881 }
16882
16883 pub fn move_to_end_of_larger_syntax_node(
16884 &mut self,
16885 _: &MoveToEndOfLargerSyntaxNode,
16886 window: &mut Window,
16887 cx: &mut Context<Self>,
16888 ) {
16889 self.move_cursors_to_syntax_nodes(window, cx, true);
16890 }
16891
16892 fn find_syntax_node_boundary(
16893 &self,
16894 selection_pos: MultiBufferOffset,
16895 move_to_end: bool,
16896 display_map: &DisplaySnapshot,
16897 buffer: &MultiBufferSnapshot,
16898 ) -> MultiBufferOffset {
16899 let old_range = selection_pos..selection_pos;
16900 let mut new_pos = selection_pos;
16901 let mut search_range = old_range;
16902 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16903 search_range = range.clone();
16904 if !node.is_named()
16905 || display_map.intersects_fold(range.start)
16906 || display_map.intersects_fold(range.end)
16907 // If cursor is already at the end of the syntax node, continue searching
16908 || (move_to_end && range.end == selection_pos)
16909 // If cursor is already at the start of the syntax node, continue searching
16910 || (!move_to_end && range.start == selection_pos)
16911 {
16912 continue;
16913 }
16914
16915 // If we found a string_content node, find the largest parent that is still string_content
16916 // Enables us to skip to the end of strings without taking multiple steps inside the string
16917 let (_, final_range) = if node.kind() == "string_content" {
16918 let mut current_node = node;
16919 let mut current_range = range;
16920 while let Some((parent, parent_range)) =
16921 buffer.syntax_ancestor(current_range.clone())
16922 {
16923 if parent.kind() == "string_content" {
16924 current_node = parent;
16925 current_range = parent_range;
16926 } else {
16927 break;
16928 }
16929 }
16930
16931 (current_node, current_range)
16932 } else {
16933 (node, range)
16934 };
16935
16936 new_pos = if move_to_end {
16937 final_range.end
16938 } else {
16939 final_range.start
16940 };
16941
16942 break;
16943 }
16944
16945 new_pos
16946 }
16947
16948 fn move_cursors_to_syntax_nodes(
16949 &mut self,
16950 window: &mut Window,
16951 cx: &mut Context<Self>,
16952 move_to_end: bool,
16953 ) -> bool {
16954 let old_selections: Box<[_]> = self
16955 .selections
16956 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16957 .into();
16958 if old_selections.is_empty() {
16959 return false;
16960 }
16961
16962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16963
16964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16965 let buffer = self.buffer.read(cx).snapshot(cx);
16966
16967 let mut any_cursor_moved = false;
16968 let new_selections = old_selections
16969 .iter()
16970 .map(|selection| {
16971 if !selection.is_empty() {
16972 return selection.clone();
16973 }
16974
16975 let selection_pos = selection.head();
16976 let new_pos = self.find_syntax_node_boundary(
16977 selection_pos,
16978 move_to_end,
16979 &display_map,
16980 &buffer,
16981 );
16982
16983 any_cursor_moved |= new_pos != selection_pos;
16984
16985 Selection {
16986 id: selection.id,
16987 start: new_pos,
16988 end: new_pos,
16989 goal: SelectionGoal::None,
16990 reversed: false,
16991 }
16992 })
16993 .collect::<Vec<_>>();
16994
16995 self.change_selections(Default::default(), window, cx, |s| {
16996 s.select(new_selections);
16997 });
16998 self.request_autoscroll(Autoscroll::newest(), cx);
16999
17000 any_cursor_moved
17001 }
17002
17003 pub fn select_to_start_of_larger_syntax_node(
17004 &mut self,
17005 _: &SelectToStartOfLargerSyntaxNode,
17006 window: &mut Window,
17007 cx: &mut Context<Self>,
17008 ) {
17009 self.select_to_syntax_nodes(window, cx, false);
17010 }
17011
17012 pub fn select_to_end_of_larger_syntax_node(
17013 &mut self,
17014 _: &SelectToEndOfLargerSyntaxNode,
17015 window: &mut Window,
17016 cx: &mut Context<Self>,
17017 ) {
17018 self.select_to_syntax_nodes(window, cx, true);
17019 }
17020
17021 fn select_to_syntax_nodes(
17022 &mut self,
17023 window: &mut Window,
17024 cx: &mut Context<Self>,
17025 move_to_end: bool,
17026 ) {
17027 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17028
17029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17030 let buffer = self.buffer.read(cx).snapshot(cx);
17031 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17032
17033 let new_selections = old_selections
17034 .iter()
17035 .map(|selection| {
17036 let new_pos = self.find_syntax_node_boundary(
17037 selection.head(),
17038 move_to_end,
17039 &display_map,
17040 &buffer,
17041 );
17042
17043 let mut new_selection = selection.clone();
17044 new_selection.set_head(new_pos, SelectionGoal::None);
17045 new_selection
17046 })
17047 .collect::<Vec<_>>();
17048
17049 self.change_selections(Default::default(), window, cx, |s| {
17050 s.select(new_selections);
17051 });
17052 }
17053
17054 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
17055 if !EditorSettings::get_global(cx).gutter.runnables || !self.enable_runnables {
17056 self.clear_tasks();
17057 return Task::ready(());
17058 }
17059 let project = self.project().map(Entity::downgrade);
17060 let task_sources = self.lsp_task_sources(cx);
17061 let multi_buffer = self.buffer.downgrade();
17062 cx.spawn_in(window, async move |editor, cx| {
17063 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
17064 let Some(project) = project.and_then(|p| p.upgrade()) else {
17065 return;
17066 };
17067 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
17068 this.display_map.update(cx, |map, cx| map.snapshot(cx))
17069 }) else {
17070 return;
17071 };
17072
17073 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
17074 if hide_runnables {
17075 return;
17076 }
17077 let new_rows =
17078 cx.background_spawn({
17079 let snapshot = display_snapshot.clone();
17080 async move {
17081 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
17082 }
17083 })
17084 .await;
17085 let Ok(lsp_tasks) =
17086 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
17087 else {
17088 return;
17089 };
17090 let lsp_tasks = lsp_tasks.await;
17091
17092 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
17093 lsp_tasks
17094 .into_iter()
17095 .flat_map(|(kind, tasks)| {
17096 tasks.into_iter().filter_map(move |(location, task)| {
17097 Some((kind.clone(), location?, task))
17098 })
17099 })
17100 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
17101 let buffer = location.target.buffer;
17102 let buffer_snapshot = buffer.read(cx).snapshot();
17103 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
17104 |(excerpt_id, snapshot, _)| {
17105 if snapshot.remote_id() == buffer_snapshot.remote_id() {
17106 display_snapshot
17107 .buffer_snapshot()
17108 .anchor_in_excerpt(excerpt_id, location.target.range.start)
17109 } else {
17110 None
17111 }
17112 },
17113 );
17114 if let Some(offset) = offset {
17115 let task_buffer_range =
17116 location.target.range.to_point(&buffer_snapshot);
17117 let context_buffer_range =
17118 task_buffer_range.to_offset(&buffer_snapshot);
17119 let context_range = BufferOffset(context_buffer_range.start)
17120 ..BufferOffset(context_buffer_range.end);
17121
17122 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
17123 .or_insert_with(|| RunnableTasks {
17124 templates: Vec::new(),
17125 offset,
17126 column: task_buffer_range.start.column,
17127 extra_variables: HashMap::default(),
17128 context_range,
17129 })
17130 .templates
17131 .push((kind, task.original_task().clone()));
17132 }
17133
17134 acc
17135 })
17136 }) else {
17137 return;
17138 };
17139
17140 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
17141 buffer.language_settings(cx).tasks.prefer_lsp
17142 }) else {
17143 return;
17144 };
17145
17146 let rows = Self::runnable_rows(
17147 project,
17148 display_snapshot,
17149 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
17150 new_rows,
17151 cx.clone(),
17152 )
17153 .await;
17154 editor
17155 .update(cx, |editor, _| {
17156 editor.clear_tasks();
17157 for (key, mut value) in rows {
17158 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
17159 value.templates.extend(lsp_tasks.templates);
17160 }
17161
17162 editor.insert_tasks(key, value);
17163 }
17164 for (key, value) in lsp_tasks_by_rows {
17165 editor.insert_tasks(key, value);
17166 }
17167 })
17168 .ok();
17169 })
17170 }
17171 fn fetch_runnable_ranges(
17172 snapshot: &DisplaySnapshot,
17173 range: Range<Anchor>,
17174 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
17175 snapshot.buffer_snapshot().runnable_ranges(range).collect()
17176 }
17177
17178 fn runnable_rows(
17179 project: Entity<Project>,
17180 snapshot: DisplaySnapshot,
17181 prefer_lsp: bool,
17182 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
17183 cx: AsyncWindowContext,
17184 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
17185 cx.spawn(async move |cx| {
17186 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
17187 for (run_range, mut runnable) in runnable_ranges {
17188 let Some(tasks) = cx
17189 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
17190 .ok()
17191 else {
17192 continue;
17193 };
17194 let mut tasks = tasks.await;
17195
17196 if prefer_lsp {
17197 tasks.retain(|(task_kind, _)| {
17198 !matches!(task_kind, TaskSourceKind::Language { .. })
17199 });
17200 }
17201 if tasks.is_empty() {
17202 continue;
17203 }
17204
17205 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
17206 let Some(row) = snapshot
17207 .buffer_snapshot()
17208 .buffer_line_for_row(MultiBufferRow(point.row))
17209 .map(|(_, range)| range.start.row)
17210 else {
17211 continue;
17212 };
17213
17214 let context_range =
17215 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
17216 runnable_rows.push((
17217 (runnable.buffer_id, row),
17218 RunnableTasks {
17219 templates: tasks,
17220 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
17221 context_range,
17222 column: point.column,
17223 extra_variables: runnable.extra_captures,
17224 },
17225 ));
17226 }
17227 runnable_rows
17228 })
17229 }
17230
17231 fn templates_with_tags(
17232 project: &Entity<Project>,
17233 runnable: &mut Runnable,
17234 cx: &mut App,
17235 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
17236 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
17237 let (worktree_id, file) = project
17238 .buffer_for_id(runnable.buffer, cx)
17239 .and_then(|buffer| buffer.read(cx).file())
17240 .map(|file| (file.worktree_id(cx), file.clone()))
17241 .unzip();
17242
17243 (
17244 project.task_store().read(cx).task_inventory().cloned(),
17245 worktree_id,
17246 file,
17247 )
17248 });
17249
17250 let tags = mem::take(&mut runnable.tags);
17251 let language = runnable.language.clone();
17252 cx.spawn(async move |cx| {
17253 let mut templates_with_tags = Vec::new();
17254 if let Some(inventory) = inventory {
17255 for RunnableTag(tag) in tags {
17256 let new_tasks = inventory.update(cx, |inventory, cx| {
17257 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
17258 });
17259 templates_with_tags.extend(new_tasks.await.into_iter().filter(
17260 move |(_, template)| {
17261 template.tags.iter().any(|source_tag| source_tag == &tag)
17262 },
17263 ));
17264 }
17265 }
17266 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
17267
17268 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
17269 // Strongest source wins; if we have worktree tag binding, prefer that to
17270 // global and language bindings;
17271 // if we have a global binding, prefer that to language binding.
17272 let first_mismatch = templates_with_tags
17273 .iter()
17274 .position(|(tag_source, _)| tag_source != leading_tag_source);
17275 if let Some(index) = first_mismatch {
17276 templates_with_tags.truncate(index);
17277 }
17278 }
17279
17280 templates_with_tags
17281 })
17282 }
17283
17284 pub fn move_to_enclosing_bracket(
17285 &mut self,
17286 _: &MoveToEnclosingBracket,
17287 window: &mut Window,
17288 cx: &mut Context<Self>,
17289 ) {
17290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17291 self.change_selections(Default::default(), window, cx, |s| {
17292 s.move_offsets_with(&mut |snapshot, selection| {
17293 let Some(enclosing_bracket_ranges) =
17294 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17295 else {
17296 return;
17297 };
17298
17299 let mut best_length = usize::MAX;
17300 let mut best_inside = false;
17301 let mut best_in_bracket_range = false;
17302 let mut best_destination = None;
17303 for (open, close) in enclosing_bracket_ranges {
17304 let close = close.to_inclusive();
17305 let length = *close.end() - open.start;
17306 let inside = selection.start >= open.end && selection.end <= *close.start();
17307 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17308 || close.contains(&selection.head());
17309
17310 // If best is next to a bracket and current isn't, skip
17311 if !in_bracket_range && best_in_bracket_range {
17312 continue;
17313 }
17314
17315 // Prefer smaller lengths unless best is inside and current isn't
17316 if length > best_length && (best_inside || !inside) {
17317 continue;
17318 }
17319
17320 best_length = length;
17321 best_inside = inside;
17322 best_in_bracket_range = in_bracket_range;
17323 best_destination = Some(
17324 if close.contains(&selection.start) && close.contains(&selection.end) {
17325 if inside { open.end } else { open.start }
17326 } else if inside {
17327 *close.start()
17328 } else {
17329 *close.end()
17330 },
17331 );
17332 }
17333
17334 if let Some(destination) = best_destination {
17335 selection.collapse_to(destination, SelectionGoal::None);
17336 }
17337 })
17338 });
17339 }
17340
17341 pub fn undo_selection(
17342 &mut self,
17343 _: &UndoSelection,
17344 window: &mut Window,
17345 cx: &mut Context<Self>,
17346 ) {
17347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17348 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17349 self.selection_history.mode = SelectionHistoryMode::Undoing;
17350 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17351 this.end_selection(window, cx);
17352 this.change_selections(
17353 SelectionEffects::scroll(Autoscroll::newest()),
17354 window,
17355 cx,
17356 |s| s.select_anchors(entry.selections.to_vec()),
17357 );
17358 });
17359 self.selection_history.mode = SelectionHistoryMode::Normal;
17360
17361 self.select_next_state = entry.select_next_state;
17362 self.select_prev_state = entry.select_prev_state;
17363 self.add_selections_state = entry.add_selections_state;
17364 }
17365 }
17366
17367 pub fn redo_selection(
17368 &mut self,
17369 _: &RedoSelection,
17370 window: &mut Window,
17371 cx: &mut Context<Self>,
17372 ) {
17373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17374 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17375 self.selection_history.mode = SelectionHistoryMode::Redoing;
17376 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17377 this.end_selection(window, cx);
17378 this.change_selections(
17379 SelectionEffects::scroll(Autoscroll::newest()),
17380 window,
17381 cx,
17382 |s| s.select_anchors(entry.selections.to_vec()),
17383 );
17384 });
17385 self.selection_history.mode = SelectionHistoryMode::Normal;
17386
17387 self.select_next_state = entry.select_next_state;
17388 self.select_prev_state = entry.select_prev_state;
17389 self.add_selections_state = entry.add_selections_state;
17390 }
17391 }
17392
17393 pub fn expand_excerpts(
17394 &mut self,
17395 action: &ExpandExcerpts,
17396 _: &mut Window,
17397 cx: &mut Context<Self>,
17398 ) {
17399 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17400 }
17401
17402 pub fn expand_excerpts_down(
17403 &mut self,
17404 action: &ExpandExcerptsDown,
17405 _: &mut Window,
17406 cx: &mut Context<Self>,
17407 ) {
17408 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17409 }
17410
17411 pub fn expand_excerpts_up(
17412 &mut self,
17413 action: &ExpandExcerptsUp,
17414 _: &mut Window,
17415 cx: &mut Context<Self>,
17416 ) {
17417 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17418 }
17419
17420 pub fn expand_excerpts_for_direction(
17421 &mut self,
17422 lines: u32,
17423 direction: ExpandExcerptDirection,
17424 cx: &mut Context<Self>,
17425 ) {
17426 let selections = self.selections.disjoint_anchors_arc();
17427
17428 let lines = if lines == 0 {
17429 EditorSettings::get_global(cx).expand_excerpt_lines
17430 } else {
17431 lines
17432 };
17433
17434 let snapshot = self.buffer.read(cx).snapshot(cx);
17435 let excerpt_ids = selections
17436 .iter()
17437 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17438 .unique()
17439 .sorted()
17440 .collect::<Vec<_>>();
17441
17442 if self.delegate_expand_excerpts {
17443 cx.emit(EditorEvent::ExpandExcerptsRequested {
17444 excerpt_ids,
17445 lines,
17446 direction,
17447 });
17448 return;
17449 }
17450
17451 self.buffer.update(cx, |buffer, cx| {
17452 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17453 })
17454 }
17455
17456 pub fn expand_excerpt(
17457 &mut self,
17458 excerpt: ExcerptId,
17459 direction: ExpandExcerptDirection,
17460 window: &mut Window,
17461 cx: &mut Context<Self>,
17462 ) {
17463 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17464
17465 if self.delegate_expand_excerpts {
17466 cx.emit(EditorEvent::ExpandExcerptsRequested {
17467 excerpt_ids: vec![excerpt],
17468 lines: lines_to_expand,
17469 direction,
17470 });
17471 return;
17472 }
17473
17474 let current_scroll_position = self.scroll_position(cx);
17475 let mut scroll = None;
17476
17477 if direction == ExpandExcerptDirection::Down {
17478 let multi_buffer = self.buffer.read(cx);
17479 let snapshot = multi_buffer.snapshot(cx);
17480 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17481 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17482 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17483 {
17484 let buffer_snapshot = buffer.read(cx).snapshot();
17485 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17486 let last_row = buffer_snapshot.max_point().row;
17487 let lines_below = last_row.saturating_sub(excerpt_end_row);
17488 if lines_below >= lines_to_expand {
17489 scroll = Some(
17490 current_scroll_position
17491 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17492 );
17493 }
17494 }
17495 }
17496 if direction == ExpandExcerptDirection::Up
17497 && self
17498 .buffer
17499 .read(cx)
17500 .snapshot(cx)
17501 .excerpt_before(excerpt)
17502 .is_none()
17503 {
17504 scroll = Some(current_scroll_position);
17505 }
17506
17507 self.buffer.update(cx, |buffer, cx| {
17508 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17509 });
17510
17511 if let Some(new_scroll_position) = scroll {
17512 self.set_scroll_position(new_scroll_position, window, cx);
17513 }
17514 }
17515
17516 pub fn go_to_singleton_buffer_point(
17517 &mut self,
17518 point: Point,
17519 window: &mut Window,
17520 cx: &mut Context<Self>,
17521 ) {
17522 self.go_to_singleton_buffer_range(point..point, window, cx);
17523 }
17524
17525 pub fn go_to_singleton_buffer_range(
17526 &mut self,
17527 range: Range<Point>,
17528 window: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 let multibuffer = self.buffer().read(cx);
17532 let Some(buffer) = multibuffer.as_singleton() else {
17533 return;
17534 };
17535 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17536 return;
17537 };
17538 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17539 return;
17540 };
17541 self.change_selections(
17542 SelectionEffects::default().nav_history(true),
17543 window,
17544 cx,
17545 |s| s.select_anchor_ranges([start..end]),
17546 );
17547 }
17548
17549 pub fn go_to_diagnostic(
17550 &mut self,
17551 action: &GoToDiagnostic,
17552 window: &mut Window,
17553 cx: &mut Context<Self>,
17554 ) {
17555 if !self.diagnostics_enabled() {
17556 return;
17557 }
17558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17559 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17560 }
17561
17562 pub fn go_to_prev_diagnostic(
17563 &mut self,
17564 action: &GoToPreviousDiagnostic,
17565 window: &mut Window,
17566 cx: &mut Context<Self>,
17567 ) {
17568 if !self.diagnostics_enabled() {
17569 return;
17570 }
17571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17572 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17573 }
17574
17575 pub fn go_to_diagnostic_impl(
17576 &mut self,
17577 direction: Direction,
17578 severity: GoToDiagnosticSeverityFilter,
17579 window: &mut Window,
17580 cx: &mut Context<Self>,
17581 ) {
17582 let buffer = self.buffer.read(cx).snapshot(cx);
17583 let selection = self
17584 .selections
17585 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17586
17587 let mut active_group_id = None;
17588 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17589 && active_group.active_range.start.to_offset(&buffer) == selection.start
17590 {
17591 active_group_id = Some(active_group.group_id);
17592 }
17593
17594 fn filtered<'a>(
17595 severity: GoToDiagnosticSeverityFilter,
17596 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17597 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17598 diagnostics
17599 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17600 .filter(|entry| entry.range.start != entry.range.end)
17601 .filter(|entry| !entry.diagnostic.is_unnecessary)
17602 }
17603
17604 let before = filtered(
17605 severity,
17606 buffer
17607 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17608 .filter(|entry| entry.range.start <= selection.start),
17609 );
17610 let after = filtered(
17611 severity,
17612 buffer
17613 .diagnostics_in_range(selection.start..buffer.len())
17614 .filter(|entry| entry.range.start >= selection.start),
17615 );
17616
17617 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17618 if direction == Direction::Prev {
17619 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17620 {
17621 for diagnostic in prev_diagnostics.into_iter().rev() {
17622 if diagnostic.range.start != selection.start
17623 || active_group_id
17624 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17625 {
17626 found = Some(diagnostic);
17627 break 'outer;
17628 }
17629 }
17630 }
17631 } else {
17632 for diagnostic in after.chain(before) {
17633 if diagnostic.range.start != selection.start
17634 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17635 {
17636 found = Some(diagnostic);
17637 break;
17638 }
17639 }
17640 }
17641 let Some(next_diagnostic) = found else {
17642 return;
17643 };
17644
17645 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17646 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17647 return;
17648 };
17649 let snapshot = self.snapshot(window, cx);
17650 if snapshot.intersects_fold(next_diagnostic.range.start) {
17651 self.unfold_ranges(
17652 std::slice::from_ref(&next_diagnostic.range),
17653 true,
17654 false,
17655 cx,
17656 );
17657 }
17658 self.change_selections(Default::default(), window, cx, |s| {
17659 s.select_ranges(vec![
17660 next_diagnostic.range.start..next_diagnostic.range.start,
17661 ])
17662 });
17663 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17664 self.refresh_edit_prediction(false, true, window, cx);
17665 }
17666
17667 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17669 let snapshot = self.snapshot(window, cx);
17670 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17671 self.go_to_hunk_before_or_after_position(
17672 &snapshot,
17673 selection.head(),
17674 Direction::Next,
17675 window,
17676 cx,
17677 );
17678 }
17679
17680 pub fn go_to_hunk_before_or_after_position(
17681 &mut self,
17682 snapshot: &EditorSnapshot,
17683 position: Point,
17684 direction: Direction,
17685 window: &mut Window,
17686 cx: &mut Context<Editor>,
17687 ) {
17688 let row = if direction == Direction::Next {
17689 self.hunk_after_position(snapshot, position)
17690 .map(|hunk| hunk.row_range.start)
17691 } else {
17692 self.hunk_before_position(snapshot, position)
17693 };
17694
17695 if let Some(row) = row {
17696 let destination = Point::new(row.0, 0);
17697 let autoscroll = Autoscroll::center();
17698
17699 self.unfold_ranges(&[destination..destination], false, false, cx);
17700 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17701 s.select_ranges([destination..destination]);
17702 });
17703 }
17704 }
17705
17706 fn hunk_after_position(
17707 &mut self,
17708 snapshot: &EditorSnapshot,
17709 position: Point,
17710 ) -> Option<MultiBufferDiffHunk> {
17711 snapshot
17712 .buffer_snapshot()
17713 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17714 .find(|hunk| hunk.row_range.start.0 > position.row)
17715 .or_else(|| {
17716 snapshot
17717 .buffer_snapshot()
17718 .diff_hunks_in_range(Point::zero()..position)
17719 .find(|hunk| hunk.row_range.end.0 < position.row)
17720 })
17721 }
17722
17723 fn go_to_prev_hunk(
17724 &mut self,
17725 _: &GoToPreviousHunk,
17726 window: &mut Window,
17727 cx: &mut Context<Self>,
17728 ) {
17729 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17730 let snapshot = self.snapshot(window, cx);
17731 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17732 self.go_to_hunk_before_or_after_position(
17733 &snapshot,
17734 selection.head(),
17735 Direction::Prev,
17736 window,
17737 cx,
17738 );
17739 }
17740
17741 fn hunk_before_position(
17742 &mut self,
17743 snapshot: &EditorSnapshot,
17744 position: Point,
17745 ) -> Option<MultiBufferRow> {
17746 snapshot
17747 .buffer_snapshot()
17748 .diff_hunk_before(position)
17749 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17750 }
17751
17752 fn go_to_next_change(
17753 &mut self,
17754 _: &GoToNextChange,
17755 window: &mut Window,
17756 cx: &mut Context<Self>,
17757 ) {
17758 if let Some(selections) = self
17759 .change_list
17760 .next_change(1, Direction::Next)
17761 .map(|s| s.to_vec())
17762 {
17763 self.change_selections(Default::default(), window, cx, |s| {
17764 let map = s.display_snapshot();
17765 s.select_display_ranges(selections.iter().map(|a| {
17766 let point = a.to_display_point(&map);
17767 point..point
17768 }))
17769 })
17770 }
17771 }
17772
17773 fn go_to_previous_change(
17774 &mut self,
17775 _: &GoToPreviousChange,
17776 window: &mut Window,
17777 cx: &mut Context<Self>,
17778 ) {
17779 if let Some(selections) = self
17780 .change_list
17781 .next_change(1, Direction::Prev)
17782 .map(|s| s.to_vec())
17783 {
17784 self.change_selections(Default::default(), window, cx, |s| {
17785 let map = s.display_snapshot();
17786 s.select_display_ranges(selections.iter().map(|a| {
17787 let point = a.to_display_point(&map);
17788 point..point
17789 }))
17790 })
17791 }
17792 }
17793
17794 pub fn go_to_next_document_highlight(
17795 &mut self,
17796 _: &GoToNextDocumentHighlight,
17797 window: &mut Window,
17798 cx: &mut Context<Self>,
17799 ) {
17800 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17801 }
17802
17803 pub fn go_to_prev_document_highlight(
17804 &mut self,
17805 _: &GoToPreviousDocumentHighlight,
17806 window: &mut Window,
17807 cx: &mut Context<Self>,
17808 ) {
17809 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17810 }
17811
17812 pub fn go_to_document_highlight_before_or_after_position(
17813 &mut self,
17814 direction: Direction,
17815 window: &mut Window,
17816 cx: &mut Context<Editor>,
17817 ) {
17818 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17819 let snapshot = self.snapshot(window, cx);
17820 let buffer = &snapshot.buffer_snapshot();
17821 let position = self
17822 .selections
17823 .newest::<Point>(&snapshot.display_snapshot)
17824 .head();
17825 let anchor_position = buffer.anchor_after(position);
17826
17827 // Get all document highlights (both read and write)
17828 let mut all_highlights = Vec::new();
17829
17830 if let Some((_, read_highlights)) = self
17831 .background_highlights
17832 .get(&HighlightKey::DocumentHighlightRead)
17833 {
17834 all_highlights.extend(read_highlights.iter());
17835 }
17836
17837 if let Some((_, write_highlights)) = self
17838 .background_highlights
17839 .get(&HighlightKey::DocumentHighlightWrite)
17840 {
17841 all_highlights.extend(write_highlights.iter());
17842 }
17843
17844 if all_highlights.is_empty() {
17845 return;
17846 }
17847
17848 // Sort highlights by position
17849 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17850
17851 let target_highlight = match direction {
17852 Direction::Next => {
17853 // Find the first highlight after the current position
17854 all_highlights
17855 .iter()
17856 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17857 }
17858 Direction::Prev => {
17859 // Find the last highlight before the current position
17860 all_highlights
17861 .iter()
17862 .rev()
17863 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17864 }
17865 };
17866
17867 if let Some(highlight) = target_highlight {
17868 let destination = highlight.start.to_point(buffer);
17869 let autoscroll = Autoscroll::center();
17870
17871 self.unfold_ranges(&[destination..destination], false, false, cx);
17872 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17873 s.select_ranges([destination..destination]);
17874 });
17875 }
17876 }
17877
17878 fn go_to_line<T: 'static>(
17879 &mut self,
17880 position: Anchor,
17881 highlight_color: Option<Hsla>,
17882 window: &mut Window,
17883 cx: &mut Context<Self>,
17884 ) {
17885 let snapshot = self.snapshot(window, cx).display_snapshot;
17886 let position = position.to_point(&snapshot.buffer_snapshot());
17887 let start = snapshot
17888 .buffer_snapshot()
17889 .clip_point(Point::new(position.row, 0), Bias::Left);
17890 let end = start + Point::new(1, 0);
17891 let start = snapshot.buffer_snapshot().anchor_before(start);
17892 let end = snapshot.buffer_snapshot().anchor_before(end);
17893
17894 self.highlight_rows::<T>(
17895 start..end,
17896 highlight_color
17897 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17898 Default::default(),
17899 cx,
17900 );
17901
17902 if self.buffer.read(cx).is_singleton() {
17903 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17904 }
17905 }
17906
17907 pub fn go_to_definition(
17908 &mut self,
17909 _: &GoToDefinition,
17910 window: &mut Window,
17911 cx: &mut Context<Self>,
17912 ) -> Task<Result<Navigated>> {
17913 let definition =
17914 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17915 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17916 cx.spawn_in(window, async move |editor, cx| {
17917 if definition.await? == Navigated::Yes {
17918 return Ok(Navigated::Yes);
17919 }
17920 match fallback_strategy {
17921 GoToDefinitionFallback::None => Ok(Navigated::No),
17922 GoToDefinitionFallback::FindAllReferences => {
17923 match editor.update_in(cx, |editor, window, cx| {
17924 editor.find_all_references(&FindAllReferences::default(), window, cx)
17925 })? {
17926 Some(references) => references.await,
17927 None => Ok(Navigated::No),
17928 }
17929 }
17930 }
17931 })
17932 }
17933
17934 pub fn go_to_declaration(
17935 &mut self,
17936 _: &GoToDeclaration,
17937 window: &mut Window,
17938 cx: &mut Context<Self>,
17939 ) -> Task<Result<Navigated>> {
17940 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17941 }
17942
17943 pub fn go_to_declaration_split(
17944 &mut self,
17945 _: &GoToDeclaration,
17946 window: &mut Window,
17947 cx: &mut Context<Self>,
17948 ) -> Task<Result<Navigated>> {
17949 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17950 }
17951
17952 pub fn go_to_implementation(
17953 &mut self,
17954 _: &GoToImplementation,
17955 window: &mut Window,
17956 cx: &mut Context<Self>,
17957 ) -> Task<Result<Navigated>> {
17958 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17959 }
17960
17961 pub fn go_to_implementation_split(
17962 &mut self,
17963 _: &GoToImplementationSplit,
17964 window: &mut Window,
17965 cx: &mut Context<Self>,
17966 ) -> Task<Result<Navigated>> {
17967 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17968 }
17969
17970 pub fn go_to_type_definition(
17971 &mut self,
17972 _: &GoToTypeDefinition,
17973 window: &mut Window,
17974 cx: &mut Context<Self>,
17975 ) -> Task<Result<Navigated>> {
17976 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17977 }
17978
17979 pub fn go_to_definition_split(
17980 &mut self,
17981 _: &GoToDefinitionSplit,
17982 window: &mut Window,
17983 cx: &mut Context<Self>,
17984 ) -> Task<Result<Navigated>> {
17985 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17986 }
17987
17988 pub fn go_to_type_definition_split(
17989 &mut self,
17990 _: &GoToTypeDefinitionSplit,
17991 window: &mut Window,
17992 cx: &mut Context<Self>,
17993 ) -> Task<Result<Navigated>> {
17994 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17995 }
17996
17997 fn go_to_definition_of_kind(
17998 &mut self,
17999 kind: GotoDefinitionKind,
18000 split: bool,
18001 window: &mut Window,
18002 cx: &mut Context<Self>,
18003 ) -> Task<Result<Navigated>> {
18004 let Some(provider) = self.semantics_provider.clone() else {
18005 return Task::ready(Ok(Navigated::No));
18006 };
18007 let head = self
18008 .selections
18009 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18010 .head();
18011 let buffer = self.buffer.read(cx);
18012 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18013 return Task::ready(Ok(Navigated::No));
18014 };
18015 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18016 return Task::ready(Ok(Navigated::No));
18017 };
18018
18019 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18020
18021 cx.spawn_in(window, async move |editor, cx| {
18022 let Some(definitions) = definitions.await? else {
18023 return Ok(Navigated::No);
18024 };
18025 let navigated = editor
18026 .update_in(cx, |editor, window, cx| {
18027 editor.navigate_to_hover_links(
18028 Some(kind),
18029 definitions
18030 .into_iter()
18031 .filter(|location| {
18032 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18033 })
18034 .map(HoverLink::Text)
18035 .collect::<Vec<_>>(),
18036 nav_entry,
18037 split,
18038 window,
18039 cx,
18040 )
18041 })?
18042 .await?;
18043 anyhow::Ok(navigated)
18044 })
18045 }
18046
18047 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18048 let selection = self.selections.newest_anchor();
18049 let head = selection.head();
18050 let tail = selection.tail();
18051
18052 let Some((buffer, start_position)) =
18053 self.buffer.read(cx).text_anchor_for_position(head, cx)
18054 else {
18055 return;
18056 };
18057
18058 let end_position = if head != tail {
18059 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18060 return;
18061 };
18062 Some(pos)
18063 } else {
18064 None
18065 };
18066
18067 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18068 let url = if let Some(end_pos) = end_position {
18069 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18070 } else {
18071 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18072 };
18073
18074 if let Some(url) = url {
18075 cx.update(|window, cx| {
18076 if parse_zed_link(&url, cx).is_some() {
18077 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18078 } else {
18079 cx.open_url(&url);
18080 }
18081 })?;
18082 }
18083
18084 anyhow::Ok(())
18085 });
18086
18087 url_finder.detach();
18088 }
18089
18090 pub fn open_selected_filename(
18091 &mut self,
18092 _: &OpenSelectedFilename,
18093 window: &mut Window,
18094 cx: &mut Context<Self>,
18095 ) {
18096 let Some(workspace) = self.workspace() else {
18097 return;
18098 };
18099
18100 let position = self.selections.newest_anchor().head();
18101
18102 let Some((buffer, buffer_position)) =
18103 self.buffer.read(cx).text_anchor_for_position(position, cx)
18104 else {
18105 return;
18106 };
18107
18108 let project = self.project.clone();
18109
18110 cx.spawn_in(window, async move |_, cx| {
18111 let result = find_file(&buffer, project, buffer_position, cx).await;
18112
18113 if let Some((_, path)) = result {
18114 workspace
18115 .update_in(cx, |workspace, window, cx| {
18116 workspace.open_resolved_path(path, window, cx)
18117 })?
18118 .await?;
18119 }
18120 anyhow::Ok(())
18121 })
18122 .detach();
18123 }
18124
18125 pub(crate) fn navigate_to_hover_links(
18126 &mut self,
18127 kind: Option<GotoDefinitionKind>,
18128 definitions: Vec<HoverLink>,
18129 origin: Option<NavigationEntry>,
18130 split: bool,
18131 window: &mut Window,
18132 cx: &mut Context<Editor>,
18133 ) -> Task<Result<Navigated>> {
18134 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18135 let mut first_url_or_file = None;
18136 let definitions: Vec<_> = definitions
18137 .into_iter()
18138 .filter_map(|def| match def {
18139 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18140 HoverLink::InlayHint(lsp_location, server_id) => {
18141 let computation =
18142 self.compute_target_location(lsp_location, server_id, window, cx);
18143 Some(cx.background_spawn(computation))
18144 }
18145 HoverLink::Url(url) => {
18146 first_url_or_file = Some(Either::Left(url));
18147 None
18148 }
18149 HoverLink::File(path) => {
18150 first_url_or_file = Some(Either::Right(path));
18151 None
18152 }
18153 })
18154 .collect();
18155
18156 let workspace = self.workspace();
18157
18158 cx.spawn_in(window, async move |editor, cx| {
18159 let locations: Vec<Location> = future::join_all(definitions)
18160 .await
18161 .into_iter()
18162 .filter_map(|location| location.transpose())
18163 .collect::<Result<_>>()
18164 .context("location tasks")?;
18165 let mut locations = cx.update(|_, cx| {
18166 locations
18167 .into_iter()
18168 .map(|location| {
18169 let buffer = location.buffer.read(cx);
18170 (location.buffer, location.range.to_point(buffer))
18171 })
18172 .into_group_map()
18173 })?;
18174 let mut num_locations = 0;
18175 for ranges in locations.values_mut() {
18176 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18177 ranges.dedup();
18178 num_locations += ranges.len();
18179 }
18180
18181 if num_locations > 1 {
18182 let tab_kind = match kind {
18183 Some(GotoDefinitionKind::Implementation) => "Implementations",
18184 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18185 Some(GotoDefinitionKind::Declaration) => "Declarations",
18186 Some(GotoDefinitionKind::Type) => "Types",
18187 };
18188 let title = editor
18189 .update_in(cx, |_, _, cx| {
18190 let target = locations
18191 .iter()
18192 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18193 .map(|(buffer, location)| {
18194 buffer
18195 .read(cx)
18196 .text_for_range(location.clone())
18197 .collect::<String>()
18198 })
18199 .filter(|text| !text.contains('\n'))
18200 .unique()
18201 .take(3)
18202 .join(", ");
18203 if target.is_empty() {
18204 tab_kind.to_owned()
18205 } else {
18206 format!("{tab_kind} for {target}")
18207 }
18208 })
18209 .context("buffer title")?;
18210
18211 let Some(workspace) = workspace else {
18212 return Ok(Navigated::No);
18213 };
18214
18215 let opened = workspace
18216 .update_in(cx, |workspace, window, cx| {
18217 let allow_preview = PreviewTabsSettings::get_global(cx)
18218 .enable_preview_multibuffer_from_code_navigation;
18219 if let Some((target_editor, target_pane)) =
18220 Self::open_locations_in_multibuffer(
18221 workspace,
18222 locations,
18223 title,
18224 split,
18225 allow_preview,
18226 MultibufferSelectionMode::First,
18227 window,
18228 cx,
18229 )
18230 {
18231 // We create our own nav history instead of using
18232 // `target_editor.nav_history` because `nav_history`
18233 // seems to be populated asynchronously when an item
18234 // is added to a pane
18235 let mut nav_history = target_pane
18236 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18237 target_editor.update(cx, |editor, cx| {
18238 let nav_data = editor
18239 .navigation_data(editor.selections.newest_anchor().head(), cx);
18240 let target =
18241 Some(nav_history.navigation_entry(Some(
18242 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18243 )));
18244 nav_history.push_tag(origin, target);
18245 })
18246 }
18247 })
18248 .is_ok();
18249
18250 anyhow::Ok(Navigated::from_bool(opened))
18251 } else if num_locations == 0 {
18252 // If there is one url or file, open it directly
18253 match first_url_or_file {
18254 Some(Either::Left(url)) => {
18255 cx.update(|window, cx| {
18256 if parse_zed_link(&url, cx).is_some() {
18257 window
18258 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18259 } else {
18260 cx.open_url(&url);
18261 }
18262 })?;
18263 Ok(Navigated::Yes)
18264 }
18265 Some(Either::Right(path)) => {
18266 // TODO(andrew): respect preview tab settings
18267 // `enable_keep_preview_on_code_navigation` and
18268 // `enable_preview_file_from_code_navigation`
18269 let Some(workspace) = workspace else {
18270 return Ok(Navigated::No);
18271 };
18272 workspace
18273 .update_in(cx, |workspace, window, cx| {
18274 workspace.open_resolved_path(path, window, cx)
18275 })?
18276 .await?;
18277 Ok(Navigated::Yes)
18278 }
18279 None => Ok(Navigated::No),
18280 }
18281 } else {
18282 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18283 let target_range = target_ranges.first().unwrap().clone();
18284
18285 editor.update_in(cx, |editor, window, cx| {
18286 let range = editor.range_for_match(&target_range);
18287 let range = collapse_multiline_range(range);
18288
18289 if !split
18290 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18291 {
18292 editor.go_to_singleton_buffer_range(range, window, cx);
18293
18294 let target =
18295 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18296 if let Some(mut nav_history) = editor.nav_history.clone() {
18297 nav_history.push_tag(origin, target);
18298 }
18299 } else {
18300 let Some(workspace) = workspace else {
18301 return Navigated::No;
18302 };
18303 let pane = workspace.read(cx).active_pane().clone();
18304 window.defer(cx, move |window, cx| {
18305 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18306 workspace.update(cx, |workspace, cx| {
18307 let pane = if split {
18308 workspace.adjacent_pane(window, cx)
18309 } else {
18310 workspace.active_pane().clone()
18311 };
18312
18313 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18314 let keep_old_preview = preview_tabs_settings
18315 .enable_keep_preview_on_code_navigation;
18316 let allow_new_preview = preview_tabs_settings
18317 .enable_preview_file_from_code_navigation;
18318
18319 let editor = workspace.open_project_item(
18320 pane.clone(),
18321 target_buffer.clone(),
18322 true,
18323 true,
18324 keep_old_preview,
18325 allow_new_preview,
18326 window,
18327 cx,
18328 );
18329 (editor, pane)
18330 });
18331 // We create our own nav history instead of using
18332 // `target_editor.nav_history` because `nav_history`
18333 // seems to be populated asynchronously when an item
18334 // is added to a pane
18335 let mut nav_history = target_pane
18336 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18337 target_editor.update(cx, |target_editor, cx| {
18338 // When selecting a definition in a different buffer, disable the nav history
18339 // to avoid creating a history entry at the previous cursor location.
18340 pane.update(cx, |pane, _| pane.disable_history());
18341 target_editor.go_to_singleton_buffer_range(range, window, cx);
18342
18343 let nav_data = target_editor.navigation_data(
18344 target_editor.selections.newest_anchor().head(),
18345 cx,
18346 );
18347 let target =
18348 Some(nav_history.navigation_entry(Some(
18349 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18350 )));
18351 nav_history.push_tag(origin, target);
18352 pane.update(cx, |pane, _| pane.enable_history());
18353 });
18354 });
18355 }
18356 Navigated::Yes
18357 })
18358 }
18359 })
18360 }
18361
18362 fn compute_target_location(
18363 &self,
18364 lsp_location: lsp::Location,
18365 server_id: LanguageServerId,
18366 window: &mut Window,
18367 cx: &mut Context<Self>,
18368 ) -> Task<anyhow::Result<Option<Location>>> {
18369 let Some(project) = self.project.clone() else {
18370 return Task::ready(Ok(None));
18371 };
18372
18373 cx.spawn_in(window, async move |editor, cx| {
18374 let location_task = editor.update(cx, |_, cx| {
18375 project.update(cx, |project, cx| {
18376 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18377 })
18378 })?;
18379 let location = Some({
18380 let target_buffer_handle = location_task.await.context("open local buffer")?;
18381 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18382 let target_start = target_buffer
18383 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18384 let target_end = target_buffer
18385 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18386 target_buffer.anchor_after(target_start)
18387 ..target_buffer.anchor_before(target_end)
18388 });
18389 Location {
18390 buffer: target_buffer_handle,
18391 range,
18392 }
18393 });
18394 Ok(location)
18395 })
18396 }
18397
18398 fn go_to_next_reference(
18399 &mut self,
18400 _: &GoToNextReference,
18401 window: &mut Window,
18402 cx: &mut Context<Self>,
18403 ) {
18404 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18405 if let Some(task) = task {
18406 task.detach();
18407 };
18408 }
18409
18410 fn go_to_prev_reference(
18411 &mut self,
18412 _: &GoToPreviousReference,
18413 window: &mut Window,
18414 cx: &mut Context<Self>,
18415 ) {
18416 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18417 if let Some(task) = task {
18418 task.detach();
18419 };
18420 }
18421
18422 pub fn go_to_reference_before_or_after_position(
18423 &mut self,
18424 direction: Direction,
18425 count: usize,
18426 window: &mut Window,
18427 cx: &mut Context<Self>,
18428 ) -> Option<Task<Result<()>>> {
18429 let selection = self.selections.newest_anchor();
18430 let head = selection.head();
18431
18432 let multi_buffer = self.buffer.read(cx);
18433
18434 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18435 let workspace = self.workspace()?;
18436 let project = workspace.read(cx).project().clone();
18437 let references =
18438 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18439 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18440 let Some(locations) = references.await? else {
18441 return Ok(());
18442 };
18443
18444 if locations.is_empty() {
18445 // totally normal - the cursor may be on something which is not
18446 // a symbol (e.g. a keyword)
18447 log::info!("no references found under cursor");
18448 return Ok(());
18449 }
18450
18451 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18452
18453 let (locations, current_location_index) =
18454 multi_buffer.update(cx, |multi_buffer, cx| {
18455 let mut locations = locations
18456 .into_iter()
18457 .filter_map(|loc| {
18458 let start = multi_buffer.buffer_anchor_to_anchor(
18459 &loc.buffer,
18460 loc.range.start,
18461 cx,
18462 )?;
18463 let end = multi_buffer.buffer_anchor_to_anchor(
18464 &loc.buffer,
18465 loc.range.end,
18466 cx,
18467 )?;
18468 Some(start..end)
18469 })
18470 .collect::<Vec<_>>();
18471
18472 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18473 // There is an O(n) implementation, but given this list will be
18474 // small (usually <100 items), the extra O(log(n)) factor isn't
18475 // worth the (surprisingly large amount of) extra complexity.
18476 locations
18477 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18478
18479 let head_offset = head.to_offset(&multi_buffer_snapshot);
18480
18481 let current_location_index = locations.iter().position(|loc| {
18482 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18483 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18484 });
18485
18486 (locations, current_location_index)
18487 });
18488
18489 let Some(current_location_index) = current_location_index else {
18490 // This indicates something has gone wrong, because we already
18491 // handle the "no references" case above
18492 log::error!(
18493 "failed to find current reference under cursor. Total references: {}",
18494 locations.len()
18495 );
18496 return Ok(());
18497 };
18498
18499 let destination_location_index = match direction {
18500 Direction::Next => (current_location_index + count) % locations.len(),
18501 Direction::Prev => {
18502 (current_location_index + locations.len() - count % locations.len())
18503 % locations.len()
18504 }
18505 };
18506
18507 // TODO(cameron): is this needed?
18508 // the thinking is to avoid "jumping to the current location" (avoid
18509 // polluting "jumplist" in vim terms)
18510 if current_location_index == destination_location_index {
18511 return Ok(());
18512 }
18513
18514 let Range { start, end } = locations[destination_location_index];
18515
18516 editor.update_in(cx, |editor, window, cx| {
18517 let effects = SelectionEffects::default();
18518
18519 editor.unfold_ranges(&[start..end], false, false, cx);
18520 editor.change_selections(effects, window, cx, |s| {
18521 s.select_ranges([start..start]);
18522 });
18523 })?;
18524
18525 Ok(())
18526 }))
18527 }
18528
18529 pub fn find_all_references(
18530 &mut self,
18531 action: &FindAllReferences,
18532 window: &mut Window,
18533 cx: &mut Context<Self>,
18534 ) -> Option<Task<Result<Navigated>>> {
18535 let always_open_multibuffer = action.always_open_multibuffer;
18536 let selection = self.selections.newest_anchor();
18537 let multi_buffer = self.buffer.read(cx);
18538 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18539 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18540 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18541 let head = selection_offset.head();
18542
18543 let head_anchor = multi_buffer_snapshot.anchor_at(
18544 head,
18545 if head < selection_offset.tail() {
18546 Bias::Right
18547 } else {
18548 Bias::Left
18549 },
18550 );
18551
18552 match self
18553 .find_all_references_task_sources
18554 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18555 {
18556 Ok(_) => {
18557 log::info!(
18558 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18559 );
18560 return None;
18561 }
18562 Err(i) => {
18563 self.find_all_references_task_sources.insert(i, head_anchor);
18564 }
18565 }
18566
18567 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18568 let workspace = self.workspace()?;
18569 let project = workspace.read(cx).project().clone();
18570 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18571 Some(cx.spawn_in(window, async move |editor, cx| {
18572 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18573 if let Ok(i) = editor
18574 .find_all_references_task_sources
18575 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18576 {
18577 editor.find_all_references_task_sources.remove(i);
18578 }
18579 });
18580
18581 let Some(locations) = references.await? else {
18582 return anyhow::Ok(Navigated::No);
18583 };
18584 let mut locations = cx.update(|_, cx| {
18585 locations
18586 .into_iter()
18587 .map(|location| {
18588 let buffer = location.buffer.read(cx);
18589 (location.buffer, location.range.to_point(buffer))
18590 })
18591 // if special-casing the single-match case, remove ranges
18592 // that intersect current selection
18593 .filter(|(location_buffer, location)| {
18594 if always_open_multibuffer || &buffer != location_buffer {
18595 return true;
18596 }
18597
18598 !location.contains_inclusive(&selection_point.range())
18599 })
18600 .into_group_map()
18601 })?;
18602 if locations.is_empty() {
18603 return anyhow::Ok(Navigated::No);
18604 }
18605 for ranges in locations.values_mut() {
18606 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18607 ranges.dedup();
18608 }
18609 let mut num_locations = 0;
18610 for ranges in locations.values_mut() {
18611 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18612 ranges.dedup();
18613 num_locations += ranges.len();
18614 }
18615
18616 if num_locations == 1 && !always_open_multibuffer {
18617 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18618 let target_range = target_ranges.first().unwrap().clone();
18619
18620 return editor.update_in(cx, |editor, window, cx| {
18621 let range = target_range.to_point(target_buffer.read(cx));
18622 let range = editor.range_for_match(&range);
18623 let range = range.start..range.start;
18624
18625 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18626 editor.go_to_singleton_buffer_range(range, window, cx);
18627 } else {
18628 let pane = workspace.read(cx).active_pane().clone();
18629 window.defer(cx, move |window, cx| {
18630 let target_editor: Entity<Self> =
18631 workspace.update(cx, |workspace, cx| {
18632 let pane = workspace.active_pane().clone();
18633
18634 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18635 let keep_old_preview = preview_tabs_settings
18636 .enable_keep_preview_on_code_navigation;
18637 let allow_new_preview = preview_tabs_settings
18638 .enable_preview_file_from_code_navigation;
18639
18640 workspace.open_project_item(
18641 pane,
18642 target_buffer.clone(),
18643 true,
18644 true,
18645 keep_old_preview,
18646 allow_new_preview,
18647 window,
18648 cx,
18649 )
18650 });
18651 target_editor.update(cx, |target_editor, cx| {
18652 // When selecting a definition in a different buffer, disable the nav history
18653 // to avoid creating a history entry at the previous cursor location.
18654 pane.update(cx, |pane, _| pane.disable_history());
18655 target_editor.go_to_singleton_buffer_range(range, window, cx);
18656 pane.update(cx, |pane, _| pane.enable_history());
18657 });
18658 });
18659 }
18660 Navigated::No
18661 });
18662 }
18663
18664 workspace.update_in(cx, |workspace, window, cx| {
18665 let target = locations
18666 .iter()
18667 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18668 .map(|(buffer, location)| {
18669 buffer
18670 .read(cx)
18671 .text_for_range(location.clone())
18672 .collect::<String>()
18673 })
18674 .filter(|text| !text.contains('\n'))
18675 .unique()
18676 .take(3)
18677 .join(", ");
18678 let title = if target.is_empty() {
18679 "References".to_owned()
18680 } else {
18681 format!("References to {target}")
18682 };
18683 let allow_preview = PreviewTabsSettings::get_global(cx)
18684 .enable_preview_multibuffer_from_code_navigation;
18685 Self::open_locations_in_multibuffer(
18686 workspace,
18687 locations,
18688 title,
18689 false,
18690 allow_preview,
18691 MultibufferSelectionMode::First,
18692 window,
18693 cx,
18694 );
18695 Navigated::Yes
18696 })
18697 }))
18698 }
18699
18700 /// Opens a multibuffer with the given project locations in it.
18701 pub fn open_locations_in_multibuffer(
18702 workspace: &mut Workspace,
18703 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18704 title: String,
18705 split: bool,
18706 allow_preview: bool,
18707 multibuffer_selection_mode: MultibufferSelectionMode,
18708 window: &mut Window,
18709 cx: &mut Context<Workspace>,
18710 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18711 if locations.is_empty() {
18712 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18713 return None;
18714 }
18715
18716 let capability = workspace.project().read(cx).capability();
18717 let mut ranges = <Vec<Range<Anchor>>>::new();
18718
18719 // a key to find existing multibuffer editors with the same set of locations
18720 // to prevent us from opening more and more multibuffer tabs for searches and the like
18721 let mut key = (title.clone(), vec![]);
18722 let excerpt_buffer = cx.new(|cx| {
18723 let key = &mut key.1;
18724 let mut multibuffer = MultiBuffer::new(capability);
18725 for (buffer, mut ranges_for_buffer) in locations {
18726 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18727 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18728 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18729 PathKey::for_buffer(&buffer, cx),
18730 buffer.clone(),
18731 ranges_for_buffer,
18732 multibuffer_context_lines(cx),
18733 cx,
18734 );
18735 ranges.extend(new_ranges)
18736 }
18737
18738 multibuffer.with_title(title)
18739 });
18740 let existing = workspace.active_pane().update(cx, |pane, cx| {
18741 pane.items()
18742 .filter_map(|item| item.downcast::<Editor>())
18743 .find(|editor| {
18744 editor
18745 .read(cx)
18746 .lookup_key
18747 .as_ref()
18748 .and_then(|it| {
18749 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18750 })
18751 .is_some_and(|it| *it == key)
18752 })
18753 });
18754 let was_existing = existing.is_some();
18755 let editor = existing.unwrap_or_else(|| {
18756 cx.new(|cx| {
18757 let mut editor = Editor::for_multibuffer(
18758 excerpt_buffer,
18759 Some(workspace.project().clone()),
18760 window,
18761 cx,
18762 );
18763 editor.lookup_key = Some(Box::new(key));
18764 editor
18765 })
18766 });
18767 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18768 MultibufferSelectionMode::First => {
18769 if let Some(first_range) = ranges.first() {
18770 editor.change_selections(
18771 SelectionEffects::no_scroll(),
18772 window,
18773 cx,
18774 |selections| {
18775 selections.clear_disjoint();
18776 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18777 },
18778 );
18779 }
18780 editor.highlight_background(
18781 HighlightKey::Editor,
18782 &ranges,
18783 |_, theme| theme.colors().editor_highlighted_line_background,
18784 cx,
18785 );
18786 }
18787 MultibufferSelectionMode::All => {
18788 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18789 selections.clear_disjoint();
18790 selections.select_anchor_ranges(ranges);
18791 });
18792 }
18793 });
18794
18795 let item = Box::new(editor.clone());
18796
18797 let pane = if split {
18798 workspace.adjacent_pane(window, cx)
18799 } else {
18800 workspace.active_pane().clone()
18801 };
18802 let activate_pane = split;
18803
18804 let mut destination_index = None;
18805 pane.update(cx, |pane, cx| {
18806 if allow_preview && !was_existing {
18807 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18808 }
18809 if was_existing && !allow_preview {
18810 pane.unpreview_item_if_preview(item.item_id());
18811 }
18812 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18813 });
18814
18815 Some((editor, pane))
18816 }
18817
18818 pub fn rename(
18819 &mut self,
18820 _: &Rename,
18821 window: &mut Window,
18822 cx: &mut Context<Self>,
18823 ) -> Option<Task<Result<()>>> {
18824 use language::ToOffset as _;
18825
18826 let provider = self.semantics_provider.clone()?;
18827 let selection = self.selections.newest_anchor().clone();
18828 let (cursor_buffer, cursor_buffer_position) = self
18829 .buffer
18830 .read(cx)
18831 .text_anchor_for_position(selection.head(), cx)?;
18832 let (tail_buffer, cursor_buffer_position_end) = self
18833 .buffer
18834 .read(cx)
18835 .text_anchor_for_position(selection.tail(), cx)?;
18836 if tail_buffer != cursor_buffer {
18837 return None;
18838 }
18839
18840 let snapshot = cursor_buffer.read(cx).snapshot();
18841 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18842 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18843 let prepare_rename = provider
18844 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18845 .unwrap_or_else(|| Task::ready(Ok(None)));
18846 drop(snapshot);
18847
18848 Some(cx.spawn_in(window, async move |this, cx| {
18849 let rename_range = if let Some(range) = prepare_rename.await? {
18850 Some(range)
18851 } else {
18852 this.update(cx, |this, cx| {
18853 let buffer = this.buffer.read(cx).snapshot(cx);
18854 let mut buffer_highlights = this
18855 .document_highlights_for_position(selection.head(), &buffer)
18856 .filter(|highlight| {
18857 highlight.start.excerpt_id == selection.head().excerpt_id
18858 && highlight.end.excerpt_id == selection.head().excerpt_id
18859 });
18860 buffer_highlights
18861 .next()
18862 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18863 })?
18864 };
18865 if let Some(rename_range) = rename_range {
18866 this.update_in(cx, |this, window, cx| {
18867 let snapshot = cursor_buffer.read(cx).snapshot();
18868 let rename_buffer_range = rename_range.to_offset(&snapshot);
18869 let cursor_offset_in_rename_range =
18870 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18871 let cursor_offset_in_rename_range_end =
18872 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18873
18874 this.take_rename(false, window, cx);
18875 let buffer = this.buffer.read(cx).read(cx);
18876 let cursor_offset = selection.head().to_offset(&buffer);
18877 let rename_start =
18878 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18879 let rename_end = rename_start + rename_buffer_range.len();
18880 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18881 let mut old_highlight_id = None;
18882 let old_name: Arc<str> = buffer
18883 .chunks(rename_start..rename_end, true)
18884 .map(|chunk| {
18885 if old_highlight_id.is_none() {
18886 old_highlight_id = chunk.syntax_highlight_id;
18887 }
18888 chunk.text
18889 })
18890 .collect::<String>()
18891 .into();
18892
18893 drop(buffer);
18894
18895 // Position the selection in the rename editor so that it matches the current selection.
18896 this.show_local_selections = false;
18897 let rename_editor = cx.new(|cx| {
18898 let mut editor = Editor::single_line(window, cx);
18899 editor.buffer.update(cx, |buffer, cx| {
18900 buffer.edit(
18901 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18902 None,
18903 cx,
18904 )
18905 });
18906 let cursor_offset_in_rename_range =
18907 MultiBufferOffset(cursor_offset_in_rename_range);
18908 let cursor_offset_in_rename_range_end =
18909 MultiBufferOffset(cursor_offset_in_rename_range_end);
18910 let rename_selection_range = match cursor_offset_in_rename_range
18911 .cmp(&cursor_offset_in_rename_range_end)
18912 {
18913 Ordering::Equal => {
18914 editor.select_all(&SelectAll, window, cx);
18915 return editor;
18916 }
18917 Ordering::Less => {
18918 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18919 }
18920 Ordering::Greater => {
18921 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18922 }
18923 };
18924 if rename_selection_range.end.0 > old_name.len() {
18925 editor.select_all(&SelectAll, window, cx);
18926 } else {
18927 editor.change_selections(Default::default(), window, cx, |s| {
18928 s.select_ranges([rename_selection_range]);
18929 });
18930 }
18931 editor
18932 });
18933 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18934 if e == &EditorEvent::Focused {
18935 cx.emit(EditorEvent::FocusedIn)
18936 }
18937 })
18938 .detach();
18939
18940 let write_highlights =
18941 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
18942 let read_highlights =
18943 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
18944 let ranges = write_highlights
18945 .iter()
18946 .flat_map(|(_, ranges)| ranges.iter())
18947 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18948 .cloned()
18949 .collect();
18950
18951 this.highlight_text(
18952 HighlightKey::Rename,
18953 ranges,
18954 HighlightStyle {
18955 fade_out: Some(0.6),
18956 ..Default::default()
18957 },
18958 cx,
18959 );
18960 let rename_focus_handle = rename_editor.focus_handle(cx);
18961 window.focus(&rename_focus_handle, cx);
18962 let block_id = this.insert_blocks(
18963 [BlockProperties {
18964 style: BlockStyle::Flex,
18965 placement: BlockPlacement::Below(range.start),
18966 height: Some(1),
18967 render: Arc::new({
18968 let rename_editor = rename_editor.clone();
18969 move |cx: &mut BlockContext| {
18970 let mut text_style = cx.editor_style.text.clone();
18971 if let Some(highlight_style) = old_highlight_id
18972 .and_then(|h| h.style(&cx.editor_style.syntax))
18973 {
18974 text_style = text_style.highlight(highlight_style);
18975 }
18976 div()
18977 .block_mouse_except_scroll()
18978 .pl(cx.anchor_x)
18979 .child(EditorElement::new(
18980 &rename_editor,
18981 EditorStyle {
18982 background: cx.theme().system().transparent,
18983 local_player: cx.editor_style.local_player,
18984 text: text_style,
18985 scrollbar_width: cx.editor_style.scrollbar_width,
18986 syntax: cx.editor_style.syntax.clone(),
18987 status: cx.editor_style.status.clone(),
18988 inlay_hints_style: HighlightStyle {
18989 font_weight: Some(FontWeight::BOLD),
18990 ..make_inlay_hints_style(cx.app)
18991 },
18992 edit_prediction_styles: make_suggestion_styles(
18993 cx.app,
18994 ),
18995 ..EditorStyle::default()
18996 },
18997 ))
18998 .into_any_element()
18999 }
19000 }),
19001 priority: 0,
19002 }],
19003 Some(Autoscroll::fit()),
19004 cx,
19005 )[0];
19006 this.pending_rename = Some(RenameState {
19007 range,
19008 old_name,
19009 editor: rename_editor,
19010 block_id,
19011 });
19012 })?;
19013 }
19014
19015 Ok(())
19016 }))
19017 }
19018
19019 pub fn confirm_rename(
19020 &mut self,
19021 _: &ConfirmRename,
19022 window: &mut Window,
19023 cx: &mut Context<Self>,
19024 ) -> Option<Task<Result<()>>> {
19025 let rename = self.take_rename(false, window, cx)?;
19026 let workspace = self.workspace()?.downgrade();
19027 let (buffer, start) = self
19028 .buffer
19029 .read(cx)
19030 .text_anchor_for_position(rename.range.start, cx)?;
19031 let (end_buffer, _) = self
19032 .buffer
19033 .read(cx)
19034 .text_anchor_for_position(rename.range.end, cx)?;
19035 if buffer != end_buffer {
19036 return None;
19037 }
19038
19039 let old_name = rename.old_name;
19040 let new_name = rename.editor.read(cx).text(cx);
19041
19042 let rename = self.semantics_provider.as_ref()?.perform_rename(
19043 &buffer,
19044 start,
19045 new_name.clone(),
19046 cx,
19047 )?;
19048
19049 Some(cx.spawn_in(window, async move |editor, cx| {
19050 let project_transaction = rename.await?;
19051 Self::open_project_transaction(
19052 &editor,
19053 workspace,
19054 project_transaction,
19055 format!("Rename: {} → {}", old_name, new_name),
19056 cx,
19057 )
19058 .await?;
19059
19060 editor.update(cx, |editor, cx| {
19061 editor.refresh_document_highlights(cx);
19062 })?;
19063 Ok(())
19064 }))
19065 }
19066
19067 fn take_rename(
19068 &mut self,
19069 moving_cursor: bool,
19070 window: &mut Window,
19071 cx: &mut Context<Self>,
19072 ) -> Option<RenameState> {
19073 let rename = self.pending_rename.take()?;
19074 if rename.editor.focus_handle(cx).is_focused(window) {
19075 window.focus(&self.focus_handle, cx);
19076 }
19077
19078 self.remove_blocks(
19079 [rename.block_id].into_iter().collect(),
19080 Some(Autoscroll::fit()),
19081 cx,
19082 );
19083 self.clear_highlights(HighlightKey::Rename, cx);
19084 self.show_local_selections = true;
19085
19086 if moving_cursor {
19087 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19088 editor
19089 .selections
19090 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19091 .head()
19092 });
19093
19094 // Update the selection to match the position of the selection inside
19095 // the rename editor.
19096 let snapshot = self.buffer.read(cx).read(cx);
19097 let rename_range = rename.range.to_offset(&snapshot);
19098 let cursor_in_editor = snapshot
19099 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19100 .min(rename_range.end);
19101 drop(snapshot);
19102
19103 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19104 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19105 });
19106 } else {
19107 self.refresh_document_highlights(cx);
19108 }
19109
19110 Some(rename)
19111 }
19112
19113 pub fn pending_rename(&self) -> Option<&RenameState> {
19114 self.pending_rename.as_ref()
19115 }
19116
19117 fn format(
19118 &mut self,
19119 _: &Format,
19120 window: &mut Window,
19121 cx: &mut Context<Self>,
19122 ) -> Option<Task<Result<()>>> {
19123 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19124
19125 let project = match &self.project {
19126 Some(project) => project.clone(),
19127 None => return None,
19128 };
19129
19130 Some(self.perform_format(
19131 project,
19132 FormatTrigger::Manual,
19133 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19134 window,
19135 cx,
19136 ))
19137 }
19138
19139 fn format_selections(
19140 &mut self,
19141 _: &FormatSelections,
19142 window: &mut Window,
19143 cx: &mut Context<Self>,
19144 ) -> Option<Task<Result<()>>> {
19145 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19146
19147 let project = match &self.project {
19148 Some(project) => project.clone(),
19149 None => return None,
19150 };
19151
19152 let ranges = self
19153 .selections
19154 .all_adjusted(&self.display_snapshot(cx))
19155 .into_iter()
19156 .map(|selection| selection.range())
19157 .collect_vec();
19158
19159 Some(self.perform_format(
19160 project,
19161 FormatTrigger::Manual,
19162 FormatTarget::Ranges(ranges),
19163 window,
19164 cx,
19165 ))
19166 }
19167
19168 fn perform_format(
19169 &mut self,
19170 project: Entity<Project>,
19171 trigger: FormatTrigger,
19172 target: FormatTarget,
19173 window: &mut Window,
19174 cx: &mut Context<Self>,
19175 ) -> Task<Result<()>> {
19176 let buffer = self.buffer.clone();
19177 let (buffers, target) = match target {
19178 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19179 FormatTarget::Ranges(selection_ranges) => {
19180 let multi_buffer = buffer.read(cx);
19181 let snapshot = multi_buffer.read(cx);
19182 let mut buffers = HashSet::default();
19183 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19184 BTreeMap::new();
19185 for selection_range in selection_ranges {
19186 for (buffer, buffer_range, _) in
19187 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19188 {
19189 let buffer_id = buffer.remote_id();
19190 let start = buffer.anchor_before(buffer_range.start);
19191 let end = buffer.anchor_after(buffer_range.end);
19192 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19193 buffer_id_to_ranges
19194 .entry(buffer_id)
19195 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19196 .or_insert_with(|| vec![start..end]);
19197 }
19198 }
19199 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19200 }
19201 };
19202
19203 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19204 let selections_prev = transaction_id_prev
19205 .and_then(|transaction_id_prev| {
19206 // default to selections as they were after the last edit, if we have them,
19207 // instead of how they are now.
19208 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19209 // will take you back to where you made the last edit, instead of staying where you scrolled
19210 self.selection_history
19211 .transaction(transaction_id_prev)
19212 .map(|t| t.0.clone())
19213 })
19214 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19215
19216 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19217 let format = project.update(cx, |project, cx| {
19218 project.format(buffers, target, true, trigger, cx)
19219 });
19220
19221 cx.spawn_in(window, async move |editor, cx| {
19222 let transaction = futures::select_biased! {
19223 transaction = format.log_err().fuse() => transaction,
19224 () = timeout => {
19225 log::warn!("timed out waiting for formatting");
19226 None
19227 }
19228 };
19229
19230 buffer.update(cx, |buffer, cx| {
19231 if let Some(transaction) = transaction
19232 && !buffer.is_singleton()
19233 {
19234 buffer.push_transaction(&transaction.0, cx);
19235 }
19236 cx.notify();
19237 });
19238
19239 if let Some(transaction_id_now) =
19240 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19241 {
19242 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19243 if has_new_transaction {
19244 editor
19245 .update(cx, |editor, _| {
19246 editor
19247 .selection_history
19248 .insert_transaction(transaction_id_now, selections_prev);
19249 })
19250 .ok();
19251 }
19252 }
19253
19254 Ok(())
19255 })
19256 }
19257
19258 fn organize_imports(
19259 &mut self,
19260 _: &OrganizeImports,
19261 window: &mut Window,
19262 cx: &mut Context<Self>,
19263 ) -> Option<Task<Result<()>>> {
19264 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19265 let project = match &self.project {
19266 Some(project) => project.clone(),
19267 None => return None,
19268 };
19269 Some(self.perform_code_action_kind(
19270 project,
19271 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19272 window,
19273 cx,
19274 ))
19275 }
19276
19277 fn perform_code_action_kind(
19278 &mut self,
19279 project: Entity<Project>,
19280 kind: CodeActionKind,
19281 window: &mut Window,
19282 cx: &mut Context<Self>,
19283 ) -> Task<Result<()>> {
19284 let buffer = self.buffer.clone();
19285 let buffers = buffer.read(cx).all_buffers();
19286 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19287 let apply_action = project.update(cx, |project, cx| {
19288 project.apply_code_action_kind(buffers, kind, true, cx)
19289 });
19290 cx.spawn_in(window, async move |_, cx| {
19291 let transaction = futures::select_biased! {
19292 () = timeout => {
19293 log::warn!("timed out waiting for executing code action");
19294 None
19295 }
19296 transaction = apply_action.log_err().fuse() => transaction,
19297 };
19298 buffer.update(cx, |buffer, cx| {
19299 // check if we need this
19300 if let Some(transaction) = transaction
19301 && !buffer.is_singleton()
19302 {
19303 buffer.push_transaction(&transaction.0, cx);
19304 }
19305 cx.notify();
19306 });
19307 Ok(())
19308 })
19309 }
19310
19311 pub fn restart_language_server(
19312 &mut self,
19313 _: &RestartLanguageServer,
19314 _: &mut Window,
19315 cx: &mut Context<Self>,
19316 ) {
19317 if let Some(project) = self.project.clone() {
19318 self.buffer.update(cx, |multi_buffer, cx| {
19319 project.update(cx, |project, cx| {
19320 project.restart_language_servers_for_buffers(
19321 multi_buffer.all_buffers().into_iter().collect(),
19322 HashSet::default(),
19323 cx,
19324 );
19325 });
19326 })
19327 }
19328 }
19329
19330 pub fn stop_language_server(
19331 &mut self,
19332 _: &StopLanguageServer,
19333 _: &mut Window,
19334 cx: &mut Context<Self>,
19335 ) {
19336 if let Some(project) = self.project.clone() {
19337 self.buffer.update(cx, |multi_buffer, cx| {
19338 project.update(cx, |project, cx| {
19339 project.stop_language_servers_for_buffers(
19340 multi_buffer.all_buffers().into_iter().collect(),
19341 HashSet::default(),
19342 cx,
19343 );
19344 });
19345 });
19346 }
19347 }
19348
19349 fn cancel_language_server_work(
19350 workspace: &mut Workspace,
19351 _: &actions::CancelLanguageServerWork,
19352 _: &mut Window,
19353 cx: &mut Context<Workspace>,
19354 ) {
19355 let project = workspace.project();
19356 let buffers = workspace
19357 .active_item(cx)
19358 .and_then(|item| item.act_as::<Editor>(cx))
19359 .map_or(HashSet::default(), |editor| {
19360 editor.read(cx).buffer.read(cx).all_buffers()
19361 });
19362 project.update(cx, |project, cx| {
19363 project.cancel_language_server_work_for_buffers(buffers, cx);
19364 });
19365 }
19366
19367 fn show_character_palette(
19368 &mut self,
19369 _: &ShowCharacterPalette,
19370 window: &mut Window,
19371 _: &mut Context<Self>,
19372 ) {
19373 window.show_character_palette();
19374 }
19375
19376 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19377 if !self.diagnostics_enabled() {
19378 return;
19379 }
19380
19381 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19382 let buffer = self.buffer.read(cx).snapshot(cx);
19383 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19384 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19385 let is_valid = buffer
19386 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19387 .any(|entry| {
19388 entry.diagnostic.is_primary
19389 && !entry.range.is_empty()
19390 && entry.range.start == primary_range_start
19391 && entry.diagnostic.message == active_diagnostics.active_message
19392 });
19393
19394 if !is_valid {
19395 self.dismiss_diagnostics(cx);
19396 }
19397 }
19398 }
19399
19400 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19401 match &self.active_diagnostics {
19402 ActiveDiagnostic::Group(group) => Some(group),
19403 _ => None,
19404 }
19405 }
19406
19407 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19408 if !self.diagnostics_enabled() {
19409 return;
19410 }
19411 self.dismiss_diagnostics(cx);
19412 self.active_diagnostics = ActiveDiagnostic::All;
19413 }
19414
19415 fn activate_diagnostics(
19416 &mut self,
19417 buffer_id: BufferId,
19418 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19419 window: &mut Window,
19420 cx: &mut Context<Self>,
19421 ) {
19422 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19423 return;
19424 }
19425 self.dismiss_diagnostics(cx);
19426 let snapshot = self.snapshot(window, cx);
19427 let buffer = self.buffer.read(cx).snapshot(cx);
19428 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19429 return;
19430 };
19431
19432 let diagnostic_group = buffer
19433 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19434 .collect::<Vec<_>>();
19435
19436 let language_registry = self
19437 .project()
19438 .map(|project| project.read(cx).languages().clone());
19439
19440 let blocks = renderer.render_group(
19441 diagnostic_group,
19442 buffer_id,
19443 snapshot,
19444 cx.weak_entity(),
19445 language_registry,
19446 cx,
19447 );
19448
19449 let blocks = self.display_map.update(cx, |display_map, cx| {
19450 display_map.insert_blocks(blocks, cx).into_iter().collect()
19451 });
19452 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19453 active_range: buffer.anchor_before(diagnostic.range.start)
19454 ..buffer.anchor_after(diagnostic.range.end),
19455 active_message: diagnostic.diagnostic.message.clone(),
19456 group_id: diagnostic.diagnostic.group_id,
19457 blocks,
19458 });
19459 cx.notify();
19460 }
19461
19462 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19463 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19464 return;
19465 };
19466
19467 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19468 if let ActiveDiagnostic::Group(group) = prev {
19469 self.display_map.update(cx, |display_map, cx| {
19470 display_map.remove_blocks(group.blocks, cx);
19471 });
19472 cx.notify();
19473 }
19474 }
19475
19476 /// Disable inline diagnostics rendering for this editor.
19477 pub fn disable_inline_diagnostics(&mut self) {
19478 self.inline_diagnostics_enabled = false;
19479 self.inline_diagnostics_update = Task::ready(());
19480 self.inline_diagnostics.clear();
19481 }
19482
19483 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19484 self.diagnostics_enabled = false;
19485 self.dismiss_diagnostics(cx);
19486 self.inline_diagnostics_update = Task::ready(());
19487 self.inline_diagnostics.clear();
19488 }
19489
19490 pub fn disable_word_completions(&mut self) {
19491 self.word_completions_enabled = false;
19492 }
19493
19494 pub fn diagnostics_enabled(&self) -> bool {
19495 self.diagnostics_enabled && self.mode.is_full()
19496 }
19497
19498 pub fn inline_diagnostics_enabled(&self) -> bool {
19499 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19500 }
19501
19502 pub fn show_inline_diagnostics(&self) -> bool {
19503 self.show_inline_diagnostics
19504 }
19505
19506 pub fn toggle_inline_diagnostics(
19507 &mut self,
19508 _: &ToggleInlineDiagnostics,
19509 window: &mut Window,
19510 cx: &mut Context<Editor>,
19511 ) {
19512 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19513 self.refresh_inline_diagnostics(false, window, cx);
19514 }
19515
19516 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19517 self.diagnostics_max_severity = severity;
19518 self.display_map.update(cx, |display_map, _| {
19519 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19520 });
19521 }
19522
19523 pub fn toggle_diagnostics(
19524 &mut self,
19525 _: &ToggleDiagnostics,
19526 window: &mut Window,
19527 cx: &mut Context<Editor>,
19528 ) {
19529 if !self.diagnostics_enabled() {
19530 return;
19531 }
19532
19533 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19534 EditorSettings::get_global(cx)
19535 .diagnostics_max_severity
19536 .filter(|severity| severity != &DiagnosticSeverity::Off)
19537 .unwrap_or(DiagnosticSeverity::Hint)
19538 } else {
19539 DiagnosticSeverity::Off
19540 };
19541 self.set_max_diagnostics_severity(new_severity, cx);
19542 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19543 self.active_diagnostics = ActiveDiagnostic::None;
19544 self.inline_diagnostics_update = Task::ready(());
19545 self.inline_diagnostics.clear();
19546 } else {
19547 self.refresh_inline_diagnostics(false, window, cx);
19548 }
19549
19550 cx.notify();
19551 }
19552
19553 pub fn toggle_minimap(
19554 &mut self,
19555 _: &ToggleMinimap,
19556 window: &mut Window,
19557 cx: &mut Context<Editor>,
19558 ) {
19559 if self.supports_minimap(cx) {
19560 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19561 }
19562 }
19563
19564 fn refresh_inline_diagnostics(
19565 &mut self,
19566 debounce: bool,
19567 window: &mut Window,
19568 cx: &mut Context<Self>,
19569 ) {
19570 let max_severity = ProjectSettings::get_global(cx)
19571 .diagnostics
19572 .inline
19573 .max_severity
19574 .unwrap_or(self.diagnostics_max_severity);
19575
19576 if !self.inline_diagnostics_enabled()
19577 || !self.diagnostics_enabled()
19578 || !self.show_inline_diagnostics
19579 || max_severity == DiagnosticSeverity::Off
19580 {
19581 self.inline_diagnostics_update = Task::ready(());
19582 self.inline_diagnostics.clear();
19583 return;
19584 }
19585
19586 let debounce_ms = ProjectSettings::get_global(cx)
19587 .diagnostics
19588 .inline
19589 .update_debounce_ms;
19590 let debounce = if debounce && debounce_ms > 0 {
19591 Some(Duration::from_millis(debounce_ms))
19592 } else {
19593 None
19594 };
19595 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19596 if let Some(debounce) = debounce {
19597 cx.background_executor().timer(debounce).await;
19598 }
19599 let Some(snapshot) = editor.upgrade().map(|editor| {
19600 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19601 }) else {
19602 return;
19603 };
19604
19605 let new_inline_diagnostics = cx
19606 .background_spawn(async move {
19607 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19608 for diagnostic_entry in
19609 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19610 {
19611 let message = diagnostic_entry
19612 .diagnostic
19613 .message
19614 .split_once('\n')
19615 .map(|(line, _)| line)
19616 .map(SharedString::new)
19617 .unwrap_or_else(|| {
19618 SharedString::new(&*diagnostic_entry.diagnostic.message)
19619 });
19620 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19621 let (Ok(i) | Err(i)) = inline_diagnostics
19622 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19623 inline_diagnostics.insert(
19624 i,
19625 (
19626 start_anchor,
19627 InlineDiagnostic {
19628 message,
19629 group_id: diagnostic_entry.diagnostic.group_id,
19630 start: diagnostic_entry.range.start.to_point(&snapshot),
19631 is_primary: diagnostic_entry.diagnostic.is_primary,
19632 severity: diagnostic_entry.diagnostic.severity,
19633 },
19634 ),
19635 );
19636 }
19637 inline_diagnostics
19638 })
19639 .await;
19640
19641 editor
19642 .update(cx, |editor, cx| {
19643 editor.inline_diagnostics = new_inline_diagnostics;
19644 cx.notify();
19645 })
19646 .ok();
19647 });
19648 }
19649
19650 fn pull_diagnostics(
19651 &mut self,
19652 buffer_id: BufferId,
19653 _window: &Window,
19654 cx: &mut Context<Self>,
19655 ) -> Option<()> {
19656 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19657 // skip any LSP updates for it.
19658
19659 if self.active_diagnostics == ActiveDiagnostic::All
19660 || !self.mode().is_full()
19661 || !self.diagnostics_enabled()
19662 {
19663 return None;
19664 }
19665 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19666 .diagnostics
19667 .lsp_pull_diagnostics;
19668 if !pull_diagnostics_settings.enabled {
19669 return None;
19670 }
19671 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19672 let project = self.project()?.downgrade();
19673 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19674
19675 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19676 cx.background_executor().timer(debounce).await;
19677 if let Ok(task) = project.update(cx, |project, cx| {
19678 project.lsp_store().update(cx, |lsp_store, cx| {
19679 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19680 })
19681 }) {
19682 task.await.log_err();
19683 }
19684 project
19685 .update(cx, |project, cx| {
19686 project.lsp_store().update(cx, |lsp_store, cx| {
19687 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19688 })
19689 })
19690 .log_err();
19691 });
19692
19693 Some(())
19694 }
19695
19696 pub fn set_selections_from_remote(
19697 &mut self,
19698 selections: Vec<Selection<Anchor>>,
19699 pending_selection: Option<Selection<Anchor>>,
19700 window: &mut Window,
19701 cx: &mut Context<Self>,
19702 ) {
19703 let old_cursor_position = self.selections.newest_anchor().head();
19704 self.selections
19705 .change_with(&self.display_snapshot(cx), |s| {
19706 s.select_anchors(selections);
19707 if let Some(pending_selection) = pending_selection {
19708 s.set_pending(pending_selection, SelectMode::Character);
19709 } else {
19710 s.clear_pending();
19711 }
19712 });
19713 self.selections_did_change(
19714 false,
19715 &old_cursor_position,
19716 SelectionEffects::default(),
19717 window,
19718 cx,
19719 );
19720 }
19721
19722 pub fn transact(
19723 &mut self,
19724 window: &mut Window,
19725 cx: &mut Context<Self>,
19726 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19727 ) -> Option<TransactionId> {
19728 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19729 this.start_transaction_at(Instant::now(), window, cx);
19730 update(this, window, cx);
19731 this.end_transaction_at(Instant::now(), cx)
19732 })
19733 }
19734
19735 pub fn start_transaction_at(
19736 &mut self,
19737 now: Instant,
19738 window: &mut Window,
19739 cx: &mut Context<Self>,
19740 ) -> Option<TransactionId> {
19741 self.end_selection(window, cx);
19742 if let Some(tx_id) = self
19743 .buffer
19744 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19745 {
19746 self.selection_history
19747 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19748 cx.emit(EditorEvent::TransactionBegun {
19749 transaction_id: tx_id,
19750 });
19751 Some(tx_id)
19752 } else {
19753 None
19754 }
19755 }
19756
19757 pub fn end_transaction_at(
19758 &mut self,
19759 now: Instant,
19760 cx: &mut Context<Self>,
19761 ) -> Option<TransactionId> {
19762 if let Some(transaction_id) = self
19763 .buffer
19764 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19765 {
19766 if let Some((_, end_selections)) =
19767 self.selection_history.transaction_mut(transaction_id)
19768 {
19769 *end_selections = Some(self.selections.disjoint_anchors_arc());
19770 } else {
19771 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19772 }
19773
19774 cx.emit(EditorEvent::Edited { transaction_id });
19775 Some(transaction_id)
19776 } else {
19777 None
19778 }
19779 }
19780
19781 pub fn modify_transaction_selection_history(
19782 &mut self,
19783 transaction_id: TransactionId,
19784 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19785 ) -> bool {
19786 self.selection_history
19787 .transaction_mut(transaction_id)
19788 .map(modify)
19789 .is_some()
19790 }
19791
19792 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19793 if self.selection_mark_mode {
19794 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19795 s.move_with(&mut |_, sel| {
19796 sel.collapse_to(sel.head(), SelectionGoal::None);
19797 });
19798 })
19799 }
19800 self.selection_mark_mode = true;
19801 cx.notify();
19802 }
19803
19804 pub fn swap_selection_ends(
19805 &mut self,
19806 _: &actions::SwapSelectionEnds,
19807 window: &mut Window,
19808 cx: &mut Context<Self>,
19809 ) {
19810 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19811 s.move_with(&mut |_, sel| {
19812 if sel.start != sel.end {
19813 sel.reversed = !sel.reversed
19814 }
19815 });
19816 });
19817 self.request_autoscroll(Autoscroll::newest(), cx);
19818 cx.notify();
19819 }
19820
19821 pub fn toggle_focus(
19822 workspace: &mut Workspace,
19823 _: &actions::ToggleFocus,
19824 window: &mut Window,
19825 cx: &mut Context<Workspace>,
19826 ) {
19827 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19828 return;
19829 };
19830 workspace.activate_item(&item, true, true, window, cx);
19831 }
19832
19833 pub fn toggle_fold(
19834 &mut self,
19835 _: &actions::ToggleFold,
19836 window: &mut Window,
19837 cx: &mut Context<Self>,
19838 ) {
19839 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19841 let selection = self.selections.newest::<Point>(&display_map);
19842
19843 let range = if selection.is_empty() {
19844 let point = selection.head().to_display_point(&display_map);
19845 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19846 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19847 .to_point(&display_map);
19848 start..end
19849 } else {
19850 selection.range()
19851 };
19852 if display_map.folds_in_range(range).next().is_some() {
19853 self.unfold_lines(&Default::default(), window, cx)
19854 } else {
19855 self.fold(&Default::default(), window, cx)
19856 }
19857 } else {
19858 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19859 let buffer_ids: HashSet<_> = self
19860 .selections
19861 .disjoint_anchor_ranges()
19862 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19863 .collect();
19864
19865 let should_unfold = buffer_ids
19866 .iter()
19867 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19868
19869 for buffer_id in buffer_ids {
19870 if should_unfold {
19871 self.unfold_buffer(buffer_id, cx);
19872 } else {
19873 self.fold_buffer(buffer_id, cx);
19874 }
19875 }
19876 }
19877 }
19878
19879 pub fn toggle_fold_recursive(
19880 &mut self,
19881 _: &actions::ToggleFoldRecursive,
19882 window: &mut Window,
19883 cx: &mut Context<Self>,
19884 ) {
19885 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19886
19887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19888 let range = if selection.is_empty() {
19889 let point = selection.head().to_display_point(&display_map);
19890 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19891 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19892 .to_point(&display_map);
19893 start..end
19894 } else {
19895 selection.range()
19896 };
19897 if display_map.folds_in_range(range).next().is_some() {
19898 self.unfold_recursive(&Default::default(), window, cx)
19899 } else {
19900 self.fold_recursive(&Default::default(), window, cx)
19901 }
19902 }
19903
19904 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19905 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19906 let mut to_fold = Vec::new();
19907 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19908 let selections = self.selections.all_adjusted(&display_map);
19909
19910 for selection in selections {
19911 let range = selection.range().sorted();
19912 let buffer_start_row = range.start.row;
19913
19914 if range.start.row != range.end.row {
19915 let mut found = false;
19916 let mut row = range.start.row;
19917 while row <= range.end.row {
19918 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19919 {
19920 found = true;
19921 row = crease.range().end.row + 1;
19922 to_fold.push(crease);
19923 } else {
19924 row += 1
19925 }
19926 }
19927 if found {
19928 continue;
19929 }
19930 }
19931
19932 for row in (0..=range.start.row).rev() {
19933 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19934 && crease.range().end.row >= buffer_start_row
19935 {
19936 to_fold.push(crease);
19937 if row <= range.start.row {
19938 break;
19939 }
19940 }
19941 }
19942 }
19943
19944 self.fold_creases(to_fold, true, window, cx);
19945 } else {
19946 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19947 let buffer_ids = self
19948 .selections
19949 .disjoint_anchor_ranges()
19950 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19951 .collect::<HashSet<_>>();
19952 for buffer_id in buffer_ids {
19953 self.fold_buffer(buffer_id, cx);
19954 }
19955 }
19956 }
19957
19958 pub fn toggle_fold_all(
19959 &mut self,
19960 _: &actions::ToggleFoldAll,
19961 window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 let has_folds = if self.buffer.read(cx).is_singleton() {
19965 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19966 let has_folds = display_map
19967 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19968 .next()
19969 .is_some();
19970 has_folds
19971 } else {
19972 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19973 let has_folds = buffer_ids
19974 .iter()
19975 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19976 has_folds
19977 };
19978
19979 if has_folds {
19980 self.unfold_all(&actions::UnfoldAll, window, cx);
19981 } else {
19982 self.fold_all(&actions::FoldAll, window, cx);
19983 }
19984 }
19985
19986 fn fold_at_level(
19987 &mut self,
19988 fold_at: &FoldAtLevel,
19989 window: &mut Window,
19990 cx: &mut Context<Self>,
19991 ) {
19992 if !self.buffer.read(cx).is_singleton() {
19993 return;
19994 }
19995
19996 let fold_at_level = fold_at.0;
19997 let snapshot = self.buffer.read(cx).snapshot(cx);
19998 let mut to_fold = Vec::new();
19999 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20000
20001 let row_ranges_to_keep: Vec<Range<u32>> = self
20002 .selections
20003 .all::<Point>(&self.display_snapshot(cx))
20004 .into_iter()
20005 .map(|sel| sel.start.row..sel.end.row)
20006 .collect();
20007
20008 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20009 while start_row < end_row {
20010 match self
20011 .snapshot(window, cx)
20012 .crease_for_buffer_row(MultiBufferRow(start_row))
20013 {
20014 Some(crease) => {
20015 let nested_start_row = crease.range().start.row + 1;
20016 let nested_end_row = crease.range().end.row;
20017
20018 if current_level < fold_at_level {
20019 stack.push((nested_start_row, nested_end_row, current_level + 1));
20020 } else if current_level == fold_at_level {
20021 // Fold iff there is no selection completely contained within the fold region
20022 if !row_ranges_to_keep.iter().any(|selection| {
20023 selection.end >= nested_start_row
20024 && selection.start <= nested_end_row
20025 }) {
20026 to_fold.push(crease);
20027 }
20028 }
20029
20030 start_row = nested_end_row + 1;
20031 }
20032 None => start_row += 1,
20033 }
20034 }
20035 }
20036
20037 self.fold_creases(to_fold, true, window, cx);
20038 }
20039
20040 pub fn fold_at_level_1(
20041 &mut self,
20042 _: &actions::FoldAtLevel1,
20043 window: &mut Window,
20044 cx: &mut Context<Self>,
20045 ) {
20046 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20047 }
20048
20049 pub fn fold_at_level_2(
20050 &mut self,
20051 _: &actions::FoldAtLevel2,
20052 window: &mut Window,
20053 cx: &mut Context<Self>,
20054 ) {
20055 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20056 }
20057
20058 pub fn fold_at_level_3(
20059 &mut self,
20060 _: &actions::FoldAtLevel3,
20061 window: &mut Window,
20062 cx: &mut Context<Self>,
20063 ) {
20064 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20065 }
20066
20067 pub fn fold_at_level_4(
20068 &mut self,
20069 _: &actions::FoldAtLevel4,
20070 window: &mut Window,
20071 cx: &mut Context<Self>,
20072 ) {
20073 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20074 }
20075
20076 pub fn fold_at_level_5(
20077 &mut self,
20078 _: &actions::FoldAtLevel5,
20079 window: &mut Window,
20080 cx: &mut Context<Self>,
20081 ) {
20082 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20083 }
20084
20085 pub fn fold_at_level_6(
20086 &mut self,
20087 _: &actions::FoldAtLevel6,
20088 window: &mut Window,
20089 cx: &mut Context<Self>,
20090 ) {
20091 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20092 }
20093
20094 pub fn fold_at_level_7(
20095 &mut self,
20096 _: &actions::FoldAtLevel7,
20097 window: &mut Window,
20098 cx: &mut Context<Self>,
20099 ) {
20100 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20101 }
20102
20103 pub fn fold_at_level_8(
20104 &mut self,
20105 _: &actions::FoldAtLevel8,
20106 window: &mut Window,
20107 cx: &mut Context<Self>,
20108 ) {
20109 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20110 }
20111
20112 pub fn fold_at_level_9(
20113 &mut self,
20114 _: &actions::FoldAtLevel9,
20115 window: &mut Window,
20116 cx: &mut Context<Self>,
20117 ) {
20118 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20119 }
20120
20121 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20122 if self.buffer.read(cx).is_singleton() {
20123 let mut fold_ranges = Vec::new();
20124 let snapshot = self.buffer.read(cx).snapshot(cx);
20125
20126 for row in 0..snapshot.max_row().0 {
20127 if let Some(foldable_range) = self
20128 .snapshot(window, cx)
20129 .crease_for_buffer_row(MultiBufferRow(row))
20130 {
20131 fold_ranges.push(foldable_range);
20132 }
20133 }
20134
20135 self.fold_creases(fold_ranges, true, window, cx);
20136 } else {
20137 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20138 editor
20139 .update_in(cx, |editor, _, cx| {
20140 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20141 editor.fold_buffer(buffer_id, cx);
20142 }
20143 })
20144 .ok();
20145 });
20146 }
20147 }
20148
20149 pub fn fold_function_bodies(
20150 &mut self,
20151 _: &actions::FoldFunctionBodies,
20152 window: &mut Window,
20153 cx: &mut Context<Self>,
20154 ) {
20155 let snapshot = self.buffer.read(cx).snapshot(cx);
20156
20157 let ranges = snapshot
20158 .text_object_ranges(
20159 MultiBufferOffset(0)..snapshot.len(),
20160 TreeSitterOptions::default(),
20161 )
20162 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20163 .collect::<Vec<_>>();
20164
20165 let creases = ranges
20166 .into_iter()
20167 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20168 .collect();
20169
20170 self.fold_creases(creases, true, window, cx);
20171 }
20172
20173 pub fn fold_recursive(
20174 &mut self,
20175 _: &actions::FoldRecursive,
20176 window: &mut Window,
20177 cx: &mut Context<Self>,
20178 ) {
20179 let mut to_fold = Vec::new();
20180 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20181 let selections = self.selections.all_adjusted(&display_map);
20182
20183 for selection in selections {
20184 let range = selection.range().sorted();
20185 let buffer_start_row = range.start.row;
20186
20187 if range.start.row != range.end.row {
20188 let mut found = false;
20189 for row in range.start.row..=range.end.row {
20190 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20191 found = true;
20192 to_fold.push(crease);
20193 }
20194 }
20195 if found {
20196 continue;
20197 }
20198 }
20199
20200 for row in (0..=range.start.row).rev() {
20201 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20202 if crease.range().end.row >= buffer_start_row {
20203 to_fold.push(crease);
20204 } else {
20205 break;
20206 }
20207 }
20208 }
20209 }
20210
20211 self.fold_creases(to_fold, true, window, cx);
20212 }
20213
20214 pub fn fold_at(
20215 &mut self,
20216 buffer_row: MultiBufferRow,
20217 window: &mut Window,
20218 cx: &mut Context<Self>,
20219 ) {
20220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20221
20222 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20223 let autoscroll = self
20224 .selections
20225 .all::<Point>(&display_map)
20226 .iter()
20227 .any(|selection| crease.range().overlaps(&selection.range()));
20228
20229 self.fold_creases(vec![crease], autoscroll, window, cx);
20230 }
20231 }
20232
20233 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20234 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20235 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20236 let buffer = display_map.buffer_snapshot();
20237 let selections = self.selections.all::<Point>(&display_map);
20238 let ranges = selections
20239 .iter()
20240 .map(|s| {
20241 let range = s.display_range(&display_map).sorted();
20242 let mut start = range.start.to_point(&display_map);
20243 let mut end = range.end.to_point(&display_map);
20244 start.column = 0;
20245 end.column = buffer.line_len(MultiBufferRow(end.row));
20246 start..end
20247 })
20248 .collect::<Vec<_>>();
20249
20250 self.unfold_ranges(&ranges, true, true, cx);
20251 } else {
20252 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20253 let buffer_ids = self
20254 .selections
20255 .disjoint_anchor_ranges()
20256 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20257 .collect::<HashSet<_>>();
20258 for buffer_id in buffer_ids {
20259 self.unfold_buffer(buffer_id, cx);
20260 }
20261 }
20262 }
20263
20264 pub fn unfold_recursive(
20265 &mut self,
20266 _: &UnfoldRecursive,
20267 _window: &mut Window,
20268 cx: &mut Context<Self>,
20269 ) {
20270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20271 let selections = self.selections.all::<Point>(&display_map);
20272 let ranges = selections
20273 .iter()
20274 .map(|s| {
20275 let mut range = s.display_range(&display_map).sorted();
20276 *range.start.column_mut() = 0;
20277 *range.end.column_mut() = display_map.line_len(range.end.row());
20278 let start = range.start.to_point(&display_map);
20279 let end = range.end.to_point(&display_map);
20280 start..end
20281 })
20282 .collect::<Vec<_>>();
20283
20284 self.unfold_ranges(&ranges, true, true, cx);
20285 }
20286
20287 pub fn unfold_at(
20288 &mut self,
20289 buffer_row: MultiBufferRow,
20290 _window: &mut Window,
20291 cx: &mut Context<Self>,
20292 ) {
20293 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20294
20295 let intersection_range = Point::new(buffer_row.0, 0)
20296 ..Point::new(
20297 buffer_row.0,
20298 display_map.buffer_snapshot().line_len(buffer_row),
20299 );
20300
20301 let autoscroll = self
20302 .selections
20303 .all::<Point>(&display_map)
20304 .iter()
20305 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20306
20307 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20308 }
20309
20310 pub fn unfold_all(
20311 &mut self,
20312 _: &actions::UnfoldAll,
20313 _window: &mut Window,
20314 cx: &mut Context<Self>,
20315 ) {
20316 if self.buffer.read(cx).is_singleton() {
20317 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20318 self.unfold_ranges(
20319 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20320 true,
20321 true,
20322 cx,
20323 );
20324 } else {
20325 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20326 editor
20327 .update(cx, |editor, cx| {
20328 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20329 editor.unfold_buffer(buffer_id, cx);
20330 }
20331 })
20332 .ok();
20333 });
20334 }
20335 }
20336
20337 pub fn fold_selected_ranges(
20338 &mut self,
20339 _: &FoldSelectedRanges,
20340 window: &mut Window,
20341 cx: &mut Context<Self>,
20342 ) {
20343 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20344 let selections = self.selections.all_adjusted(&display_map);
20345 let ranges = selections
20346 .into_iter()
20347 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20348 .collect::<Vec<_>>();
20349 self.fold_creases(ranges, true, window, cx);
20350 }
20351
20352 pub fn fold_ranges<T: ToOffset + Clone>(
20353 &mut self,
20354 ranges: Vec<Range<T>>,
20355 auto_scroll: bool,
20356 window: &mut Window,
20357 cx: &mut Context<Self>,
20358 ) {
20359 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20360 let ranges = ranges
20361 .into_iter()
20362 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20363 .collect::<Vec<_>>();
20364 self.fold_creases(ranges, auto_scroll, window, cx);
20365 }
20366
20367 pub fn fold_creases<T: ToOffset + Clone>(
20368 &mut self,
20369 creases: Vec<Crease<T>>,
20370 auto_scroll: bool,
20371 _window: &mut Window,
20372 cx: &mut Context<Self>,
20373 ) {
20374 if creases.is_empty() {
20375 return;
20376 }
20377
20378 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20379
20380 if auto_scroll {
20381 self.request_autoscroll(Autoscroll::fit(), cx);
20382 }
20383
20384 cx.notify();
20385
20386 self.scrollbar_marker_state.dirty = true;
20387 self.folds_did_change(cx);
20388 }
20389
20390 /// Removes any folds whose ranges intersect any of the given ranges.
20391 pub fn unfold_ranges<T: ToOffset + Clone>(
20392 &mut self,
20393 ranges: &[Range<T>],
20394 inclusive: bool,
20395 auto_scroll: bool,
20396 cx: &mut Context<Self>,
20397 ) {
20398 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20399 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20400 });
20401 self.folds_did_change(cx);
20402 }
20403
20404 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20405 self.fold_buffers([buffer_id], cx);
20406 }
20407
20408 pub fn fold_buffers(
20409 &mut self,
20410 buffer_ids: impl IntoIterator<Item = BufferId>,
20411 cx: &mut Context<Self>,
20412 ) {
20413 if self.buffer().read(cx).is_singleton() {
20414 return;
20415 }
20416
20417 let ids_to_fold: Vec<BufferId> = buffer_ids
20418 .into_iter()
20419 .filter(|id| !self.is_buffer_folded(*id, cx))
20420 .collect();
20421
20422 if ids_to_fold.is_empty() {
20423 return;
20424 }
20425
20426 let mut all_folded_excerpt_ids = Vec::new();
20427 for buffer_id in &ids_to_fold {
20428 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20429 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _)| id));
20430 }
20431
20432 self.display_map.update(cx, |display_map, cx| {
20433 display_map.fold_buffers(ids_to_fold.clone(), cx)
20434 });
20435
20436 let snapshot = self.display_snapshot(cx);
20437 self.selections.change_with(&snapshot, |selections| {
20438 for buffer_id in ids_to_fold {
20439 selections.remove_selections_from_buffer(buffer_id);
20440 }
20441 });
20442
20443 cx.emit(EditorEvent::BufferFoldToggled {
20444 ids: all_folded_excerpt_ids,
20445 folded: true,
20446 });
20447 cx.notify();
20448 }
20449
20450 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20451 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20452 return;
20453 }
20454 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20455 self.display_map.update(cx, |display_map, cx| {
20456 display_map.unfold_buffers([buffer_id], cx);
20457 });
20458 cx.emit(EditorEvent::BufferFoldToggled {
20459 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20460 folded: false,
20461 });
20462 cx.notify();
20463 }
20464
20465 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20466 self.display_map.read(cx).is_buffer_folded(buffer)
20467 }
20468
20469 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20470 if self.buffer().read(cx).is_singleton() {
20471 return false;
20472 }
20473 !self.folded_buffers(cx).is_empty()
20474 }
20475
20476 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20477 self.display_map.read(cx).folded_buffers()
20478 }
20479
20480 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20481 self.display_map.update(cx, |display_map, cx| {
20482 display_map.disable_header_for_buffer(buffer_id, cx);
20483 });
20484 cx.notify();
20485 }
20486
20487 /// Removes any folds with the given ranges.
20488 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20489 &mut self,
20490 ranges: &[Range<T>],
20491 type_id: TypeId,
20492 auto_scroll: bool,
20493 cx: &mut Context<Self>,
20494 ) {
20495 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20496 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20497 });
20498 self.folds_did_change(cx);
20499 }
20500
20501 fn remove_folds_with<T: ToOffset + Clone>(
20502 &mut self,
20503 ranges: &[Range<T>],
20504 auto_scroll: bool,
20505 cx: &mut Context<Self>,
20506 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20507 ) {
20508 if ranges.is_empty() {
20509 return;
20510 }
20511
20512 let mut buffers_affected = HashSet::default();
20513 let multi_buffer = self.buffer().read(cx);
20514 for range in ranges {
20515 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20516 buffers_affected.insert(buffer.read(cx).remote_id());
20517 };
20518 }
20519
20520 self.display_map.update(cx, update);
20521
20522 if auto_scroll {
20523 self.request_autoscroll(Autoscroll::fit(), cx);
20524 }
20525
20526 cx.notify();
20527 self.scrollbar_marker_state.dirty = true;
20528 self.active_indent_guides_state.dirty = true;
20529 }
20530
20531 pub fn update_renderer_widths(
20532 &mut self,
20533 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20534 cx: &mut Context<Self>,
20535 ) -> bool {
20536 self.display_map
20537 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20538 }
20539
20540 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20541 self.display_map.read(cx).fold_placeholder.clone()
20542 }
20543
20544 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20545 self.buffer.update(cx, |buffer, cx| {
20546 buffer.set_all_diff_hunks_expanded(cx);
20547 });
20548 }
20549
20550 pub fn expand_all_diff_hunks(
20551 &mut self,
20552 _: &ExpandAllDiffHunks,
20553 _window: &mut Window,
20554 cx: &mut Context<Self>,
20555 ) {
20556 self.buffer.update(cx, |buffer, cx| {
20557 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20558 });
20559 }
20560
20561 pub fn collapse_all_diff_hunks(
20562 &mut self,
20563 _: &CollapseAllDiffHunks,
20564 _window: &mut Window,
20565 cx: &mut Context<Self>,
20566 ) {
20567 self.buffer.update(cx, |buffer, cx| {
20568 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20569 });
20570 }
20571
20572 pub fn toggle_selected_diff_hunks(
20573 &mut self,
20574 _: &ToggleSelectedDiffHunks,
20575 _window: &mut Window,
20576 cx: &mut Context<Self>,
20577 ) {
20578 let ranges: Vec<_> = self
20579 .selections
20580 .disjoint_anchors()
20581 .iter()
20582 .map(|s| s.range())
20583 .collect();
20584 self.toggle_diff_hunks_in_ranges(ranges, cx);
20585 }
20586
20587 pub fn diff_hunks_in_ranges<'a>(
20588 &'a self,
20589 ranges: &'a [Range<Anchor>],
20590 buffer: &'a MultiBufferSnapshot,
20591 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20592 ranges.iter().flat_map(move |range| {
20593 let end_excerpt_id = range.end.excerpt_id;
20594 let range = range.to_point(buffer);
20595 let mut peek_end = range.end;
20596 if range.end.row < buffer.max_row().0 {
20597 peek_end = Point::new(range.end.row + 1, 0);
20598 }
20599 buffer
20600 .diff_hunks_in_range(range.start..peek_end)
20601 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20602 })
20603 }
20604
20605 pub fn has_stageable_diff_hunks_in_ranges(
20606 &self,
20607 ranges: &[Range<Anchor>],
20608 snapshot: &MultiBufferSnapshot,
20609 ) -> bool {
20610 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20611 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20612 }
20613
20614 pub fn toggle_staged_selected_diff_hunks(
20615 &mut self,
20616 _: &::git::ToggleStaged,
20617 _: &mut Window,
20618 cx: &mut Context<Self>,
20619 ) {
20620 let snapshot = self.buffer.read(cx).snapshot(cx);
20621 let ranges: Vec<_> = self
20622 .selections
20623 .disjoint_anchors()
20624 .iter()
20625 .map(|s| s.range())
20626 .collect();
20627 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20628 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20629 }
20630
20631 pub fn set_render_diff_hunk_controls(
20632 &mut self,
20633 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20634 cx: &mut Context<Self>,
20635 ) {
20636 self.render_diff_hunk_controls = render_diff_hunk_controls;
20637 cx.notify();
20638 }
20639
20640 pub fn stage_and_next(
20641 &mut self,
20642 _: &::git::StageAndNext,
20643 window: &mut Window,
20644 cx: &mut Context<Self>,
20645 ) {
20646 self.do_stage_or_unstage_and_next(true, window, cx);
20647 }
20648
20649 pub fn unstage_and_next(
20650 &mut self,
20651 _: &::git::UnstageAndNext,
20652 window: &mut Window,
20653 cx: &mut Context<Self>,
20654 ) {
20655 self.do_stage_or_unstage_and_next(false, window, cx);
20656 }
20657
20658 pub fn stage_or_unstage_diff_hunks(
20659 &mut self,
20660 stage: bool,
20661 ranges: Vec<Range<Anchor>>,
20662 cx: &mut Context<Self>,
20663 ) {
20664 if self.delegate_stage_and_restore {
20665 let snapshot = self.buffer.read(cx).snapshot(cx);
20666 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20667 if !hunks.is_empty() {
20668 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20669 }
20670 return;
20671 }
20672 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20673 cx.spawn(async move |this, cx| {
20674 task.await?;
20675 this.update(cx, |this, cx| {
20676 let snapshot = this.buffer.read(cx).snapshot(cx);
20677 let chunk_by = this
20678 .diff_hunks_in_ranges(&ranges, &snapshot)
20679 .chunk_by(|hunk| hunk.buffer_id);
20680 for (buffer_id, hunks) in &chunk_by {
20681 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20682 }
20683 })
20684 })
20685 .detach_and_log_err(cx);
20686 }
20687
20688 fn save_buffers_for_ranges_if_needed(
20689 &mut self,
20690 ranges: &[Range<Anchor>],
20691 cx: &mut Context<Editor>,
20692 ) -> Task<Result<()>> {
20693 let multibuffer = self.buffer.read(cx);
20694 let snapshot = multibuffer.read(cx);
20695 let buffer_ids: HashSet<_> = ranges
20696 .iter()
20697 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20698 .collect();
20699 drop(snapshot);
20700
20701 let mut buffers = HashSet::default();
20702 for buffer_id in buffer_ids {
20703 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20704 let buffer = buffer_entity.read(cx);
20705 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20706 {
20707 buffers.insert(buffer_entity);
20708 }
20709 }
20710 }
20711
20712 if let Some(project) = &self.project {
20713 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20714 } else {
20715 Task::ready(Ok(()))
20716 }
20717 }
20718
20719 fn do_stage_or_unstage_and_next(
20720 &mut self,
20721 stage: bool,
20722 window: &mut Window,
20723 cx: &mut Context<Self>,
20724 ) {
20725 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20726
20727 if ranges.iter().any(|range| range.start != range.end) {
20728 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20729 return;
20730 }
20731
20732 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20733 let snapshot = self.snapshot(window, cx);
20734 let position = self
20735 .selections
20736 .newest::<Point>(&snapshot.display_snapshot)
20737 .head();
20738 let mut row = snapshot
20739 .buffer_snapshot()
20740 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20741 .find(|hunk| hunk.row_range.start.0 > position.row)
20742 .map(|hunk| hunk.row_range.start);
20743
20744 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20745 // Outside of the project diff editor, wrap around to the beginning.
20746 if !all_diff_hunks_expanded {
20747 row = row.or_else(|| {
20748 snapshot
20749 .buffer_snapshot()
20750 .diff_hunks_in_range(Point::zero()..position)
20751 .find(|hunk| hunk.row_range.end.0 < position.row)
20752 .map(|hunk| hunk.row_range.start)
20753 });
20754 }
20755
20756 if let Some(row) = row {
20757 let destination = Point::new(row.0, 0);
20758 let autoscroll = Autoscroll::center();
20759
20760 self.unfold_ranges(&[destination..destination], false, false, cx);
20761 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20762 s.select_ranges([destination..destination]);
20763 });
20764 }
20765 }
20766
20767 pub(crate) fn do_stage_or_unstage(
20768 &self,
20769 stage: bool,
20770 buffer_id: BufferId,
20771 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20772 cx: &mut App,
20773 ) -> Option<()> {
20774 let project = self.project()?;
20775 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20776 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20777 let buffer_snapshot = buffer.read(cx).snapshot();
20778 let file_exists = buffer_snapshot
20779 .file()
20780 .is_some_and(|file| file.disk_state().exists());
20781 diff.update(cx, |diff, cx| {
20782 diff.stage_or_unstage_hunks(
20783 stage,
20784 &hunks
20785 .map(|hunk| buffer_diff::DiffHunk {
20786 buffer_range: hunk.buffer_range,
20787 // We don't need to pass in word diffs here because they're only used for rendering and
20788 // this function changes internal state
20789 base_word_diffs: Vec::default(),
20790 buffer_word_diffs: Vec::default(),
20791 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20792 ..hunk.diff_base_byte_range.end.0,
20793 secondary_status: hunk.status.secondary,
20794 range: Point::zero()..Point::zero(), // unused
20795 })
20796 .collect::<Vec<_>>(),
20797 &buffer_snapshot,
20798 file_exists,
20799 cx,
20800 )
20801 });
20802 None
20803 }
20804
20805 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20806 let ranges: Vec<_> = self
20807 .selections
20808 .disjoint_anchors()
20809 .iter()
20810 .map(|s| s.range())
20811 .collect();
20812 self.buffer
20813 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20814 }
20815
20816 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20817 self.buffer.update(cx, |buffer, cx| {
20818 let ranges = vec![Anchor::min()..Anchor::max()];
20819 if !buffer.all_diff_hunks_expanded()
20820 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20821 {
20822 buffer.collapse_diff_hunks(ranges, cx);
20823 true
20824 } else {
20825 false
20826 }
20827 })
20828 }
20829
20830 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20831 if self.buffer.read(cx).all_diff_hunks_expanded() {
20832 return true;
20833 }
20834 let ranges = vec![Anchor::min()..Anchor::max()];
20835 self.buffer
20836 .read(cx)
20837 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20838 }
20839
20840 fn toggle_diff_hunks_in_ranges(
20841 &mut self,
20842 ranges: Vec<Range<Anchor>>,
20843 cx: &mut Context<Editor>,
20844 ) {
20845 self.buffer.update(cx, |buffer, cx| {
20846 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20847 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20848 })
20849 }
20850
20851 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20852 self.buffer.update(cx, |buffer, cx| {
20853 buffer.toggle_single_diff_hunk(range, cx);
20854 })
20855 }
20856
20857 pub(crate) fn apply_all_diff_hunks(
20858 &mut self,
20859 _: &ApplyAllDiffHunks,
20860 window: &mut Window,
20861 cx: &mut Context<Self>,
20862 ) {
20863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20864
20865 let buffers = self.buffer.read(cx).all_buffers();
20866 for branch_buffer in buffers {
20867 branch_buffer.update(cx, |branch_buffer, cx| {
20868 branch_buffer.merge_into_base(Vec::new(), cx);
20869 });
20870 }
20871
20872 if let Some(project) = self.project.clone() {
20873 self.save(
20874 SaveOptions {
20875 format: true,
20876 autosave: false,
20877 },
20878 project,
20879 window,
20880 cx,
20881 )
20882 .detach_and_log_err(cx);
20883 }
20884 }
20885
20886 pub(crate) fn apply_selected_diff_hunks(
20887 &mut self,
20888 _: &ApplyDiffHunk,
20889 window: &mut Window,
20890 cx: &mut Context<Self>,
20891 ) {
20892 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20893 let snapshot = self.snapshot(window, cx);
20894 let hunks = snapshot.hunks_for_ranges(
20895 self.selections
20896 .all(&snapshot.display_snapshot)
20897 .into_iter()
20898 .map(|selection| selection.range()),
20899 );
20900 let mut ranges_by_buffer = HashMap::default();
20901 self.transact(window, cx, |editor, _window, cx| {
20902 for hunk in hunks {
20903 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20904 ranges_by_buffer
20905 .entry(buffer.clone())
20906 .or_insert_with(Vec::new)
20907 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20908 }
20909 }
20910
20911 for (buffer, ranges) in ranges_by_buffer {
20912 buffer.update(cx, |buffer, cx| {
20913 buffer.merge_into_base(ranges, cx);
20914 });
20915 }
20916 });
20917
20918 if let Some(project) = self.project.clone() {
20919 self.save(
20920 SaveOptions {
20921 format: true,
20922 autosave: false,
20923 },
20924 project,
20925 window,
20926 cx,
20927 )
20928 .detach_and_log_err(cx);
20929 }
20930 }
20931
20932 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20933 if hovered != self.gutter_hovered {
20934 self.gutter_hovered = hovered;
20935 cx.notify();
20936 }
20937 }
20938
20939 pub fn insert_blocks(
20940 &mut self,
20941 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20942 autoscroll: Option<Autoscroll>,
20943 cx: &mut Context<Self>,
20944 ) -> Vec<CustomBlockId> {
20945 let blocks = self
20946 .display_map
20947 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20948 if let Some(autoscroll) = autoscroll {
20949 self.request_autoscroll(autoscroll, cx);
20950 }
20951 cx.notify();
20952 blocks
20953 }
20954
20955 pub fn resize_blocks(
20956 &mut self,
20957 heights: HashMap<CustomBlockId, u32>,
20958 autoscroll: Option<Autoscroll>,
20959 cx: &mut Context<Self>,
20960 ) {
20961 self.display_map
20962 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20963 if let Some(autoscroll) = autoscroll {
20964 self.request_autoscroll(autoscroll, cx);
20965 }
20966 cx.notify();
20967 }
20968
20969 pub fn replace_blocks(
20970 &mut self,
20971 renderers: HashMap<CustomBlockId, RenderBlock>,
20972 autoscroll: Option<Autoscroll>,
20973 cx: &mut Context<Self>,
20974 ) {
20975 self.display_map
20976 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20977 if let Some(autoscroll) = autoscroll {
20978 self.request_autoscroll(autoscroll, cx);
20979 }
20980 cx.notify();
20981 }
20982
20983 pub fn remove_blocks(
20984 &mut self,
20985 block_ids: HashSet<CustomBlockId>,
20986 autoscroll: Option<Autoscroll>,
20987 cx: &mut Context<Self>,
20988 ) {
20989 self.display_map.update(cx, |display_map, cx| {
20990 display_map.remove_blocks(block_ids, cx)
20991 });
20992 if let Some(autoscroll) = autoscroll {
20993 self.request_autoscroll(autoscroll, cx);
20994 }
20995 cx.notify();
20996 }
20997
20998 pub fn row_for_block(
20999 &self,
21000 block_id: CustomBlockId,
21001 cx: &mut Context<Self>,
21002 ) -> Option<DisplayRow> {
21003 self.display_map
21004 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21005 }
21006
21007 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21008 self.focused_block = Some(focused_block);
21009 }
21010
21011 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21012 self.focused_block.take()
21013 }
21014
21015 pub fn insert_creases(
21016 &mut self,
21017 creases: impl IntoIterator<Item = Crease<Anchor>>,
21018 cx: &mut Context<Self>,
21019 ) -> Vec<CreaseId> {
21020 self.display_map
21021 .update(cx, |map, cx| map.insert_creases(creases, cx))
21022 }
21023
21024 pub fn remove_creases(
21025 &mut self,
21026 ids: impl IntoIterator<Item = CreaseId>,
21027 cx: &mut Context<Self>,
21028 ) -> Vec<(CreaseId, Range<Anchor>)> {
21029 self.display_map
21030 .update(cx, |map, cx| map.remove_creases(ids, cx))
21031 }
21032
21033 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21034 self.display_map
21035 .update(cx, |map, cx| map.snapshot(cx))
21036 .longest_row()
21037 }
21038
21039 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21040 self.display_map
21041 .update(cx, |map, cx| map.snapshot(cx))
21042 .max_point()
21043 }
21044
21045 pub fn text(&self, cx: &App) -> String {
21046 self.buffer.read(cx).read(cx).text()
21047 }
21048
21049 pub fn is_empty(&self, cx: &App) -> bool {
21050 self.buffer.read(cx).read(cx).is_empty()
21051 }
21052
21053 pub fn text_option(&self, cx: &App) -> Option<String> {
21054 let text = self.text(cx);
21055 let text = text.trim();
21056
21057 if text.is_empty() {
21058 return None;
21059 }
21060
21061 Some(text.to_string())
21062 }
21063
21064 pub fn set_text(
21065 &mut self,
21066 text: impl Into<Arc<str>>,
21067 window: &mut Window,
21068 cx: &mut Context<Self>,
21069 ) {
21070 self.transact(window, cx, |this, _, cx| {
21071 this.buffer
21072 .read(cx)
21073 .as_singleton()
21074 .expect("you can only call set_text on editors for singleton buffers")
21075 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21076 });
21077 }
21078
21079 pub fn display_text(&self, cx: &mut App) -> String {
21080 self.display_map
21081 .update(cx, |map, cx| map.snapshot(cx))
21082 .text()
21083 }
21084
21085 fn create_minimap(
21086 &self,
21087 minimap_settings: MinimapSettings,
21088 window: &mut Window,
21089 cx: &mut Context<Self>,
21090 ) -> Option<Entity<Self>> {
21091 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21092 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21093 }
21094
21095 fn initialize_new_minimap(
21096 &self,
21097 minimap_settings: MinimapSettings,
21098 window: &mut Window,
21099 cx: &mut Context<Self>,
21100 ) -> Entity<Self> {
21101 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21102 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21103
21104 let mut minimap = Editor::new_internal(
21105 EditorMode::Minimap {
21106 parent: cx.weak_entity(),
21107 },
21108 self.buffer.clone(),
21109 None,
21110 Some(self.display_map.clone()),
21111 window,
21112 cx,
21113 );
21114 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21115 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21116 minimap.scroll_manager.clone_state(
21117 &self.scroll_manager,
21118 &my_snapshot,
21119 &minimap_snapshot,
21120 cx,
21121 );
21122 minimap.set_text_style_refinement(TextStyleRefinement {
21123 font_size: Some(MINIMAP_FONT_SIZE),
21124 font_weight: Some(MINIMAP_FONT_WEIGHT),
21125 font_family: Some(MINIMAP_FONT_FAMILY),
21126 ..Default::default()
21127 });
21128 minimap.update_minimap_configuration(minimap_settings, cx);
21129 cx.new(|_| minimap)
21130 }
21131
21132 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21133 let current_line_highlight = minimap_settings
21134 .current_line_highlight
21135 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21136 self.set_current_line_highlight(Some(current_line_highlight));
21137 }
21138
21139 pub fn minimap(&self) -> Option<&Entity<Self>> {
21140 self.minimap
21141 .as_ref()
21142 .filter(|_| self.minimap_visibility.visible())
21143 }
21144
21145 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21146 let mut wrap_guides = smallvec![];
21147
21148 if self.show_wrap_guides == Some(false) {
21149 return wrap_guides;
21150 }
21151
21152 let settings = self.buffer.read(cx).language_settings(cx);
21153 if settings.show_wrap_guides {
21154 match self.soft_wrap_mode(cx) {
21155 SoftWrap::Column(soft_wrap) => {
21156 wrap_guides.push((soft_wrap as usize, true));
21157 }
21158 SoftWrap::Bounded(soft_wrap) => {
21159 wrap_guides.push((soft_wrap as usize, true));
21160 }
21161 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21162 }
21163 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21164 }
21165
21166 wrap_guides
21167 }
21168
21169 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21170 let settings = self.buffer.read(cx).language_settings(cx);
21171 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21172 match mode {
21173 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21174 SoftWrap::None
21175 }
21176 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21177 language_settings::SoftWrap::PreferredLineLength => {
21178 SoftWrap::Column(settings.preferred_line_length)
21179 }
21180 language_settings::SoftWrap::Bounded => {
21181 SoftWrap::Bounded(settings.preferred_line_length)
21182 }
21183 }
21184 }
21185
21186 pub fn set_soft_wrap_mode(
21187 &mut self,
21188 mode: language_settings::SoftWrap,
21189 cx: &mut Context<Self>,
21190 ) {
21191 self.soft_wrap_mode_override = Some(mode);
21192 cx.notify();
21193 }
21194
21195 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21196 self.hard_wrap = hard_wrap;
21197 cx.notify();
21198 }
21199
21200 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21201 self.text_style_refinement = Some(style);
21202 }
21203
21204 /// called by the Element so we know what style we were most recently rendered with.
21205 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21206 // We intentionally do not inform the display map about the minimap style
21207 // so that wrapping is not recalculated and stays consistent for the editor
21208 // and its linked minimap.
21209 if !self.mode.is_minimap() {
21210 let font = style.text.font();
21211 let font_size = style.text.font_size.to_pixels(window.rem_size());
21212 let display_map = self
21213 .placeholder_display_map
21214 .as_ref()
21215 .filter(|_| self.is_empty(cx))
21216 .unwrap_or(&self.display_map);
21217
21218 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21219 }
21220 self.style = Some(style);
21221 }
21222
21223 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21224 if self.style.is_none() {
21225 self.style = Some(self.create_style(cx));
21226 }
21227 self.style.as_ref().unwrap()
21228 }
21229
21230 // Called by the element. This method is not designed to be called outside of the editor
21231 // element's layout code because it does not notify when rewrapping is computed synchronously.
21232 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21233 if self.is_empty(cx) {
21234 self.placeholder_display_map
21235 .as_ref()
21236 .map_or(false, |display_map| {
21237 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21238 })
21239 } else {
21240 self.display_map
21241 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21242 }
21243 }
21244
21245 pub fn set_soft_wrap(&mut self) {
21246 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21247 }
21248
21249 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21250 if self.soft_wrap_mode_override.is_some() {
21251 self.soft_wrap_mode_override.take();
21252 } else {
21253 let soft_wrap = match self.soft_wrap_mode(cx) {
21254 SoftWrap::GitDiff => return,
21255 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21256 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21257 language_settings::SoftWrap::None
21258 }
21259 };
21260 self.soft_wrap_mode_override = Some(soft_wrap);
21261 }
21262 cx.notify();
21263 }
21264
21265 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21266 let Some(workspace) = self.workspace() else {
21267 return;
21268 };
21269 let fs = workspace.read(cx).app_state().fs.clone();
21270 let current_show = TabBarSettings::get_global(cx).show;
21271 update_settings_file(fs, cx, move |setting, _| {
21272 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21273 });
21274 }
21275
21276 pub fn toggle_indent_guides(
21277 &mut self,
21278 _: &ToggleIndentGuides,
21279 _: &mut Window,
21280 cx: &mut Context<Self>,
21281 ) {
21282 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21283 self.buffer
21284 .read(cx)
21285 .language_settings(cx)
21286 .indent_guides
21287 .enabled
21288 });
21289 self.show_indent_guides = Some(!currently_enabled);
21290 cx.notify();
21291 }
21292
21293 fn should_show_indent_guides(&self) -> Option<bool> {
21294 self.show_indent_guides
21295 }
21296
21297 pub fn disable_indent_guides_for_buffer(
21298 &mut self,
21299 buffer_id: BufferId,
21300 cx: &mut Context<Self>,
21301 ) {
21302 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21303 cx.notify();
21304 }
21305
21306 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21307 self.buffers_with_disabled_indent_guides
21308 .contains(&buffer_id)
21309 }
21310
21311 pub fn toggle_line_numbers(
21312 &mut self,
21313 _: &ToggleLineNumbers,
21314 _: &mut Window,
21315 cx: &mut Context<Self>,
21316 ) {
21317 let mut editor_settings = EditorSettings::get_global(cx).clone();
21318 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21319 EditorSettings::override_global(editor_settings, cx);
21320 }
21321
21322 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21323 if let Some(show_line_numbers) = self.show_line_numbers {
21324 return show_line_numbers;
21325 }
21326 EditorSettings::get_global(cx).gutter.line_numbers
21327 }
21328
21329 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21330 match (
21331 self.use_relative_line_numbers,
21332 EditorSettings::get_global(cx).relative_line_numbers,
21333 ) {
21334 (None, setting) => setting,
21335 (Some(false), _) => RelativeLineNumbers::Disabled,
21336 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21337 (Some(true), _) => RelativeLineNumbers::Enabled,
21338 }
21339 }
21340
21341 pub fn toggle_relative_line_numbers(
21342 &mut self,
21343 _: &ToggleRelativeLineNumbers,
21344 _: &mut Window,
21345 cx: &mut Context<Self>,
21346 ) {
21347 let is_relative = self.relative_line_numbers(cx);
21348 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21349 }
21350
21351 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21352 self.use_relative_line_numbers = is_relative;
21353 cx.notify();
21354 }
21355
21356 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21357 self.show_gutter = show_gutter;
21358 cx.notify();
21359 }
21360
21361 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21362 self.show_scrollbars = ScrollbarAxes {
21363 horizontal: show,
21364 vertical: show,
21365 };
21366 cx.notify();
21367 }
21368
21369 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21370 self.show_scrollbars.vertical = show;
21371 cx.notify();
21372 }
21373
21374 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21375 self.show_scrollbars.horizontal = show;
21376 cx.notify();
21377 }
21378
21379 pub fn set_minimap_visibility(
21380 &mut self,
21381 minimap_visibility: MinimapVisibility,
21382 window: &mut Window,
21383 cx: &mut Context<Self>,
21384 ) {
21385 if self.minimap_visibility != minimap_visibility {
21386 if minimap_visibility.visible() && self.minimap.is_none() {
21387 let minimap_settings = EditorSettings::get_global(cx).minimap;
21388 self.minimap =
21389 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21390 }
21391 self.minimap_visibility = minimap_visibility;
21392 cx.notify();
21393 }
21394 }
21395
21396 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21397 self.set_show_scrollbars(false, cx);
21398 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21399 }
21400
21401 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21402 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21403 }
21404
21405 /// Normally the text in full mode and auto height editors is padded on the
21406 /// left side by roughly half a character width for improved hit testing.
21407 ///
21408 /// Use this method to disable this for cases where this is not wanted (e.g.
21409 /// if you want to align the editor text with some other text above or below)
21410 /// or if you want to add this padding to single-line editors.
21411 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21412 self.offset_content = offset_content;
21413 cx.notify();
21414 }
21415
21416 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21417 self.show_line_numbers = Some(show_line_numbers);
21418 cx.notify();
21419 }
21420
21421 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21422 self.disable_expand_excerpt_buttons = true;
21423 cx.notify();
21424 }
21425
21426 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21427 self.number_deleted_lines = number;
21428 cx.notify();
21429 }
21430
21431 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21432 self.delegate_expand_excerpts = delegate;
21433 }
21434
21435 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21436 self.delegate_stage_and_restore = delegate;
21437 }
21438
21439 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21440 self.delegate_open_excerpts = delegate;
21441 }
21442
21443 pub fn set_on_local_selections_changed(
21444 &mut self,
21445 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21446 ) {
21447 self.on_local_selections_changed = callback;
21448 }
21449
21450 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21451 self.suppress_selection_callback = suppress;
21452 }
21453
21454 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21455 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21456 cx.notify();
21457 }
21458
21459 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21460 self.show_code_actions = Some(show_code_actions);
21461 cx.notify();
21462 }
21463
21464 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21465 self.show_runnables = Some(show_runnables);
21466 cx.notify();
21467 }
21468
21469 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21470 self.show_breakpoints = Some(show_breakpoints);
21471 cx.notify();
21472 }
21473
21474 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21475 self.show_diff_review_button = show;
21476 cx.notify();
21477 }
21478
21479 pub fn show_diff_review_button(&self) -> bool {
21480 self.show_diff_review_button
21481 }
21482
21483 pub fn render_diff_review_button(
21484 &self,
21485 display_row: DisplayRow,
21486 width: Pixels,
21487 cx: &mut Context<Self>,
21488 ) -> impl IntoElement {
21489 let text_color = cx.theme().colors().text;
21490 let icon_color = cx.theme().colors().icon_accent;
21491
21492 h_flex()
21493 .id("diff_review_button")
21494 .cursor_pointer()
21495 .w(width - px(1.))
21496 .h(relative(0.9))
21497 .justify_center()
21498 .rounded_sm()
21499 .border_1()
21500 .border_color(text_color.opacity(0.1))
21501 .bg(text_color.opacity(0.15))
21502 .hover(|s| {
21503 s.bg(icon_color.opacity(0.4))
21504 .border_color(icon_color.opacity(0.5))
21505 })
21506 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21507 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21508 .on_mouse_down(
21509 gpui::MouseButton::Left,
21510 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21511 editor.start_diff_review_drag(display_row, window, cx);
21512 }),
21513 )
21514 }
21515
21516 pub fn start_diff_review_drag(
21517 &mut self,
21518 display_row: DisplayRow,
21519 window: &mut Window,
21520 cx: &mut Context<Self>,
21521 ) {
21522 let snapshot = self.snapshot(window, cx);
21523 let point = snapshot
21524 .display_snapshot
21525 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21526 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21527 self.diff_review_drag_state = Some(DiffReviewDragState {
21528 start_anchor: anchor,
21529 current_anchor: anchor,
21530 });
21531 cx.notify();
21532 }
21533
21534 pub fn update_diff_review_drag(
21535 &mut self,
21536 display_row: DisplayRow,
21537 window: &mut Window,
21538 cx: &mut Context<Self>,
21539 ) {
21540 if self.diff_review_drag_state.is_none() {
21541 return;
21542 }
21543 let snapshot = self.snapshot(window, cx);
21544 let point = snapshot
21545 .display_snapshot
21546 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21547 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21548 if let Some(drag_state) = &mut self.diff_review_drag_state {
21549 drag_state.current_anchor = anchor;
21550 cx.notify();
21551 }
21552 }
21553
21554 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21555 if let Some(drag_state) = self.diff_review_drag_state.take() {
21556 let snapshot = self.snapshot(window, cx);
21557 let range = drag_state.row_range(&snapshot.display_snapshot);
21558 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21559 }
21560 cx.notify();
21561 }
21562
21563 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21564 self.diff_review_drag_state = None;
21565 cx.notify();
21566 }
21567
21568 /// Calculates the appropriate block height for the diff review overlay.
21569 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21570 /// and 2 lines per comment when expanded.
21571 fn calculate_overlay_height(
21572 &self,
21573 hunk_key: &DiffHunkKey,
21574 comments_expanded: bool,
21575 snapshot: &MultiBufferSnapshot,
21576 ) -> u32 {
21577 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21578 let base_height: u32 = 2; // Input row with avatar and buttons
21579
21580 if comment_count == 0 {
21581 base_height
21582 } else if comments_expanded {
21583 // Header (1 line) + 2 lines per comment
21584 base_height + 1 + (comment_count as u32 * 2)
21585 } else {
21586 // Just header when collapsed
21587 base_height + 1
21588 }
21589 }
21590
21591 pub fn show_diff_review_overlay(
21592 &mut self,
21593 display_range: Range<DisplayRow>,
21594 window: &mut Window,
21595 cx: &mut Context<Self>,
21596 ) {
21597 let Range { start, end } = display_range.sorted();
21598
21599 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21600 let editor_snapshot = self.snapshot(window, cx);
21601
21602 // Convert display rows to multibuffer points
21603 let start_point = editor_snapshot
21604 .display_snapshot
21605 .display_point_to_point(start.as_display_point(), Bias::Left);
21606 let end_point = editor_snapshot
21607 .display_snapshot
21608 .display_point_to_point(end.as_display_point(), Bias::Left);
21609 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21610
21611 // Create anchor range for the selected lines (start of first line to end of last line)
21612 let line_end = Point::new(
21613 end_point.row,
21614 buffer_snapshot.line_len(end_multi_buffer_row),
21615 );
21616 let anchor_range =
21617 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21618
21619 // Compute the hunk key for this display row
21620 let file_path = buffer_snapshot
21621 .file_at(start_point)
21622 .map(|file: &Arc<dyn language::File>| file.path().clone())
21623 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21624 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21625 let new_hunk_key = DiffHunkKey {
21626 file_path,
21627 hunk_start_anchor,
21628 };
21629
21630 // Check if we already have an overlay for this hunk
21631 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21632 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21633 }) {
21634 // Just focus the existing overlay's prompt editor
21635 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21636 window.focus(&focus_handle, cx);
21637 return;
21638 }
21639
21640 // Dismiss overlays that have no comments for their hunks
21641 self.dismiss_overlays_without_comments(cx);
21642
21643 // Get the current user's avatar URI from the project's user_store
21644 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21645 let user_store = project.read(cx).user_store();
21646 user_store
21647 .read(cx)
21648 .current_user()
21649 .map(|user| user.avatar_uri.clone())
21650 });
21651
21652 // Create anchor at the end of the last row so the block appears immediately below it
21653 // Use multibuffer coordinates for anchor creation
21654 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21655 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21656
21657 // Use the hunk key we already computed
21658 let hunk_key = new_hunk_key;
21659
21660 // Create the prompt editor for the review input
21661 let prompt_editor = cx.new(|cx| {
21662 let mut editor = Editor::single_line(window, cx);
21663 editor.set_placeholder_text("Add a review comment...", window, cx);
21664 editor
21665 });
21666
21667 // Register the Newline action on the prompt editor to submit the review
21668 let parent_editor = cx.entity().downgrade();
21669 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21670 prompt_editor.register_action({
21671 let parent_editor = parent_editor.clone();
21672 move |_: &crate::actions::Newline, window, cx| {
21673 if let Some(editor) = parent_editor.upgrade() {
21674 editor.update(cx, |editor, cx| {
21675 editor.submit_diff_review_comment(window, cx);
21676 });
21677 }
21678 }
21679 })
21680 });
21681
21682 // Calculate initial height based on existing comments for this hunk
21683 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21684
21685 // Create the overlay block
21686 let prompt_editor_for_render = prompt_editor.clone();
21687 let hunk_key_for_render = hunk_key.clone();
21688 let editor_handle = cx.entity().downgrade();
21689 let block = BlockProperties {
21690 style: BlockStyle::Sticky,
21691 placement: BlockPlacement::Below(anchor),
21692 height: Some(initial_height),
21693 render: Arc::new(move |cx| {
21694 Self::render_diff_review_overlay(
21695 &prompt_editor_for_render,
21696 &hunk_key_for_render,
21697 &editor_handle,
21698 cx,
21699 )
21700 }),
21701 priority: 0,
21702 };
21703
21704 let block_ids = self.insert_blocks([block], None, cx);
21705 let Some(block_id) = block_ids.into_iter().next() else {
21706 log::error!("Failed to insert diff review overlay block");
21707 return;
21708 };
21709
21710 self.diff_review_overlays.push(DiffReviewOverlay {
21711 anchor_range,
21712 block_id,
21713 prompt_editor: prompt_editor.clone(),
21714 hunk_key,
21715 comments_expanded: true,
21716 inline_edit_editors: HashMap::default(),
21717 inline_edit_subscriptions: HashMap::default(),
21718 user_avatar_uri,
21719 _subscription: subscription,
21720 });
21721
21722 // Focus the prompt editor
21723 let focus_handle = prompt_editor.focus_handle(cx);
21724 window.focus(&focus_handle, cx);
21725
21726 cx.notify();
21727 }
21728
21729 /// Dismisses all diff review overlays.
21730 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21731 if self.diff_review_overlays.is_empty() {
21732 return;
21733 }
21734 let block_ids: HashSet<_> = self
21735 .diff_review_overlays
21736 .drain(..)
21737 .map(|overlay| overlay.block_id)
21738 .collect();
21739 self.remove_blocks(block_ids, None, cx);
21740 cx.notify();
21741 }
21742
21743 /// Dismisses overlays that have no comments stored for their hunks.
21744 /// Keeps overlays that have at least one comment.
21745 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21746 let snapshot = self.buffer.read(cx).snapshot(cx);
21747
21748 // First, compute which overlays have comments (to avoid borrow issues with retain)
21749 let overlays_with_comments: Vec<bool> = self
21750 .diff_review_overlays
21751 .iter()
21752 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21753 .collect();
21754
21755 // Now collect block IDs to remove and retain overlays
21756 let mut block_ids_to_remove = HashSet::default();
21757 let mut index = 0;
21758 self.diff_review_overlays.retain(|overlay| {
21759 let has_comments = overlays_with_comments[index];
21760 index += 1;
21761 if !has_comments {
21762 block_ids_to_remove.insert(overlay.block_id);
21763 }
21764 has_comments
21765 });
21766
21767 if !block_ids_to_remove.is_empty() {
21768 self.remove_blocks(block_ids_to_remove, None, cx);
21769 cx.notify();
21770 }
21771 }
21772
21773 /// Refreshes the diff review overlay block to update its height and render function.
21774 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21775 fn refresh_diff_review_overlay_height(
21776 &mut self,
21777 hunk_key: &DiffHunkKey,
21778 _window: &mut Window,
21779 cx: &mut Context<Self>,
21780 ) {
21781 // Extract all needed data from overlay first to avoid borrow conflicts
21782 let snapshot = self.buffer.read(cx).snapshot(cx);
21783 let (comments_expanded, block_id, prompt_editor) = {
21784 let Some(overlay) = self
21785 .diff_review_overlays
21786 .iter()
21787 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21788 else {
21789 return;
21790 };
21791
21792 (
21793 overlay.comments_expanded,
21794 overlay.block_id,
21795 overlay.prompt_editor.clone(),
21796 )
21797 };
21798
21799 // Calculate new height
21800 let snapshot = self.buffer.read(cx).snapshot(cx);
21801 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21802
21803 // Update the block height using resize_blocks (avoids flicker)
21804 let mut heights = HashMap::default();
21805 heights.insert(block_id, new_height);
21806 self.resize_blocks(heights, None, cx);
21807
21808 // Update the render function using replace_blocks (avoids flicker)
21809 let hunk_key_for_render = hunk_key.clone();
21810 let editor_handle = cx.entity().downgrade();
21811 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21812 Arc::new(move |cx| {
21813 Self::render_diff_review_overlay(
21814 &prompt_editor,
21815 &hunk_key_for_render,
21816 &editor_handle,
21817 cx,
21818 )
21819 });
21820
21821 let mut renderers = HashMap::default();
21822 renderers.insert(block_id, render);
21823 self.replace_blocks(renderers, None, cx);
21824 }
21825
21826 /// Action handler for SubmitDiffReviewComment.
21827 pub fn submit_diff_review_comment_action(
21828 &mut self,
21829 _: &SubmitDiffReviewComment,
21830 window: &mut Window,
21831 cx: &mut Context<Self>,
21832 ) {
21833 self.submit_diff_review_comment(window, cx);
21834 }
21835
21836 /// Stores the diff review comment locally.
21837 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21838 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21839 // Find the overlay that currently has focus
21840 let overlay_index = self
21841 .diff_review_overlays
21842 .iter()
21843 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21844 let Some(overlay_index) = overlay_index else {
21845 return;
21846 };
21847 let overlay = &self.diff_review_overlays[overlay_index];
21848
21849 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21850 if comment_text.is_empty() {
21851 return;
21852 }
21853
21854 let anchor_range = overlay.anchor_range.clone();
21855 let hunk_key = overlay.hunk_key.clone();
21856
21857 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21858
21859 // Clear the prompt editor but keep the overlay open
21860 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21861 overlay.prompt_editor.update(cx, |editor, cx| {
21862 editor.clear(window, cx);
21863 });
21864 }
21865
21866 // Refresh the overlay to update the block height for the new comment
21867 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21868
21869 cx.notify();
21870 }
21871
21872 /// Returns the prompt editor for the diff review overlay, if one is active.
21873 /// This is primarily used for testing.
21874 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21875 self.diff_review_overlays
21876 .first()
21877 .map(|overlay| &overlay.prompt_editor)
21878 }
21879
21880 /// Returns the line range for the first diff review overlay, if one is active.
21881 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21882 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21883 let overlay = self.diff_review_overlays.first()?;
21884 let snapshot = self.buffer.read(cx).snapshot(cx);
21885 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21886 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21887 let start_row = snapshot
21888 .point_to_buffer_point(start_point)
21889 .map(|(_, p, _)| p.row)
21890 .unwrap_or(start_point.row);
21891 let end_row = snapshot
21892 .point_to_buffer_point(end_point)
21893 .map(|(_, p, _)| p.row)
21894 .unwrap_or(end_point.row);
21895 Some((start_row, end_row))
21896 }
21897
21898 /// Sets whether the comments section is expanded in the diff review overlay.
21899 /// This is primarily used for testing.
21900 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21901 for overlay in &mut self.diff_review_overlays {
21902 overlay.comments_expanded = expanded;
21903 }
21904 cx.notify();
21905 }
21906
21907 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21908 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21909 a.file_path == b.file_path
21910 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21911 }
21912
21913 /// Returns comments for a specific hunk, ordered by creation time.
21914 pub fn comments_for_hunk<'a>(
21915 &'a self,
21916 key: &DiffHunkKey,
21917 snapshot: &MultiBufferSnapshot,
21918 ) -> &'a [StoredReviewComment] {
21919 let key_point = key.hunk_start_anchor.to_point(snapshot);
21920 self.stored_review_comments
21921 .iter()
21922 .find(|(k, _)| {
21923 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21924 })
21925 .map(|(_, comments)| comments.as_slice())
21926 .unwrap_or(&[])
21927 }
21928
21929 /// Returns the total count of stored review comments across all hunks.
21930 pub fn total_review_comment_count(&self) -> usize {
21931 self.stored_review_comments
21932 .iter()
21933 .map(|(_, v)| v.len())
21934 .sum()
21935 }
21936
21937 /// Returns the count of comments for a specific hunk.
21938 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21939 let key_point = key.hunk_start_anchor.to_point(snapshot);
21940 self.stored_review_comments
21941 .iter()
21942 .find(|(k, _)| {
21943 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21944 })
21945 .map(|(_, v)| v.len())
21946 .unwrap_or(0)
21947 }
21948
21949 /// Adds a new review comment to a specific hunk.
21950 pub fn add_review_comment(
21951 &mut self,
21952 hunk_key: DiffHunkKey,
21953 comment: String,
21954 anchor_range: Range<Anchor>,
21955 cx: &mut Context<Self>,
21956 ) -> usize {
21957 let id = self.next_review_comment_id;
21958 self.next_review_comment_id += 1;
21959
21960 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21961
21962 let snapshot = self.buffer.read(cx).snapshot(cx);
21963 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21964
21965 // Find existing entry for this hunk or add a new one
21966 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21967 k.file_path == hunk_key.file_path
21968 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21969 }) {
21970 comments.push(stored_comment);
21971 } else {
21972 self.stored_review_comments
21973 .push((hunk_key, vec![stored_comment]));
21974 }
21975
21976 cx.emit(EditorEvent::ReviewCommentsChanged {
21977 total_count: self.total_review_comment_count(),
21978 });
21979 cx.notify();
21980 id
21981 }
21982
21983 /// Removes a review comment by ID from any hunk.
21984 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21985 for (_, comments) in self.stored_review_comments.iter_mut() {
21986 if let Some(index) = comments.iter().position(|c| c.id == id) {
21987 comments.remove(index);
21988 cx.emit(EditorEvent::ReviewCommentsChanged {
21989 total_count: self.total_review_comment_count(),
21990 });
21991 cx.notify();
21992 return true;
21993 }
21994 }
21995 false
21996 }
21997
21998 /// Updates a review comment's text by ID.
21999 pub fn update_review_comment(
22000 &mut self,
22001 id: usize,
22002 new_comment: String,
22003 cx: &mut Context<Self>,
22004 ) -> bool {
22005 for (_, comments) in self.stored_review_comments.iter_mut() {
22006 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22007 comment.comment = new_comment;
22008 comment.is_editing = false;
22009 cx.emit(EditorEvent::ReviewCommentsChanged {
22010 total_count: self.total_review_comment_count(),
22011 });
22012 cx.notify();
22013 return true;
22014 }
22015 }
22016 false
22017 }
22018
22019 /// Sets a comment's editing state.
22020 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22021 for (_, comments) in self.stored_review_comments.iter_mut() {
22022 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22023 comment.is_editing = is_editing;
22024 cx.notify();
22025 return;
22026 }
22027 }
22028 }
22029
22030 /// Takes all stored comments from all hunks, clearing the storage.
22031 /// Returns a Vec of (hunk_key, comments) pairs.
22032 pub fn take_all_review_comments(
22033 &mut self,
22034 cx: &mut Context<Self>,
22035 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22036 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22037 self.dismiss_all_diff_review_overlays(cx);
22038 let comments = std::mem::take(&mut self.stored_review_comments);
22039 // Reset the ID counter since all comments have been taken
22040 self.next_review_comment_id = 0;
22041 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22042 cx.notify();
22043 comments
22044 }
22045
22046 /// Removes review comments whose anchors are no longer valid or whose
22047 /// associated diff hunks no longer exist.
22048 ///
22049 /// This should be called when the buffer changes to prevent orphaned comments
22050 /// from accumulating.
22051 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22052 let snapshot = self.buffer.read(cx).snapshot(cx);
22053 let original_count = self.total_review_comment_count();
22054
22055 // Remove comments with invalid hunk anchors
22056 self.stored_review_comments
22057 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22058
22059 // Also clean up individual comments with invalid anchor ranges
22060 for (_, comments) in &mut self.stored_review_comments {
22061 comments.retain(|comment| {
22062 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22063 });
22064 }
22065
22066 // Remove empty hunk entries
22067 self.stored_review_comments
22068 .retain(|(_, comments)| !comments.is_empty());
22069
22070 let new_count = self.total_review_comment_count();
22071 if new_count != original_count {
22072 cx.emit(EditorEvent::ReviewCommentsChanged {
22073 total_count: new_count,
22074 });
22075 cx.notify();
22076 }
22077 }
22078
22079 /// Toggles the expanded state of the comments section in the overlay.
22080 pub fn toggle_review_comments_expanded(
22081 &mut self,
22082 _: &ToggleReviewCommentsExpanded,
22083 window: &mut Window,
22084 cx: &mut Context<Self>,
22085 ) {
22086 // Find the overlay that currently has focus, or use the first one
22087 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22088 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22089 overlay.comments_expanded = !overlay.comments_expanded;
22090 Some(overlay.hunk_key.clone())
22091 } else {
22092 None
22093 }
22094 });
22095
22096 // If no focused overlay found, toggle the first one
22097 let hunk_key = overlay_info.or_else(|| {
22098 self.diff_review_overlays.first_mut().map(|overlay| {
22099 overlay.comments_expanded = !overlay.comments_expanded;
22100 overlay.hunk_key.clone()
22101 })
22102 });
22103
22104 if let Some(hunk_key) = hunk_key {
22105 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22106 cx.notify();
22107 }
22108 }
22109
22110 /// Handles the EditReviewComment action - sets a comment into editing mode.
22111 pub fn edit_review_comment(
22112 &mut self,
22113 action: &EditReviewComment,
22114 window: &mut Window,
22115 cx: &mut Context<Self>,
22116 ) {
22117 let comment_id = action.id;
22118
22119 // Set the comment to editing mode
22120 self.set_comment_editing(comment_id, true, cx);
22121
22122 // Find the overlay that contains this comment and create an inline editor if needed
22123 // First, find which hunk this comment belongs to
22124 let hunk_key = self
22125 .stored_review_comments
22126 .iter()
22127 .find_map(|(key, comments)| {
22128 if comments.iter().any(|c| c.id == comment_id) {
22129 Some(key.clone())
22130 } else {
22131 None
22132 }
22133 });
22134
22135 let snapshot = self.buffer.read(cx).snapshot(cx);
22136 if let Some(hunk_key) = hunk_key {
22137 if let Some(overlay) = self
22138 .diff_review_overlays
22139 .iter_mut()
22140 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22141 {
22142 if let std::collections::hash_map::Entry::Vacant(entry) =
22143 overlay.inline_edit_editors.entry(comment_id)
22144 {
22145 // Find the comment text
22146 let comment_text = self
22147 .stored_review_comments
22148 .iter()
22149 .flat_map(|(_, comments)| comments)
22150 .find(|c| c.id == comment_id)
22151 .map(|c| c.comment.clone())
22152 .unwrap_or_default();
22153
22154 // Create inline editor
22155 let parent_editor = cx.entity().downgrade();
22156 let inline_editor = cx.new(|cx| {
22157 let mut editor = Editor::single_line(window, cx);
22158 editor.set_text(&*comment_text, window, cx);
22159 // Select all text for easy replacement
22160 editor.select_all(&crate::actions::SelectAll, window, cx);
22161 editor
22162 });
22163
22164 // Register the Newline action to confirm the edit
22165 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22166 inline_editor.register_action({
22167 let parent_editor = parent_editor.clone();
22168 move |_: &crate::actions::Newline, window, cx| {
22169 if let Some(editor) = parent_editor.upgrade() {
22170 editor.update(cx, |editor, cx| {
22171 editor.confirm_edit_review_comment(comment_id, window, cx);
22172 });
22173 }
22174 }
22175 })
22176 });
22177
22178 // Store the subscription to keep the action handler alive
22179 overlay
22180 .inline_edit_subscriptions
22181 .insert(comment_id, subscription);
22182
22183 // Focus the inline editor
22184 let focus_handle = inline_editor.focus_handle(cx);
22185 window.focus(&focus_handle, cx);
22186
22187 entry.insert(inline_editor);
22188 }
22189 }
22190 }
22191
22192 cx.notify();
22193 }
22194
22195 /// Confirms an inline edit of a review comment.
22196 pub fn confirm_edit_review_comment(
22197 &mut self,
22198 comment_id: usize,
22199 _window: &mut Window,
22200 cx: &mut Context<Self>,
22201 ) {
22202 // Get the new text from the inline editor
22203 // Find the overlay containing this comment's inline editor
22204 let snapshot = self.buffer.read(cx).snapshot(cx);
22205 let hunk_key = self
22206 .stored_review_comments
22207 .iter()
22208 .find_map(|(key, comments)| {
22209 if comments.iter().any(|c| c.id == comment_id) {
22210 Some(key.clone())
22211 } else {
22212 None
22213 }
22214 });
22215
22216 let new_text = hunk_key
22217 .as_ref()
22218 .and_then(|hunk_key| {
22219 self.diff_review_overlays
22220 .iter()
22221 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22222 })
22223 .as_ref()
22224 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22225 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22226
22227 if let Some(new_text) = new_text {
22228 if !new_text.is_empty() {
22229 self.update_review_comment(comment_id, new_text, cx);
22230 }
22231 }
22232
22233 // Remove the inline editor and its subscription
22234 if let Some(hunk_key) = hunk_key {
22235 if let Some(overlay) = self
22236 .diff_review_overlays
22237 .iter_mut()
22238 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22239 {
22240 overlay.inline_edit_editors.remove(&comment_id);
22241 overlay.inline_edit_subscriptions.remove(&comment_id);
22242 }
22243 }
22244
22245 // Clear editing state
22246 self.set_comment_editing(comment_id, false, cx);
22247 }
22248
22249 /// Cancels an inline edit of a review comment.
22250 pub fn cancel_edit_review_comment(
22251 &mut self,
22252 comment_id: usize,
22253 _window: &mut Window,
22254 cx: &mut Context<Self>,
22255 ) {
22256 // Find which hunk this comment belongs to
22257 let hunk_key = self
22258 .stored_review_comments
22259 .iter()
22260 .find_map(|(key, comments)| {
22261 if comments.iter().any(|c| c.id == comment_id) {
22262 Some(key.clone())
22263 } else {
22264 None
22265 }
22266 });
22267
22268 // Remove the inline editor and its subscription
22269 if let Some(hunk_key) = hunk_key {
22270 let snapshot = self.buffer.read(cx).snapshot(cx);
22271 if let Some(overlay) = self
22272 .diff_review_overlays
22273 .iter_mut()
22274 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22275 {
22276 overlay.inline_edit_editors.remove(&comment_id);
22277 overlay.inline_edit_subscriptions.remove(&comment_id);
22278 }
22279 }
22280
22281 // Clear editing state
22282 self.set_comment_editing(comment_id, false, cx);
22283 }
22284
22285 /// Action handler for ConfirmEditReviewComment.
22286 pub fn confirm_edit_review_comment_action(
22287 &mut self,
22288 action: &ConfirmEditReviewComment,
22289 window: &mut Window,
22290 cx: &mut Context<Self>,
22291 ) {
22292 self.confirm_edit_review_comment(action.id, window, cx);
22293 }
22294
22295 /// Action handler for CancelEditReviewComment.
22296 pub fn cancel_edit_review_comment_action(
22297 &mut self,
22298 action: &CancelEditReviewComment,
22299 window: &mut Window,
22300 cx: &mut Context<Self>,
22301 ) {
22302 self.cancel_edit_review_comment(action.id, window, cx);
22303 }
22304
22305 /// Handles the DeleteReviewComment action - removes a comment.
22306 pub fn delete_review_comment(
22307 &mut self,
22308 action: &DeleteReviewComment,
22309 window: &mut Window,
22310 cx: &mut Context<Self>,
22311 ) {
22312 // Get the hunk key before removing the comment
22313 // Find the hunk key from the comment itself
22314 let comment_id = action.id;
22315 let hunk_key = self
22316 .stored_review_comments
22317 .iter()
22318 .find_map(|(key, comments)| {
22319 if comments.iter().any(|c| c.id == comment_id) {
22320 Some(key.clone())
22321 } else {
22322 None
22323 }
22324 });
22325
22326 // Also get it from the overlay for refresh purposes
22327 let overlay_hunk_key = self
22328 .diff_review_overlays
22329 .first()
22330 .map(|o| o.hunk_key.clone());
22331
22332 self.remove_review_comment(action.id, cx);
22333
22334 // Refresh the overlay height after removing a comment
22335 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22336 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22337 }
22338 }
22339
22340 fn render_diff_review_overlay(
22341 prompt_editor: &Entity<Editor>,
22342 hunk_key: &DiffHunkKey,
22343 editor_handle: &WeakEntity<Editor>,
22344 cx: &mut BlockContext,
22345 ) -> AnyElement {
22346 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22347 if ranges.is_empty() {
22348 return None;
22349 }
22350 let formatted: Vec<String> = ranges
22351 .iter()
22352 .map(|(start, end)| {
22353 let start_line = start + 1;
22354 let end_line = end + 1;
22355 if start_line == end_line {
22356 format!("Line {start_line}")
22357 } else {
22358 format!("Lines {start_line}-{end_line}")
22359 }
22360 })
22361 .collect();
22362 // Don't show label for single line in single excerpt
22363 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22364 return None;
22365 }
22366 Some(formatted.join(" ⋯ "))
22367 }
22368
22369 let theme = cx.theme();
22370 let colors = theme.colors();
22371
22372 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22373 editor_handle
22374 .upgrade()
22375 .map(|editor| {
22376 let editor = editor.read(cx);
22377 let snapshot = editor.buffer().read(cx).snapshot(cx);
22378 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22379 let (expanded, editors, avatar_uri, line_ranges) = editor
22380 .diff_review_overlays
22381 .iter()
22382 .find(|overlay| {
22383 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22384 })
22385 .map(|o| {
22386 let start_point = o.anchor_range.start.to_point(&snapshot);
22387 let end_point = o.anchor_range.end.to_point(&snapshot);
22388 // Get line ranges per excerpt to detect discontinuities
22389 let buffer_ranges =
22390 snapshot.range_to_buffer_ranges(start_point..end_point);
22391 let ranges: Vec<(u32, u32)> = buffer_ranges
22392 .iter()
22393 .map(|(buffer, range, _)| {
22394 let start = buffer.offset_to_point(range.start.0).row;
22395 let end = buffer.offset_to_point(range.end.0).row;
22396 (start, end)
22397 })
22398 .collect();
22399 (
22400 o.comments_expanded,
22401 o.inline_edit_editors.clone(),
22402 o.user_avatar_uri.clone(),
22403 if ranges.is_empty() {
22404 None
22405 } else {
22406 Some(ranges)
22407 },
22408 )
22409 })
22410 .unwrap_or((true, HashMap::default(), None, None));
22411 (comments, expanded, editors, avatar_uri, line_ranges)
22412 })
22413 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22414
22415 let comment_count = comments.len();
22416 let avatar_size = px(20.);
22417 let action_icon_size = IconSize::XSmall;
22418
22419 v_flex()
22420 .w_full()
22421 .bg(colors.editor_background)
22422 .border_b_1()
22423 .border_color(colors.border)
22424 .px_2()
22425 .pb_2()
22426 .gap_2()
22427 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22428 .when_some(line_ranges, |el, ranges| {
22429 let label = format_line_ranges(&ranges);
22430 if let Some(label) = label {
22431 el.child(
22432 h_flex()
22433 .w_full()
22434 .px_2()
22435 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22436 )
22437 } else {
22438 el
22439 }
22440 })
22441 // Top row: editable input with user's avatar
22442 .child(
22443 h_flex()
22444 .w_full()
22445 .items_center()
22446 .gap_2()
22447 .px_2()
22448 .py_1p5()
22449 .rounded_md()
22450 .bg(colors.surface_background)
22451 .child(
22452 div()
22453 .size(avatar_size)
22454 .flex_shrink_0()
22455 .rounded_full()
22456 .overflow_hidden()
22457 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22458 Avatar::new(avatar_uri.clone())
22459 .size(avatar_size)
22460 .into_any_element()
22461 } else {
22462 Icon::new(IconName::Person)
22463 .size(IconSize::Small)
22464 .color(ui::Color::Muted)
22465 .into_any_element()
22466 }),
22467 )
22468 .child(
22469 div()
22470 .flex_1()
22471 .border_1()
22472 .border_color(colors.border)
22473 .rounded_md()
22474 .bg(colors.editor_background)
22475 .px_2()
22476 .py_1()
22477 .child(prompt_editor.clone()),
22478 )
22479 .child(
22480 h_flex()
22481 .flex_shrink_0()
22482 .gap_1()
22483 .child(
22484 IconButton::new("diff-review-close", IconName::Close)
22485 .icon_color(ui::Color::Muted)
22486 .icon_size(action_icon_size)
22487 .tooltip(Tooltip::text("Close"))
22488 .on_click(|_, window, cx| {
22489 window
22490 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22491 }),
22492 )
22493 .child(
22494 IconButton::new("diff-review-add", IconName::Return)
22495 .icon_color(ui::Color::Muted)
22496 .icon_size(action_icon_size)
22497 .tooltip(Tooltip::text("Add comment"))
22498 .on_click(|_, window, cx| {
22499 window.dispatch_action(
22500 Box::new(crate::actions::SubmitDiffReviewComment),
22501 cx,
22502 );
22503 }),
22504 ),
22505 ),
22506 )
22507 // Expandable comments section (only shown when there are comments)
22508 .when(comment_count > 0, |el| {
22509 el.child(Self::render_comments_section(
22510 comments,
22511 comments_expanded,
22512 inline_editors,
22513 user_avatar_uri,
22514 avatar_size,
22515 action_icon_size,
22516 colors,
22517 ))
22518 })
22519 .into_any_element()
22520 }
22521
22522 fn render_comments_section(
22523 comments: Vec<StoredReviewComment>,
22524 expanded: bool,
22525 inline_editors: HashMap<usize, Entity<Editor>>,
22526 user_avatar_uri: Option<SharedUri>,
22527 avatar_size: Pixels,
22528 action_icon_size: IconSize,
22529 colors: &theme::ThemeColors,
22530 ) -> impl IntoElement {
22531 let comment_count = comments.len();
22532
22533 v_flex()
22534 .w_full()
22535 .gap_1()
22536 // Header with expand/collapse toggle
22537 .child(
22538 h_flex()
22539 .id("review-comments-header")
22540 .w_full()
22541 .items_center()
22542 .gap_1()
22543 .px_2()
22544 .py_1()
22545 .cursor_pointer()
22546 .rounded_md()
22547 .hover(|style| style.bg(colors.ghost_element_hover))
22548 .on_click(|_, window: &mut Window, cx| {
22549 window.dispatch_action(
22550 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22551 cx,
22552 );
22553 })
22554 .child(
22555 Icon::new(if expanded {
22556 IconName::ChevronDown
22557 } else {
22558 IconName::ChevronRight
22559 })
22560 .size(IconSize::Small)
22561 .color(ui::Color::Muted),
22562 )
22563 .child(
22564 Label::new(format!(
22565 "{} Comment{}",
22566 comment_count,
22567 if comment_count == 1 { "" } else { "s" }
22568 ))
22569 .size(LabelSize::Small)
22570 .color(Color::Muted),
22571 ),
22572 )
22573 // Comments list (when expanded)
22574 .when(expanded, |el| {
22575 el.children(comments.into_iter().map(|comment| {
22576 let inline_editor = inline_editors.get(&comment.id).cloned();
22577 Self::render_comment_row(
22578 comment,
22579 inline_editor,
22580 user_avatar_uri.clone(),
22581 avatar_size,
22582 action_icon_size,
22583 colors,
22584 )
22585 }))
22586 })
22587 }
22588
22589 fn render_comment_row(
22590 comment: StoredReviewComment,
22591 inline_editor: Option<Entity<Editor>>,
22592 user_avatar_uri: Option<SharedUri>,
22593 avatar_size: Pixels,
22594 action_icon_size: IconSize,
22595 colors: &theme::ThemeColors,
22596 ) -> impl IntoElement {
22597 let comment_id = comment.id;
22598 let is_editing = inline_editor.is_some();
22599
22600 h_flex()
22601 .w_full()
22602 .items_center()
22603 .gap_2()
22604 .px_2()
22605 .py_1p5()
22606 .rounded_md()
22607 .bg(colors.surface_background)
22608 .child(
22609 div()
22610 .size(avatar_size)
22611 .flex_shrink_0()
22612 .rounded_full()
22613 .overflow_hidden()
22614 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22615 Avatar::new(avatar_uri.clone())
22616 .size(avatar_size)
22617 .into_any_element()
22618 } else {
22619 Icon::new(IconName::Person)
22620 .size(IconSize::Small)
22621 .color(ui::Color::Muted)
22622 .into_any_element()
22623 }),
22624 )
22625 .child(if let Some(editor) = inline_editor {
22626 // Inline edit mode: show an editable text field
22627 div()
22628 .flex_1()
22629 .border_1()
22630 .border_color(colors.border)
22631 .rounded_md()
22632 .bg(colors.editor_background)
22633 .px_2()
22634 .py_1()
22635 .child(editor)
22636 .into_any_element()
22637 } else {
22638 // Display mode: show the comment text
22639 div()
22640 .flex_1()
22641 .text_sm()
22642 .text_color(colors.text)
22643 .child(comment.comment)
22644 .into_any_element()
22645 })
22646 .child(if is_editing {
22647 // Editing mode: show close and confirm buttons
22648 h_flex()
22649 .gap_1()
22650 .child(
22651 IconButton::new(
22652 format!("diff-review-cancel-edit-{comment_id}"),
22653 IconName::Close,
22654 )
22655 .icon_color(ui::Color::Muted)
22656 .icon_size(action_icon_size)
22657 .tooltip(Tooltip::text("Cancel"))
22658 .on_click(move |_, window, cx| {
22659 window.dispatch_action(
22660 Box::new(crate::actions::CancelEditReviewComment {
22661 id: comment_id,
22662 }),
22663 cx,
22664 );
22665 }),
22666 )
22667 .child(
22668 IconButton::new(
22669 format!("diff-review-confirm-edit-{comment_id}"),
22670 IconName::Return,
22671 )
22672 .icon_color(ui::Color::Muted)
22673 .icon_size(action_icon_size)
22674 .tooltip(Tooltip::text("Confirm"))
22675 .on_click(move |_, window, cx| {
22676 window.dispatch_action(
22677 Box::new(crate::actions::ConfirmEditReviewComment {
22678 id: comment_id,
22679 }),
22680 cx,
22681 );
22682 }),
22683 )
22684 .into_any_element()
22685 } else {
22686 // Display mode: no action buttons for now (edit/delete not yet implemented)
22687 gpui::Empty.into_any_element()
22688 })
22689 }
22690
22691 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22692 if self.display_map.read(cx).masked != masked {
22693 self.display_map.update(cx, |map, _| map.masked = masked);
22694 }
22695 cx.notify()
22696 }
22697
22698 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22699 self.show_wrap_guides = Some(show_wrap_guides);
22700 cx.notify();
22701 }
22702
22703 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22704 self.show_indent_guides = Some(show_indent_guides);
22705 cx.notify();
22706 }
22707
22708 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22709 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22710 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22711 && let Some(dir) = file.abs_path(cx).parent()
22712 {
22713 return Some(dir.to_owned());
22714 }
22715 }
22716
22717 None
22718 }
22719
22720 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22721 self.active_excerpt(cx)?
22722 .1
22723 .read(cx)
22724 .file()
22725 .and_then(|f| f.as_local())
22726 }
22727
22728 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22729 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22730 let buffer = buffer.read(cx);
22731 if let Some(project_path) = buffer.project_path(cx) {
22732 let project = self.project()?.read(cx);
22733 project.absolute_path(&project_path, cx)
22734 } else {
22735 buffer
22736 .file()
22737 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22738 }
22739 })
22740 }
22741
22742 pub fn reveal_in_finder(
22743 &mut self,
22744 _: &RevealInFileManager,
22745 _window: &mut Window,
22746 cx: &mut Context<Self>,
22747 ) {
22748 if let Some(path) = self.target_file_abs_path(cx) {
22749 if let Some(project) = self.project() {
22750 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22751 } else {
22752 cx.reveal_path(&path);
22753 }
22754 }
22755 }
22756
22757 pub fn copy_path(
22758 &mut self,
22759 _: &zed_actions::workspace::CopyPath,
22760 _window: &mut Window,
22761 cx: &mut Context<Self>,
22762 ) {
22763 if let Some(path) = self.target_file_abs_path(cx)
22764 && let Some(path) = path.to_str()
22765 {
22766 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22767 } else {
22768 cx.propagate();
22769 }
22770 }
22771
22772 pub fn copy_relative_path(
22773 &mut self,
22774 _: &zed_actions::workspace::CopyRelativePath,
22775 _window: &mut Window,
22776 cx: &mut Context<Self>,
22777 ) {
22778 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22779 let project = self.project()?.read(cx);
22780 let path = buffer.read(cx).file()?.path();
22781 let path = path.display(project.path_style(cx));
22782 Some(path)
22783 }) {
22784 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22785 } else {
22786 cx.propagate();
22787 }
22788 }
22789
22790 /// Returns the project path for the editor's buffer, if any buffer is
22791 /// opened in the editor.
22792 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22793 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22794 buffer.read(cx).project_path(cx)
22795 } else {
22796 None
22797 }
22798 }
22799
22800 // Returns true if the editor handled a go-to-line request
22801 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22802 maybe!({
22803 let breakpoint_store = self.breakpoint_store.as_ref()?;
22804
22805 let (active_stack_frame, debug_line_pane_id) = {
22806 let store = breakpoint_store.read(cx);
22807 let active_stack_frame = store.active_position().cloned();
22808 let debug_line_pane_id = store.active_debug_line_pane_id();
22809 (active_stack_frame, debug_line_pane_id)
22810 };
22811
22812 let Some(active_stack_frame) = active_stack_frame else {
22813 self.clear_row_highlights::<ActiveDebugLine>();
22814 return None;
22815 };
22816
22817 if let Some(debug_line_pane_id) = debug_line_pane_id {
22818 if let Some(workspace) = self
22819 .workspace
22820 .as_ref()
22821 .and_then(|(workspace, _)| workspace.upgrade())
22822 {
22823 let editor_pane_id = workspace
22824 .read(cx)
22825 .pane_for_item_id(cx.entity_id())
22826 .map(|pane| pane.entity_id());
22827
22828 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22829 self.clear_row_highlights::<ActiveDebugLine>();
22830 return None;
22831 }
22832 }
22833 }
22834
22835 let position = active_stack_frame.position;
22836 let buffer_id = position.buffer_id?;
22837 let snapshot = self
22838 .project
22839 .as_ref()?
22840 .read(cx)
22841 .buffer_for_id(buffer_id, cx)?
22842 .read(cx)
22843 .snapshot();
22844
22845 let mut handled = false;
22846 for (id, ExcerptRange { context, .. }) in
22847 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22848 {
22849 if context.start.cmp(&position, &snapshot).is_ge()
22850 || context.end.cmp(&position, &snapshot).is_lt()
22851 {
22852 continue;
22853 }
22854 let snapshot = self.buffer.read(cx).snapshot(cx);
22855 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22856
22857 handled = true;
22858 self.clear_row_highlights::<ActiveDebugLine>();
22859
22860 self.go_to_line::<ActiveDebugLine>(
22861 multibuffer_anchor,
22862 Some(cx.theme().colors().editor_debugger_active_line_background),
22863 window,
22864 cx,
22865 );
22866
22867 cx.notify();
22868 }
22869
22870 handled.then_some(())
22871 })
22872 .is_some()
22873 }
22874
22875 pub fn copy_file_name_without_extension(
22876 &mut self,
22877 _: &CopyFileNameWithoutExtension,
22878 _: &mut Window,
22879 cx: &mut Context<Self>,
22880 ) {
22881 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22882 let file = buffer.read(cx).file()?;
22883 file.path().file_stem()
22884 }) {
22885 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22886 }
22887 }
22888
22889 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22890 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22891 let file = buffer.read(cx).file()?;
22892 Some(file.file_name(cx))
22893 }) {
22894 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22895 }
22896 }
22897
22898 pub fn toggle_git_blame(
22899 &mut self,
22900 _: &::git::Blame,
22901 window: &mut Window,
22902 cx: &mut Context<Self>,
22903 ) {
22904 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22905
22906 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22907 self.start_git_blame(true, window, cx);
22908 }
22909
22910 cx.notify();
22911 }
22912
22913 pub fn toggle_git_blame_inline(
22914 &mut self,
22915 _: &ToggleGitBlameInline,
22916 window: &mut Window,
22917 cx: &mut Context<Self>,
22918 ) {
22919 self.toggle_git_blame_inline_internal(true, window, cx);
22920 cx.notify();
22921 }
22922
22923 pub fn open_git_blame_commit(
22924 &mut self,
22925 _: &OpenGitBlameCommit,
22926 window: &mut Window,
22927 cx: &mut Context<Self>,
22928 ) {
22929 self.open_git_blame_commit_internal(window, cx);
22930 }
22931
22932 fn open_git_blame_commit_internal(
22933 &mut self,
22934 window: &mut Window,
22935 cx: &mut Context<Self>,
22936 ) -> Option<()> {
22937 let blame = self.blame.as_ref()?;
22938 let snapshot = self.snapshot(window, cx);
22939 let cursor = self
22940 .selections
22941 .newest::<Point>(&snapshot.display_snapshot)
22942 .head();
22943 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22944 let (_, blame_entry) = blame
22945 .update(cx, |blame, cx| {
22946 blame
22947 .blame_for_rows(
22948 &[RowInfo {
22949 buffer_id: Some(buffer.remote_id()),
22950 buffer_row: Some(point.row),
22951 ..Default::default()
22952 }],
22953 cx,
22954 )
22955 .next()
22956 })
22957 .flatten()?;
22958 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22959 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22960 let workspace = self.workspace()?.downgrade();
22961 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22962 None
22963 }
22964
22965 pub fn git_blame_inline_enabled(&self) -> bool {
22966 self.git_blame_inline_enabled
22967 }
22968
22969 pub fn toggle_selection_menu(
22970 &mut self,
22971 _: &ToggleSelectionMenu,
22972 _: &mut Window,
22973 cx: &mut Context<Self>,
22974 ) {
22975 self.show_selection_menu = self
22976 .show_selection_menu
22977 .map(|show_selections_menu| !show_selections_menu)
22978 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22979
22980 cx.notify();
22981 }
22982
22983 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22984 self.show_selection_menu
22985 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22986 }
22987
22988 fn start_git_blame(
22989 &mut self,
22990 user_triggered: bool,
22991 window: &mut Window,
22992 cx: &mut Context<Self>,
22993 ) {
22994 if let Some(project) = self.project() {
22995 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22996 && buffer.read(cx).file().is_none()
22997 {
22998 return;
22999 }
23000
23001 let focused = self.focus_handle(cx).contains_focused(window, cx);
23002
23003 let project = project.clone();
23004 let blame = cx
23005 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23006 self.blame_subscription =
23007 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23008 self.blame = Some(blame);
23009 }
23010 }
23011
23012 fn toggle_git_blame_inline_internal(
23013 &mut self,
23014 user_triggered: bool,
23015 window: &mut Window,
23016 cx: &mut Context<Self>,
23017 ) {
23018 if self.git_blame_inline_enabled {
23019 self.git_blame_inline_enabled = false;
23020 self.show_git_blame_inline = false;
23021 self.show_git_blame_inline_delay_task.take();
23022 } else {
23023 self.git_blame_inline_enabled = true;
23024 self.start_git_blame_inline(user_triggered, window, cx);
23025 }
23026
23027 cx.notify();
23028 }
23029
23030 fn start_git_blame_inline(
23031 &mut self,
23032 user_triggered: bool,
23033 window: &mut Window,
23034 cx: &mut Context<Self>,
23035 ) {
23036 self.start_git_blame(user_triggered, window, cx);
23037
23038 if ProjectSettings::get_global(cx)
23039 .git
23040 .inline_blame_delay()
23041 .is_some()
23042 {
23043 self.start_inline_blame_timer(window, cx);
23044 } else {
23045 self.show_git_blame_inline = true
23046 }
23047 }
23048
23049 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23050 self.blame.as_ref()
23051 }
23052
23053 pub fn show_git_blame_gutter(&self) -> bool {
23054 self.show_git_blame_gutter
23055 }
23056
23057 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23058 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23059 }
23060
23061 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23062 self.show_git_blame_inline
23063 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23064 && !self.newest_selection_head_on_empty_line(cx)
23065 && self.has_blame_entries(cx)
23066 }
23067
23068 fn has_blame_entries(&self, cx: &App) -> bool {
23069 self.blame()
23070 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23071 }
23072
23073 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23074 let cursor_anchor = self.selections.newest_anchor().head();
23075
23076 let snapshot = self.buffer.read(cx).snapshot(cx);
23077 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23078
23079 snapshot.line_len(buffer_row) == 0
23080 }
23081
23082 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23083 let buffer_and_selection = maybe!({
23084 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23085 let selection_range = selection.range();
23086
23087 let multi_buffer = self.buffer().read(cx);
23088 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23089 let buffer_ranges = multi_buffer_snapshot
23090 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23091
23092 let (buffer, range, _) = if selection.reversed {
23093 buffer_ranges.first()
23094 } else {
23095 buffer_ranges.last()
23096 }?;
23097
23098 let buffer_range = range.to_point(buffer);
23099
23100 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23101 return Some((
23102 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23103 buffer_range.start.row..buffer_range.end.row,
23104 ));
23105 };
23106
23107 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23108 let start =
23109 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23110 let end =
23111 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23112
23113 Some((
23114 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23115 start.row..end.row,
23116 ))
23117 });
23118
23119 let Some((buffer, selection)) = buffer_and_selection else {
23120 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23121 };
23122
23123 let Some(project) = self.project() else {
23124 return Task::ready(Err(anyhow!("editor does not have project")));
23125 };
23126
23127 project.update(cx, |project, cx| {
23128 project.get_permalink_to_line(&buffer, selection, cx)
23129 })
23130 }
23131
23132 pub fn copy_permalink_to_line(
23133 &mut self,
23134 _: &CopyPermalinkToLine,
23135 window: &mut Window,
23136 cx: &mut Context<Self>,
23137 ) {
23138 let permalink_task = self.get_permalink_to_line(cx);
23139 let workspace = self.workspace();
23140
23141 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23142 Ok(permalink) => {
23143 cx.update(|_, cx| {
23144 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23145 })
23146 .ok();
23147 }
23148 Err(err) => {
23149 let message = format!("Failed to copy permalink: {err}");
23150
23151 anyhow::Result::<()>::Err(err).log_err();
23152
23153 if let Some(workspace) = workspace {
23154 workspace
23155 .update_in(cx, |workspace, _, cx| {
23156 struct CopyPermalinkToLine;
23157
23158 workspace.show_toast(
23159 Toast::new(
23160 NotificationId::unique::<CopyPermalinkToLine>(),
23161 message,
23162 ),
23163 cx,
23164 )
23165 })
23166 .ok();
23167 }
23168 }
23169 })
23170 .detach();
23171 }
23172
23173 pub fn copy_file_location(
23174 &mut self,
23175 _: &CopyFileLocation,
23176 _: &mut Window,
23177 cx: &mut Context<Self>,
23178 ) {
23179 let selection = self
23180 .selections
23181 .newest::<Point>(&self.display_snapshot(cx))
23182 .start
23183 .row
23184 + 1;
23185 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23186 let project = self.project()?.read(cx);
23187 let file = buffer.read(cx).file()?;
23188 let path = file.path().display(project.path_style(cx));
23189
23190 Some(format!("{path}:{selection}"))
23191 }) {
23192 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23193 }
23194 }
23195
23196 pub fn open_permalink_to_line(
23197 &mut self,
23198 _: &OpenPermalinkToLine,
23199 window: &mut Window,
23200 cx: &mut Context<Self>,
23201 ) {
23202 let permalink_task = self.get_permalink_to_line(cx);
23203 let workspace = self.workspace();
23204
23205 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23206 Ok(permalink) => {
23207 cx.update(|_, cx| {
23208 cx.open_url(permalink.as_ref());
23209 })
23210 .ok();
23211 }
23212 Err(err) => {
23213 let message = format!("Failed to open permalink: {err}");
23214
23215 anyhow::Result::<()>::Err(err).log_err();
23216
23217 if let Some(workspace) = workspace {
23218 workspace.update(cx, |workspace, cx| {
23219 struct OpenPermalinkToLine;
23220
23221 workspace.show_toast(
23222 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23223 cx,
23224 )
23225 });
23226 }
23227 }
23228 })
23229 .detach();
23230 }
23231
23232 pub fn insert_uuid_v4(
23233 &mut self,
23234 _: &InsertUuidV4,
23235 window: &mut Window,
23236 cx: &mut Context<Self>,
23237 ) {
23238 self.insert_uuid(UuidVersion::V4, window, cx);
23239 }
23240
23241 pub fn insert_uuid_v7(
23242 &mut self,
23243 _: &InsertUuidV7,
23244 window: &mut Window,
23245 cx: &mut Context<Self>,
23246 ) {
23247 self.insert_uuid(UuidVersion::V7, window, cx);
23248 }
23249
23250 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23251 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23252 self.transact(window, cx, |this, window, cx| {
23253 let edits = this
23254 .selections
23255 .all::<Point>(&this.display_snapshot(cx))
23256 .into_iter()
23257 .map(|selection| {
23258 let uuid = match version {
23259 UuidVersion::V4 => uuid::Uuid::new_v4(),
23260 UuidVersion::V7 => uuid::Uuid::now_v7(),
23261 };
23262
23263 (selection.range(), uuid.to_string())
23264 });
23265 this.edit(edits, cx);
23266 this.refresh_edit_prediction(true, false, window, cx);
23267 });
23268 }
23269
23270 pub fn open_selections_in_multibuffer(
23271 &mut self,
23272 _: &OpenSelectionsInMultibuffer,
23273 window: &mut Window,
23274 cx: &mut Context<Self>,
23275 ) {
23276 let multibuffer = self.buffer.read(cx);
23277
23278 let Some(buffer) = multibuffer.as_singleton() else {
23279 return;
23280 };
23281
23282 let Some(workspace) = self.workspace() else {
23283 return;
23284 };
23285
23286 let title = multibuffer.title(cx).to_string();
23287
23288 let locations = self
23289 .selections
23290 .all_anchors(&self.display_snapshot(cx))
23291 .iter()
23292 .map(|selection| {
23293 (
23294 buffer.clone(),
23295 (selection.start.text_anchor..selection.end.text_anchor)
23296 .to_point(buffer.read(cx)),
23297 )
23298 })
23299 .into_group_map();
23300
23301 cx.spawn_in(window, async move |_, cx| {
23302 workspace.update_in(cx, |workspace, window, cx| {
23303 Self::open_locations_in_multibuffer(
23304 workspace,
23305 locations,
23306 format!("Selections for '{title}'"),
23307 false,
23308 false,
23309 MultibufferSelectionMode::All,
23310 window,
23311 cx,
23312 );
23313 })
23314 })
23315 .detach();
23316 }
23317
23318 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23319 /// last highlight added will be used.
23320 ///
23321 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23322 pub fn highlight_rows<T: 'static>(
23323 &mut self,
23324 range: Range<Anchor>,
23325 color: Hsla,
23326 options: RowHighlightOptions,
23327 cx: &mut Context<Self>,
23328 ) {
23329 let snapshot = self.buffer().read(cx).snapshot(cx);
23330 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23331 let ix = row_highlights.binary_search_by(|highlight| {
23332 Ordering::Equal
23333 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23334 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23335 });
23336
23337 if let Err(mut ix) = ix {
23338 let index = post_inc(&mut self.highlight_order);
23339
23340 // If this range intersects with the preceding highlight, then merge it with
23341 // the preceding highlight. Otherwise insert a new highlight.
23342 let mut merged = false;
23343 if ix > 0 {
23344 let prev_highlight = &mut row_highlights[ix - 1];
23345 if prev_highlight
23346 .range
23347 .end
23348 .cmp(&range.start, &snapshot)
23349 .is_ge()
23350 {
23351 ix -= 1;
23352 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23353 prev_highlight.range.end = range.end;
23354 }
23355 merged = true;
23356 prev_highlight.index = index;
23357 prev_highlight.color = color;
23358 prev_highlight.options = options;
23359 }
23360 }
23361
23362 if !merged {
23363 row_highlights.insert(
23364 ix,
23365 RowHighlight {
23366 range,
23367 index,
23368 color,
23369 options,
23370 type_id: TypeId::of::<T>(),
23371 },
23372 );
23373 }
23374
23375 // If any of the following highlights intersect with this one, merge them.
23376 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23377 let highlight = &row_highlights[ix];
23378 if next_highlight
23379 .range
23380 .start
23381 .cmp(&highlight.range.end, &snapshot)
23382 .is_le()
23383 {
23384 if next_highlight
23385 .range
23386 .end
23387 .cmp(&highlight.range.end, &snapshot)
23388 .is_gt()
23389 {
23390 row_highlights[ix].range.end = next_highlight.range.end;
23391 }
23392 row_highlights.remove(ix + 1);
23393 } else {
23394 break;
23395 }
23396 }
23397 }
23398 }
23399
23400 /// Remove any highlighted row ranges of the given type that intersect the
23401 /// given ranges.
23402 pub fn remove_highlighted_rows<T: 'static>(
23403 &mut self,
23404 ranges_to_remove: Vec<Range<Anchor>>,
23405 cx: &mut Context<Self>,
23406 ) {
23407 let snapshot = self.buffer().read(cx).snapshot(cx);
23408 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23409 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23410 row_highlights.retain(|highlight| {
23411 while let Some(range_to_remove) = ranges_to_remove.peek() {
23412 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23413 Ordering::Less | Ordering::Equal => {
23414 ranges_to_remove.next();
23415 }
23416 Ordering::Greater => {
23417 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23418 Ordering::Less | Ordering::Equal => {
23419 return false;
23420 }
23421 Ordering::Greater => break,
23422 }
23423 }
23424 }
23425 }
23426
23427 true
23428 })
23429 }
23430
23431 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23432 pub fn clear_row_highlights<T: 'static>(&mut self) {
23433 self.highlighted_rows.remove(&TypeId::of::<T>());
23434 }
23435
23436 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23437 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23438 self.highlighted_rows
23439 .get(&TypeId::of::<T>())
23440 .map_or(&[] as &[_], |vec| vec.as_slice())
23441 .iter()
23442 .map(|highlight| (highlight.range.clone(), highlight.color))
23443 }
23444
23445 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23446 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23447 /// Allows to ignore certain kinds of highlights.
23448 pub fn highlighted_display_rows(
23449 &self,
23450 window: &mut Window,
23451 cx: &mut App,
23452 ) -> BTreeMap<DisplayRow, LineHighlight> {
23453 let snapshot = self.snapshot(window, cx);
23454 let mut used_highlight_orders = HashMap::default();
23455 self.highlighted_rows
23456 .iter()
23457 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23458 .fold(
23459 BTreeMap::<DisplayRow, LineHighlight>::new(),
23460 |mut unique_rows, highlight| {
23461 let start = highlight.range.start.to_display_point(&snapshot);
23462 let end = highlight.range.end.to_display_point(&snapshot);
23463 let start_row = start.row().0;
23464 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23465 {
23466 end.row().0.saturating_sub(1)
23467 } else {
23468 end.row().0
23469 };
23470 for row in start_row..=end_row {
23471 let used_index =
23472 used_highlight_orders.entry(row).or_insert(highlight.index);
23473 if highlight.index >= *used_index {
23474 *used_index = highlight.index;
23475 unique_rows.insert(
23476 DisplayRow(row),
23477 LineHighlight {
23478 include_gutter: highlight.options.include_gutter,
23479 border: None,
23480 background: highlight.color.into(),
23481 type_id: Some(highlight.type_id),
23482 },
23483 );
23484 }
23485 }
23486 unique_rows
23487 },
23488 )
23489 }
23490
23491 pub fn highlighted_display_row_for_autoscroll(
23492 &self,
23493 snapshot: &DisplaySnapshot,
23494 ) -> Option<DisplayRow> {
23495 self.highlighted_rows
23496 .values()
23497 .flat_map(|highlighted_rows| highlighted_rows.iter())
23498 .filter_map(|highlight| {
23499 if highlight.options.autoscroll {
23500 Some(highlight.range.start.to_display_point(snapshot).row())
23501 } else {
23502 None
23503 }
23504 })
23505 .min()
23506 }
23507
23508 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23509 self.highlight_background(
23510 HighlightKey::SearchWithinRange,
23511 ranges,
23512 |_, colors| colors.colors().editor_document_highlight_read_background,
23513 cx,
23514 )
23515 }
23516
23517 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23518 self.breadcrumb_header = Some(new_header);
23519 }
23520
23521 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23522 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23523 }
23524
23525 pub fn highlight_background(
23526 &mut self,
23527 key: HighlightKey,
23528 ranges: &[Range<Anchor>],
23529 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23530 cx: &mut Context<Self>,
23531 ) {
23532 self.background_highlights
23533 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23534 self.scrollbar_marker_state.dirty = true;
23535 cx.notify();
23536 }
23537
23538 pub fn clear_background_highlights(
23539 &mut self,
23540 key: HighlightKey,
23541 cx: &mut Context<Self>,
23542 ) -> Option<BackgroundHighlight> {
23543 let text_highlights = self.background_highlights.remove(&key)?;
23544 if !text_highlights.1.is_empty() {
23545 self.scrollbar_marker_state.dirty = true;
23546 cx.notify();
23547 }
23548 Some(text_highlights)
23549 }
23550
23551 pub fn highlight_gutter<T: 'static>(
23552 &mut self,
23553 ranges: impl Into<Vec<Range<Anchor>>>,
23554 color_fetcher: fn(&App) -> Hsla,
23555 cx: &mut Context<Self>,
23556 ) {
23557 self.gutter_highlights
23558 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23559 cx.notify();
23560 }
23561
23562 pub fn clear_gutter_highlights<T: 'static>(
23563 &mut self,
23564 cx: &mut Context<Self>,
23565 ) -> Option<GutterHighlight> {
23566 cx.notify();
23567 self.gutter_highlights.remove(&TypeId::of::<T>())
23568 }
23569
23570 pub fn insert_gutter_highlight<T: 'static>(
23571 &mut self,
23572 range: Range<Anchor>,
23573 color_fetcher: fn(&App) -> Hsla,
23574 cx: &mut Context<Self>,
23575 ) {
23576 let snapshot = self.buffer().read(cx).snapshot(cx);
23577 let mut highlights = self
23578 .gutter_highlights
23579 .remove(&TypeId::of::<T>())
23580 .map(|(_, highlights)| highlights)
23581 .unwrap_or_default();
23582 let ix = highlights.binary_search_by(|highlight| {
23583 Ordering::Equal
23584 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23585 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23586 });
23587 if let Err(ix) = ix {
23588 highlights.insert(ix, range);
23589 }
23590 self.gutter_highlights
23591 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23592 }
23593
23594 pub fn remove_gutter_highlights<T: 'static>(
23595 &mut self,
23596 ranges_to_remove: Vec<Range<Anchor>>,
23597 cx: &mut Context<Self>,
23598 ) {
23599 let snapshot = self.buffer().read(cx).snapshot(cx);
23600 let Some((color_fetcher, mut gutter_highlights)) =
23601 self.gutter_highlights.remove(&TypeId::of::<T>())
23602 else {
23603 return;
23604 };
23605 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23606 gutter_highlights.retain(|highlight| {
23607 while let Some(range_to_remove) = ranges_to_remove.peek() {
23608 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23609 Ordering::Less | Ordering::Equal => {
23610 ranges_to_remove.next();
23611 }
23612 Ordering::Greater => {
23613 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23614 Ordering::Less | Ordering::Equal => {
23615 return false;
23616 }
23617 Ordering::Greater => break,
23618 }
23619 }
23620 }
23621 }
23622
23623 true
23624 });
23625 self.gutter_highlights
23626 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23627 }
23628
23629 #[cfg(any(test, feature = "test-support"))]
23630 pub fn all_text_highlights(
23631 &self,
23632 window: &mut Window,
23633 cx: &mut Context<Self>,
23634 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23635 let snapshot = self.snapshot(window, cx);
23636 self.display_map.update(cx, |display_map, _| {
23637 display_map
23638 .all_text_highlights()
23639 .map(|(_, highlight)| {
23640 let (style, ranges) = highlight.as_ref();
23641 (
23642 *style,
23643 ranges
23644 .iter()
23645 .map(|range| range.clone().to_display_points(&snapshot))
23646 .collect(),
23647 )
23648 })
23649 .collect()
23650 })
23651 }
23652
23653 #[cfg(any(test, feature = "test-support"))]
23654 pub fn all_text_background_highlights(
23655 &self,
23656 window: &mut Window,
23657 cx: &mut Context<Self>,
23658 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23659 let snapshot = self.snapshot(window, cx);
23660 let buffer = &snapshot.buffer_snapshot();
23661 let start = buffer.anchor_before(MultiBufferOffset(0));
23662 let end = buffer.anchor_after(buffer.len());
23663 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23664 }
23665
23666 #[cfg(any(test, feature = "test-support"))]
23667 pub fn sorted_background_highlights_in_range(
23668 &self,
23669 search_range: Range<Anchor>,
23670 display_snapshot: &DisplaySnapshot,
23671 theme: &Theme,
23672 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23673 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23674 res.sort_by(|a, b| {
23675 a.0.start
23676 .cmp(&b.0.start)
23677 .then_with(|| a.0.end.cmp(&b.0.end))
23678 .then_with(|| a.1.cmp(&b.1))
23679 });
23680 res
23681 }
23682
23683 #[cfg(any(test, feature = "test-support"))]
23684 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23685 let snapshot = self.buffer().read(cx).snapshot(cx);
23686
23687 let highlights = self
23688 .background_highlights
23689 .get(&HighlightKey::BufferSearchHighlights);
23690
23691 if let Some((_color, ranges)) = highlights {
23692 ranges
23693 .iter()
23694 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23695 .collect_vec()
23696 } else {
23697 vec![]
23698 }
23699 }
23700
23701 fn document_highlights_for_position<'a>(
23702 &'a self,
23703 position: Anchor,
23704 buffer: &'a MultiBufferSnapshot,
23705 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23706 let read_highlights = self
23707 .background_highlights
23708 .get(&HighlightKey::DocumentHighlightRead)
23709 .map(|h| &h.1);
23710 let write_highlights = self
23711 .background_highlights
23712 .get(&HighlightKey::DocumentHighlightWrite)
23713 .map(|h| &h.1);
23714 let left_position = position.bias_left(buffer);
23715 let right_position = position.bias_right(buffer);
23716 read_highlights
23717 .into_iter()
23718 .chain(write_highlights)
23719 .flat_map(move |ranges| {
23720 let start_ix = match ranges.binary_search_by(|probe| {
23721 let cmp = probe.end.cmp(&left_position, buffer);
23722 if cmp.is_ge() {
23723 Ordering::Greater
23724 } else {
23725 Ordering::Less
23726 }
23727 }) {
23728 Ok(i) | Err(i) => i,
23729 };
23730
23731 ranges[start_ix..]
23732 .iter()
23733 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23734 })
23735 }
23736
23737 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23738 self.background_highlights
23739 .get(&key)
23740 .is_some_and(|(_, highlights)| !highlights.is_empty())
23741 }
23742
23743 /// Returns all background highlights for a given range.
23744 ///
23745 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23746 pub fn background_highlights_in_range(
23747 &self,
23748 search_range: Range<Anchor>,
23749 display_snapshot: &DisplaySnapshot,
23750 theme: &Theme,
23751 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23752 let mut results = Vec::new();
23753 for (color_fetcher, ranges) in self.background_highlights.values() {
23754 let start_ix = match ranges.binary_search_by(|probe| {
23755 let cmp = probe
23756 .end
23757 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23758 if cmp.is_gt() {
23759 Ordering::Greater
23760 } else {
23761 Ordering::Less
23762 }
23763 }) {
23764 Ok(i) | Err(i) => i,
23765 };
23766 for (index, range) in ranges[start_ix..].iter().enumerate() {
23767 if range
23768 .start
23769 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23770 .is_ge()
23771 {
23772 break;
23773 }
23774
23775 let color = color_fetcher(&(start_ix + index), theme);
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 pub fn gutter_highlights_in_range(
23785 &self,
23786 search_range: Range<Anchor>,
23787 display_snapshot: &DisplaySnapshot,
23788 cx: &App,
23789 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23790 let mut results = Vec::new();
23791 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23792 let color = color_fetcher(cx);
23793 let start_ix = match ranges.binary_search_by(|probe| {
23794 let cmp = probe
23795 .end
23796 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23797 if cmp.is_gt() {
23798 Ordering::Greater
23799 } else {
23800 Ordering::Less
23801 }
23802 }) {
23803 Ok(i) | Err(i) => i,
23804 };
23805 for range in &ranges[start_ix..] {
23806 if range
23807 .start
23808 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23809 .is_ge()
23810 {
23811 break;
23812 }
23813
23814 let start = range.start.to_display_point(display_snapshot);
23815 let end = range.end.to_display_point(display_snapshot);
23816 results.push((start..end, color))
23817 }
23818 }
23819 results
23820 }
23821
23822 /// Get the text ranges corresponding to the redaction query
23823 pub fn redacted_ranges(
23824 &self,
23825 search_range: Range<Anchor>,
23826 display_snapshot: &DisplaySnapshot,
23827 cx: &App,
23828 ) -> Vec<Range<DisplayPoint>> {
23829 display_snapshot
23830 .buffer_snapshot()
23831 .redacted_ranges(search_range, |file| {
23832 if let Some(file) = file {
23833 file.is_private()
23834 && EditorSettings::get(
23835 Some(SettingsLocation {
23836 worktree_id: file.worktree_id(cx),
23837 path: file.path().as_ref(),
23838 }),
23839 cx,
23840 )
23841 .redact_private_values
23842 } else {
23843 false
23844 }
23845 })
23846 .map(|range| {
23847 range.start.to_display_point(display_snapshot)
23848 ..range.end.to_display_point(display_snapshot)
23849 })
23850 .collect()
23851 }
23852
23853 pub fn highlight_text_key(
23854 &mut self,
23855 key: HighlightKey,
23856 ranges: Vec<Range<Anchor>>,
23857 style: HighlightStyle,
23858 merge: bool,
23859 cx: &mut Context<Self>,
23860 ) {
23861 self.display_map.update(cx, |map, cx| {
23862 map.highlight_text(key, ranges, style, merge, cx);
23863 });
23864 cx.notify();
23865 }
23866
23867 pub fn highlight_text(
23868 &mut self,
23869 key: HighlightKey,
23870 ranges: Vec<Range<Anchor>>,
23871 style: HighlightStyle,
23872 cx: &mut Context<Self>,
23873 ) {
23874 self.display_map.update(cx, |map, cx| {
23875 map.highlight_text(key, ranges, style, false, cx)
23876 });
23877 cx.notify();
23878 }
23879
23880 pub fn text_highlights<'a>(
23881 &'a self,
23882 key: HighlightKey,
23883 cx: &'a App,
23884 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23885 self.display_map.read(cx).text_highlights(key)
23886 }
23887
23888 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23889 let cleared = self
23890 .display_map
23891 .update(cx, |map, _| map.clear_highlights(key));
23892 if cleared {
23893 cx.notify();
23894 }
23895 }
23896
23897 pub fn clear_highlights_with(
23898 &mut self,
23899 f: &mut dyn FnMut(&HighlightKey) -> bool,
23900 cx: &mut Context<Self>,
23901 ) {
23902 let cleared = self
23903 .display_map
23904 .update(cx, |map, _| map.clear_highlights_with(f));
23905 if cleared {
23906 cx.notify();
23907 }
23908 }
23909
23910 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23911 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23912 && self.focus_handle.is_focused(window)
23913 }
23914
23915 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23916 self.show_cursor_when_unfocused = is_enabled;
23917 cx.notify();
23918 }
23919
23920 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23921 cx.notify();
23922 }
23923
23924 fn on_debug_session_event(
23925 &mut self,
23926 _session: Entity<Session>,
23927 event: &SessionEvent,
23928 cx: &mut Context<Self>,
23929 ) {
23930 if let SessionEvent::InvalidateInlineValue = event {
23931 self.refresh_inline_values(cx);
23932 }
23933 }
23934
23935 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23936 let Some(project) = self.project.clone() else {
23937 return;
23938 };
23939
23940 if !self.inline_value_cache.enabled {
23941 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23942 self.splice_inlays(&inlays, Vec::new(), cx);
23943 return;
23944 }
23945
23946 let current_execution_position = self
23947 .highlighted_rows
23948 .get(&TypeId::of::<ActiveDebugLine>())
23949 .and_then(|lines| lines.last().map(|line| line.range.end));
23950
23951 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23952 let inline_values = editor
23953 .update(cx, |editor, cx| {
23954 let Some(current_execution_position) = current_execution_position else {
23955 return Some(Task::ready(Ok(Vec::new())));
23956 };
23957
23958 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23959 let snapshot = buffer.snapshot(cx);
23960
23961 let excerpt = snapshot.excerpt_containing(
23962 current_execution_position..current_execution_position,
23963 )?;
23964
23965 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23966 })?;
23967
23968 let range =
23969 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23970
23971 project.inline_values(buffer, range, cx)
23972 })
23973 .ok()
23974 .flatten()?
23975 .await
23976 .context("refreshing debugger inlays")
23977 .log_err()?;
23978
23979 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23980
23981 for (buffer_id, inline_value) in inline_values
23982 .into_iter()
23983 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23984 {
23985 buffer_inline_values
23986 .entry(buffer_id)
23987 .or_default()
23988 .push(inline_value);
23989 }
23990
23991 editor
23992 .update(cx, |editor, cx| {
23993 let snapshot = editor.buffer.read(cx).snapshot(cx);
23994 let mut new_inlays = Vec::default();
23995
23996 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23997 let buffer_id = buffer_snapshot.remote_id();
23998 buffer_inline_values
23999 .get(&buffer_id)
24000 .into_iter()
24001 .flatten()
24002 .for_each(|hint| {
24003 let inlay = Inlay::debugger(
24004 post_inc(&mut editor.next_inlay_id),
24005 Anchor::in_buffer(excerpt_id, hint.position),
24006 hint.text(),
24007 );
24008 if !inlay.text().chars().contains(&'\n') {
24009 new_inlays.push(inlay);
24010 }
24011 });
24012 }
24013
24014 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24015 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24016
24017 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24018 })
24019 .ok()?;
24020 Some(())
24021 });
24022 }
24023
24024 fn on_buffer_event(
24025 &mut self,
24026 multibuffer: &Entity<MultiBuffer>,
24027 event: &multi_buffer::Event,
24028 window: &mut Window,
24029 cx: &mut Context<Self>,
24030 ) {
24031 match event {
24032 multi_buffer::Event::Edited { edited_buffer } => {
24033 self.scrollbar_marker_state.dirty = true;
24034 self.active_indent_guides_state.dirty = true;
24035 self.refresh_active_diagnostics(cx);
24036 self.refresh_code_actions(window, cx);
24037 self.refresh_single_line_folds(window, cx);
24038 let snapshot = self.snapshot(window, cx);
24039 self.refresh_matching_bracket_highlights(&snapshot, cx);
24040 self.refresh_outline_symbols_at_cursor(cx);
24041 self.refresh_sticky_headers(&snapshot, cx);
24042 if self.has_active_edit_prediction() {
24043 self.update_visible_edit_prediction(window, cx);
24044 }
24045
24046 // Clean up orphaned review comments after edits
24047 self.cleanup_orphaned_review_comments(cx);
24048
24049 if let Some(buffer) = edited_buffer {
24050 if buffer.read(cx).file().is_none() {
24051 cx.emit(EditorEvent::TitleChanged);
24052 }
24053
24054 if self.project.is_some() {
24055 let buffer_id = buffer.read(cx).remote_id();
24056 self.register_buffer(buffer_id, cx);
24057 self.update_lsp_data(Some(buffer_id), window, cx);
24058 self.refresh_inlay_hints(
24059 InlayHintRefreshReason::BufferEdited(buffer_id),
24060 cx,
24061 );
24062 }
24063 }
24064
24065 cx.emit(EditorEvent::BufferEdited);
24066 cx.emit(SearchEvent::MatchesInvalidated);
24067
24068 let Some(project) = &self.project else { return };
24069 let (telemetry, is_via_ssh) = {
24070 let project = project.read(cx);
24071 let telemetry = project.client().telemetry().clone();
24072 let is_via_ssh = project.is_via_remote_server();
24073 (telemetry, is_via_ssh)
24074 };
24075 telemetry.log_edit_event("editor", is_via_ssh);
24076 }
24077 multi_buffer::Event::ExcerptsAdded {
24078 buffer,
24079 predecessor,
24080 excerpts,
24081 } => {
24082 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24083 let buffer_id = buffer.read(cx).remote_id();
24084 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24085 && let Some(project) = &self.project
24086 {
24087 update_uncommitted_diff_for_buffer(
24088 cx.entity(),
24089 project,
24090 [buffer.clone()],
24091 self.buffer.clone(),
24092 cx,
24093 )
24094 .detach();
24095 }
24096 self.semantic_token_state
24097 .invalidate_buffer(&buffer.read(cx).remote_id());
24098 self.update_lsp_data(Some(buffer_id), window, cx);
24099 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24100 self.colorize_brackets(false, cx);
24101 self.refresh_selected_text_highlights(true, window, cx);
24102 cx.emit(EditorEvent::ExcerptsAdded {
24103 buffer: buffer.clone(),
24104 predecessor: *predecessor,
24105 excerpts: excerpts.clone(),
24106 });
24107 }
24108 multi_buffer::Event::ExcerptsRemoved {
24109 ids,
24110 removed_buffer_ids,
24111 } => {
24112 if let Some(inlay_hints) = &mut self.inlay_hints {
24113 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24114 }
24115 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24116 for buffer_id in removed_buffer_ids {
24117 self.registered_buffers.remove(buffer_id);
24118 self.tasks
24119 .retain(|(task_buffer_id, _), _| task_buffer_id != buffer_id);
24120 self.semantic_token_state.invalidate_buffer(buffer_id);
24121 self.display_map.update(cx, |display_map, cx| {
24122 display_map.invalidate_semantic_highlights(*buffer_id);
24123 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24124 });
24125 }
24126 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24127 cx.emit(EditorEvent::ExcerptsRemoved {
24128 ids: ids.clone(),
24129 removed_buffer_ids: removed_buffer_ids.clone(),
24130 });
24131 }
24132 multi_buffer::Event::ExcerptsEdited {
24133 excerpt_ids,
24134 buffer_ids,
24135 } => {
24136 self.display_map.update(cx, |map, cx| {
24137 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24138 });
24139 cx.emit(EditorEvent::ExcerptsEdited {
24140 ids: excerpt_ids.clone(),
24141 });
24142 }
24143 multi_buffer::Event::ExcerptsExpanded { ids } => {
24144 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24145 self.refresh_document_highlights(cx);
24146 let snapshot = multibuffer.read(cx).snapshot(cx);
24147 for id in ids {
24148 self.fetched_tree_sitter_chunks.remove(id);
24149 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24150 self.semantic_token_state
24151 .invalidate_buffer(&buffer.remote_id());
24152 }
24153 }
24154 self.colorize_brackets(false, cx);
24155 self.update_lsp_data(None, window, cx);
24156 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24157 }
24158 multi_buffer::Event::Reparsed(buffer_id) => {
24159 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24160 self.refresh_selected_text_highlights(true, window, cx);
24161 self.colorize_brackets(true, cx);
24162 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24163
24164 cx.emit(EditorEvent::Reparsed(*buffer_id));
24165 }
24166 multi_buffer::Event::DiffHunksToggled => {
24167 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24168 }
24169 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24170 if !is_fresh_language {
24171 self.registered_buffers.remove(&buffer_id);
24172 }
24173 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24174 cx.emit(EditorEvent::Reparsed(*buffer_id));
24175 cx.notify();
24176 }
24177 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24178 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24179 multi_buffer::Event::FileHandleChanged
24180 | multi_buffer::Event::Reloaded
24181 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24182 multi_buffer::Event::DiagnosticsUpdated => {
24183 self.update_diagnostics_state(window, cx);
24184 }
24185 _ => {}
24186 };
24187 }
24188
24189 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24190 if !self.diagnostics_enabled() {
24191 return;
24192 }
24193 self.refresh_active_diagnostics(cx);
24194 self.refresh_inline_diagnostics(true, window, cx);
24195 self.scrollbar_marker_state.dirty = true;
24196 cx.notify();
24197 }
24198
24199 pub fn start_temporary_diff_override(&mut self) {
24200 self.load_diff_task.take();
24201 self.temporary_diff_override = true;
24202 }
24203
24204 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24205 self.temporary_diff_override = false;
24206 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24207 self.buffer.update(cx, |buffer, cx| {
24208 buffer.set_all_diff_hunks_collapsed(cx);
24209 });
24210
24211 if let Some(project) = self.project.clone() {
24212 self.load_diff_task = Some(
24213 update_uncommitted_diff_for_buffer(
24214 cx.entity(),
24215 &project,
24216 self.buffer.read(cx).all_buffers(),
24217 self.buffer.clone(),
24218 cx,
24219 )
24220 .shared(),
24221 );
24222 }
24223 }
24224
24225 fn on_display_map_changed(
24226 &mut self,
24227 _: Entity<DisplayMap>,
24228 _: &mut Window,
24229 cx: &mut Context<Self>,
24230 ) {
24231 cx.notify();
24232 }
24233
24234 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24235 if !self.mode.is_full() {
24236 return None;
24237 }
24238
24239 let theme_settings = theme::ThemeSettings::get_global(cx);
24240 let theme = cx.theme();
24241 let accent_colors = theme.accents().clone();
24242
24243 let accent_overrides = theme_settings
24244 .theme_overrides
24245 .get(theme.name.as_ref())
24246 .map(|theme_style| &theme_style.accents)
24247 .into_iter()
24248 .flatten()
24249 .chain(
24250 theme_settings
24251 .experimental_theme_overrides
24252 .as_ref()
24253 .map(|overrides| &overrides.accents)
24254 .into_iter()
24255 .flatten(),
24256 )
24257 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24258 .collect();
24259
24260 Some(AccentData {
24261 colors: accent_colors,
24262 overrides: accent_overrides,
24263 })
24264 }
24265
24266 fn fetch_applicable_language_settings(
24267 &self,
24268 cx: &App,
24269 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24270 if !self.mode.is_full() {
24271 return HashMap::default();
24272 }
24273
24274 self.buffer().read(cx).all_buffers().into_iter().fold(
24275 HashMap::default(),
24276 |mut acc, buffer| {
24277 let buffer = buffer.read(cx);
24278 let language = buffer.language().map(|language| language.name());
24279 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24280 let file = buffer.file();
24281 v.insert(language_settings(language, file, cx).into_owned());
24282 }
24283 acc
24284 },
24285 )
24286 }
24287
24288 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24289 let new_language_settings = self.fetch_applicable_language_settings(cx);
24290 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24291 self.applicable_language_settings = new_language_settings;
24292
24293 let new_accents = self.fetch_accent_data(cx);
24294 let accents_changed = new_accents != self.accent_data;
24295 self.accent_data = new_accents;
24296
24297 if self.diagnostics_enabled() {
24298 let new_severity = EditorSettings::get_global(cx)
24299 .diagnostics_max_severity
24300 .unwrap_or(DiagnosticSeverity::Hint);
24301 self.set_max_diagnostics_severity(new_severity, cx);
24302 }
24303 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24304 self.update_edit_prediction_settings(cx);
24305 self.refresh_edit_prediction(true, false, window, cx);
24306 self.refresh_inline_values(cx);
24307
24308 let old_cursor_shape = self.cursor_shape;
24309 let old_show_breadcrumbs = self.show_breadcrumbs;
24310
24311 {
24312 let editor_settings = EditorSettings::get_global(cx);
24313 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24314 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24315 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24316 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24317 }
24318
24319 if old_cursor_shape != self.cursor_shape {
24320 cx.emit(EditorEvent::CursorShapeChanged);
24321 }
24322
24323 if old_show_breadcrumbs != self.show_breadcrumbs {
24324 cx.emit(EditorEvent::BreadcrumbsChanged);
24325 }
24326
24327 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24328 let project_settings = ProjectSettings::get_global(cx);
24329 (
24330 project_settings.session.restore_unsaved_buffers,
24331 project_settings.diagnostics.inline.enabled,
24332 project_settings.git.inline_blame.enabled,
24333 )
24334 };
24335 self.buffer_serialization = self
24336 .should_serialize_buffer()
24337 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24338
24339 if self.mode.is_full() {
24340 if self.show_inline_diagnostics != show_inline_diagnostics {
24341 self.show_inline_diagnostics = show_inline_diagnostics;
24342 self.refresh_inline_diagnostics(false, window, cx);
24343 }
24344
24345 if self.git_blame_inline_enabled != inline_blame_enabled {
24346 self.toggle_git_blame_inline_internal(false, window, cx);
24347 }
24348
24349 let minimap_settings = EditorSettings::get_global(cx).minimap;
24350 if self.minimap_visibility != MinimapVisibility::Disabled {
24351 if self.minimap_visibility.settings_visibility()
24352 != minimap_settings.minimap_enabled()
24353 {
24354 self.set_minimap_visibility(
24355 MinimapVisibility::for_mode(self.mode(), cx),
24356 window,
24357 cx,
24358 );
24359 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24360 minimap_entity.update(cx, |minimap_editor, cx| {
24361 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24362 })
24363 }
24364 }
24365
24366 if language_settings_changed || accents_changed {
24367 self.colorize_brackets(true, cx);
24368 }
24369
24370 if language_settings_changed {
24371 self.clear_disabled_lsp_folding_ranges(window, cx);
24372 self.refresh_document_symbols(None, cx);
24373 }
24374
24375 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24376 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24377 }) {
24378 if !inlay_splice.is_empty() {
24379 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24380 }
24381 self.refresh_document_colors(None, window, cx);
24382 }
24383
24384 self.refresh_inlay_hints(
24385 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24386 self.selections.newest_anchor().head(),
24387 &self.buffer.read(cx).snapshot(cx),
24388 cx,
24389 )),
24390 cx,
24391 );
24392
24393 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24394 .global_lsp_settings
24395 .semantic_token_rules
24396 .clone();
24397 let semantic_token_rules_changed = self
24398 .semantic_token_state
24399 .update_rules(new_semantic_token_rules);
24400 if language_settings_changed || semantic_token_rules_changed {
24401 self.invalidate_semantic_tokens(None);
24402 self.refresh_semantic_tokens(None, None, cx);
24403 }
24404 }
24405
24406 cx.notify();
24407 }
24408
24409 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24410 if !self.mode.is_full() {
24411 return;
24412 }
24413
24414 let new_accents = self.fetch_accent_data(cx);
24415 if new_accents != self.accent_data {
24416 self.accent_data = new_accents;
24417 self.colorize_brackets(true, cx);
24418 }
24419
24420 self.invalidate_semantic_tokens(None);
24421 self.refresh_semantic_tokens(None, None, cx);
24422 }
24423
24424 pub fn set_searchable(&mut self, searchable: bool) {
24425 self.searchable = searchable;
24426 }
24427
24428 pub fn searchable(&self) -> bool {
24429 self.searchable
24430 }
24431
24432 pub fn open_excerpts_in_split(
24433 &mut self,
24434 _: &OpenExcerptsSplit,
24435 window: &mut Window,
24436 cx: &mut Context<Self>,
24437 ) {
24438 self.open_excerpts_common(None, true, window, cx)
24439 }
24440
24441 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24442 self.open_excerpts_common(None, false, window, cx)
24443 }
24444
24445 pub(crate) fn open_excerpts_common(
24446 &mut self,
24447 jump_data: Option<JumpData>,
24448 split: bool,
24449 window: &mut Window,
24450 cx: &mut Context<Self>,
24451 ) {
24452 if self.buffer.read(cx).is_singleton() {
24453 cx.propagate();
24454 return;
24455 }
24456
24457 let mut new_selections_by_buffer = HashMap::default();
24458 match &jump_data {
24459 Some(JumpData::MultiBufferPoint {
24460 excerpt_id,
24461 position,
24462 anchor,
24463 line_offset_from_top,
24464 }) => {
24465 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24466 if let Some(buffer) = multi_buffer_snapshot
24467 .buffer_id_for_excerpt(*excerpt_id)
24468 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24469 {
24470 let buffer_snapshot = buffer.read(cx).snapshot();
24471 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24472 language::ToPoint::to_point(anchor, &buffer_snapshot)
24473 } else {
24474 buffer_snapshot.clip_point(*position, Bias::Left)
24475 };
24476 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24477 new_selections_by_buffer.insert(
24478 buffer,
24479 (
24480 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24481 Some(*line_offset_from_top),
24482 ),
24483 );
24484 }
24485 }
24486 Some(JumpData::MultiBufferRow {
24487 row,
24488 line_offset_from_top,
24489 }) => {
24490 let point = MultiBufferPoint::new(row.0, 0);
24491 if let Some((buffer, buffer_point, _)) =
24492 self.buffer.read(cx).point_to_buffer_point(point, cx)
24493 {
24494 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24495 new_selections_by_buffer
24496 .entry(buffer)
24497 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24498 .0
24499 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24500 }
24501 }
24502 None => {
24503 let selections = self
24504 .selections
24505 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24506 let multi_buffer = self.buffer.read(cx);
24507 for selection in selections {
24508 for (snapshot, range, _, anchor) in multi_buffer
24509 .snapshot(cx)
24510 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24511 {
24512 if let Some(anchor) = anchor {
24513 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24514 else {
24515 continue;
24516 };
24517 let offset = text::ToOffset::to_offset(
24518 &anchor.text_anchor,
24519 &buffer_handle.read(cx).snapshot(),
24520 );
24521 let range = BufferOffset(offset)..BufferOffset(offset);
24522 new_selections_by_buffer
24523 .entry(buffer_handle)
24524 .or_insert((Vec::new(), None))
24525 .0
24526 .push(range)
24527 } else {
24528 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24529 else {
24530 continue;
24531 };
24532 new_selections_by_buffer
24533 .entry(buffer_handle)
24534 .or_insert((Vec::new(), None))
24535 .0
24536 .push(range)
24537 }
24538 }
24539 }
24540 }
24541 }
24542
24543 if self.delegate_open_excerpts {
24544 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24545 .into_iter()
24546 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24547 .collect();
24548 if !selections_by_buffer.is_empty() {
24549 cx.emit(EditorEvent::OpenExcerptsRequested {
24550 selections_by_buffer,
24551 split,
24552 });
24553 }
24554 return;
24555 }
24556
24557 let Some(workspace) = self.workspace() else {
24558 cx.propagate();
24559 return;
24560 };
24561
24562 new_selections_by_buffer
24563 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24564
24565 if new_selections_by_buffer.is_empty() {
24566 return;
24567 }
24568
24569 Self::open_buffers_in_workspace(
24570 workspace.downgrade(),
24571 new_selections_by_buffer,
24572 split,
24573 window,
24574 cx,
24575 );
24576 }
24577
24578 pub(crate) fn open_buffers_in_workspace(
24579 workspace: WeakEntity<Workspace>,
24580 new_selections_by_buffer: HashMap<
24581 Entity<language::Buffer>,
24582 (Vec<Range<BufferOffset>>, Option<u32>),
24583 >,
24584 split: bool,
24585 window: &mut Window,
24586 cx: &mut App,
24587 ) {
24588 // We defer the pane interaction because we ourselves are a workspace item
24589 // and activating a new item causes the pane to call a method on us reentrantly,
24590 // which panics if we're on the stack.
24591 window.defer(cx, move |window, cx| {
24592 workspace
24593 .update(cx, |workspace, cx| {
24594 let pane = if split {
24595 workspace.adjacent_pane(window, cx)
24596 } else {
24597 workspace.active_pane().clone()
24598 };
24599
24600 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24601 let buffer_read = buffer.read(cx);
24602 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24603 (true, project::File::from_dyn(Some(file)).is_some())
24604 } else {
24605 (false, false)
24606 };
24607
24608 // If project file is none workspace.open_project_item will fail to open the excerpt
24609 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24610 // so we check if there's a tab match in that case first
24611 let editor = (!has_file || !is_project_file)
24612 .then(|| {
24613 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24614 // so `workspace.open_project_item` will never find them, always opening a new editor.
24615 // Instead, we try to activate the existing editor in the pane first.
24616 let (editor, pane_item_index, pane_item_id) =
24617 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24618 let editor = item.downcast::<Editor>()?;
24619 let singleton_buffer =
24620 editor.read(cx).buffer().read(cx).as_singleton()?;
24621 if singleton_buffer == buffer {
24622 Some((editor, i, item.item_id()))
24623 } else {
24624 None
24625 }
24626 })?;
24627 pane.update(cx, |pane, cx| {
24628 pane.activate_item(pane_item_index, true, true, window, cx);
24629 if !PreviewTabsSettings::get_global(cx)
24630 .enable_preview_from_multibuffer
24631 {
24632 pane.unpreview_item_if_preview(pane_item_id);
24633 }
24634 });
24635 Some(editor)
24636 })
24637 .flatten()
24638 .unwrap_or_else(|| {
24639 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24640 .enable_keep_preview_on_code_navigation;
24641 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24642 .enable_preview_from_multibuffer;
24643 workspace.open_project_item::<Self>(
24644 pane.clone(),
24645 buffer,
24646 true,
24647 true,
24648 keep_old_preview,
24649 allow_new_preview,
24650 window,
24651 cx,
24652 )
24653 });
24654
24655 editor.update(cx, |editor, cx| {
24656 if has_file && !is_project_file {
24657 editor.set_read_only(true);
24658 }
24659 let autoscroll = match scroll_offset {
24660 Some(scroll_offset) => {
24661 Autoscroll::top_relative(scroll_offset as usize)
24662 }
24663 None => Autoscroll::newest(),
24664 };
24665 let nav_history = editor.nav_history.take();
24666 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24667 let Some((excerpt_id, _, buffer_snapshot)) =
24668 multibuffer_snapshot.as_singleton()
24669 else {
24670 return;
24671 };
24672 editor.change_selections(
24673 SelectionEffects::scroll(autoscroll),
24674 window,
24675 cx,
24676 |s| {
24677 s.select_ranges(ranges.into_iter().map(|range| {
24678 let range = buffer_snapshot.anchor_before(range.start)
24679 ..buffer_snapshot.anchor_after(range.end);
24680 multibuffer_snapshot
24681 .anchor_range_in_excerpt(excerpt_id, range)
24682 .unwrap()
24683 }));
24684 },
24685 );
24686 editor.nav_history = nav_history;
24687 });
24688 }
24689 })
24690 .ok();
24691 });
24692 }
24693
24694 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24695 let snapshot = self.buffer.read(cx).read(cx);
24696 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24697 Some(
24698 ranges
24699 .iter()
24700 .map(move |range| {
24701 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24702 })
24703 .collect(),
24704 )
24705 }
24706
24707 fn selection_replacement_ranges(
24708 &self,
24709 range: Range<MultiBufferOffsetUtf16>,
24710 cx: &mut App,
24711 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24712 let selections = self
24713 .selections
24714 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24715 let newest_selection = selections
24716 .iter()
24717 .max_by_key(|selection| selection.id)
24718 .unwrap();
24719 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24720 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24721 let snapshot = self.buffer.read(cx).read(cx);
24722 selections
24723 .into_iter()
24724 .map(|mut selection| {
24725 selection.start.0.0 =
24726 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24727 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24728 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24729 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24730 })
24731 .collect()
24732 }
24733
24734 fn report_editor_event(
24735 &self,
24736 reported_event: ReportEditorEvent,
24737 file_extension: Option<String>,
24738 cx: &App,
24739 ) {
24740 if cfg!(any(test, feature = "test-support")) {
24741 return;
24742 }
24743
24744 let Some(project) = &self.project else { return };
24745
24746 // If None, we are in a file without an extension
24747 let file = self
24748 .buffer
24749 .read(cx)
24750 .as_singleton()
24751 .and_then(|b| b.read(cx).file());
24752 let file_extension = file_extension.or(file
24753 .as_ref()
24754 .and_then(|file| Path::new(file.file_name(cx)).extension())
24755 .and_then(|e| e.to_str())
24756 .map(|a| a.to_string()));
24757
24758 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24759 .map(|vim_mode| vim_mode.0)
24760 .unwrap_or(false);
24761
24762 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24763 let copilot_enabled = edit_predictions_provider
24764 == language::language_settings::EditPredictionProvider::Copilot;
24765 let copilot_enabled_for_language = self
24766 .buffer
24767 .read(cx)
24768 .language_settings(cx)
24769 .show_edit_predictions;
24770
24771 let project = project.read(cx);
24772 let event_type = reported_event.event_type();
24773
24774 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24775 telemetry::event!(
24776 event_type,
24777 type = if auto_saved {"autosave"} else {"manual"},
24778 file_extension,
24779 vim_mode,
24780 copilot_enabled,
24781 copilot_enabled_for_language,
24782 edit_predictions_provider,
24783 is_via_ssh = project.is_via_remote_server(),
24784 );
24785 } else {
24786 telemetry::event!(
24787 event_type,
24788 file_extension,
24789 vim_mode,
24790 copilot_enabled,
24791 copilot_enabled_for_language,
24792 edit_predictions_provider,
24793 is_via_ssh = project.is_via_remote_server(),
24794 );
24795 };
24796 }
24797
24798 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24799 /// with each line being an array of {text, highlight} objects.
24800 fn copy_highlight_json(
24801 &mut self,
24802 _: &CopyHighlightJson,
24803 window: &mut Window,
24804 cx: &mut Context<Self>,
24805 ) {
24806 #[derive(Serialize)]
24807 struct Chunk<'a> {
24808 text: String,
24809 highlight: Option<&'a str>,
24810 }
24811
24812 let snapshot = self.buffer.read(cx).snapshot(cx);
24813 let range = self
24814 .selected_text_range(false, window, cx)
24815 .and_then(|selection| {
24816 if selection.range.is_empty() {
24817 None
24818 } else {
24819 Some(
24820 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24821 selection.range.start,
24822 )))
24823 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24824 selection.range.end,
24825 ))),
24826 )
24827 }
24828 })
24829 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24830
24831 let chunks = snapshot.chunks(range, true);
24832 let mut lines = Vec::new();
24833 let mut line: VecDeque<Chunk> = VecDeque::new();
24834
24835 let Some(style) = self.style.as_ref() else {
24836 return;
24837 };
24838
24839 for chunk in chunks {
24840 let highlight = chunk
24841 .syntax_highlight_id
24842 .and_then(|id| id.name(&style.syntax));
24843 let mut chunk_lines = chunk.text.split('\n').peekable();
24844 while let Some(text) = chunk_lines.next() {
24845 let mut merged_with_last_token = false;
24846 if let Some(last_token) = line.back_mut()
24847 && last_token.highlight == highlight
24848 {
24849 last_token.text.push_str(text);
24850 merged_with_last_token = true;
24851 }
24852
24853 if !merged_with_last_token {
24854 line.push_back(Chunk {
24855 text: text.into(),
24856 highlight,
24857 });
24858 }
24859
24860 if chunk_lines.peek().is_some() {
24861 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24862 line.pop_front();
24863 }
24864 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24865 line.pop_back();
24866 }
24867
24868 lines.push(mem::take(&mut line));
24869 }
24870 }
24871 }
24872
24873 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24874 return;
24875 };
24876 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24877 }
24878
24879 pub fn open_context_menu(
24880 &mut self,
24881 _: &OpenContextMenu,
24882 window: &mut Window,
24883 cx: &mut Context<Self>,
24884 ) {
24885 self.request_autoscroll(Autoscroll::newest(), cx);
24886 let position = self
24887 .selections
24888 .newest_display(&self.display_snapshot(cx))
24889 .start;
24890 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24891 }
24892
24893 pub fn replay_insert_event(
24894 &mut self,
24895 text: &str,
24896 relative_utf16_range: Option<Range<isize>>,
24897 window: &mut Window,
24898 cx: &mut Context<Self>,
24899 ) {
24900 if !self.input_enabled {
24901 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24902 return;
24903 }
24904 if let Some(relative_utf16_range) = relative_utf16_range {
24905 let selections = self
24906 .selections
24907 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24908 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24909 let new_ranges = selections.into_iter().map(|range| {
24910 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24911 range
24912 .head()
24913 .0
24914 .0
24915 .saturating_add_signed(relative_utf16_range.start),
24916 ));
24917 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24918 range
24919 .head()
24920 .0
24921 .0
24922 .saturating_add_signed(relative_utf16_range.end),
24923 ));
24924 start..end
24925 });
24926 s.select_ranges(new_ranges);
24927 });
24928 }
24929
24930 self.handle_input(text, window, cx);
24931 }
24932
24933 pub fn is_focused(&self, window: &Window) -> bool {
24934 self.focus_handle.is_focused(window)
24935 }
24936
24937 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24938 cx.emit(EditorEvent::Focused);
24939
24940 if let Some(descendant) = self
24941 .last_focused_descendant
24942 .take()
24943 .and_then(|descendant| descendant.upgrade())
24944 {
24945 window.focus(&descendant, cx);
24946 } else {
24947 if let Some(blame) = self.blame.as_ref() {
24948 blame.update(cx, GitBlame::focus)
24949 }
24950
24951 self.blink_manager.update(cx, BlinkManager::enable);
24952 self.show_cursor_names(window, cx);
24953 self.buffer.update(cx, |buffer, cx| {
24954 buffer.finalize_last_transaction(cx);
24955 if self.leader_id.is_none() {
24956 buffer.set_active_selections(
24957 &self.selections.disjoint_anchors_arc(),
24958 self.selections.line_mode(),
24959 self.cursor_shape,
24960 cx,
24961 );
24962 }
24963 });
24964
24965 if let Some(position_map) = self.last_position_map.clone() {
24966 EditorElement::mouse_moved(
24967 self,
24968 &MouseMoveEvent {
24969 position: window.mouse_position(),
24970 pressed_button: None,
24971 modifiers: window.modifiers(),
24972 },
24973 &position_map,
24974 None,
24975 window,
24976 cx,
24977 );
24978 }
24979 }
24980 }
24981
24982 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24983 cx.emit(EditorEvent::FocusedIn)
24984 }
24985
24986 fn handle_focus_out(
24987 &mut self,
24988 event: FocusOutEvent,
24989 _window: &mut Window,
24990 cx: &mut Context<Self>,
24991 ) {
24992 if event.blurred != self.focus_handle {
24993 self.last_focused_descendant = Some(event.blurred);
24994 }
24995 self.selection_drag_state = SelectionDragState::None;
24996 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24997 }
24998
24999 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25000 self.blink_manager.update(cx, BlinkManager::disable);
25001 self.buffer
25002 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25003
25004 if let Some(blame) = self.blame.as_ref() {
25005 blame.update(cx, GitBlame::blur)
25006 }
25007 if !self.hover_state.focused(window, cx) {
25008 hide_hover(self, cx);
25009 }
25010 if !self
25011 .context_menu
25012 .borrow()
25013 .as_ref()
25014 .is_some_and(|context_menu| context_menu.focused(window, cx))
25015 {
25016 self.hide_context_menu(window, cx);
25017 }
25018 self.take_active_edit_prediction(cx);
25019 cx.emit(EditorEvent::Blurred);
25020 cx.notify();
25021 }
25022
25023 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25024 let mut pending: String = window
25025 .pending_input_keystrokes()
25026 .into_iter()
25027 .flatten()
25028 .filter_map(|keystroke| keystroke.key_char.clone())
25029 .collect();
25030
25031 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25032 pending = "".to_string();
25033 }
25034
25035 let existing_pending = self
25036 .text_highlights(HighlightKey::PendingInput, cx)
25037 .map(|(_, ranges)| ranges.to_vec());
25038 if existing_pending.is_none() && pending.is_empty() {
25039 return;
25040 }
25041 let transaction =
25042 self.transact(window, cx, |this, window, cx| {
25043 let selections = this
25044 .selections
25045 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25046 let edits = selections
25047 .iter()
25048 .map(|selection| (selection.end..selection.end, pending.clone()));
25049 this.edit(edits, cx);
25050 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25051 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25052 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25053 }));
25054 });
25055 if let Some(existing_ranges) = existing_pending {
25056 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25057 this.edit(edits, cx);
25058 }
25059 });
25060
25061 let snapshot = self.snapshot(window, cx);
25062 let ranges = self
25063 .selections
25064 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25065 .into_iter()
25066 .map(|selection| {
25067 snapshot.buffer_snapshot().anchor_after(selection.end)
25068 ..snapshot
25069 .buffer_snapshot()
25070 .anchor_before(selection.end + pending.len())
25071 })
25072 .collect();
25073
25074 if pending.is_empty() {
25075 self.clear_highlights(HighlightKey::PendingInput, cx);
25076 } else {
25077 self.highlight_text(
25078 HighlightKey::PendingInput,
25079 ranges,
25080 HighlightStyle {
25081 underline: Some(UnderlineStyle {
25082 thickness: px(1.),
25083 color: None,
25084 wavy: false,
25085 }),
25086 ..Default::default()
25087 },
25088 cx,
25089 );
25090 }
25091
25092 self.ime_transaction = self.ime_transaction.or(transaction);
25093 if let Some(transaction) = self.ime_transaction {
25094 self.buffer.update(cx, |buffer, cx| {
25095 buffer.group_until_transaction(transaction, cx);
25096 });
25097 }
25098
25099 if self
25100 .text_highlights(HighlightKey::PendingInput, cx)
25101 .is_none()
25102 {
25103 self.ime_transaction.take();
25104 }
25105 }
25106
25107 pub fn register_action_renderer(
25108 &mut self,
25109 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25110 ) -> Subscription {
25111 let id = self.next_editor_action_id.post_inc();
25112 self.editor_actions
25113 .borrow_mut()
25114 .insert(id, Box::new(listener));
25115
25116 let editor_actions = self.editor_actions.clone();
25117 Subscription::new(move || {
25118 editor_actions.borrow_mut().remove(&id);
25119 })
25120 }
25121
25122 pub fn register_action<A: Action>(
25123 &mut self,
25124 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25125 ) -> Subscription {
25126 let id = self.next_editor_action_id.post_inc();
25127 let listener = Arc::new(listener);
25128 self.editor_actions.borrow_mut().insert(
25129 id,
25130 Box::new(move |_, window, _| {
25131 let listener = listener.clone();
25132 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25133 let action = action.downcast_ref().unwrap();
25134 if phase == DispatchPhase::Bubble {
25135 listener(action, window, cx)
25136 }
25137 })
25138 }),
25139 );
25140
25141 let editor_actions = self.editor_actions.clone();
25142 Subscription::new(move || {
25143 editor_actions.borrow_mut().remove(&id);
25144 })
25145 }
25146
25147 pub fn file_header_size(&self) -> u32 {
25148 FILE_HEADER_HEIGHT
25149 }
25150
25151 pub fn restore(
25152 &mut self,
25153 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25154 window: &mut Window,
25155 cx: &mut Context<Self>,
25156 ) {
25157 self.buffer().update(cx, |multi_buffer, cx| {
25158 for (buffer_id, changes) in revert_changes {
25159 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25160 buffer.update(cx, |buffer, cx| {
25161 buffer.edit(
25162 changes
25163 .into_iter()
25164 .map(|(range, text)| (range, text.to_string())),
25165 None,
25166 cx,
25167 );
25168 });
25169 }
25170 }
25171 });
25172 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25173 selections.refresh()
25174 });
25175 }
25176
25177 pub fn to_pixel_point(
25178 &mut self,
25179 source: Anchor,
25180 editor_snapshot: &EditorSnapshot,
25181 window: &mut Window,
25182 cx: &mut App,
25183 ) -> Option<gpui::Point<Pixels>> {
25184 let source_point = source.to_display_point(editor_snapshot);
25185 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25186 }
25187
25188 pub fn display_to_pixel_point(
25189 &mut self,
25190 source: DisplayPoint,
25191 editor_snapshot: &EditorSnapshot,
25192 window: &mut Window,
25193 cx: &mut App,
25194 ) -> Option<gpui::Point<Pixels>> {
25195 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25196 let text_layout_details = self.text_layout_details(window, cx);
25197 let scroll_top = text_layout_details
25198 .scroll_anchor
25199 .scroll_position(editor_snapshot)
25200 .y;
25201
25202 if source.row().as_f64() < scroll_top.floor() {
25203 return None;
25204 }
25205 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25206 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25207 Some(gpui::Point::new(source_x, source_y))
25208 }
25209
25210 pub fn has_visible_completions_menu(&self) -> bool {
25211 !self.edit_prediction_preview_is_active()
25212 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25213 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25214 })
25215 }
25216
25217 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25218 if self.mode.is_minimap() {
25219 return;
25220 }
25221 self.addons
25222 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25223 }
25224
25225 pub fn unregister_addon<T: Addon>(&mut self) {
25226 self.addons.remove(&std::any::TypeId::of::<T>());
25227 }
25228
25229 pub fn addon<T: Addon>(&self) -> Option<&T> {
25230 let type_id = std::any::TypeId::of::<T>();
25231 self.addons
25232 .get(&type_id)
25233 .and_then(|item| item.to_any().downcast_ref::<T>())
25234 }
25235
25236 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25237 let type_id = std::any::TypeId::of::<T>();
25238 self.addons
25239 .get_mut(&type_id)
25240 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25241 }
25242
25243 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25244 let text_layout_details = self.text_layout_details(window, cx);
25245 let style = &text_layout_details.editor_style;
25246 let font_id = window.text_system().resolve_font(&style.text.font());
25247 let font_size = style.text.font_size.to_pixels(window.rem_size());
25248 let line_height = style.text.line_height_in_pixels(window.rem_size());
25249 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25250 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25251
25252 CharacterDimensions {
25253 em_width,
25254 em_advance,
25255 line_height,
25256 }
25257 }
25258
25259 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25260 self.load_diff_task.clone()
25261 }
25262
25263 fn read_metadata_from_db(
25264 &mut self,
25265 item_id: u64,
25266 workspace_id: WorkspaceId,
25267 window: &mut Window,
25268 cx: &mut Context<Editor>,
25269 ) {
25270 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25271 && !self.mode.is_minimap()
25272 && WorkspaceSettings::get(None, cx).restore_on_startup
25273 != RestoreOnStartupBehavior::EmptyTab
25274 {
25275 let buffer_snapshot = OnceCell::new();
25276
25277 // Get file path for path-based fold lookup
25278 let file_path: Option<Arc<Path>> =
25279 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25280 project::File::from_dyn(buffer.read(cx).file())
25281 .map(|file| Arc::from(file.abs_path(cx)))
25282 });
25283
25284 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25285 let (folds, needs_migration) = if let Some(ref path) = file_path {
25286 if let Some(folds) = DB.get_file_folds(workspace_id, path).log_err()
25287 && !folds.is_empty()
25288 {
25289 (Some(folds), false)
25290 } else if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
25291 && !folds.is_empty()
25292 {
25293 // Found old editor_folds data, will migrate to file_folds
25294 (Some(folds), true)
25295 } else {
25296 (None, false)
25297 }
25298 } else {
25299 // No file path, try editor_folds as fallback
25300 let folds = DB.get_editor_folds(item_id, workspace_id).log_err();
25301 (folds.filter(|f| !f.is_empty()), false)
25302 };
25303
25304 if let Some(folds) = folds {
25305 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25306 let snapshot_len = snapshot.len().0;
25307
25308 // Helper: search for fingerprint in buffer, return offset if found
25309 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25310 // Ensure we start at a character boundary (defensive)
25311 let search_start = snapshot
25312 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25313 .0;
25314 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25315
25316 let mut byte_offset = search_start;
25317 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25318 if byte_offset > search_end {
25319 break;
25320 }
25321 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25322 return Some(byte_offset);
25323 }
25324 byte_offset += ch.len_utf8();
25325 }
25326 None
25327 };
25328
25329 // Track search position to handle duplicate fingerprints correctly.
25330 // Folds are stored in document order, so we advance after each match.
25331 let mut search_start = 0usize;
25332
25333 // Collect db_folds for migration (only folds with valid fingerprints)
25334 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25335
25336 let valid_folds: Vec<_> = folds
25337 .into_iter()
25338 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25339 // Skip folds without fingerprints (old data before migration)
25340 let sfp = start_fp?;
25341 let efp = end_fp?;
25342 let efp_len = efp.len();
25343
25344 // Fast path: check if fingerprints match at stored offsets
25345 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25346 let start_matches = stored_start < snapshot_len
25347 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25348 let efp_check_pos = stored_end.saturating_sub(efp_len);
25349 let end_matches = efp_check_pos >= stored_start
25350 && stored_end <= snapshot_len
25351 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25352
25353 let (new_start, new_end) = if start_matches && end_matches {
25354 // Offsets unchanged, use stored values
25355 (stored_start, stored_end)
25356 } else if sfp == efp {
25357 // Short fold: identical fingerprints can only match once per search
25358 // Use stored fold length to compute new_end
25359 let new_start = find_fingerprint(&sfp, search_start)?;
25360 let fold_len = stored_end - stored_start;
25361 let new_end = new_start + fold_len;
25362 (new_start, new_end)
25363 } else {
25364 // Slow path: search for fingerprints in buffer
25365 let new_start = find_fingerprint(&sfp, search_start)?;
25366 // Search for end_fp after start, then add efp_len to get actual fold end
25367 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25368 let new_end = efp_pos + efp_len;
25369 (new_start, new_end)
25370 };
25371
25372 // Advance search position for next fold
25373 search_start = new_end;
25374
25375 // Validate fold makes sense (end must be after start)
25376 if new_end <= new_start {
25377 return None;
25378 }
25379
25380 // Collect for migration if needed
25381 if needs_migration {
25382 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25383 }
25384
25385 Some(
25386 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25387 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25388 )
25389 })
25390 .collect();
25391
25392 if !valid_folds.is_empty() {
25393 self.fold_ranges(valid_folds, false, window, cx);
25394
25395 // Migrate from editor_folds to file_folds if we loaded from old table
25396 if needs_migration {
25397 if let Some(ref path) = file_path {
25398 let path = path.clone();
25399 cx.spawn(async move |_, _| {
25400 DB.save_file_folds(workspace_id, path, db_folds_for_migration)
25401 .await
25402 .log_err();
25403 })
25404 .detach();
25405 }
25406 }
25407 }
25408 }
25409
25410 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25411 && !selections.is_empty()
25412 {
25413 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25414 // skip adding the initial selection to selection history
25415 self.selection_history.mode = SelectionHistoryMode::Skipping;
25416 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25417 s.select_ranges(selections.into_iter().map(|(start, end)| {
25418 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25419 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25420 }));
25421 });
25422 self.selection_history.mode = SelectionHistoryMode::Normal;
25423 };
25424 }
25425
25426 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25427 }
25428
25429 /// Load folds from the file_folds database table by file path.
25430 /// Used when manually opening a file that was previously closed.
25431 fn load_folds_from_db(
25432 &mut self,
25433 workspace_id: WorkspaceId,
25434 file_path: PathBuf,
25435 window: &mut Window,
25436 cx: &mut Context<Editor>,
25437 ) {
25438 if self.mode.is_minimap()
25439 || WorkspaceSettings::get(None, cx).restore_on_startup
25440 == RestoreOnStartupBehavior::EmptyTab
25441 {
25442 return;
25443 }
25444
25445 let Some(folds) = DB.get_file_folds(workspace_id, &file_path).log_err() else {
25446 return;
25447 };
25448 if folds.is_empty() {
25449 return;
25450 }
25451
25452 let snapshot = self.buffer.read(cx).snapshot(cx);
25453 let snapshot_len = snapshot.len().0;
25454
25455 // Helper: search for fingerprint in buffer, return offset if found
25456 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25457 let search_start = snapshot
25458 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25459 .0;
25460 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25461
25462 let mut byte_offset = search_start;
25463 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25464 if byte_offset > search_end {
25465 break;
25466 }
25467 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25468 return Some(byte_offset);
25469 }
25470 byte_offset += ch.len_utf8();
25471 }
25472 None
25473 };
25474
25475 let mut search_start = 0usize;
25476
25477 let valid_folds: Vec<_> = folds
25478 .into_iter()
25479 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25480 let sfp = start_fp?;
25481 let efp = end_fp?;
25482 let efp_len = efp.len();
25483
25484 let start_matches = stored_start < snapshot_len
25485 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25486 let efp_check_pos = stored_end.saturating_sub(efp_len);
25487 let end_matches = efp_check_pos >= stored_start
25488 && stored_end <= snapshot_len
25489 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25490
25491 let (new_start, new_end) = if start_matches && end_matches {
25492 (stored_start, stored_end)
25493 } else if sfp == efp {
25494 let new_start = find_fingerprint(&sfp, search_start)?;
25495 let fold_len = stored_end - stored_start;
25496 let new_end = new_start + fold_len;
25497 (new_start, new_end)
25498 } else {
25499 let new_start = find_fingerprint(&sfp, search_start)?;
25500 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25501 let new_end = efp_pos + efp_len;
25502 (new_start, new_end)
25503 };
25504
25505 search_start = new_end;
25506
25507 if new_end <= new_start {
25508 return None;
25509 }
25510
25511 Some(
25512 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25513 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25514 )
25515 })
25516 .collect();
25517
25518 if !valid_folds.is_empty() {
25519 self.fold_ranges(valid_folds, false, window, cx);
25520 }
25521 }
25522
25523 fn update_lsp_data(
25524 &mut self,
25525 for_buffer: Option<BufferId>,
25526 window: &mut Window,
25527 cx: &mut Context<'_, Self>,
25528 ) {
25529 if !self.enable_lsp_data {
25530 return;
25531 }
25532
25533 if let Some(buffer_id) = for_buffer {
25534 self.pull_diagnostics(buffer_id, window, cx);
25535 }
25536 self.refresh_semantic_tokens(for_buffer, None, cx);
25537 self.refresh_document_colors(for_buffer, window, cx);
25538 self.refresh_folding_ranges(for_buffer, window, cx);
25539 self.refresh_document_symbols(for_buffer, cx);
25540 }
25541
25542 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25543 if !self.mode().is_full() {
25544 return;
25545 }
25546 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25547 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25548 }
25549 }
25550
25551 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25552 if !self.mode().is_full() {
25553 return;
25554 }
25555
25556 if !self.registered_buffers.contains_key(&buffer_id)
25557 && let Some(project) = self.project.as_ref()
25558 {
25559 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25560 project.update(cx, |project, cx| {
25561 self.registered_buffers.insert(
25562 buffer_id,
25563 project.register_buffer_with_language_servers(&buffer, cx),
25564 );
25565 });
25566 } else {
25567 self.registered_buffers.remove(&buffer_id);
25568 }
25569 }
25570 }
25571
25572 fn create_style(&self, cx: &App) -> EditorStyle {
25573 let settings = ThemeSettings::get_global(cx);
25574
25575 let mut text_style = match self.mode {
25576 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25577 color: cx.theme().colors().editor_foreground,
25578 font_family: settings.ui_font.family.clone(),
25579 font_features: settings.ui_font.features.clone(),
25580 font_fallbacks: settings.ui_font.fallbacks.clone(),
25581 font_size: rems(0.875).into(),
25582 font_weight: settings.ui_font.weight,
25583 line_height: relative(settings.buffer_line_height.value()),
25584 ..Default::default()
25585 },
25586 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25587 color: cx.theme().colors().editor_foreground,
25588 font_family: settings.buffer_font.family.clone(),
25589 font_features: settings.buffer_font.features.clone(),
25590 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25591 font_size: settings.buffer_font_size(cx).into(),
25592 font_weight: settings.buffer_font.weight,
25593 line_height: relative(settings.buffer_line_height.value()),
25594 ..Default::default()
25595 },
25596 };
25597 if let Some(text_style_refinement) = &self.text_style_refinement {
25598 text_style.refine(text_style_refinement)
25599 }
25600
25601 let background = match self.mode {
25602 EditorMode::SingleLine => cx.theme().system().transparent,
25603 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25604 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25605 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25606 };
25607
25608 EditorStyle {
25609 background,
25610 border: cx.theme().colors().border,
25611 local_player: cx.theme().players().local(),
25612 text: text_style,
25613 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25614 syntax: cx.theme().syntax().clone(),
25615 status: cx.theme().status().clone(),
25616 inlay_hints_style: make_inlay_hints_style(cx),
25617 edit_prediction_styles: make_suggestion_styles(cx),
25618 unnecessary_code_fade: settings.unnecessary_code_fade,
25619 show_underlines: self.diagnostics_enabled(),
25620 }
25621 }
25622
25623 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
25624 let multibuffer = self.buffer().read(cx);
25625 let is_singleton = multibuffer.is_singleton();
25626 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25627 let buffer = multibuffer.buffer(*buffer_id)?;
25628
25629 let buffer = buffer.read(cx);
25630 let settings = ThemeSettings::get_global(cx);
25631 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25632 let mut breadcrumbs = if is_singleton {
25633 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25634 buffer
25635 .snapshot()
25636 .resolve_file_path(
25637 self.project
25638 .as_ref()
25639 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25640 .unwrap_or_default(),
25641 cx,
25642 )
25643 .unwrap_or_else(|| {
25644 if multibuffer.is_singleton() {
25645 multibuffer.title(cx).to_string()
25646 } else {
25647 "untitled".to_string()
25648 }
25649 })
25650 });
25651 vec![BreadcrumbText {
25652 text,
25653 highlights: None,
25654 font: Some(settings.buffer_font.clone()),
25655 }]
25656 } else {
25657 vec![]
25658 };
25659
25660 breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
25661 text: symbol.text.clone(),
25662 highlights: Some(symbol.highlight_ranges.clone()),
25663 font: Some(settings.buffer_font.clone()),
25664 }));
25665 Some(breadcrumbs)
25666 }
25667
25668 fn disable_lsp_data(&mut self) {
25669 self.enable_lsp_data = false;
25670 }
25671
25672 fn disable_runnables(&mut self) {
25673 self.enable_runnables = false;
25674 }
25675}
25676
25677fn edit_for_markdown_paste<'a>(
25678 buffer: &MultiBufferSnapshot,
25679 range: Range<MultiBufferOffset>,
25680 to_insert: &'a str,
25681 url: Option<url::Url>,
25682) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25683 if url.is_none() {
25684 return (range, Cow::Borrowed(to_insert));
25685 };
25686
25687 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25688
25689 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25690 Cow::Borrowed(to_insert)
25691 } else {
25692 Cow::Owned(format!("[{old_text}]({to_insert})"))
25693 };
25694 (range, new_text)
25695}
25696
25697fn process_completion_for_edit(
25698 completion: &Completion,
25699 intent: CompletionIntent,
25700 buffer: &Entity<Buffer>,
25701 cursor_position: &text::Anchor,
25702 cx: &mut Context<Editor>,
25703) -> CompletionEdit {
25704 let buffer = buffer.read(cx);
25705 let buffer_snapshot = buffer.snapshot();
25706 let (snippet, new_text) = if completion.is_snippet() {
25707 let mut snippet_source = completion.new_text.clone();
25708 // Workaround for typescript language server issues so that methods don't expand within
25709 // strings and functions with type expressions. The previous point is used because the query
25710 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25711 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25712 let previous_point = if previous_point.column > 0 {
25713 cursor_position.to_previous_offset(&buffer_snapshot)
25714 } else {
25715 cursor_position.to_offset(&buffer_snapshot)
25716 };
25717 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25718 && scope.prefers_label_for_snippet_in_completion()
25719 && let Some(label) = completion.label()
25720 && matches!(
25721 completion.kind(),
25722 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25723 )
25724 {
25725 snippet_source = label;
25726 }
25727 match Snippet::parse(&snippet_source).log_err() {
25728 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25729 None => (None, completion.new_text.clone()),
25730 }
25731 } else {
25732 (None, completion.new_text.clone())
25733 };
25734
25735 let mut range_to_replace = {
25736 let replace_range = &completion.replace_range;
25737 if let CompletionSource::Lsp {
25738 insert_range: Some(insert_range),
25739 ..
25740 } = &completion.source
25741 {
25742 debug_assert_eq!(
25743 insert_range.start, replace_range.start,
25744 "insert_range and replace_range should start at the same position"
25745 );
25746 debug_assert!(
25747 insert_range
25748 .start
25749 .cmp(cursor_position, &buffer_snapshot)
25750 .is_le(),
25751 "insert_range should start before or at cursor position"
25752 );
25753 debug_assert!(
25754 replace_range
25755 .start
25756 .cmp(cursor_position, &buffer_snapshot)
25757 .is_le(),
25758 "replace_range should start before or at cursor position"
25759 );
25760
25761 let should_replace = match intent {
25762 CompletionIntent::CompleteWithInsert => false,
25763 CompletionIntent::CompleteWithReplace => true,
25764 CompletionIntent::Complete | CompletionIntent::Compose => {
25765 let insert_mode =
25766 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25767 .completions
25768 .lsp_insert_mode;
25769 match insert_mode {
25770 LspInsertMode::Insert => false,
25771 LspInsertMode::Replace => true,
25772 LspInsertMode::ReplaceSubsequence => {
25773 let mut text_to_replace = buffer.chars_for_range(
25774 buffer.anchor_before(replace_range.start)
25775 ..buffer.anchor_after(replace_range.end),
25776 );
25777 let mut current_needle = text_to_replace.next();
25778 for haystack_ch in completion.label.text.chars() {
25779 if let Some(needle_ch) = current_needle
25780 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25781 {
25782 current_needle = text_to_replace.next();
25783 }
25784 }
25785 current_needle.is_none()
25786 }
25787 LspInsertMode::ReplaceSuffix => {
25788 if replace_range
25789 .end
25790 .cmp(cursor_position, &buffer_snapshot)
25791 .is_gt()
25792 {
25793 let range_after_cursor = *cursor_position..replace_range.end;
25794 let text_after_cursor = buffer
25795 .text_for_range(
25796 buffer.anchor_before(range_after_cursor.start)
25797 ..buffer.anchor_after(range_after_cursor.end),
25798 )
25799 .collect::<String>()
25800 .to_ascii_lowercase();
25801 completion
25802 .label
25803 .text
25804 .to_ascii_lowercase()
25805 .ends_with(&text_after_cursor)
25806 } else {
25807 true
25808 }
25809 }
25810 }
25811 }
25812 };
25813
25814 if should_replace {
25815 replace_range.clone()
25816 } else {
25817 insert_range.clone()
25818 }
25819 } else {
25820 replace_range.clone()
25821 }
25822 };
25823
25824 if range_to_replace
25825 .end
25826 .cmp(cursor_position, &buffer_snapshot)
25827 .is_lt()
25828 {
25829 range_to_replace.end = *cursor_position;
25830 }
25831
25832 let replace_range = range_to_replace.to_offset(buffer);
25833 CompletionEdit {
25834 new_text,
25835 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25836 snippet,
25837 }
25838}
25839
25840struct CompletionEdit {
25841 new_text: String,
25842 replace_range: Range<BufferOffset>,
25843 snippet: Option<Snippet>,
25844}
25845
25846fn comment_delimiter_for_newline(
25847 start_point: &Point,
25848 buffer: &MultiBufferSnapshot,
25849 language: &LanguageScope,
25850) -> Option<Arc<str>> {
25851 let delimiters = language.line_comment_prefixes();
25852 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25853 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25854
25855 let num_of_whitespaces = snapshot
25856 .chars_for_range(range.clone())
25857 .take_while(|c| c.is_whitespace())
25858 .count();
25859 let comment_candidate = snapshot
25860 .chars_for_range(range.clone())
25861 .skip(num_of_whitespaces)
25862 .take(max_len_of_delimiter + 2)
25863 .collect::<String>();
25864 let (delimiter, trimmed_len, is_repl) = delimiters
25865 .iter()
25866 .filter_map(|delimiter| {
25867 let prefix = delimiter.trim_end();
25868 if comment_candidate.starts_with(prefix) {
25869 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
25870 {
25871 stripped_comment.starts_with(" %%")
25872 } else {
25873 false
25874 };
25875 Some((delimiter, prefix.len(), is_repl))
25876 } else {
25877 None
25878 }
25879 })
25880 .max_by_key(|(_, len, _)| *len)?;
25881
25882 if let Some(BlockCommentConfig {
25883 start: block_start, ..
25884 }) = language.block_comment()
25885 {
25886 let block_start_trimmed = block_start.trim_end();
25887 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25888 let line_content = snapshot
25889 .chars_for_range(range.clone())
25890 .skip(num_of_whitespaces)
25891 .take(block_start_trimmed.len())
25892 .collect::<String>();
25893
25894 if line_content.starts_with(block_start_trimmed) {
25895 return None;
25896 }
25897 }
25898 }
25899
25900 let cursor_is_placed_after_comment_marker =
25901 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25902 if cursor_is_placed_after_comment_marker {
25903 if !is_repl {
25904 return Some(delimiter.clone());
25905 }
25906
25907 let line_content_after_cursor: String = snapshot
25908 .chars_for_range(range)
25909 .skip(start_point.column as usize)
25910 .collect();
25911
25912 if line_content_after_cursor.trim().is_empty() {
25913 return None;
25914 } else {
25915 return Some(delimiter.clone());
25916 }
25917 } else {
25918 None
25919 }
25920}
25921
25922fn documentation_delimiter_for_newline(
25923 start_point: &Point,
25924 buffer: &MultiBufferSnapshot,
25925 language: &LanguageScope,
25926 newline_config: &mut NewlineConfig,
25927) -> Option<Arc<str>> {
25928 let BlockCommentConfig {
25929 start: start_tag,
25930 end: end_tag,
25931 prefix: delimiter,
25932 tab_size: len,
25933 } = language.documentation_comment()?;
25934 let is_within_block_comment = buffer
25935 .language_scope_at(*start_point)
25936 .is_some_and(|scope| scope.override_name() == Some("comment"));
25937 if !is_within_block_comment {
25938 return None;
25939 }
25940
25941 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25942
25943 let num_of_whitespaces = snapshot
25944 .chars_for_range(range.clone())
25945 .take_while(|c| c.is_whitespace())
25946 .count();
25947
25948 // 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.
25949 let column = start_point.column;
25950 let cursor_is_after_start_tag = {
25951 let start_tag_len = start_tag.len();
25952 let start_tag_line = snapshot
25953 .chars_for_range(range.clone())
25954 .skip(num_of_whitespaces)
25955 .take(start_tag_len)
25956 .collect::<String>();
25957 if start_tag_line.starts_with(start_tag.as_ref()) {
25958 num_of_whitespaces + start_tag_len <= column as usize
25959 } else {
25960 false
25961 }
25962 };
25963
25964 let cursor_is_after_delimiter = {
25965 let delimiter_trim = delimiter.trim_end();
25966 let delimiter_line = snapshot
25967 .chars_for_range(range.clone())
25968 .skip(num_of_whitespaces)
25969 .take(delimiter_trim.len())
25970 .collect::<String>();
25971 if delimiter_line.starts_with(delimiter_trim) {
25972 num_of_whitespaces + delimiter_trim.len() <= column as usize
25973 } else {
25974 false
25975 }
25976 };
25977
25978 let mut needs_extra_line = false;
25979 let mut extra_line_additional_indent = IndentSize::spaces(0);
25980
25981 let cursor_is_before_end_tag_if_exists = {
25982 let mut char_position = 0u32;
25983 let mut end_tag_offset = None;
25984
25985 'outer: for chunk in snapshot.text_for_range(range) {
25986 if let Some(byte_pos) = chunk.find(&**end_tag) {
25987 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25988 end_tag_offset = Some(char_position + chars_before_match);
25989 break 'outer;
25990 }
25991 char_position += chunk.chars().count() as u32;
25992 }
25993
25994 if let Some(end_tag_offset) = end_tag_offset {
25995 let cursor_is_before_end_tag = column <= end_tag_offset;
25996 if cursor_is_after_start_tag {
25997 if cursor_is_before_end_tag {
25998 needs_extra_line = true;
25999 }
26000 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26001 if cursor_is_at_start_of_end_tag {
26002 extra_line_additional_indent.len = *len;
26003 }
26004 }
26005 cursor_is_before_end_tag
26006 } else {
26007 true
26008 }
26009 };
26010
26011 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26012 && cursor_is_before_end_tag_if_exists
26013 {
26014 let additional_indent = if cursor_is_after_start_tag {
26015 IndentSize::spaces(*len)
26016 } else {
26017 IndentSize::spaces(0)
26018 };
26019
26020 *newline_config = NewlineConfig::Newline {
26021 additional_indent,
26022 extra_line_additional_indent: if needs_extra_line {
26023 Some(extra_line_additional_indent)
26024 } else {
26025 None
26026 },
26027 prevent_auto_indent: true,
26028 };
26029 Some(delimiter.clone())
26030 } else {
26031 None
26032 }
26033}
26034
26035const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26036
26037fn list_delimiter_for_newline(
26038 start_point: &Point,
26039 buffer: &MultiBufferSnapshot,
26040 language: &LanguageScope,
26041 newline_config: &mut NewlineConfig,
26042) -> Option<Arc<str>> {
26043 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26044
26045 let num_of_whitespaces = snapshot
26046 .chars_for_range(range.clone())
26047 .take_while(|c| c.is_whitespace())
26048 .count();
26049
26050 let task_list_entries: Vec<_> = language
26051 .task_list()
26052 .into_iter()
26053 .flat_map(|config| {
26054 config
26055 .prefixes
26056 .iter()
26057 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26058 })
26059 .collect();
26060 let unordered_list_entries: Vec<_> = language
26061 .unordered_list()
26062 .iter()
26063 .map(|marker| (marker.as_ref(), marker.as_ref()))
26064 .collect();
26065
26066 let all_entries: Vec<_> = task_list_entries
26067 .into_iter()
26068 .chain(unordered_list_entries)
26069 .collect();
26070
26071 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26072 let candidate: String = snapshot
26073 .chars_for_range(range.clone())
26074 .skip(num_of_whitespaces)
26075 .take(max_prefix_len)
26076 .collect();
26077
26078 if let Some((prefix, continuation)) = all_entries
26079 .iter()
26080 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26081 .max_by_key(|(prefix, _)| prefix.len())
26082 {
26083 let end_of_prefix = num_of_whitespaces + prefix.len();
26084 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26085 let has_content_after_marker = snapshot
26086 .chars_for_range(range)
26087 .skip(end_of_prefix)
26088 .any(|c| !c.is_whitespace());
26089
26090 if has_content_after_marker && cursor_is_after_prefix {
26091 return Some((*continuation).into());
26092 }
26093
26094 if start_point.column as usize == end_of_prefix {
26095 if num_of_whitespaces == 0 {
26096 *newline_config = NewlineConfig::ClearCurrentLine;
26097 } else {
26098 *newline_config = NewlineConfig::UnindentCurrentLine {
26099 continuation: (*continuation).into(),
26100 };
26101 }
26102 }
26103
26104 return None;
26105 }
26106 }
26107
26108 let candidate: String = snapshot
26109 .chars_for_range(range.clone())
26110 .skip(num_of_whitespaces)
26111 .take(ORDERED_LIST_MAX_MARKER_LEN)
26112 .collect();
26113
26114 for ordered_config in language.ordered_list() {
26115 let regex = match Regex::new(&ordered_config.pattern) {
26116 Ok(r) => r,
26117 Err(_) => continue,
26118 };
26119
26120 if let Some(captures) = regex.captures(&candidate) {
26121 let full_match = captures.get(0)?;
26122 let marker_len = full_match.len();
26123 let end_of_prefix = num_of_whitespaces + marker_len;
26124 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26125
26126 let has_content_after_marker = snapshot
26127 .chars_for_range(range)
26128 .skip(end_of_prefix)
26129 .any(|c| !c.is_whitespace());
26130
26131 if has_content_after_marker && cursor_is_after_prefix {
26132 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26133 let continuation = ordered_config
26134 .format
26135 .replace("{1}", &(number + 1).to_string());
26136 return Some(continuation.into());
26137 }
26138
26139 if start_point.column as usize == end_of_prefix {
26140 let continuation = ordered_config.format.replace("{1}", "1");
26141 if num_of_whitespaces == 0 {
26142 *newline_config = NewlineConfig::ClearCurrentLine;
26143 } else {
26144 *newline_config = NewlineConfig::UnindentCurrentLine {
26145 continuation: continuation.into(),
26146 };
26147 }
26148 }
26149
26150 return None;
26151 }
26152 }
26153
26154 None
26155}
26156
26157fn is_list_prefix_row(
26158 row: MultiBufferRow,
26159 buffer: &MultiBufferSnapshot,
26160 language: &LanguageScope,
26161) -> bool {
26162 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26163 return false;
26164 };
26165
26166 let num_of_whitespaces = snapshot
26167 .chars_for_range(range.clone())
26168 .take_while(|c| c.is_whitespace())
26169 .count();
26170
26171 let task_list_prefixes: Vec<_> = language
26172 .task_list()
26173 .into_iter()
26174 .flat_map(|config| {
26175 config
26176 .prefixes
26177 .iter()
26178 .map(|p| p.as_ref())
26179 .collect::<Vec<_>>()
26180 })
26181 .collect();
26182 let unordered_list_markers: Vec<_> = language
26183 .unordered_list()
26184 .iter()
26185 .map(|marker| marker.as_ref())
26186 .collect();
26187 let all_prefixes: Vec<_> = task_list_prefixes
26188 .into_iter()
26189 .chain(unordered_list_markers)
26190 .collect();
26191 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26192 let candidate: String = snapshot
26193 .chars_for_range(range.clone())
26194 .skip(num_of_whitespaces)
26195 .take(max_prefix_len)
26196 .collect();
26197 if all_prefixes
26198 .iter()
26199 .any(|prefix| candidate.starts_with(*prefix))
26200 {
26201 return true;
26202 }
26203 }
26204
26205 let ordered_list_candidate: String = snapshot
26206 .chars_for_range(range)
26207 .skip(num_of_whitespaces)
26208 .take(ORDERED_LIST_MAX_MARKER_LEN)
26209 .collect();
26210 for ordered_config in language.ordered_list() {
26211 let regex = match Regex::new(&ordered_config.pattern) {
26212 Ok(r) => r,
26213 Err(_) => continue,
26214 };
26215 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26216 return captures.get(0).is_some();
26217 }
26218 }
26219
26220 false
26221}
26222
26223#[derive(Debug)]
26224enum NewlineConfig {
26225 /// Insert newline with optional additional indent and optional extra blank line
26226 Newline {
26227 additional_indent: IndentSize,
26228 extra_line_additional_indent: Option<IndentSize>,
26229 prevent_auto_indent: bool,
26230 },
26231 /// Clear the current line
26232 ClearCurrentLine,
26233 /// Unindent the current line and add continuation
26234 UnindentCurrentLine { continuation: Arc<str> },
26235}
26236
26237impl NewlineConfig {
26238 fn has_extra_line(&self) -> bool {
26239 matches!(
26240 self,
26241 Self::Newline {
26242 extra_line_additional_indent: Some(_),
26243 ..
26244 }
26245 )
26246 }
26247
26248 fn insert_extra_newline_brackets(
26249 buffer: &MultiBufferSnapshot,
26250 range: Range<MultiBufferOffset>,
26251 language: &language::LanguageScope,
26252 ) -> bool {
26253 let leading_whitespace_len = buffer
26254 .reversed_chars_at(range.start)
26255 .take_while(|c| c.is_whitespace() && *c != '\n')
26256 .map(|c| c.len_utf8())
26257 .sum::<usize>();
26258 let trailing_whitespace_len = buffer
26259 .chars_at(range.end)
26260 .take_while(|c| c.is_whitespace() && *c != '\n')
26261 .map(|c| c.len_utf8())
26262 .sum::<usize>();
26263 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26264
26265 language.brackets().any(|(pair, enabled)| {
26266 let pair_start = pair.start.trim_end();
26267 let pair_end = pair.end.trim_start();
26268
26269 enabled
26270 && pair.newline
26271 && buffer.contains_str_at(range.end, pair_end)
26272 && buffer.contains_str_at(
26273 range.start.saturating_sub_usize(pair_start.len()),
26274 pair_start,
26275 )
26276 })
26277 }
26278
26279 fn insert_extra_newline_tree_sitter(
26280 buffer: &MultiBufferSnapshot,
26281 range: Range<MultiBufferOffset>,
26282 ) -> bool {
26283 let (buffer, range) = match buffer
26284 .range_to_buffer_ranges(range.start..=range.end)
26285 .as_slice()
26286 {
26287 [(buffer, range, _)] => (*buffer, range.clone()),
26288 _ => return false,
26289 };
26290 let pair = {
26291 let mut result: Option<BracketMatch<usize>> = None;
26292
26293 for pair in buffer
26294 .all_bracket_ranges(range.start.0..range.end.0)
26295 .filter(move |pair| {
26296 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26297 })
26298 {
26299 let len = pair.close_range.end - pair.open_range.start;
26300
26301 if let Some(existing) = &result {
26302 let existing_len = existing.close_range.end - existing.open_range.start;
26303 if len > existing_len {
26304 continue;
26305 }
26306 }
26307
26308 result = Some(pair);
26309 }
26310
26311 result
26312 };
26313 let Some(pair) = pair else {
26314 return false;
26315 };
26316 pair.newline_only
26317 && buffer
26318 .chars_for_range(pair.open_range.end..range.start.0)
26319 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26320 .all(|c| c.is_whitespace() && c != '\n')
26321 }
26322}
26323
26324fn update_uncommitted_diff_for_buffer(
26325 editor: Entity<Editor>,
26326 project: &Entity<Project>,
26327 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26328 buffer: Entity<MultiBuffer>,
26329 cx: &mut App,
26330) -> Task<()> {
26331 let mut tasks = Vec::new();
26332 project.update(cx, |project, cx| {
26333 for buffer in buffers {
26334 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26335 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26336 }
26337 }
26338 });
26339 cx.spawn(async move |cx| {
26340 let diffs = future::join_all(tasks).await;
26341 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26342 return;
26343 }
26344
26345 buffer.update(cx, |buffer, cx| {
26346 for diff in diffs.into_iter().flatten() {
26347 buffer.add_diff(diff, cx);
26348 }
26349 });
26350 })
26351}
26352
26353fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26354 let tab_size = tab_size.get() as usize;
26355 let mut width = offset;
26356
26357 for ch in text.chars() {
26358 width += if ch == '\t' {
26359 tab_size - (width % tab_size)
26360 } else {
26361 1
26362 };
26363 }
26364
26365 width - offset
26366}
26367
26368#[cfg(test)]
26369mod tests {
26370 use super::*;
26371
26372 #[test]
26373 fn test_string_size_with_expanded_tabs() {
26374 let nz = |val| NonZeroU32::new(val).unwrap();
26375 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26376 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26377 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26378 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26379 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26380 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26381 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26382 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26383 }
26384}
26385
26386/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26387struct WordBreakingTokenizer<'a> {
26388 input: &'a str,
26389}
26390
26391impl<'a> WordBreakingTokenizer<'a> {
26392 fn new(input: &'a str) -> Self {
26393 Self { input }
26394 }
26395}
26396
26397fn is_char_ideographic(ch: char) -> bool {
26398 use unicode_script::Script::*;
26399 use unicode_script::UnicodeScript;
26400 matches!(ch.script(), Han | Tangut | Yi)
26401}
26402
26403fn is_grapheme_ideographic(text: &str) -> bool {
26404 text.chars().any(is_char_ideographic)
26405}
26406
26407fn is_grapheme_whitespace(text: &str) -> bool {
26408 text.chars().any(|x| x.is_whitespace())
26409}
26410
26411fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26412 text.chars()
26413 .next()
26414 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26415}
26416
26417#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26418enum WordBreakToken<'a> {
26419 Word { token: &'a str, grapheme_len: usize },
26420 InlineWhitespace { token: &'a str, grapheme_len: usize },
26421 Newline,
26422}
26423
26424impl<'a> Iterator for WordBreakingTokenizer<'a> {
26425 /// Yields a span, the count of graphemes in the token, and whether it was
26426 /// whitespace. Note that it also breaks at word boundaries.
26427 type Item = WordBreakToken<'a>;
26428
26429 fn next(&mut self) -> Option<Self::Item> {
26430 use unicode_segmentation::UnicodeSegmentation;
26431 if self.input.is_empty() {
26432 return None;
26433 }
26434
26435 let mut iter = self.input.graphemes(true).peekable();
26436 let mut offset = 0;
26437 let mut grapheme_len = 0;
26438 if let Some(first_grapheme) = iter.next() {
26439 let is_newline = first_grapheme == "\n";
26440 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26441 offset += first_grapheme.len();
26442 grapheme_len += 1;
26443 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26444 if let Some(grapheme) = iter.peek().copied()
26445 && should_stay_with_preceding_ideograph(grapheme)
26446 {
26447 offset += grapheme.len();
26448 grapheme_len += 1;
26449 }
26450 } else {
26451 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26452 let mut next_word_bound = words.peek().copied();
26453 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26454 next_word_bound = words.next();
26455 }
26456 while let Some(grapheme) = iter.peek().copied() {
26457 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26458 break;
26459 };
26460 if is_grapheme_whitespace(grapheme) != is_whitespace
26461 || (grapheme == "\n") != is_newline
26462 {
26463 break;
26464 };
26465 offset += grapheme.len();
26466 grapheme_len += 1;
26467 iter.next();
26468 }
26469 }
26470 let token = &self.input[..offset];
26471 self.input = &self.input[offset..];
26472 if token == "\n" {
26473 Some(WordBreakToken::Newline)
26474 } else if is_whitespace {
26475 Some(WordBreakToken::InlineWhitespace {
26476 token,
26477 grapheme_len,
26478 })
26479 } else {
26480 Some(WordBreakToken::Word {
26481 token,
26482 grapheme_len,
26483 })
26484 }
26485 } else {
26486 None
26487 }
26488 }
26489}
26490
26491#[test]
26492fn test_word_breaking_tokenizer() {
26493 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26494 ("", &[]),
26495 (" ", &[whitespace(" ", 2)]),
26496 ("Ʒ", &[word("Ʒ", 1)]),
26497 ("Ǽ", &[word("Ǽ", 1)]),
26498 ("⋑", &[word("⋑", 1)]),
26499 ("⋑⋑", &[word("⋑⋑", 2)]),
26500 (
26501 "原理,进而",
26502 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26503 ),
26504 (
26505 "hello world",
26506 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26507 ),
26508 (
26509 "hello, world",
26510 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26511 ),
26512 (
26513 " hello world",
26514 &[
26515 whitespace(" ", 2),
26516 word("hello", 5),
26517 whitespace(" ", 1),
26518 word("world", 5),
26519 ],
26520 ),
26521 (
26522 "这是什么 \n 钢笔",
26523 &[
26524 word("这", 1),
26525 word("是", 1),
26526 word("什", 1),
26527 word("么", 1),
26528 whitespace(" ", 1),
26529 newline(),
26530 whitespace(" ", 1),
26531 word("钢", 1),
26532 word("笔", 1),
26533 ],
26534 ),
26535 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26536 ];
26537
26538 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26539 WordBreakToken::Word {
26540 token,
26541 grapheme_len,
26542 }
26543 }
26544
26545 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26546 WordBreakToken::InlineWhitespace {
26547 token,
26548 grapheme_len,
26549 }
26550 }
26551
26552 fn newline() -> WordBreakToken<'static> {
26553 WordBreakToken::Newline
26554 }
26555
26556 for (input, result) in tests {
26557 assert_eq!(
26558 WordBreakingTokenizer::new(input)
26559 .collect::<Vec<_>>()
26560 .as_slice(),
26561 *result,
26562 );
26563 }
26564}
26565
26566fn wrap_with_prefix(
26567 first_line_prefix: String,
26568 subsequent_lines_prefix: String,
26569 unwrapped_text: String,
26570 wrap_column: usize,
26571 tab_size: NonZeroU32,
26572 preserve_existing_whitespace: bool,
26573) -> String {
26574 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26575 let subsequent_lines_prefix_len =
26576 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26577 let mut wrapped_text = String::new();
26578 let mut current_line = first_line_prefix;
26579 let mut is_first_line = true;
26580
26581 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26582 let mut current_line_len = first_line_prefix_len;
26583 let mut in_whitespace = false;
26584 for token in tokenizer {
26585 let have_preceding_whitespace = in_whitespace;
26586 match token {
26587 WordBreakToken::Word {
26588 token,
26589 grapheme_len,
26590 } => {
26591 in_whitespace = false;
26592 let current_prefix_len = if is_first_line {
26593 first_line_prefix_len
26594 } else {
26595 subsequent_lines_prefix_len
26596 };
26597 if current_line_len + grapheme_len > wrap_column
26598 && current_line_len != current_prefix_len
26599 {
26600 wrapped_text.push_str(current_line.trim_end());
26601 wrapped_text.push('\n');
26602 is_first_line = false;
26603 current_line = subsequent_lines_prefix.clone();
26604 current_line_len = subsequent_lines_prefix_len;
26605 }
26606 current_line.push_str(token);
26607 current_line_len += grapheme_len;
26608 }
26609 WordBreakToken::InlineWhitespace {
26610 mut token,
26611 mut grapheme_len,
26612 } => {
26613 in_whitespace = true;
26614 if have_preceding_whitespace && !preserve_existing_whitespace {
26615 continue;
26616 }
26617 if !preserve_existing_whitespace {
26618 // Keep a single whitespace grapheme as-is
26619 if let Some(first) =
26620 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26621 {
26622 token = first;
26623 } else {
26624 token = " ";
26625 }
26626 grapheme_len = 1;
26627 }
26628 let current_prefix_len = if is_first_line {
26629 first_line_prefix_len
26630 } else {
26631 subsequent_lines_prefix_len
26632 };
26633 if current_line_len + grapheme_len > wrap_column {
26634 wrapped_text.push_str(current_line.trim_end());
26635 wrapped_text.push('\n');
26636 is_first_line = false;
26637 current_line = subsequent_lines_prefix.clone();
26638 current_line_len = subsequent_lines_prefix_len;
26639 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26640 current_line.push_str(token);
26641 current_line_len += grapheme_len;
26642 }
26643 }
26644 WordBreakToken::Newline => {
26645 in_whitespace = true;
26646 let current_prefix_len = if is_first_line {
26647 first_line_prefix_len
26648 } else {
26649 subsequent_lines_prefix_len
26650 };
26651 if preserve_existing_whitespace {
26652 wrapped_text.push_str(current_line.trim_end());
26653 wrapped_text.push('\n');
26654 is_first_line = false;
26655 current_line = subsequent_lines_prefix.clone();
26656 current_line_len = subsequent_lines_prefix_len;
26657 } else if have_preceding_whitespace {
26658 continue;
26659 } else if current_line_len + 1 > wrap_column
26660 && current_line_len != current_prefix_len
26661 {
26662 wrapped_text.push_str(current_line.trim_end());
26663 wrapped_text.push('\n');
26664 is_first_line = false;
26665 current_line = subsequent_lines_prefix.clone();
26666 current_line_len = subsequent_lines_prefix_len;
26667 } else if current_line_len != current_prefix_len {
26668 current_line.push(' ');
26669 current_line_len += 1;
26670 }
26671 }
26672 }
26673 }
26674
26675 if !current_line.is_empty() {
26676 wrapped_text.push_str(¤t_line);
26677 }
26678 wrapped_text
26679}
26680
26681#[test]
26682fn test_wrap_with_prefix() {
26683 assert_eq!(
26684 wrap_with_prefix(
26685 "# ".to_string(),
26686 "# ".to_string(),
26687 "abcdefg".to_string(),
26688 4,
26689 NonZeroU32::new(4).unwrap(),
26690 false,
26691 ),
26692 "# abcdefg"
26693 );
26694 assert_eq!(
26695 wrap_with_prefix(
26696 "".to_string(),
26697 "".to_string(),
26698 "\thello world".to_string(),
26699 8,
26700 NonZeroU32::new(4).unwrap(),
26701 false,
26702 ),
26703 "hello\nworld"
26704 );
26705 assert_eq!(
26706 wrap_with_prefix(
26707 "// ".to_string(),
26708 "// ".to_string(),
26709 "xx \nyy zz aa bb cc".to_string(),
26710 12,
26711 NonZeroU32::new(4).unwrap(),
26712 false,
26713 ),
26714 "// xx yy zz\n// aa bb cc"
26715 );
26716 assert_eq!(
26717 wrap_with_prefix(
26718 String::new(),
26719 String::new(),
26720 "这是什么 \n 钢笔".to_string(),
26721 3,
26722 NonZeroU32::new(4).unwrap(),
26723 false,
26724 ),
26725 "这是什\n么 钢\n笔"
26726 );
26727 assert_eq!(
26728 wrap_with_prefix(
26729 String::new(),
26730 String::new(),
26731 format!("foo{}bar", '\u{2009}'), // thin space
26732 80,
26733 NonZeroU32::new(4).unwrap(),
26734 false,
26735 ),
26736 format!("foo{}bar", '\u{2009}')
26737 );
26738}
26739
26740pub trait CollaborationHub {
26741 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26742 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26743 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26744}
26745
26746impl CollaborationHub for Entity<Project> {
26747 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26748 self.read(cx).collaborators()
26749 }
26750
26751 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26752 self.read(cx).user_store().read(cx).participant_indices()
26753 }
26754
26755 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26756 let this = self.read(cx);
26757 let user_ids = this.collaborators().values().map(|c| c.user_id);
26758 this.user_store().read(cx).participant_names(user_ids, cx)
26759 }
26760}
26761
26762pub trait SemanticsProvider {
26763 fn hover(
26764 &self,
26765 buffer: &Entity<Buffer>,
26766 position: text::Anchor,
26767 cx: &mut App,
26768 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26769
26770 fn inline_values(
26771 &self,
26772 buffer_handle: Entity<Buffer>,
26773 range: Range<text::Anchor>,
26774 cx: &mut App,
26775 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26776
26777 fn applicable_inlay_chunks(
26778 &self,
26779 buffer: &Entity<Buffer>,
26780 ranges: &[Range<text::Anchor>],
26781 cx: &mut App,
26782 ) -> Vec<Range<BufferRow>>;
26783
26784 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26785
26786 fn inlay_hints(
26787 &self,
26788 invalidate: InvalidationStrategy,
26789 buffer: Entity<Buffer>,
26790 ranges: Vec<Range<text::Anchor>>,
26791 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26792 cx: &mut App,
26793 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26794
26795 fn semantic_tokens(
26796 &self,
26797 buffer: Entity<Buffer>,
26798 refresh: Option<RefreshForServer>,
26799 cx: &mut App,
26800 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>;
26801
26802 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26803
26804 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26805
26806 fn document_highlights(
26807 &self,
26808 buffer: &Entity<Buffer>,
26809 position: text::Anchor,
26810 cx: &mut App,
26811 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26812
26813 fn definitions(
26814 &self,
26815 buffer: &Entity<Buffer>,
26816 position: text::Anchor,
26817 kind: GotoDefinitionKind,
26818 cx: &mut App,
26819 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26820
26821 fn range_for_rename(
26822 &self,
26823 buffer: &Entity<Buffer>,
26824 position: text::Anchor,
26825 cx: &mut App,
26826 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26827
26828 fn perform_rename(
26829 &self,
26830 buffer: &Entity<Buffer>,
26831 position: text::Anchor,
26832 new_name: String,
26833 cx: &mut App,
26834 ) -> Option<Task<Result<ProjectTransaction>>>;
26835}
26836
26837pub trait CompletionProvider {
26838 fn completions(
26839 &self,
26840 excerpt_id: ExcerptId,
26841 buffer: &Entity<Buffer>,
26842 buffer_position: text::Anchor,
26843 trigger: CompletionContext,
26844 window: &mut Window,
26845 cx: &mut Context<Editor>,
26846 ) -> Task<Result<Vec<CompletionResponse>>>;
26847
26848 fn resolve_completions(
26849 &self,
26850 _buffer: Entity<Buffer>,
26851 _completion_indices: Vec<usize>,
26852 _completions: Rc<RefCell<Box<[Completion]>>>,
26853 _cx: &mut Context<Editor>,
26854 ) -> Task<Result<bool>> {
26855 Task::ready(Ok(false))
26856 }
26857
26858 fn apply_additional_edits_for_completion(
26859 &self,
26860 _buffer: Entity<Buffer>,
26861 _completions: Rc<RefCell<Box<[Completion]>>>,
26862 _completion_index: usize,
26863 _push_to_history: bool,
26864 _cx: &mut Context<Editor>,
26865 ) -> Task<Result<Option<language::Transaction>>> {
26866 Task::ready(Ok(None))
26867 }
26868
26869 fn is_completion_trigger(
26870 &self,
26871 buffer: &Entity<Buffer>,
26872 position: language::Anchor,
26873 text: &str,
26874 trigger_in_words: bool,
26875 cx: &mut Context<Editor>,
26876 ) -> bool;
26877
26878 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26879
26880 fn sort_completions(&self) -> bool {
26881 true
26882 }
26883
26884 fn filter_completions(&self) -> bool {
26885 true
26886 }
26887
26888 fn show_snippets(&self) -> bool {
26889 false
26890 }
26891}
26892
26893pub trait CodeActionProvider {
26894 fn id(&self) -> Arc<str>;
26895
26896 fn code_actions(
26897 &self,
26898 buffer: &Entity<Buffer>,
26899 range: Range<text::Anchor>,
26900 window: &mut Window,
26901 cx: &mut App,
26902 ) -> Task<Result<Vec<CodeAction>>>;
26903
26904 fn apply_code_action(
26905 &self,
26906 buffer_handle: Entity<Buffer>,
26907 action: CodeAction,
26908 excerpt_id: ExcerptId,
26909 push_to_history: bool,
26910 window: &mut Window,
26911 cx: &mut App,
26912 ) -> Task<Result<ProjectTransaction>>;
26913}
26914
26915impl CodeActionProvider for Entity<Project> {
26916 fn id(&self) -> Arc<str> {
26917 "project".into()
26918 }
26919
26920 fn code_actions(
26921 &self,
26922 buffer: &Entity<Buffer>,
26923 range: Range<text::Anchor>,
26924 _window: &mut Window,
26925 cx: &mut App,
26926 ) -> Task<Result<Vec<CodeAction>>> {
26927 self.update(cx, |project, cx| {
26928 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26929 let code_actions = project.code_actions(buffer, range, None, cx);
26930 cx.background_spawn(async move {
26931 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26932 Ok(code_lens_actions
26933 .context("code lens fetch")?
26934 .into_iter()
26935 .flatten()
26936 .chain(
26937 code_actions
26938 .context("code action fetch")?
26939 .into_iter()
26940 .flatten(),
26941 )
26942 .collect())
26943 })
26944 })
26945 }
26946
26947 fn apply_code_action(
26948 &self,
26949 buffer_handle: Entity<Buffer>,
26950 action: CodeAction,
26951 _excerpt_id: ExcerptId,
26952 push_to_history: bool,
26953 _window: &mut Window,
26954 cx: &mut App,
26955 ) -> Task<Result<ProjectTransaction>> {
26956 self.update(cx, |project, cx| {
26957 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26958 })
26959 }
26960}
26961
26962fn snippet_completions(
26963 project: &Project,
26964 buffer: &Entity<Buffer>,
26965 buffer_anchor: text::Anchor,
26966 classifier: CharClassifier,
26967 cx: &mut App,
26968) -> Task<Result<CompletionResponse>> {
26969 let languages = buffer.read(cx).languages_at(buffer_anchor);
26970 let snippet_store = project.snippets().read(cx);
26971
26972 let scopes: Vec<_> = languages
26973 .iter()
26974 .filter_map(|language| {
26975 let language_name = language.lsp_id();
26976 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26977
26978 if snippets.is_empty() {
26979 None
26980 } else {
26981 Some((language.default_scope(), snippets))
26982 }
26983 })
26984 .collect();
26985
26986 if scopes.is_empty() {
26987 return Task::ready(Ok(CompletionResponse {
26988 completions: vec![],
26989 display_options: CompletionDisplayOptions::default(),
26990 is_incomplete: false,
26991 }));
26992 }
26993
26994 let snapshot = buffer.read(cx).text_snapshot();
26995 let executor = cx.background_executor().clone();
26996
26997 cx.background_spawn(async move {
26998 let is_word_char = |c| classifier.is_word(c);
26999
27000 let mut is_incomplete = false;
27001 let mut completions: Vec<Completion> = Vec::new();
27002
27003 const MAX_PREFIX_LEN: usize = 128;
27004 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27005 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27006 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27007
27008 let max_buffer_window: String = snapshot
27009 .text_for_range(window_start..buffer_offset)
27010 .collect();
27011
27012 if max_buffer_window.is_empty() {
27013 return Ok(CompletionResponse {
27014 completions: vec![],
27015 display_options: CompletionDisplayOptions::default(),
27016 is_incomplete: true,
27017 });
27018 }
27019
27020 for (_scope, snippets) in scopes.into_iter() {
27021 // Sort snippets by word count to match longer snippet prefixes first.
27022 let mut sorted_snippet_candidates = snippets
27023 .iter()
27024 .enumerate()
27025 .flat_map(|(snippet_ix, snippet)| {
27026 snippet
27027 .prefix
27028 .iter()
27029 .enumerate()
27030 .map(move |(prefix_ix, prefix)| {
27031 let word_count =
27032 snippet_candidate_suffixes(prefix, &is_word_char).count();
27033 ((snippet_ix, prefix_ix), prefix, word_count)
27034 })
27035 })
27036 .collect_vec();
27037 sorted_snippet_candidates
27038 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27039
27040 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27041
27042 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27043 .take(
27044 sorted_snippet_candidates
27045 .first()
27046 .map(|(_, _, word_count)| *word_count)
27047 .unwrap_or_default(),
27048 )
27049 .collect_vec();
27050
27051 const MAX_RESULTS: usize = 100;
27052 // Each match also remembers how many characters from the buffer it consumed
27053 let mut matches: Vec<(StringMatch, usize)> = vec![];
27054
27055 let mut snippet_list_cutoff_index = 0;
27056 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27057 let word_count = buffer_index + 1;
27058 // Increase `snippet_list_cutoff_index` until we have all of the
27059 // snippets with sufficiently many words.
27060 while sorted_snippet_candidates
27061 .get(snippet_list_cutoff_index)
27062 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27063 *snippet_word_count >= word_count
27064 })
27065 {
27066 snippet_list_cutoff_index += 1;
27067 }
27068
27069 // Take only the candidates with at least `word_count` many words
27070 let snippet_candidates_at_word_len =
27071 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27072
27073 let candidates = snippet_candidates_at_word_len
27074 .iter()
27075 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27076 .enumerate() // index in `sorted_snippet_candidates`
27077 // First char must match
27078 .filter(|(_ix, prefix)| {
27079 itertools::equal(
27080 prefix
27081 .chars()
27082 .next()
27083 .into_iter()
27084 .flat_map(|c| c.to_lowercase()),
27085 buffer_window
27086 .chars()
27087 .next()
27088 .into_iter()
27089 .flat_map(|c| c.to_lowercase()),
27090 )
27091 })
27092 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27093 .collect::<Vec<StringMatchCandidate>>();
27094
27095 matches.extend(
27096 fuzzy::match_strings(
27097 &candidates,
27098 &buffer_window,
27099 buffer_window.chars().any(|c| c.is_uppercase()),
27100 true,
27101 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27102 &Default::default(),
27103 executor.clone(),
27104 )
27105 .await
27106 .into_iter()
27107 .map(|string_match| (string_match, buffer_window.len())),
27108 );
27109
27110 if matches.len() >= MAX_RESULTS {
27111 break;
27112 }
27113 }
27114
27115 let to_lsp = |point: &text::Anchor| {
27116 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27117 point_to_lsp(end)
27118 };
27119 let lsp_end = to_lsp(&buffer_anchor);
27120
27121 if matches.len() >= MAX_RESULTS {
27122 is_incomplete = true;
27123 }
27124
27125 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27126 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27127 sorted_snippet_candidates[string_match.candidate_id];
27128 let snippet = &snippets[snippet_index];
27129 let start = buffer_offset - buffer_window_len;
27130 let start = snapshot.anchor_before(start);
27131 let range = start..buffer_anchor;
27132 let lsp_start = to_lsp(&start);
27133 let lsp_range = lsp::Range {
27134 start: lsp_start,
27135 end: lsp_end,
27136 };
27137 Completion {
27138 replace_range: range,
27139 new_text: snippet.body.clone(),
27140 source: CompletionSource::Lsp {
27141 insert_range: None,
27142 server_id: LanguageServerId(usize::MAX),
27143 resolved: true,
27144 lsp_completion: Box::new(lsp::CompletionItem {
27145 label: snippet.prefix.first().unwrap().clone(),
27146 kind: Some(CompletionItemKind::SNIPPET),
27147 label_details: snippet.description.as_ref().map(|description| {
27148 lsp::CompletionItemLabelDetails {
27149 detail: Some(description.clone()),
27150 description: None,
27151 }
27152 }),
27153 insert_text_format: Some(InsertTextFormat::SNIPPET),
27154 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27155 lsp::InsertReplaceEdit {
27156 new_text: snippet.body.clone(),
27157 insert: lsp_range,
27158 replace: lsp_range,
27159 },
27160 )),
27161 filter_text: Some(snippet.body.clone()),
27162 sort_text: Some(char::MAX.to_string()),
27163 ..lsp::CompletionItem::default()
27164 }),
27165 lsp_defaults: None,
27166 },
27167 label: CodeLabel {
27168 text: matching_prefix.clone(),
27169 runs: Vec::new(),
27170 filter_range: 0..matching_prefix.len(),
27171 },
27172 icon_path: None,
27173 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27174 single_line: snippet.name.clone().into(),
27175 plain_text: snippet
27176 .description
27177 .clone()
27178 .map(|description| description.into()),
27179 }),
27180 insert_text_mode: None,
27181 confirm: None,
27182 match_start: Some(start),
27183 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27184 }
27185 }));
27186 }
27187
27188 Ok(CompletionResponse {
27189 completions,
27190 display_options: CompletionDisplayOptions::default(),
27191 is_incomplete,
27192 })
27193 })
27194}
27195
27196impl CompletionProvider for Entity<Project> {
27197 fn completions(
27198 &self,
27199 _excerpt_id: ExcerptId,
27200 buffer: &Entity<Buffer>,
27201 buffer_position: text::Anchor,
27202 options: CompletionContext,
27203 _window: &mut Window,
27204 cx: &mut Context<Editor>,
27205 ) -> Task<Result<Vec<CompletionResponse>>> {
27206 self.update(cx, |project, cx| {
27207 let task = project.completions(buffer, buffer_position, options, cx);
27208 cx.background_spawn(task)
27209 })
27210 }
27211
27212 fn resolve_completions(
27213 &self,
27214 buffer: Entity<Buffer>,
27215 completion_indices: Vec<usize>,
27216 completions: Rc<RefCell<Box<[Completion]>>>,
27217 cx: &mut Context<Editor>,
27218 ) -> Task<Result<bool>> {
27219 self.update(cx, |project, cx| {
27220 project.lsp_store().update(cx, |lsp_store, cx| {
27221 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27222 })
27223 })
27224 }
27225
27226 fn apply_additional_edits_for_completion(
27227 &self,
27228 buffer: Entity<Buffer>,
27229 completions: Rc<RefCell<Box<[Completion]>>>,
27230 completion_index: usize,
27231 push_to_history: bool,
27232 cx: &mut Context<Editor>,
27233 ) -> Task<Result<Option<language::Transaction>>> {
27234 self.update(cx, |project, cx| {
27235 project.lsp_store().update(cx, |lsp_store, cx| {
27236 lsp_store.apply_additional_edits_for_completion(
27237 buffer,
27238 completions,
27239 completion_index,
27240 push_to_history,
27241 cx,
27242 )
27243 })
27244 })
27245 }
27246
27247 fn is_completion_trigger(
27248 &self,
27249 buffer: &Entity<Buffer>,
27250 position: language::Anchor,
27251 text: &str,
27252 trigger_in_words: bool,
27253 cx: &mut Context<Editor>,
27254 ) -> bool {
27255 let mut chars = text.chars();
27256 let char = if let Some(char) = chars.next() {
27257 char
27258 } else {
27259 return false;
27260 };
27261 if chars.next().is_some() {
27262 return false;
27263 }
27264
27265 let buffer = buffer.read(cx);
27266 let snapshot = buffer.snapshot();
27267 let classifier = snapshot
27268 .char_classifier_at(position)
27269 .scope_context(Some(CharScopeContext::Completion));
27270 if trigger_in_words && classifier.is_word(char) {
27271 return true;
27272 }
27273
27274 buffer.completion_triggers().contains(text)
27275 }
27276
27277 fn show_snippets(&self) -> bool {
27278 true
27279 }
27280}
27281
27282impl SemanticsProvider for Entity<Project> {
27283 fn hover(
27284 &self,
27285 buffer: &Entity<Buffer>,
27286 position: text::Anchor,
27287 cx: &mut App,
27288 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27289 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
27290 }
27291
27292 fn document_highlights(
27293 &self,
27294 buffer: &Entity<Buffer>,
27295 position: text::Anchor,
27296 cx: &mut App,
27297 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27298 Some(self.update(cx, |project, cx| {
27299 project.document_highlights(buffer, position, cx)
27300 }))
27301 }
27302
27303 fn definitions(
27304 &self,
27305 buffer: &Entity<Buffer>,
27306 position: text::Anchor,
27307 kind: GotoDefinitionKind,
27308 cx: &mut App,
27309 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27310 Some(self.update(cx, |project, cx| match kind {
27311 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27312 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27313 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27314 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27315 }))
27316 }
27317
27318 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27319 self.update(cx, |project, cx| {
27320 if project
27321 .active_debug_session(cx)
27322 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27323 {
27324 return true;
27325 }
27326
27327 buffer.update(cx, |buffer, cx| {
27328 project.any_language_server_supports_inlay_hints(buffer, cx)
27329 })
27330 })
27331 }
27332
27333 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27334 self.update(cx, |project, cx| {
27335 buffer.update(cx, |buffer, cx| {
27336 project.any_language_server_supports_semantic_tokens(buffer, cx)
27337 })
27338 })
27339 }
27340
27341 fn inline_values(
27342 &self,
27343 buffer_handle: Entity<Buffer>,
27344 range: Range<text::Anchor>,
27345 cx: &mut App,
27346 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27347 self.update(cx, |project, cx| {
27348 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27349
27350 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27351 })
27352 }
27353
27354 fn applicable_inlay_chunks(
27355 &self,
27356 buffer: &Entity<Buffer>,
27357 ranges: &[Range<text::Anchor>],
27358 cx: &mut App,
27359 ) -> Vec<Range<BufferRow>> {
27360 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27361 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27362 })
27363 }
27364
27365 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27366 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
27367 lsp_store.invalidate_inlay_hints(for_buffers)
27368 });
27369 }
27370
27371 fn inlay_hints(
27372 &self,
27373 invalidate: InvalidationStrategy,
27374 buffer: Entity<Buffer>,
27375 ranges: Vec<Range<text::Anchor>>,
27376 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27377 cx: &mut App,
27378 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27379 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27380 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27381 }))
27382 }
27383
27384 fn semantic_tokens(
27385 &self,
27386 buffer: Entity<Buffer>,
27387 refresh: Option<RefreshForServer>,
27388 cx: &mut App,
27389 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>> {
27390 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27391 lsp_store.semantic_tokens(buffer, refresh, cx)
27392 })
27393 }
27394
27395 fn range_for_rename(
27396 &self,
27397 buffer: &Entity<Buffer>,
27398 position: text::Anchor,
27399 cx: &mut App,
27400 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27401 Some(self.update(cx, |project, cx| {
27402 let buffer = buffer.clone();
27403 let task = project.prepare_rename(buffer.clone(), position, cx);
27404 cx.spawn(async move |_, cx| {
27405 Ok(match task.await? {
27406 PrepareRenameResponse::Success(range) => Some(range),
27407 PrepareRenameResponse::InvalidPosition => None,
27408 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27409 // Fallback on using TreeSitter info to determine identifier range
27410 buffer.read_with(cx, |buffer, _| {
27411 let snapshot = buffer.snapshot();
27412 let (range, kind) = snapshot.surrounding_word(position, None);
27413 if kind != Some(CharKind::Word) {
27414 return None;
27415 }
27416 Some(
27417 snapshot.anchor_before(range.start)
27418 ..snapshot.anchor_after(range.end),
27419 )
27420 })
27421 }
27422 })
27423 })
27424 }))
27425 }
27426
27427 fn perform_rename(
27428 &self,
27429 buffer: &Entity<Buffer>,
27430 position: text::Anchor,
27431 new_name: String,
27432 cx: &mut App,
27433 ) -> Option<Task<Result<ProjectTransaction>>> {
27434 Some(self.update(cx, |project, cx| {
27435 project.perform_rename(buffer.clone(), position, new_name, cx)
27436 }))
27437 }
27438}
27439
27440fn consume_contiguous_rows(
27441 contiguous_row_selections: &mut Vec<Selection<Point>>,
27442 selection: &Selection<Point>,
27443 display_map: &DisplaySnapshot,
27444 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27445) -> (MultiBufferRow, MultiBufferRow) {
27446 contiguous_row_selections.push(selection.clone());
27447 let start_row = starting_row(selection, display_map);
27448 let mut end_row = ending_row(selection, display_map);
27449
27450 while let Some(next_selection) = selections.peek() {
27451 if next_selection.start.row <= end_row.0 {
27452 end_row = ending_row(next_selection, display_map);
27453 contiguous_row_selections.push(selections.next().unwrap().clone());
27454 } else {
27455 break;
27456 }
27457 }
27458 (start_row, end_row)
27459}
27460
27461fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27462 if selection.start.column > 0 {
27463 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27464 } else {
27465 MultiBufferRow(selection.start.row)
27466 }
27467}
27468
27469fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27470 if next_selection.end.column > 0 || next_selection.is_empty() {
27471 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27472 } else {
27473 MultiBufferRow(next_selection.end.row)
27474 }
27475}
27476
27477impl EditorSnapshot {
27478 pub fn remote_selections_in_range<'a>(
27479 &'a self,
27480 range: &'a Range<Anchor>,
27481 collaboration_hub: &dyn CollaborationHub,
27482 cx: &'a App,
27483 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27484 let participant_names = collaboration_hub.user_names(cx);
27485 let participant_indices = collaboration_hub.user_participant_indices(cx);
27486 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27487 let collaborators_by_replica_id = collaborators_by_peer_id
27488 .values()
27489 .map(|collaborator| (collaborator.replica_id, collaborator))
27490 .collect::<HashMap<_, _>>();
27491 self.buffer_snapshot()
27492 .selections_in_range(range, false)
27493 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27494 if replica_id == ReplicaId::AGENT {
27495 Some(RemoteSelection {
27496 replica_id,
27497 selection,
27498 cursor_shape,
27499 line_mode,
27500 collaborator_id: CollaboratorId::Agent,
27501 user_name: Some("Agent".into()),
27502 color: cx.theme().players().agent(),
27503 })
27504 } else {
27505 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27506 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27507 let user_name = participant_names.get(&collaborator.user_id).cloned();
27508 Some(RemoteSelection {
27509 replica_id,
27510 selection,
27511 cursor_shape,
27512 line_mode,
27513 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27514 user_name,
27515 color: if let Some(index) = participant_index {
27516 cx.theme().players().color_for_participant(index.0)
27517 } else {
27518 cx.theme().players().absent()
27519 },
27520 })
27521 }
27522 })
27523 }
27524
27525 pub fn hunks_for_ranges(
27526 &self,
27527 ranges: impl IntoIterator<Item = Range<Point>>,
27528 ) -> Vec<MultiBufferDiffHunk> {
27529 let mut hunks = Vec::new();
27530 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27531 HashMap::default();
27532 for query_range in ranges {
27533 let query_rows =
27534 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27535 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27536 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27537 ) {
27538 // Include deleted hunks that are adjacent to the query range, because
27539 // otherwise they would be missed.
27540 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27541 if hunk.status().is_deleted() {
27542 intersects_range |= hunk.row_range.start == query_rows.end;
27543 intersects_range |= hunk.row_range.end == query_rows.start;
27544 }
27545 if intersects_range {
27546 if !processed_buffer_rows
27547 .entry(hunk.buffer_id)
27548 .or_default()
27549 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27550 {
27551 continue;
27552 }
27553 hunks.push(hunk);
27554 }
27555 }
27556 }
27557
27558 hunks
27559 }
27560
27561 fn display_diff_hunks_for_rows<'a>(
27562 &'a self,
27563 display_rows: Range<DisplayRow>,
27564 folded_buffers: &'a HashSet<BufferId>,
27565 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27566 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27567 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27568
27569 self.buffer_snapshot()
27570 .diff_hunks_in_range(buffer_start..buffer_end)
27571 .filter_map(|hunk| {
27572 if folded_buffers.contains(&hunk.buffer_id)
27573 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27574 {
27575 return None;
27576 }
27577
27578 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27579 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27580 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27581 let line_len = self.buffer_snapshot().line_len(last_row);
27582 Point::new(last_row.0, line_len)
27583 } else {
27584 Point::new(hunk.row_range.end.0, 0)
27585 };
27586
27587 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27588 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27589
27590 let display_hunk = if hunk_display_start.column() != 0 {
27591 DisplayDiffHunk::Folded {
27592 display_row: hunk_display_start.row(),
27593 }
27594 } else {
27595 let mut end_row = hunk_display_end.row();
27596 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27597 end_row.0 += 1;
27598 }
27599 let is_created_file = hunk.is_created_file();
27600
27601 DisplayDiffHunk::Unfolded {
27602 status: hunk.status(),
27603 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27604 ..hunk.diff_base_byte_range.end.0,
27605 word_diffs: hunk.word_diffs,
27606 display_row_range: hunk_display_start.row()..end_row,
27607 multi_buffer_range: Anchor::range_in_buffer(
27608 hunk.excerpt_id,
27609 hunk.buffer_range,
27610 ),
27611 is_created_file,
27612 }
27613 };
27614
27615 Some(display_hunk)
27616 })
27617 }
27618
27619 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27620 self.display_snapshot
27621 .buffer_snapshot()
27622 .language_at(position)
27623 }
27624
27625 pub fn is_focused(&self) -> bool {
27626 self.is_focused
27627 }
27628
27629 pub fn placeholder_text(&self) -> Option<String> {
27630 self.placeholder_display_snapshot
27631 .as_ref()
27632 .map(|display_map| display_map.text())
27633 }
27634
27635 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27636 self.scroll_anchor.scroll_position(&self.display_snapshot)
27637 }
27638
27639 pub fn gutter_dimensions(
27640 &self,
27641 font_id: FontId,
27642 font_size: Pixels,
27643 style: &EditorStyle,
27644 window: &mut Window,
27645 cx: &App,
27646 ) -> GutterDimensions {
27647 if self.show_gutter
27648 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27649 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27650 {
27651 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27652 matches!(
27653 ProjectSettings::get_global(cx).git.git_gutter,
27654 GitGutterSetting::TrackedFiles
27655 )
27656 });
27657 let gutter_settings = EditorSettings::get_global(cx).gutter;
27658 let show_line_numbers = self
27659 .show_line_numbers
27660 .unwrap_or(gutter_settings.line_numbers);
27661 let line_gutter_width = if show_line_numbers {
27662 // Avoid flicker-like gutter resizes when the line number gains another digit by
27663 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27664 let min_width_for_number_on_gutter =
27665 ch_advance * gutter_settings.min_line_number_digits as f32;
27666 self.max_line_number_width(style, window)
27667 .max(min_width_for_number_on_gutter)
27668 } else {
27669 0.0.into()
27670 };
27671
27672 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27673 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27674
27675 let git_blame_entries_width =
27676 self.git_blame_gutter_max_author_length
27677 .map(|max_author_length| {
27678 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27679 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27680
27681 /// The number of characters to dedicate to gaps and margins.
27682 const SPACING_WIDTH: usize = 4;
27683
27684 let max_char_count = max_author_length.min(renderer.max_author_length())
27685 + ::git::SHORT_SHA_LENGTH
27686 + MAX_RELATIVE_TIMESTAMP.len()
27687 + SPACING_WIDTH;
27688
27689 ch_advance * max_char_count
27690 });
27691
27692 let is_singleton = self.buffer_snapshot().is_singleton();
27693
27694 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27695 left_padding += if !is_singleton {
27696 ch_width * 4.0
27697 } else if show_runnables || show_breakpoints {
27698 ch_width * 3.0
27699 } else if show_git_gutter && show_line_numbers {
27700 ch_width * 2.0
27701 } else if show_git_gutter || show_line_numbers {
27702 ch_width
27703 } else {
27704 px(0.)
27705 };
27706
27707 let shows_folds = is_singleton && gutter_settings.folds;
27708
27709 let right_padding = if shows_folds && show_line_numbers {
27710 ch_width * 4.0
27711 } else if shows_folds || (!is_singleton && show_line_numbers) {
27712 ch_width * 3.0
27713 } else if show_line_numbers {
27714 ch_width
27715 } else {
27716 px(0.)
27717 };
27718
27719 GutterDimensions {
27720 left_padding,
27721 right_padding,
27722 width: line_gutter_width + left_padding + right_padding,
27723 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27724 git_blame_entries_width,
27725 }
27726 } else if self.offset_content {
27727 GutterDimensions::default_with_margin(font_id, font_size, cx)
27728 } else {
27729 GutterDimensions::default()
27730 }
27731 }
27732
27733 pub fn render_crease_toggle(
27734 &self,
27735 buffer_row: MultiBufferRow,
27736 row_contains_cursor: bool,
27737 editor: Entity<Editor>,
27738 window: &mut Window,
27739 cx: &mut App,
27740 ) -> Option<AnyElement> {
27741 let folded = self.is_line_folded(buffer_row);
27742 let mut is_foldable = false;
27743
27744 if let Some(crease) = self
27745 .crease_snapshot
27746 .query_row(buffer_row, self.buffer_snapshot())
27747 {
27748 is_foldable = true;
27749 match crease {
27750 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27751 if let Some(render_toggle) = render_toggle {
27752 let toggle_callback =
27753 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27754 if folded {
27755 editor.update(cx, |editor, cx| {
27756 editor.fold_at(buffer_row, window, cx)
27757 });
27758 } else {
27759 editor.update(cx, |editor, cx| {
27760 editor.unfold_at(buffer_row, window, cx)
27761 });
27762 }
27763 });
27764 return Some((render_toggle)(
27765 buffer_row,
27766 folded,
27767 toggle_callback,
27768 window,
27769 cx,
27770 ));
27771 }
27772 }
27773 }
27774 }
27775
27776 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27777
27778 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27779 Some(
27780 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27781 .toggle_state(folded)
27782 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27783 if folded {
27784 this.unfold_at(buffer_row, window, cx);
27785 } else {
27786 this.fold_at(buffer_row, window, cx);
27787 }
27788 }))
27789 .into_any_element(),
27790 )
27791 } else {
27792 None
27793 }
27794 }
27795
27796 pub fn render_crease_trailer(
27797 &self,
27798 buffer_row: MultiBufferRow,
27799 window: &mut Window,
27800 cx: &mut App,
27801 ) -> Option<AnyElement> {
27802 let folded = self.is_line_folded(buffer_row);
27803 if let Crease::Inline { render_trailer, .. } = self
27804 .crease_snapshot
27805 .query_row(buffer_row, self.buffer_snapshot())?
27806 {
27807 let render_trailer = render_trailer.as_ref()?;
27808 Some(render_trailer(buffer_row, folded, window, cx))
27809 } else {
27810 None
27811 }
27812 }
27813
27814 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27815 let digit_count = self.widest_line_number().ilog10() + 1;
27816 column_pixels(style, digit_count as usize, window)
27817 }
27818
27819 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27820 ///
27821 /// This is positive if `base` is before `line`.
27822 fn relative_line_delta(
27823 &self,
27824 current_selection_head: DisplayRow,
27825 first_visible_row: DisplayRow,
27826 consider_wrapped_lines: bool,
27827 ) -> i64 {
27828 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27829 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27830
27831 if consider_wrapped_lines {
27832 let wrap_snapshot = self.wrap_snapshot();
27833 let base_wrap_row = wrap_snapshot
27834 .make_wrap_point(current_selection_head, Bias::Left)
27835 .row();
27836 let wrap_row = wrap_snapshot
27837 .make_wrap_point(first_visible_row, Bias::Left)
27838 .row();
27839
27840 wrap_row.0 as i64 - base_wrap_row.0 as i64
27841 } else {
27842 let fold_snapshot = self.fold_snapshot();
27843 let base_fold_row = fold_snapshot
27844 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27845 .row();
27846 let fold_row = fold_snapshot
27847 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27848 .row();
27849
27850 fold_row as i64 - base_fold_row as i64
27851 }
27852 }
27853
27854 /// Returns the unsigned relative line number to display for each row in `rows`.
27855 ///
27856 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27857 pub fn calculate_relative_line_numbers(
27858 &self,
27859 rows: &Range<DisplayRow>,
27860 current_selection_head: DisplayRow,
27861 count_wrapped_lines: bool,
27862 ) -> HashMap<DisplayRow, u32> {
27863 let initial_offset =
27864 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27865
27866 self.row_infos(rows.start)
27867 .take(rows.len())
27868 .enumerate()
27869 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27870 .filter(|(_row, row_info)| {
27871 row_info.buffer_row.is_some()
27872 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27873 })
27874 .enumerate()
27875 .filter_map(|(i, (row, row_info))| {
27876 // We want to ensure here that the current line has absolute
27877 // numbering, even if we are in a soft-wrapped line. With the
27878 // exception that if we are in a deleted line, we should number this
27879 // relative with 0, as otherwise it would have no line number at all
27880 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27881
27882 (relative_line_number != 0
27883 || row_info
27884 .diff_status
27885 .is_some_and(|status| status.is_deleted()))
27886 .then_some((row, relative_line_number))
27887 })
27888 .collect()
27889 }
27890}
27891
27892pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27893 let font_size = style.text.font_size.to_pixels(window.rem_size());
27894 let layout = window.text_system().shape_line(
27895 SharedString::from(" ".repeat(column)),
27896 font_size,
27897 &[TextRun {
27898 len: column,
27899 font: style.text.font(),
27900 color: Hsla::default(),
27901 ..Default::default()
27902 }],
27903 None,
27904 );
27905
27906 layout.width
27907}
27908
27909impl Deref for EditorSnapshot {
27910 type Target = DisplaySnapshot;
27911
27912 fn deref(&self) -> &Self::Target {
27913 &self.display_snapshot
27914 }
27915}
27916
27917#[derive(Clone, Debug, PartialEq, Eq)]
27918pub enum EditorEvent {
27919 /// Emitted when the stored review comments change (added, removed, or updated).
27920 ReviewCommentsChanged {
27921 /// The new total count of review comments.
27922 total_count: usize,
27923 },
27924 InputIgnored {
27925 text: Arc<str>,
27926 },
27927 InputHandled {
27928 utf16_range_to_replace: Option<Range<isize>>,
27929 text: Arc<str>,
27930 },
27931 ExcerptsAdded {
27932 buffer: Entity<Buffer>,
27933 predecessor: ExcerptId,
27934 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27935 },
27936 ExcerptsRemoved {
27937 ids: Vec<ExcerptId>,
27938 removed_buffer_ids: Vec<BufferId>,
27939 },
27940 BufferFoldToggled {
27941 ids: Vec<ExcerptId>,
27942 folded: bool,
27943 },
27944 ExcerptsEdited {
27945 ids: Vec<ExcerptId>,
27946 },
27947 ExcerptsExpanded {
27948 ids: Vec<ExcerptId>,
27949 },
27950 ExpandExcerptsRequested {
27951 excerpt_ids: Vec<ExcerptId>,
27952 lines: u32,
27953 direction: ExpandExcerptDirection,
27954 },
27955 StageOrUnstageRequested {
27956 stage: bool,
27957 hunks: Vec<MultiBufferDiffHunk>,
27958 },
27959 OpenExcerptsRequested {
27960 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
27961 split: bool,
27962 },
27963 RestoreRequested {
27964 hunks: Vec<MultiBufferDiffHunk>,
27965 },
27966 BufferEdited,
27967 Edited {
27968 transaction_id: clock::Lamport,
27969 },
27970 Reparsed(BufferId),
27971 Focused,
27972 FocusedIn,
27973 Blurred,
27974 DirtyChanged,
27975 Saved,
27976 TitleChanged,
27977 SelectionsChanged {
27978 local: bool,
27979 },
27980 ScrollPositionChanged {
27981 local: bool,
27982 autoscroll: bool,
27983 },
27984 TransactionUndone {
27985 transaction_id: clock::Lamport,
27986 },
27987 TransactionBegun {
27988 transaction_id: clock::Lamport,
27989 },
27990 CursorShapeChanged,
27991 BreadcrumbsChanged,
27992 OutlineSymbolsChanged,
27993 PushedToNavHistory {
27994 anchor: Anchor,
27995 is_deactivate: bool,
27996 },
27997}
27998
27999impl EventEmitter<EditorEvent> for Editor {}
28000
28001impl Focusable for Editor {
28002 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28003 self.focus_handle.clone()
28004 }
28005}
28006
28007impl Render for Editor {
28008 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28009 EditorElement::new(&cx.entity(), self.create_style(cx))
28010 }
28011}
28012
28013impl EntityInputHandler for Editor {
28014 fn text_for_range(
28015 &mut self,
28016 range_utf16: Range<usize>,
28017 adjusted_range: &mut Option<Range<usize>>,
28018 _: &mut Window,
28019 cx: &mut Context<Self>,
28020 ) -> Option<String> {
28021 let snapshot = self.buffer.read(cx).read(cx);
28022 let start = snapshot.clip_offset_utf16(
28023 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28024 Bias::Left,
28025 );
28026 let end = snapshot.clip_offset_utf16(
28027 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28028 Bias::Right,
28029 );
28030 if (start.0.0..end.0.0) != range_utf16 {
28031 adjusted_range.replace(start.0.0..end.0.0);
28032 }
28033 Some(snapshot.text_for_range(start..end).collect())
28034 }
28035
28036 fn selected_text_range(
28037 &mut self,
28038 ignore_disabled_input: bool,
28039 _: &mut Window,
28040 cx: &mut Context<Self>,
28041 ) -> Option<UTF16Selection> {
28042 // Prevent the IME menu from appearing when holding down an alphabetic key
28043 // while input is disabled.
28044 if !ignore_disabled_input && !self.input_enabled {
28045 return None;
28046 }
28047
28048 let selection = self
28049 .selections
28050 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28051 let range = selection.range();
28052
28053 Some(UTF16Selection {
28054 range: range.start.0.0..range.end.0.0,
28055 reversed: selection.reversed,
28056 })
28057 }
28058
28059 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28060 let snapshot = self.buffer.read(cx).read(cx);
28061 let range = self
28062 .text_highlights(HighlightKey::InputComposition, cx)?
28063 .1
28064 .first()?;
28065 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28066 }
28067
28068 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28069 self.clear_highlights(HighlightKey::InputComposition, cx);
28070 self.ime_transaction.take();
28071 }
28072
28073 fn replace_text_in_range(
28074 &mut self,
28075 range_utf16: Option<Range<usize>>,
28076 text: &str,
28077 window: &mut Window,
28078 cx: &mut Context<Self>,
28079 ) {
28080 if !self.input_enabled {
28081 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28082 return;
28083 }
28084
28085 self.transact(window, cx, |this, window, cx| {
28086 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28087 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28088 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28089 Some(this.selection_replacement_ranges(range_utf16, cx))
28090 } else {
28091 this.marked_text_ranges(cx)
28092 };
28093
28094 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28095 let newest_selection_id = this.selections.newest_anchor().id;
28096 this.selections
28097 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28098 .iter()
28099 .zip(ranges_to_replace.iter())
28100 .find_map(|(selection, range)| {
28101 if selection.id == newest_selection_id {
28102 Some(
28103 (range.start.0.0 as isize - selection.head().0.0 as isize)
28104 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28105 )
28106 } else {
28107 None
28108 }
28109 })
28110 });
28111
28112 cx.emit(EditorEvent::InputHandled {
28113 utf16_range_to_replace: range_to_replace,
28114 text: text.into(),
28115 });
28116
28117 if let Some(new_selected_ranges) = new_selected_ranges {
28118 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28119 selections.select_ranges(new_selected_ranges)
28120 });
28121 this.backspace(&Default::default(), window, cx);
28122 }
28123
28124 this.handle_input(text, window, cx);
28125 });
28126
28127 if let Some(transaction) = self.ime_transaction {
28128 self.buffer.update(cx, |buffer, cx| {
28129 buffer.group_until_transaction(transaction, cx);
28130 });
28131 }
28132
28133 self.unmark_text(window, cx);
28134 }
28135
28136 fn replace_and_mark_text_in_range(
28137 &mut self,
28138 range_utf16: Option<Range<usize>>,
28139 text: &str,
28140 new_selected_range_utf16: Option<Range<usize>>,
28141 window: &mut Window,
28142 cx: &mut Context<Self>,
28143 ) {
28144 if !self.input_enabled {
28145 return;
28146 }
28147
28148 let transaction = self.transact(window, cx, |this, window, cx| {
28149 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28150 let snapshot = this.buffer.read(cx).read(cx);
28151 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28152 for marked_range in &mut marked_ranges {
28153 marked_range.end = marked_range.start + relative_range_utf16.end;
28154 marked_range.start += relative_range_utf16.start;
28155 marked_range.start =
28156 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28157 marked_range.end =
28158 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28159 }
28160 }
28161 Some(marked_ranges)
28162 } else if let Some(range_utf16) = range_utf16 {
28163 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28164 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28165 Some(this.selection_replacement_ranges(range_utf16, cx))
28166 } else {
28167 None
28168 };
28169
28170 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28171 let newest_selection_id = this.selections.newest_anchor().id;
28172 this.selections
28173 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28174 .iter()
28175 .zip(ranges_to_replace.iter())
28176 .find_map(|(selection, range)| {
28177 if selection.id == newest_selection_id {
28178 Some(
28179 (range.start.0.0 as isize - selection.head().0.0 as isize)
28180 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28181 )
28182 } else {
28183 None
28184 }
28185 })
28186 });
28187
28188 cx.emit(EditorEvent::InputHandled {
28189 utf16_range_to_replace: range_to_replace,
28190 text: text.into(),
28191 });
28192
28193 if let Some(ranges) = ranges_to_replace {
28194 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28195 s.select_ranges(ranges)
28196 });
28197 }
28198
28199 let marked_ranges = {
28200 let snapshot = this.buffer.read(cx).read(cx);
28201 this.selections
28202 .disjoint_anchors_arc()
28203 .iter()
28204 .map(|selection| {
28205 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28206 })
28207 .collect::<Vec<_>>()
28208 };
28209
28210 if text.is_empty() {
28211 this.unmark_text(window, cx);
28212 } else {
28213 this.highlight_text(
28214 HighlightKey::InputComposition,
28215 marked_ranges.clone(),
28216 HighlightStyle {
28217 underline: Some(UnderlineStyle {
28218 thickness: px(1.),
28219 color: None,
28220 wavy: false,
28221 }),
28222 ..Default::default()
28223 },
28224 cx,
28225 );
28226 }
28227
28228 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28229 let use_autoclose = this.use_autoclose;
28230 let use_auto_surround = this.use_auto_surround;
28231 this.set_use_autoclose(false);
28232 this.set_use_auto_surround(false);
28233 this.handle_input(text, window, cx);
28234 this.set_use_autoclose(use_autoclose);
28235 this.set_use_auto_surround(use_auto_surround);
28236
28237 if let Some(new_selected_range) = new_selected_range_utf16 {
28238 let snapshot = this.buffer.read(cx).read(cx);
28239 let new_selected_ranges = marked_ranges
28240 .into_iter()
28241 .map(|marked_range| {
28242 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28243 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28244 insertion_start.0 + new_selected_range.start,
28245 ));
28246 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28247 insertion_start.0 + new_selected_range.end,
28248 ));
28249 snapshot.clip_offset_utf16(new_start, Bias::Left)
28250 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28251 })
28252 .collect::<Vec<_>>();
28253
28254 drop(snapshot);
28255 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28256 selections.select_ranges(new_selected_ranges)
28257 });
28258 }
28259 });
28260
28261 self.ime_transaction = self.ime_transaction.or(transaction);
28262 if let Some(transaction) = self.ime_transaction {
28263 self.buffer.update(cx, |buffer, cx| {
28264 buffer.group_until_transaction(transaction, cx);
28265 });
28266 }
28267
28268 if self
28269 .text_highlights(HighlightKey::InputComposition, cx)
28270 .is_none()
28271 {
28272 self.ime_transaction.take();
28273 }
28274 }
28275
28276 fn bounds_for_range(
28277 &mut self,
28278 range_utf16: Range<usize>,
28279 element_bounds: gpui::Bounds<Pixels>,
28280 window: &mut Window,
28281 cx: &mut Context<Self>,
28282 ) -> Option<gpui::Bounds<Pixels>> {
28283 let text_layout_details = self.text_layout_details(window, cx);
28284 let CharacterDimensions {
28285 em_width,
28286 em_advance,
28287 line_height,
28288 } = self.character_dimensions(window, cx);
28289
28290 let snapshot = self.snapshot(window, cx);
28291 let scroll_position = snapshot.scroll_position();
28292 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28293
28294 let start =
28295 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28296 let x = Pixels::from(
28297 ScrollOffset::from(
28298 snapshot.x_for_display_point(start, &text_layout_details)
28299 + self.gutter_dimensions.full_width(),
28300 ) - scroll_left,
28301 );
28302 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28303
28304 Some(Bounds {
28305 origin: element_bounds.origin + point(x, y),
28306 size: size(em_width, line_height),
28307 })
28308 }
28309
28310 fn character_index_for_point(
28311 &mut self,
28312 point: gpui::Point<Pixels>,
28313 _window: &mut Window,
28314 _cx: &mut Context<Self>,
28315 ) -> Option<usize> {
28316 let position_map = self.last_position_map.as_ref()?;
28317 if !position_map.text_hitbox.contains(&point) {
28318 return None;
28319 }
28320 let display_point = position_map.point_for_position(point).previous_valid;
28321 let anchor = position_map
28322 .snapshot
28323 .display_point_to_anchor(display_point, Bias::Left);
28324 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28325 Some(utf16_offset.0.0)
28326 }
28327
28328 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28329 self.input_enabled
28330 }
28331}
28332
28333trait SelectionExt {
28334 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28335 fn spanned_rows(
28336 &self,
28337 include_end_if_at_line_start: bool,
28338 map: &DisplaySnapshot,
28339 ) -> Range<MultiBufferRow>;
28340}
28341
28342impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28343 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28344 let start = self
28345 .start
28346 .to_point(map.buffer_snapshot())
28347 .to_display_point(map);
28348 let end = self
28349 .end
28350 .to_point(map.buffer_snapshot())
28351 .to_display_point(map);
28352 if self.reversed {
28353 end..start
28354 } else {
28355 start..end
28356 }
28357 }
28358
28359 fn spanned_rows(
28360 &self,
28361 include_end_if_at_line_start: bool,
28362 map: &DisplaySnapshot,
28363 ) -> Range<MultiBufferRow> {
28364 let start = self.start.to_point(map.buffer_snapshot());
28365 let mut end = self.end.to_point(map.buffer_snapshot());
28366 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28367 end.row -= 1;
28368 }
28369
28370 let buffer_start = map.prev_line_boundary(start).0;
28371 let buffer_end = map.next_line_boundary(end).0;
28372 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28373 }
28374}
28375
28376impl<T: InvalidationRegion> InvalidationStack<T> {
28377 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28378 where
28379 S: Clone + ToOffset,
28380 {
28381 while let Some(region) = self.last() {
28382 let all_selections_inside_invalidation_ranges =
28383 if selections.len() == region.ranges().len() {
28384 selections
28385 .iter()
28386 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28387 .all(|(selection, invalidation_range)| {
28388 let head = selection.head().to_offset(buffer);
28389 invalidation_range.start <= head && invalidation_range.end >= head
28390 })
28391 } else {
28392 false
28393 };
28394
28395 if all_selections_inside_invalidation_ranges {
28396 break;
28397 } else {
28398 self.pop();
28399 }
28400 }
28401 }
28402}
28403
28404#[derive(Clone)]
28405struct ErasedEditorImpl(Entity<Editor>);
28406
28407impl ui_input::ErasedEditor for ErasedEditorImpl {
28408 fn text(&self, cx: &App) -> String {
28409 self.0.read(cx).text(cx)
28410 }
28411
28412 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28413 self.0.update(cx, |this, cx| {
28414 this.set_text(text, window, cx);
28415 })
28416 }
28417
28418 fn clear(&self, window: &mut Window, cx: &mut App) {
28419 self.0.update(cx, |this, cx| this.clear(window, cx));
28420 }
28421
28422 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28423 self.0.update(cx, |this, cx| {
28424 this.set_placeholder_text(text, window, cx);
28425 });
28426 }
28427
28428 fn focus_handle(&self, cx: &App) -> FocusHandle {
28429 self.0.read(cx).focus_handle(cx)
28430 }
28431
28432 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28433 let settings = ThemeSettings::get_global(cx);
28434 let theme_color = cx.theme().colors();
28435
28436 let text_style = TextStyle {
28437 font_family: settings.ui_font.family.clone(),
28438 font_features: settings.ui_font.features.clone(),
28439 font_size: rems(0.875).into(),
28440 font_weight: settings.ui_font.weight,
28441 font_style: FontStyle::Normal,
28442 line_height: relative(1.2),
28443 color: theme_color.text,
28444 ..Default::default()
28445 };
28446 let editor_style = EditorStyle {
28447 background: theme_color.ghost_element_background,
28448 local_player: cx.theme().players().local(),
28449 syntax: cx.theme().syntax().clone(),
28450 text: text_style,
28451 ..Default::default()
28452 };
28453 EditorElement::new(&self.0, editor_style).into_any()
28454 }
28455
28456 fn as_any(&self) -> &dyn Any {
28457 &self.0
28458 }
28459
28460 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28461 self.0.update(cx, |editor, cx| {
28462 let editor_offset = editor.buffer().read(cx).len(cx);
28463 editor.change_selections(
28464 SelectionEffects::scroll(Autoscroll::Next),
28465 window,
28466 cx,
28467 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28468 );
28469 });
28470 }
28471
28472 fn subscribe(
28473 &self,
28474 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28475 window: &mut Window,
28476 cx: &mut App,
28477 ) -> Subscription {
28478 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28479 let event = match event {
28480 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28481 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28482 _ => return,
28483 };
28484 (callback)(event, window, cx);
28485 })
28486 }
28487
28488 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28489 self.0.update(cx, |editor, cx| {
28490 editor.set_masked(masked, cx);
28491 });
28492 }
28493}
28494impl<T> Default for InvalidationStack<T> {
28495 fn default() -> Self {
28496 Self(Default::default())
28497 }
28498}
28499
28500impl<T> Deref for InvalidationStack<T> {
28501 type Target = Vec<T>;
28502
28503 fn deref(&self) -> &Self::Target {
28504 &self.0
28505 }
28506}
28507
28508impl<T> DerefMut for InvalidationStack<T> {
28509 fn deref_mut(&mut self) -> &mut Self::Target {
28510 &mut self.0
28511 }
28512}
28513
28514impl InvalidationRegion for SnippetState {
28515 fn ranges(&self) -> &[Range<Anchor>] {
28516 &self.ranges[self.active_index]
28517 }
28518}
28519
28520fn edit_prediction_edit_text(
28521 current_snapshot: &BufferSnapshot,
28522 edits: &[(Range<Anchor>, impl AsRef<str>)],
28523 edit_preview: &EditPreview,
28524 include_deletions: bool,
28525 cx: &App,
28526) -> HighlightedText {
28527 let edits = edits
28528 .iter()
28529 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28530 .collect::<Vec<_>>();
28531
28532 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28533}
28534
28535fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28536 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
28537 // Just show the raw edit text with basic styling
28538 let mut text = String::new();
28539 let mut highlights = Vec::new();
28540
28541 let insertion_highlight_style = HighlightStyle {
28542 color: Some(cx.theme().colors().text),
28543 ..Default::default()
28544 };
28545
28546 for (_, edit_text) in edits {
28547 let start_offset = text.len();
28548 text.push_str(edit_text);
28549 let end_offset = text.len();
28550
28551 if start_offset < end_offset {
28552 highlights.push((start_offset..end_offset, insertion_highlight_style));
28553 }
28554 }
28555
28556 HighlightedText {
28557 text: text.into(),
28558 highlights,
28559 }
28560}
28561
28562pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28563 match severity {
28564 lsp::DiagnosticSeverity::ERROR => colors.error,
28565 lsp::DiagnosticSeverity::WARNING => colors.warning,
28566 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28567 lsp::DiagnosticSeverity::HINT => colors.info,
28568 _ => colors.ignored,
28569 }
28570}
28571
28572pub fn styled_runs_for_code_label<'a>(
28573 label: &'a CodeLabel,
28574 syntax_theme: &'a theme::SyntaxTheme,
28575 local_player: &'a theme::PlayerColor,
28576) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28577 let fade_out = HighlightStyle {
28578 fade_out: Some(0.35),
28579 ..Default::default()
28580 };
28581
28582 let mut prev_end = label.filter_range.end;
28583 label
28584 .runs
28585 .iter()
28586 .enumerate()
28587 .flat_map(move |(ix, (range, highlight_id))| {
28588 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28589 HighlightStyle {
28590 color: Some(local_player.cursor),
28591 ..Default::default()
28592 }
28593 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28594 HighlightStyle {
28595 background_color: Some(local_player.selection),
28596 ..Default::default()
28597 }
28598 } else if let Some(style) = highlight_id.style(syntax_theme) {
28599 style
28600 } else {
28601 return Default::default();
28602 };
28603 let muted_style = style.highlight(fade_out);
28604
28605 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28606 if range.start >= label.filter_range.end {
28607 if range.start > prev_end {
28608 runs.push((prev_end..range.start, fade_out));
28609 }
28610 runs.push((range.clone(), muted_style));
28611 } else if range.end <= label.filter_range.end {
28612 runs.push((range.clone(), style));
28613 } else {
28614 runs.push((range.start..label.filter_range.end, style));
28615 runs.push((label.filter_range.end..range.end, muted_style));
28616 }
28617 prev_end = cmp::max(prev_end, range.end);
28618
28619 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28620 runs.push((prev_end..label.text.len(), fade_out));
28621 }
28622
28623 runs
28624 })
28625}
28626
28627pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28628 let mut prev_index = 0;
28629 let mut prev_codepoint: Option<char> = None;
28630 text.char_indices()
28631 .chain([(text.len(), '\0')])
28632 .filter_map(move |(index, codepoint)| {
28633 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28634 let is_boundary = index == text.len()
28635 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28636 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28637 if is_boundary {
28638 let chunk = &text[prev_index..index];
28639 prev_index = index;
28640 Some(chunk)
28641 } else {
28642 None
28643 }
28644 })
28645}
28646
28647/// Given a string of text immediately before the cursor, iterates over possible
28648/// strings a snippet could match to. More precisely: returns an iterator over
28649/// suffixes of `text` created by splitting at word boundaries (before & after
28650/// every non-word character).
28651///
28652/// Shorter suffixes are returned first.
28653pub(crate) fn snippet_candidate_suffixes<'a>(
28654 text: &'a str,
28655 is_word_char: &'a dyn Fn(char) -> bool,
28656) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28657 let mut prev_index = text.len();
28658 let mut prev_codepoint = None;
28659 text.char_indices()
28660 .rev()
28661 .chain([(0, '\0')])
28662 .filter_map(move |(index, codepoint)| {
28663 let prev_index = std::mem::replace(&mut prev_index, index);
28664 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28665 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28666 None
28667 } else {
28668 let chunk = &text[prev_index..]; // go to end of string
28669 Some(chunk)
28670 }
28671 })
28672}
28673
28674pub trait RangeToAnchorExt: Sized {
28675 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28676
28677 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28678 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28679 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28680 }
28681}
28682
28683impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28684 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28685 let start_offset = self.start.to_offset(snapshot);
28686 let end_offset = self.end.to_offset(snapshot);
28687 if start_offset == end_offset {
28688 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28689 } else {
28690 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28691 }
28692 }
28693}
28694
28695pub trait RowExt {
28696 fn as_f64(&self) -> f64;
28697
28698 fn next_row(&self) -> Self;
28699
28700 fn previous_row(&self) -> Self;
28701
28702 fn minus(&self, other: Self) -> u32;
28703}
28704
28705impl RowExt for DisplayRow {
28706 fn as_f64(&self) -> f64 {
28707 self.0 as _
28708 }
28709
28710 fn next_row(&self) -> Self {
28711 Self(self.0 + 1)
28712 }
28713
28714 fn previous_row(&self) -> Self {
28715 Self(self.0.saturating_sub(1))
28716 }
28717
28718 fn minus(&self, other: Self) -> u32 {
28719 self.0 - other.0
28720 }
28721}
28722
28723impl RowExt for MultiBufferRow {
28724 fn as_f64(&self) -> f64 {
28725 self.0 as _
28726 }
28727
28728 fn next_row(&self) -> Self {
28729 Self(self.0 + 1)
28730 }
28731
28732 fn previous_row(&self) -> Self {
28733 Self(self.0.saturating_sub(1))
28734 }
28735
28736 fn minus(&self, other: Self) -> u32 {
28737 self.0 - other.0
28738 }
28739}
28740
28741trait RowRangeExt {
28742 type Row;
28743
28744 fn len(&self) -> usize;
28745
28746 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28747}
28748
28749impl RowRangeExt for Range<MultiBufferRow> {
28750 type Row = MultiBufferRow;
28751
28752 fn len(&self) -> usize {
28753 (self.end.0 - self.start.0) as usize
28754 }
28755
28756 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28757 (self.start.0..self.end.0).map(MultiBufferRow)
28758 }
28759}
28760
28761impl RowRangeExt for Range<DisplayRow> {
28762 type Row = DisplayRow;
28763
28764 fn len(&self) -> usize {
28765 (self.end.0 - self.start.0) as usize
28766 }
28767
28768 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28769 (self.start.0..self.end.0).map(DisplayRow)
28770 }
28771}
28772
28773/// If select range has more than one line, we
28774/// just point the cursor to range.start.
28775fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28776 if range.start.row == range.end.row {
28777 range
28778 } else {
28779 range.start..range.start
28780 }
28781}
28782pub struct KillRing(ClipboardItem);
28783impl Global for KillRing {}
28784
28785const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28786
28787enum BreakpointPromptEditAction {
28788 Log,
28789 Condition,
28790 HitCondition,
28791}
28792
28793struct BreakpointPromptEditor {
28794 pub(crate) prompt: Entity<Editor>,
28795 editor: WeakEntity<Editor>,
28796 breakpoint_anchor: Anchor,
28797 breakpoint: Breakpoint,
28798 edit_action: BreakpointPromptEditAction,
28799 block_ids: HashSet<CustomBlockId>,
28800 editor_margins: Arc<Mutex<EditorMargins>>,
28801 _subscriptions: Vec<Subscription>,
28802}
28803
28804impl BreakpointPromptEditor {
28805 const MAX_LINES: u8 = 4;
28806
28807 fn new(
28808 editor: WeakEntity<Editor>,
28809 breakpoint_anchor: Anchor,
28810 breakpoint: Breakpoint,
28811 edit_action: BreakpointPromptEditAction,
28812 window: &mut Window,
28813 cx: &mut Context<Self>,
28814 ) -> Self {
28815 let base_text = match edit_action {
28816 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28817 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28818 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28819 }
28820 .map(|msg| msg.to_string())
28821 .unwrap_or_default();
28822
28823 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28824 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28825
28826 let prompt = cx.new(|cx| {
28827 let mut prompt = Editor::new(
28828 EditorMode::AutoHeight {
28829 min_lines: 1,
28830 max_lines: Some(Self::MAX_LINES as usize),
28831 },
28832 buffer,
28833 None,
28834 window,
28835 cx,
28836 );
28837 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28838 prompt.set_show_cursor_when_unfocused(false, cx);
28839 prompt.set_placeholder_text(
28840 match edit_action {
28841 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28842 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28843 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28844 },
28845 window,
28846 cx,
28847 );
28848
28849 prompt
28850 });
28851
28852 Self {
28853 prompt,
28854 editor,
28855 breakpoint_anchor,
28856 breakpoint,
28857 edit_action,
28858 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28859 block_ids: Default::default(),
28860 _subscriptions: vec![],
28861 }
28862 }
28863
28864 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28865 self.block_ids.extend(block_ids)
28866 }
28867
28868 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28869 if let Some(editor) = self.editor.upgrade() {
28870 let message = self
28871 .prompt
28872 .read(cx)
28873 .buffer
28874 .read(cx)
28875 .as_singleton()
28876 .expect("A multi buffer in breakpoint prompt isn't possible")
28877 .read(cx)
28878 .as_rope()
28879 .to_string();
28880
28881 editor.update(cx, |editor, cx| {
28882 editor.edit_breakpoint_at_anchor(
28883 self.breakpoint_anchor,
28884 self.breakpoint.clone(),
28885 match self.edit_action {
28886 BreakpointPromptEditAction::Log => {
28887 BreakpointEditAction::EditLogMessage(message.into())
28888 }
28889 BreakpointPromptEditAction::Condition => {
28890 BreakpointEditAction::EditCondition(message.into())
28891 }
28892 BreakpointPromptEditAction::HitCondition => {
28893 BreakpointEditAction::EditHitCondition(message.into())
28894 }
28895 },
28896 cx,
28897 );
28898
28899 editor.remove_blocks(self.block_ids.clone(), None, cx);
28900 cx.focus_self(window);
28901 });
28902 }
28903 }
28904
28905 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28906 self.editor
28907 .update(cx, |editor, cx| {
28908 editor.remove_blocks(self.block_ids.clone(), None, cx);
28909 window.focus(&editor.focus_handle, cx);
28910 })
28911 .log_err();
28912 }
28913
28914 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28915 let settings = ThemeSettings::get_global(cx);
28916 let text_style = TextStyle {
28917 color: if self.prompt.read(cx).read_only(cx) {
28918 cx.theme().colors().text_disabled
28919 } else {
28920 cx.theme().colors().text
28921 },
28922 font_family: settings.buffer_font.family.clone(),
28923 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28924 font_size: settings.buffer_font_size(cx).into(),
28925 font_weight: settings.buffer_font.weight,
28926 line_height: relative(settings.buffer_line_height.value()),
28927 ..Default::default()
28928 };
28929 EditorElement::new(
28930 &self.prompt,
28931 EditorStyle {
28932 background: cx.theme().colors().editor_background,
28933 local_player: cx.theme().players().local(),
28934 text: text_style,
28935 ..Default::default()
28936 },
28937 )
28938 }
28939}
28940
28941impl Render for BreakpointPromptEditor {
28942 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28943 let editor_margins = *self.editor_margins.lock();
28944 let gutter_dimensions = editor_margins.gutter;
28945 h_flex()
28946 .key_context("Editor")
28947 .bg(cx.theme().colors().editor_background)
28948 .border_y_1()
28949 .border_color(cx.theme().status().info_border)
28950 .size_full()
28951 .py(window.line_height() / 2.5)
28952 .on_action(cx.listener(Self::confirm))
28953 .on_action(cx.listener(Self::cancel))
28954 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28955 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28956 }
28957}
28958
28959impl Focusable for BreakpointPromptEditor {
28960 fn focus_handle(&self, cx: &App) -> FocusHandle {
28961 self.prompt.focus_handle(cx)
28962 }
28963}
28964
28965fn all_edits_insertions_or_deletions(
28966 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28967 snapshot: &MultiBufferSnapshot,
28968) -> bool {
28969 let mut all_insertions = true;
28970 let mut all_deletions = true;
28971
28972 for (range, new_text) in edits.iter() {
28973 let range_is_empty = range.to_offset(snapshot).is_empty();
28974 let text_is_empty = new_text.is_empty();
28975
28976 if range_is_empty != text_is_empty {
28977 if range_is_empty {
28978 all_deletions = false;
28979 } else {
28980 all_insertions = false;
28981 }
28982 } else {
28983 return false;
28984 }
28985
28986 if !all_insertions && !all_deletions {
28987 return false;
28988 }
28989 }
28990 all_insertions || all_deletions
28991}
28992
28993struct MissingEditPredictionKeybindingTooltip;
28994
28995impl Render for MissingEditPredictionKeybindingTooltip {
28996 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28997 ui::tooltip_container(cx, |container, cx| {
28998 container
28999 .flex_shrink_0()
29000 .max_w_80()
29001 .min_h(rems_from_px(124.))
29002 .justify_between()
29003 .child(
29004 v_flex()
29005 .flex_1()
29006 .text_ui_sm(cx)
29007 .child(Label::new("Conflict with Accept Keybinding"))
29008 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29009 )
29010 .child(
29011 h_flex()
29012 .pb_1()
29013 .gap_1()
29014 .items_end()
29015 .w_full()
29016 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29017 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29018 }))
29019 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29020 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29021 })),
29022 )
29023 })
29024 }
29025}
29026
29027#[derive(Debug, Clone, Copy, PartialEq)]
29028pub struct LineHighlight {
29029 pub background: Background,
29030 pub border: Option<gpui::Hsla>,
29031 pub include_gutter: bool,
29032 pub type_id: Option<TypeId>,
29033}
29034
29035struct LineManipulationResult {
29036 pub new_text: String,
29037 pub line_count_before: usize,
29038 pub line_count_after: usize,
29039}
29040
29041fn render_diff_hunk_controls(
29042 row: u32,
29043 status: &DiffHunkStatus,
29044 hunk_range: Range<Anchor>,
29045 is_created_file: bool,
29046 line_height: Pixels,
29047 editor: &Entity<Editor>,
29048 _window: &mut Window,
29049 cx: &mut App,
29050) -> AnyElement {
29051 h_flex()
29052 .h(line_height)
29053 .mr_1()
29054 .gap_1()
29055 .px_0p5()
29056 .pb_1()
29057 .border_x_1()
29058 .border_b_1()
29059 .border_color(cx.theme().colors().border_variant)
29060 .rounded_b_lg()
29061 .bg(cx.theme().colors().editor_background)
29062 .gap_1()
29063 .block_mouse_except_scroll()
29064 .shadow_md()
29065 .child(if status.has_secondary_hunk() {
29066 Button::new(("stage", row as u64), "Stage")
29067 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29068 .tooltip({
29069 let focus_handle = editor.focus_handle(cx);
29070 move |_window, cx| {
29071 Tooltip::for_action_in(
29072 "Stage Hunk",
29073 &::git::ToggleStaged,
29074 &focus_handle,
29075 cx,
29076 )
29077 }
29078 })
29079 .on_click({
29080 let editor = editor.clone();
29081 move |_event, _window, cx| {
29082 editor.update(cx, |editor, cx| {
29083 editor.stage_or_unstage_diff_hunks(
29084 true,
29085 vec![hunk_range.start..hunk_range.start],
29086 cx,
29087 );
29088 });
29089 }
29090 })
29091 } else {
29092 Button::new(("unstage", row as u64), "Unstage")
29093 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29094 .tooltip({
29095 let focus_handle = editor.focus_handle(cx);
29096 move |_window, cx| {
29097 Tooltip::for_action_in(
29098 "Unstage Hunk",
29099 &::git::ToggleStaged,
29100 &focus_handle,
29101 cx,
29102 )
29103 }
29104 })
29105 .on_click({
29106 let editor = editor.clone();
29107 move |_event, _window, cx| {
29108 editor.update(cx, |editor, cx| {
29109 editor.stage_or_unstage_diff_hunks(
29110 false,
29111 vec![hunk_range.start..hunk_range.start],
29112 cx,
29113 );
29114 });
29115 }
29116 })
29117 })
29118 .child(
29119 Button::new(("restore", row as u64), "Restore")
29120 .tooltip({
29121 let focus_handle = editor.focus_handle(cx);
29122 move |_window, cx| {
29123 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29124 }
29125 })
29126 .on_click({
29127 let editor = editor.clone();
29128 move |_event, window, cx| {
29129 editor.update(cx, |editor, cx| {
29130 let snapshot = editor.snapshot(window, cx);
29131 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29132 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29133 });
29134 }
29135 })
29136 .disabled(is_created_file),
29137 )
29138 .when(
29139 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29140 |el| {
29141 el.child(
29142 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29143 .shape(IconButtonShape::Square)
29144 .icon_size(IconSize::Small)
29145 // .disabled(!has_multiple_hunks)
29146 .tooltip({
29147 let focus_handle = editor.focus_handle(cx);
29148 move |_window, cx| {
29149 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29150 }
29151 })
29152 .on_click({
29153 let editor = editor.clone();
29154 move |_event, window, cx| {
29155 editor.update(cx, |editor, cx| {
29156 let snapshot = editor.snapshot(window, cx);
29157 let position =
29158 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29159 editor.go_to_hunk_before_or_after_position(
29160 &snapshot,
29161 position,
29162 Direction::Next,
29163 window,
29164 cx,
29165 );
29166 editor.expand_selected_diff_hunks(cx);
29167 });
29168 }
29169 }),
29170 )
29171 .child(
29172 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29173 .shape(IconButtonShape::Square)
29174 .icon_size(IconSize::Small)
29175 // .disabled(!has_multiple_hunks)
29176 .tooltip({
29177 let focus_handle = editor.focus_handle(cx);
29178 move |_window, cx| {
29179 Tooltip::for_action_in(
29180 "Previous Hunk",
29181 &GoToPreviousHunk,
29182 &focus_handle,
29183 cx,
29184 )
29185 }
29186 })
29187 .on_click({
29188 let editor = editor.clone();
29189 move |_event, window, cx| {
29190 editor.update(cx, |editor, cx| {
29191 let snapshot = editor.snapshot(window, cx);
29192 let point =
29193 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29194 editor.go_to_hunk_before_or_after_position(
29195 &snapshot,
29196 point,
29197 Direction::Prev,
29198 window,
29199 cx,
29200 );
29201 editor.expand_selected_diff_hunks(cx);
29202 });
29203 }
29204 }),
29205 )
29206 },
29207 )
29208 .into_any_element()
29209}
29210
29211pub fn multibuffer_context_lines(cx: &App) -> u32 {
29212 EditorSettings::try_get(cx)
29213 .map(|settings| settings.excerpt_context_lines)
29214 .unwrap_or(2)
29215 .min(32)
29216}