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 runnables;
39mod rust_analyzer_ext;
40pub mod scroll;
41mod selections_collection;
42pub mod semantic_tokens;
43mod split;
44pub mod split_editor_view;
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, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
137 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,
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::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 utils::WithRemSize,
213};
214use ui_input::ErasedEditor;
215use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
216use workspace::{
217 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
218 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
219 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
220 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
221 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
222 searchable::SearchEvent,
223};
224use zed_actions::editor::{MoveDown, MoveUp};
225
226use crate::{
227 code_context_menus::CompletionsMenuSource,
228 editor_settings::MultiCursorModifier,
229 hover_links::{find_url, find_url_from_range},
230 inlays::{
231 InlineValueCache,
232 inlay_hints::{LspInlayHintData, inlay_hint_settings},
233 },
234 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
235 scroll::{ScrollOffset, ScrollPixelOffset},
236 selections_collection::resolve_selections_wrapping_blocks,
237 semantic_tokens::SemanticTokenState,
238 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
239};
240
241pub const FILE_HEADER_HEIGHT: u32 = 2;
242pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
243pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
244const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
245const MAX_LINE_LEN: usize = 1024;
246const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
247const MAX_SELECTION_HISTORY_LEN: usize = 1024;
248pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
249#[doc(hidden)]
250pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
251pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
252
253pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
254pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
255pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
256pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
257
258pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
259pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
260pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
261
262pub type RenderDiffHunkControlsFn = Arc<
263 dyn Fn(
264 u32,
265 &DiffHunkStatus,
266 Range<Anchor>,
267 bool,
268 Pixels,
269 &Entity<Editor>,
270 &mut Window,
271 &mut App,
272 ) -> AnyElement,
273>;
274
275enum ReportEditorEvent {
276 Saved { auto_saved: bool },
277 EditorOpened,
278 Closed,
279}
280
281impl ReportEditorEvent {
282 pub fn event_type(&self) -> &'static str {
283 match self {
284 Self::Saved { .. } => "Editor Saved",
285 Self::EditorOpened => "Editor Opened",
286 Self::Closed => "Editor Closed",
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293
294pub enum ConflictsOuter {}
295pub enum ConflictsOurs {}
296pub enum ConflictsTheirs {}
297pub enum ConflictsOursMarker {}
298pub enum ConflictsTheirsMarker {}
299
300pub struct HunkAddedColor;
301pub struct HunkRemovedColor;
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 word_diffs: Vec<Range<MultiBufferOffset>>,
327 },
328}
329
330pub enum HideMouseCursorOrigin {
331 TypingAction,
332 MovementAction,
333}
334
335pub fn init(cx: &mut App) {
336 cx.set_global(GlobalBlameRenderer(Arc::new(())));
337 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
338
339 workspace::register_project_item::<Editor>(cx);
340 workspace::FollowableViewRegistry::register::<Editor>(cx);
341 workspace::register_serializable_item::<Editor>(cx);
342
343 cx.observe_new(
344 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
345 workspace.register_action(Editor::new_file);
346 workspace.register_action(Editor::new_file_split);
347 workspace.register_action(Editor::new_file_vertical);
348 workspace.register_action(Editor::new_file_horizontal);
349 workspace.register_action(Editor::cancel_language_server_work);
350 workspace.register_action(Editor::toggle_focus);
351 },
352 )
353 .detach();
354
355 cx.on_action(move |_: &workspace::NewFile, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 Editor::new_file(workspace, &Default::default(), window, cx)
364 },
365 )
366 .detach_and_log_err(cx);
367 }
368 })
369 .on_action(move |_: &workspace::NewWindow, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 cx.activate(true);
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach_and_log_err(cx);
382 }
383 });
384 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
385 Arc::new(ErasedEditorImpl(
386 cx.new(|cx| Editor::single_line(window, cx)),
387 )) as Arc<dyn ErasedEditor>
388 });
389 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
390}
391
392pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
393 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
394}
395
396pub trait DiagnosticRenderer {
397 fn render_group(
398 &self,
399 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
400 buffer_id: BufferId,
401 snapshot: EditorSnapshot,
402 editor: WeakEntity<Editor>,
403 language_registry: Option<Arc<LanguageRegistry>>,
404 cx: &mut App,
405 ) -> Vec<BlockProperties<Anchor>>;
406
407 fn render_hover(
408 &self,
409 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
410 range: Range<Point>,
411 buffer_id: BufferId,
412 language_registry: Option<Arc<LanguageRegistry>>,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
484pub enum SizingBehavior {
485 /// The editor will layout itself using `size_full` and will include the vertical
486 /// scroll margin as requested by user settings.
487 #[default]
488 Default,
489 /// The editor will layout itself using `size_full`, but will not have any
490 /// vertical overscroll.
491 ExcludeOverscrollMargin,
492 /// The editor will request a vertical size according to its content and will be
493 /// layouted without a vertical scroll margin.
494 SizeByContent,
495}
496
497#[derive(Clone, PartialEq, Eq, Debug)]
498pub enum EditorMode {
499 SingleLine,
500 AutoHeight {
501 min_lines: usize,
502 max_lines: Option<usize>,
503 },
504 Full {
505 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
506 scale_ui_elements_with_buffer_font_size: bool,
507 /// When set to `true`, the editor will render a background for the active line.
508 show_active_line_background: bool,
509 /// Determines the sizing behavior for this editor
510 sizing_behavior: SizingBehavior,
511 },
512 Minimap {
513 parent: WeakEntity<Editor>,
514 },
515}
516
517impl EditorMode {
518 pub fn full() -> Self {
519 Self::Full {
520 scale_ui_elements_with_buffer_font_size: true,
521 show_active_line_background: true,
522 sizing_behavior: SizingBehavior::Default,
523 }
524 }
525
526 #[inline]
527 pub fn is_full(&self) -> bool {
528 matches!(self, Self::Full { .. })
529 }
530
531 #[inline]
532 pub fn is_single_line(&self) -> bool {
533 matches!(self, Self::SingleLine { .. })
534 }
535
536 #[inline]
537 fn is_minimap(&self) -> bool {
538 matches!(self, Self::Minimap { .. })
539 }
540}
541
542#[derive(Copy, Clone, Debug)]
543pub enum SoftWrap {
544 /// Prefer not to wrap at all.
545 ///
546 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
547 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
548 GitDiff,
549 /// Prefer a single line generally, unless an overly long line is encountered.
550 None,
551 /// Soft wrap lines that exceed the editor width.
552 EditorWidth,
553 /// Soft wrap lines at the preferred line length.
554 Column(u32),
555 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
556 Bounded(u32),
557}
558
559#[derive(Clone)]
560pub struct EditorStyle {
561 pub background: Hsla,
562 pub border: Hsla,
563 pub local_player: PlayerColor,
564 pub text: TextStyle,
565 pub scrollbar_width: Pixels,
566 pub syntax: Arc<SyntaxTheme>,
567 pub status: StatusColors,
568 pub inlay_hints_style: HighlightStyle,
569 pub edit_prediction_styles: EditPredictionStyles,
570 pub unnecessary_code_fade: f32,
571 pub show_underlines: bool,
572}
573
574impl Default for EditorStyle {
575 fn default() -> Self {
576 Self {
577 background: Hsla::default(),
578 border: Hsla::default(),
579 local_player: PlayerColor::default(),
580 text: TextStyle::default(),
581 scrollbar_width: Pixels::default(),
582 syntax: Default::default(),
583 // HACK: Status colors don't have a real default.
584 // We should look into removing the status colors from the editor
585 // style and retrieve them directly from the theme.
586 status: StatusColors::dark(),
587 inlay_hints_style: HighlightStyle::default(),
588 edit_prediction_styles: EditPredictionStyles {
589 insertion: HighlightStyle::default(),
590 whitespace: HighlightStyle::default(),
591 },
592 unnecessary_code_fade: Default::default(),
593 show_underlines: true,
594 }
595 }
596}
597
598pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
599 let show_background = language_settings::language_settings(None, None, cx)
600 .inlay_hints
601 .show_background;
602
603 let mut style = cx.theme().syntax().get("hint");
604
605 if style.color.is_none() {
606 style.color = Some(cx.theme().status().hint);
607 }
608
609 if !show_background {
610 style.background_color = None;
611 return style;
612 }
613
614 if style.background_color.is_none() {
615 style.background_color = Some(cx.theme().status().hint_background);
616 }
617
618 style
619}
620
621pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
622 EditPredictionStyles {
623 insertion: HighlightStyle {
624 color: Some(cx.theme().status().predictive),
625 ..HighlightStyle::default()
626 },
627 whitespace: HighlightStyle {
628 background_color: Some(cx.theme().status().created_background),
629 ..HighlightStyle::default()
630 },
631 }
632}
633
634type CompletionId = usize;
635
636pub(crate) enum EditDisplayMode {
637 TabAccept,
638 DiffPopover,
639 Inline,
640}
641
642enum EditPrediction {
643 Edit {
644 edits: Vec<(Range<Anchor>, Arc<str>)>,
645 /// Predicted cursor position as (anchor, offset_from_anchor).
646 /// The anchor is in multibuffer coordinates; after applying edits,
647 /// resolve the anchor and add the offset to get the final cursor position.
648 cursor_position: Option<(Anchor, usize)>,
649 edit_preview: Option<EditPreview>,
650 display_mode: EditDisplayMode,
651 snapshot: BufferSnapshot,
652 },
653 /// Move to a specific location in the active editor
654 MoveWithin {
655 target: Anchor,
656 snapshot: BufferSnapshot,
657 },
658 /// Move to a specific location in a different editor (not the active one)
659 MoveOutside {
660 target: language::Anchor,
661 snapshot: BufferSnapshot,
662 },
663}
664
665struct EditPredictionState {
666 inlay_ids: Vec<InlayId>,
667 completion: EditPrediction,
668 completion_id: Option<SharedString>,
669 invalidation_range: Option<Range<Anchor>>,
670}
671
672enum EditPredictionSettings {
673 Disabled,
674 Enabled {
675 show_in_menu: bool,
676 preview_requires_modifier: bool,
677 },
678}
679
680#[derive(Debug, Clone)]
681struct InlineDiagnostic {
682 message: SharedString,
683 group_id: usize,
684 is_primary: bool,
685 start: Point,
686 severity: lsp::DiagnosticSeverity,
687}
688
689pub enum MenuEditPredictionsPolicy {
690 Never,
691 ByProvider,
692}
693
694pub enum EditPredictionPreview {
695 /// Modifier is not pressed
696 Inactive { released_too_fast: bool },
697 /// Modifier pressed
698 Active {
699 since: Instant,
700 previous_scroll_position: Option<SharedScrollAnchor>,
701 },
702}
703
704impl EditPredictionPreview {
705 pub fn released_too_fast(&self) -> bool {
706 match self {
707 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
708 EditPredictionPreview::Active { .. } => false,
709 }
710 }
711
712 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
713 if let EditPredictionPreview::Active {
714 previous_scroll_position,
715 ..
716 } = self
717 {
718 *previous_scroll_position = scroll_position;
719 }
720 }
721}
722
723pub struct ContextMenuOptions {
724 pub min_entries_visible: usize,
725 pub max_entries_visible: usize,
726 pub placement: Option<ContextMenuPlacement>,
727}
728
729#[derive(Debug, Clone, PartialEq, Eq)]
730pub enum ContextMenuPlacement {
731 Above,
732 Below,
733}
734
735#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
736struct EditorActionId(usize);
737
738impl EditorActionId {
739 pub fn post_inc(&mut self) -> Self {
740 let answer = self.0;
741
742 *self = Self(answer + 1);
743
744 Self(answer)
745 }
746}
747
748// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
749// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
750
751type BackgroundHighlight = (
752 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
753 Arc<[Range<Anchor>]>,
754);
755type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
756
757#[derive(Default)]
758struct ScrollbarMarkerState {
759 scrollbar_size: Size<Pixels>,
760 dirty: bool,
761 markers: Arc<[PaintQuad]>,
762 pending_refresh: Option<Task<Result<()>>>,
763}
764
765impl ScrollbarMarkerState {
766 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
767 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
768 }
769}
770
771#[derive(Clone, Copy, PartialEq, Eq)]
772pub enum MinimapVisibility {
773 Disabled,
774 Enabled {
775 /// The configuration currently present in the users settings.
776 setting_configuration: bool,
777 /// Whether to override the currently set visibility from the users setting.
778 toggle_override: bool,
779 },
780}
781
782impl MinimapVisibility {
783 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
784 if mode.is_full() {
785 Self::Enabled {
786 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
787 toggle_override: false,
788 }
789 } else {
790 Self::Disabled
791 }
792 }
793
794 fn hidden(&self) -> Self {
795 match *self {
796 Self::Enabled {
797 setting_configuration,
798 ..
799 } => Self::Enabled {
800 setting_configuration,
801 toggle_override: setting_configuration,
802 },
803 Self::Disabled => Self::Disabled,
804 }
805 }
806
807 fn disabled(&self) -> bool {
808 matches!(*self, Self::Disabled)
809 }
810
811 fn settings_visibility(&self) -> bool {
812 match *self {
813 Self::Enabled {
814 setting_configuration,
815 ..
816 } => setting_configuration,
817 _ => false,
818 }
819 }
820
821 fn visible(&self) -> bool {
822 match *self {
823 Self::Enabled {
824 setting_configuration,
825 toggle_override,
826 } => setting_configuration ^ toggle_override,
827 _ => false,
828 }
829 }
830
831 fn toggle_visibility(&self) -> Self {
832 match *self {
833 Self::Enabled {
834 toggle_override,
835 setting_configuration,
836 } => Self::Enabled {
837 setting_configuration,
838 toggle_override: !toggle_override,
839 },
840 Self::Disabled => Self::Disabled,
841 }
842 }
843}
844
845#[derive(Debug, Clone, Copy, PartialEq, Eq)]
846pub enum BufferSerialization {
847 All,
848 NonDirtyBuffers,
849}
850
851impl BufferSerialization {
852 fn new(restore_unsaved_buffers: bool) -> Self {
853 if restore_unsaved_buffers {
854 Self::All
855 } else {
856 Self::NonDirtyBuffers
857 }
858 }
859}
860
861/// Addons allow storing per-editor state in other crates (e.g. Vim)
862pub trait Addon: 'static {
863 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
864
865 fn render_buffer_header_controls(
866 &self,
867 _: &ExcerptInfo,
868 _: &Window,
869 _: &App,
870 ) -> Option<AnyElement> {
871 None
872 }
873
874 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
875 None
876 }
877
878 fn to_any(&self) -> &dyn std::any::Any;
879
880 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
881 None
882 }
883}
884
885struct ChangeLocation {
886 current: Option<Vec<Anchor>>,
887 original: Vec<Anchor>,
888}
889impl ChangeLocation {
890 fn locations(&self) -> &[Anchor] {
891 self.current.as_ref().unwrap_or(&self.original)
892 }
893}
894
895/// A set of caret positions, registered when the editor was edited.
896pub struct ChangeList {
897 changes: Vec<ChangeLocation>,
898 /// Currently "selected" change.
899 position: Option<usize>,
900}
901
902impl ChangeList {
903 pub fn new() -> Self {
904 Self {
905 changes: Vec::new(),
906 position: None,
907 }
908 }
909
910 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
911 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
912 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
913 if self.changes.is_empty() {
914 return None;
915 }
916
917 let prev = self.position.unwrap_or(self.changes.len());
918 let next = if direction == Direction::Prev {
919 prev.saturating_sub(count)
920 } else {
921 (prev + count).min(self.changes.len() - 1)
922 };
923 self.position = Some(next);
924 self.changes.get(next).map(|change| change.locations())
925 }
926
927 /// Adds a new change to the list, resetting the change list position.
928 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
929 self.position.take();
930 if let Some(last) = self.changes.last_mut()
931 && group
932 {
933 last.current = Some(new_positions)
934 } else {
935 self.changes.push(ChangeLocation {
936 original: new_positions,
937 current: None,
938 });
939 }
940 }
941
942 pub fn last(&self) -> Option<&[Anchor]> {
943 self.changes.last().map(|change| change.locations())
944 }
945
946 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
947 self.changes.last().map(|change| change.original.as_slice())
948 }
949
950 pub fn invert_last_group(&mut self) {
951 if let Some(last) = self.changes.last_mut()
952 && let Some(current) = last.current.as_mut()
953 {
954 mem::swap(&mut last.original, current);
955 }
956 }
957}
958
959#[derive(Clone)]
960struct InlineBlamePopoverState {
961 scroll_handle: ScrollHandle,
962 commit_message: Option<ParsedCommitMessage>,
963 markdown: Entity<Markdown>,
964}
965
966struct InlineBlamePopover {
967 position: gpui::Point<Pixels>,
968 hide_task: Option<Task<()>>,
969 popover_bounds: Option<Bounds<Pixels>>,
970 popover_state: InlineBlamePopoverState,
971 keyboard_grace: bool,
972}
973
974enum SelectionDragState {
975 /// State when no drag related activity is detected.
976 None,
977 /// State when the mouse is down on a selection that is about to be dragged.
978 ReadyToDrag {
979 selection: Selection<Anchor>,
980 click_position: gpui::Point<Pixels>,
981 mouse_down_time: Instant,
982 },
983 /// State when the mouse is dragging the selection in the editor.
984 Dragging {
985 selection: Selection<Anchor>,
986 drop_cursor: Selection<Anchor>,
987 hide_drop_cursor: bool,
988 },
989}
990
991enum ColumnarSelectionState {
992 FromMouse {
993 selection_tail: Anchor,
994 display_point: Option<DisplayPoint>,
995 },
996 FromSelection {
997 selection_tail: Anchor,
998 },
999}
1000
1001/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1002/// a breakpoint on them.
1003#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1004struct PhantomBreakpointIndicator {
1005 display_row: DisplayRow,
1006 /// There's a small debounce between hovering over the line and showing the indicator.
1007 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1008 is_active: bool,
1009 collides_with_existing_breakpoint: bool,
1010}
1011
1012/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1013/// in diff view mode.
1014#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1015pub(crate) struct PhantomDiffReviewIndicator {
1016 /// The starting anchor of the selection (or the only row if not dragging).
1017 pub start: Anchor,
1018 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1019 pub end: Anchor,
1020 /// There's a small debounce between hovering over the line and showing the indicator.
1021 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1022 pub is_active: bool,
1023}
1024
1025#[derive(Clone, Debug)]
1026pub(crate) struct DiffReviewDragState {
1027 pub start_anchor: Anchor,
1028 pub current_anchor: Anchor,
1029}
1030
1031impl DiffReviewDragState {
1032 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1033 let start = self.start_anchor.to_display_point(snapshot).row();
1034 let current = self.current_anchor.to_display_point(snapshot).row();
1035
1036 (start..=current).sorted()
1037 }
1038}
1039
1040/// Identifies a specific hunk in the diff buffer.
1041/// Used as a key to group comments by their location.
1042#[derive(Clone, Debug)]
1043pub struct DiffHunkKey {
1044 /// The file path (relative to worktree) this hunk belongs to.
1045 pub file_path: Arc<util::rel_path::RelPath>,
1046 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1047 pub hunk_start_anchor: Anchor,
1048}
1049
1050/// A review comment stored locally before being sent to the Agent panel.
1051#[derive(Clone)]
1052pub struct StoredReviewComment {
1053 /// Unique identifier for this comment (for edit/delete operations).
1054 pub id: usize,
1055 /// The comment text entered by the user.
1056 pub comment: String,
1057 /// Anchors for the code range being reviewed.
1058 pub range: Range<Anchor>,
1059 /// Timestamp when the comment was created (for chronological ordering).
1060 pub created_at: Instant,
1061 /// Whether this comment is currently being edited inline.
1062 pub is_editing: bool,
1063}
1064
1065impl StoredReviewComment {
1066 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1067 Self {
1068 id,
1069 comment,
1070 range: anchor_range,
1071 created_at: Instant::now(),
1072 is_editing: false,
1073 }
1074 }
1075}
1076
1077/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1078pub(crate) struct DiffReviewOverlay {
1079 pub anchor_range: Range<Anchor>,
1080 /// The block ID for the overlay.
1081 pub block_id: CustomBlockId,
1082 /// The editor entity for the review input.
1083 pub prompt_editor: Entity<Editor>,
1084 /// The hunk key this overlay belongs to.
1085 pub hunk_key: DiffHunkKey,
1086 /// Whether the comments section is expanded.
1087 pub comments_expanded: bool,
1088 /// Editors for comments currently being edited inline.
1089 /// Key: comment ID, Value: Editor entity for inline editing.
1090 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1091 /// Subscriptions for inline edit editors' action handlers.
1092 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1093 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1094 /// The current user's avatar URI for display in comment rows.
1095 pub user_avatar_uri: Option<SharedUri>,
1096 /// Subscription to keep the action handler alive.
1097 _subscription: Subscription,
1098}
1099
1100/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1101///
1102/// See the [module level documentation](self) for more information.
1103pub struct Editor {
1104 focus_handle: FocusHandle,
1105 last_focused_descendant: Option<WeakFocusHandle>,
1106 /// The text buffer being edited
1107 buffer: Entity<MultiBuffer>,
1108 /// Map of how text in the buffer should be displayed.
1109 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1110 pub display_map: Entity<DisplayMap>,
1111 placeholder_display_map: Option<Entity<DisplayMap>>,
1112 pub selections: SelectionsCollection,
1113 pub scroll_manager: ScrollManager,
1114 /// When inline assist editors are linked, they all render cursors because
1115 /// typing enters text into each of them, even the ones that aren't focused.
1116 pub(crate) show_cursor_when_unfocused: bool,
1117 columnar_selection_state: Option<ColumnarSelectionState>,
1118 add_selections_state: Option<AddSelectionsState>,
1119 select_next_state: Option<SelectNextState>,
1120 select_prev_state: Option<SelectNextState>,
1121 selection_history: SelectionHistory,
1122 defer_selection_effects: bool,
1123 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1124 autoclose_regions: Vec<AutocloseRegion>,
1125 snippet_stack: InvalidationStack<SnippetState>,
1126 select_syntax_node_history: SelectSyntaxNodeHistory,
1127 ime_transaction: Option<TransactionId>,
1128 pub diagnostics_max_severity: DiagnosticSeverity,
1129 active_diagnostics: ActiveDiagnostic,
1130 show_inline_diagnostics: bool,
1131 inline_diagnostics_update: Task<()>,
1132 inline_diagnostics_enabled: bool,
1133 diagnostics_enabled: bool,
1134 word_completions_enabled: bool,
1135 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1136 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1137 hard_wrap: Option<usize>,
1138 project: Option<Entity<Project>>,
1139 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1140 completion_provider: Option<Rc<dyn CompletionProvider>>,
1141 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1142 blink_manager: Entity<BlinkManager>,
1143 show_cursor_names: bool,
1144 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1145 pub show_local_selections: bool,
1146 mode: EditorMode,
1147 show_breadcrumbs: bool,
1148 show_gutter: bool,
1149 show_scrollbars: ScrollbarAxes,
1150 minimap_visibility: MinimapVisibility,
1151 offset_content: bool,
1152 disable_expand_excerpt_buttons: bool,
1153 delegate_expand_excerpts: bool,
1154 delegate_stage_and_restore: bool,
1155 delegate_open_excerpts: bool,
1156 enable_lsp_data: bool,
1157 enable_runnables: bool,
1158 show_line_numbers: Option<bool>,
1159 use_relative_line_numbers: Option<bool>,
1160 show_git_diff_gutter: Option<bool>,
1161 show_code_actions: Option<bool>,
1162 show_runnables: Option<bool>,
1163 show_breakpoints: Option<bool>,
1164 show_diff_review_button: bool,
1165 show_wrap_guides: Option<bool>,
1166 show_indent_guides: Option<bool>,
1167 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1168 highlight_order: usize,
1169 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1170 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1171 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1172 scrollbar_marker_state: ScrollbarMarkerState,
1173 active_indent_guides_state: ActiveIndentGuidesState,
1174 nav_history: Option<ItemNavHistory>,
1175 context_menu: RefCell<Option<CodeContextMenu>>,
1176 context_menu_options: Option<ContextMenuOptions>,
1177 mouse_context_menu: Option<MouseContextMenu>,
1178 completion_tasks: Vec<(CompletionId, Task<()>)>,
1179 inline_blame_popover: Option<InlineBlamePopover>,
1180 inline_blame_popover_show_task: Option<Task<()>>,
1181 signature_help_state: SignatureHelpState,
1182 auto_signature_help: Option<bool>,
1183 find_all_references_task_sources: Vec<Anchor>,
1184 next_completion_id: CompletionId,
1185 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1186 code_actions_task: Option<Task<Result<()>>>,
1187 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1188 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1189 debounced_selection_highlight_complete: bool,
1190 document_highlights_task: Option<Task<()>>,
1191 linked_editing_range_task: Option<Task<Option<()>>>,
1192 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1193 pending_rename: Option<RenameState>,
1194 searchable: bool,
1195 cursor_shape: CursorShape,
1196 /// Whether the cursor is offset one character to the left when something is
1197 /// selected (needed for vim visual mode)
1198 cursor_offset_on_selection: bool,
1199 current_line_highlight: Option<CurrentLineHighlight>,
1200 /// Whether to collapse search match ranges to just their start position.
1201 /// When true, navigating to a match positions the cursor at the match
1202 /// without selecting the matched text.
1203 collapse_matches: bool,
1204 autoindent_mode: Option<AutoindentMode>,
1205 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1206 input_enabled: bool,
1207 expects_character_input: bool,
1208 use_modal_editing: bool,
1209 read_only: bool,
1210 leader_id: Option<CollaboratorId>,
1211 remote_id: Option<ViewId>,
1212 pub hover_state: HoverState,
1213 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1214 prev_pressure_stage: Option<PressureStage>,
1215 gutter_hovered: bool,
1216 hovered_link_state: Option<HoveredLinkState>,
1217 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1218 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1219 active_edit_prediction: Option<EditPredictionState>,
1220 /// Used to prevent flickering as the user types while the menu is open
1221 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1222 edit_prediction_settings: EditPredictionSettings,
1223 edit_predictions_hidden_for_vim_mode: bool,
1224 show_edit_predictions_override: Option<bool>,
1225 show_completions_on_input_override: Option<bool>,
1226 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1227 edit_prediction_preview: EditPredictionPreview,
1228 edit_prediction_indent_conflict: bool,
1229 edit_prediction_requires_modifier_in_indent_conflict: bool,
1230 next_inlay_id: usize,
1231 next_color_inlay_id: usize,
1232 _subscriptions: Vec<Subscription>,
1233 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1234 gutter_dimensions: GutterDimensions,
1235 style: Option<EditorStyle>,
1236 text_style_refinement: Option<TextStyleRefinement>,
1237 next_editor_action_id: EditorActionId,
1238 editor_actions: Rc<
1239 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1240 >,
1241 use_autoclose: bool,
1242 use_auto_surround: bool,
1243 auto_replace_emoji_shortcode: bool,
1244 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1245 show_git_blame_gutter: bool,
1246 show_git_blame_inline: bool,
1247 show_git_blame_inline_delay_task: Option<Task<()>>,
1248 git_blame_inline_enabled: bool,
1249 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1250 buffer_serialization: Option<BufferSerialization>,
1251 show_selection_menu: Option<bool>,
1252 blame: Option<Entity<GitBlame>>,
1253 blame_subscription: Option<Subscription>,
1254 custom_context_menu: Option<
1255 Box<
1256 dyn 'static
1257 + Fn(
1258 &mut Self,
1259 DisplayPoint,
1260 &mut Window,
1261 &mut Context<Self>,
1262 ) -> Option<Entity<ui::ContextMenu>>,
1263 >,
1264 >,
1265 last_bounds: Option<Bounds<Pixels>>,
1266 last_position_map: Option<Rc<PositionMap>>,
1267 expect_bounds_change: Option<Bounds<Pixels>>,
1268 runnables: RunnableData,
1269 breakpoint_store: Option<Entity<BreakpointStore>>,
1270 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1271 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1272 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1273 /// Active diff review overlays. Multiple overlays can be open simultaneously
1274 /// when hunks have comments stored.
1275 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1276 /// Stored review comments grouped by hunk.
1277 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1278 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1279 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1280 /// Counter for generating unique comment IDs.
1281 next_review_comment_id: usize,
1282 hovered_diff_hunk_row: Option<DisplayRow>,
1283 pull_diagnostics_task: Task<()>,
1284 in_project_search: bool,
1285 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1286 breadcrumb_header: Option<String>,
1287 focused_block: Option<FocusedBlock>,
1288 next_scroll_position: NextScrollCursorCenterTopBottom,
1289 addons: HashMap<TypeId, Box<dyn Addon>>,
1290 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1291 load_diff_task: Option<Shared<Task<()>>>,
1292 /// Whether we are temporarily displaying a diff other than git's
1293 temporary_diff_override: bool,
1294 selection_mark_mode: bool,
1295 toggle_fold_multiple_buffers: Task<()>,
1296 _scroll_cursor_center_top_bottom_task: Task<()>,
1297 serialize_selections: Task<()>,
1298 serialize_folds: Task<()>,
1299 mouse_cursor_hidden: bool,
1300 minimap: Option<Entity<Self>>,
1301 hide_mouse_mode: HideMouseMode,
1302 pub change_list: ChangeList,
1303 inline_value_cache: InlineValueCache,
1304 number_deleted_lines: bool,
1305
1306 selection_drag_state: SelectionDragState,
1307 colors: Option<LspColorData>,
1308 post_scroll_update: Task<()>,
1309 refresh_colors_task: Task<()>,
1310 use_document_folding_ranges: bool,
1311 refresh_folding_ranges_task: Task<()>,
1312 inlay_hints: Option<LspInlayHintData>,
1313 folding_newlines: Task<()>,
1314 select_next_is_case_sensitive: Option<bool>,
1315 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1316 on_local_selections_changed:
1317 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1318 suppress_selection_callback: bool,
1319 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1320 accent_data: Option<AccentData>,
1321 bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1322 semantic_token_state: SemanticTokenState,
1323 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1324 refresh_document_symbols_task: Shared<Task<()>>,
1325 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1326 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1327 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1328 sticky_headers_task: Task<()>,
1329 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1330 pub(crate) colorize_brackets_task: Task<()>,
1331}
1332
1333#[derive(Debug, PartialEq)]
1334struct AccentData {
1335 colors: AccentColors,
1336 overrides: Vec<SharedString>,
1337}
1338
1339fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1340 if debounce_ms > 0 {
1341 Some(Duration::from_millis(debounce_ms))
1342 } else {
1343 None
1344 }
1345}
1346
1347#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1348enum NextScrollCursorCenterTopBottom {
1349 #[default]
1350 Center,
1351 Top,
1352 Bottom,
1353}
1354
1355impl NextScrollCursorCenterTopBottom {
1356 fn next(&self) -> Self {
1357 match self {
1358 Self::Center => Self::Top,
1359 Self::Top => Self::Bottom,
1360 Self::Bottom => Self::Center,
1361 }
1362 }
1363}
1364
1365#[derive(Clone)]
1366pub struct EditorSnapshot {
1367 pub mode: EditorMode,
1368 show_gutter: bool,
1369 offset_content: bool,
1370 show_line_numbers: Option<bool>,
1371 number_deleted_lines: bool,
1372 show_git_diff_gutter: Option<bool>,
1373 show_code_actions: Option<bool>,
1374 show_runnables: Option<bool>,
1375 show_breakpoints: Option<bool>,
1376 git_blame_gutter_max_author_length: Option<usize>,
1377 pub display_snapshot: DisplaySnapshot,
1378 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1379 is_focused: bool,
1380 scroll_anchor: SharedScrollAnchor,
1381 ongoing_scroll: OngoingScroll,
1382 current_line_highlight: CurrentLineHighlight,
1383 gutter_hovered: bool,
1384 semantic_tokens_enabled: bool,
1385}
1386
1387#[derive(Default, Debug, Clone, Copy)]
1388pub struct GutterDimensions {
1389 pub left_padding: Pixels,
1390 pub right_padding: Pixels,
1391 pub width: Pixels,
1392 pub margin: Pixels,
1393 pub git_blame_entries_width: Option<Pixels>,
1394}
1395
1396impl GutterDimensions {
1397 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1398 Self {
1399 margin: Self::default_gutter_margin(font_id, font_size, cx),
1400 ..Default::default()
1401 }
1402 }
1403
1404 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1405 -cx.text_system().descent(font_id, font_size)
1406 }
1407 /// The full width of the space taken up by the gutter.
1408 pub fn full_width(&self) -> Pixels {
1409 self.margin + self.width
1410 }
1411
1412 /// The width of the space reserved for the fold indicators,
1413 /// use alongside 'justify_end' and `gutter_width` to
1414 /// right align content with the line numbers
1415 pub fn fold_area_width(&self) -> Pixels {
1416 self.margin + self.right_padding
1417 }
1418}
1419
1420struct CharacterDimensions {
1421 em_width: Pixels,
1422 em_advance: Pixels,
1423 line_height: Pixels,
1424}
1425
1426#[derive(Debug)]
1427pub struct RemoteSelection {
1428 pub replica_id: ReplicaId,
1429 pub selection: Selection<Anchor>,
1430 pub cursor_shape: CursorShape,
1431 pub collaborator_id: CollaboratorId,
1432 pub line_mode: bool,
1433 pub user_name: Option<SharedString>,
1434 pub color: PlayerColor,
1435}
1436
1437#[derive(Clone, Debug)]
1438struct SelectionHistoryEntry {
1439 selections: Arc<[Selection<Anchor>]>,
1440 select_next_state: Option<SelectNextState>,
1441 select_prev_state: Option<SelectNextState>,
1442 add_selections_state: Option<AddSelectionsState>,
1443}
1444
1445#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1446enum SelectionHistoryMode {
1447 #[default]
1448 Normal,
1449 Undoing,
1450 Redoing,
1451 Skipping,
1452}
1453
1454#[derive(Clone, PartialEq, Eq, Hash)]
1455struct HoveredCursor {
1456 replica_id: ReplicaId,
1457 selection_id: usize,
1458}
1459
1460#[derive(Debug)]
1461/// SelectionEffects controls the side-effects of updating the selection.
1462///
1463/// The default behaviour does "what you mostly want":
1464/// - it pushes to the nav history if the cursor moved by >10 lines
1465/// - it re-triggers completion requests
1466/// - it scrolls to fit
1467///
1468/// You might want to modify these behaviours. For example when doing a "jump"
1469/// like go to definition, we always want to add to nav history; but when scrolling
1470/// in vim mode we never do.
1471///
1472/// Similarly, you might want to disable scrolling if you don't want the viewport to
1473/// move.
1474#[derive(Clone)]
1475pub struct SelectionEffects {
1476 nav_history: Option<bool>,
1477 completions: bool,
1478 scroll: Option<Autoscroll>,
1479}
1480
1481impl Default for SelectionEffects {
1482 fn default() -> Self {
1483 Self {
1484 nav_history: None,
1485 completions: true,
1486 scroll: Some(Autoscroll::fit()),
1487 }
1488 }
1489}
1490impl SelectionEffects {
1491 pub fn scroll(scroll: Autoscroll) -> Self {
1492 Self {
1493 scroll: Some(scroll),
1494 ..Default::default()
1495 }
1496 }
1497
1498 pub fn no_scroll() -> Self {
1499 Self {
1500 scroll: None,
1501 ..Default::default()
1502 }
1503 }
1504
1505 pub fn completions(self, completions: bool) -> Self {
1506 Self {
1507 completions,
1508 ..self
1509 }
1510 }
1511
1512 pub fn nav_history(self, nav_history: bool) -> Self {
1513 Self {
1514 nav_history: Some(nav_history),
1515 ..self
1516 }
1517 }
1518}
1519
1520struct DeferredSelectionEffectsState {
1521 changed: bool,
1522 effects: SelectionEffects,
1523 old_cursor_position: Anchor,
1524 history_entry: SelectionHistoryEntry,
1525}
1526
1527#[derive(Default)]
1528struct SelectionHistory {
1529 #[allow(clippy::type_complexity)]
1530 selections_by_transaction:
1531 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1532 mode: SelectionHistoryMode,
1533 undo_stack: VecDeque<SelectionHistoryEntry>,
1534 redo_stack: VecDeque<SelectionHistoryEntry>,
1535}
1536
1537impl SelectionHistory {
1538 #[track_caller]
1539 fn insert_transaction(
1540 &mut self,
1541 transaction_id: TransactionId,
1542 selections: Arc<[Selection<Anchor>]>,
1543 ) {
1544 if selections.is_empty() {
1545 log::error!(
1546 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1547 std::panic::Location::caller()
1548 );
1549 return;
1550 }
1551 self.selections_by_transaction
1552 .insert(transaction_id, (selections, None));
1553 }
1554
1555 #[allow(clippy::type_complexity)]
1556 fn transaction(
1557 &self,
1558 transaction_id: TransactionId,
1559 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1560 self.selections_by_transaction.get(&transaction_id)
1561 }
1562
1563 #[allow(clippy::type_complexity)]
1564 fn transaction_mut(
1565 &mut self,
1566 transaction_id: TransactionId,
1567 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1568 self.selections_by_transaction.get_mut(&transaction_id)
1569 }
1570
1571 fn push(&mut self, entry: SelectionHistoryEntry) {
1572 if !entry.selections.is_empty() {
1573 match self.mode {
1574 SelectionHistoryMode::Normal => {
1575 self.push_undo(entry);
1576 self.redo_stack.clear();
1577 }
1578 SelectionHistoryMode::Undoing => self.push_redo(entry),
1579 SelectionHistoryMode::Redoing => self.push_undo(entry),
1580 SelectionHistoryMode::Skipping => {}
1581 }
1582 }
1583 }
1584
1585 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1586 if self
1587 .undo_stack
1588 .back()
1589 .is_none_or(|e| e.selections != entry.selections)
1590 {
1591 self.undo_stack.push_back(entry);
1592 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1593 self.undo_stack.pop_front();
1594 }
1595 }
1596 }
1597
1598 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1599 if self
1600 .redo_stack
1601 .back()
1602 .is_none_or(|e| e.selections != entry.selections)
1603 {
1604 self.redo_stack.push_back(entry);
1605 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1606 self.redo_stack.pop_front();
1607 }
1608 }
1609 }
1610}
1611
1612#[derive(Clone, Copy)]
1613pub struct RowHighlightOptions {
1614 pub autoscroll: bool,
1615 pub include_gutter: bool,
1616}
1617
1618impl Default for RowHighlightOptions {
1619 fn default() -> Self {
1620 Self {
1621 autoscroll: Default::default(),
1622 include_gutter: true,
1623 }
1624 }
1625}
1626
1627struct RowHighlight {
1628 index: usize,
1629 range: Range<Anchor>,
1630 color: Hsla,
1631 options: RowHighlightOptions,
1632 type_id: TypeId,
1633}
1634
1635#[derive(Clone, Debug)]
1636struct AddSelectionsState {
1637 groups: Vec<AddSelectionsGroup>,
1638}
1639
1640#[derive(Clone, Debug)]
1641struct AddSelectionsGroup {
1642 above: bool,
1643 stack: Vec<usize>,
1644}
1645
1646#[derive(Clone)]
1647struct SelectNextState {
1648 query: AhoCorasick,
1649 wordwise: bool,
1650 done: bool,
1651}
1652
1653impl std::fmt::Debug for SelectNextState {
1654 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1655 f.debug_struct(std::any::type_name::<Self>())
1656 .field("wordwise", &self.wordwise)
1657 .field("done", &self.done)
1658 .finish()
1659 }
1660}
1661
1662#[derive(Debug)]
1663struct AutocloseRegion {
1664 selection_id: usize,
1665 range: Range<Anchor>,
1666 pair: BracketPair,
1667}
1668
1669#[derive(Debug)]
1670struct SnippetState {
1671 ranges: Vec<Vec<Range<Anchor>>>,
1672 active_index: usize,
1673 choices: Vec<Option<Vec<String>>>,
1674}
1675
1676#[doc(hidden)]
1677pub struct RenameState {
1678 pub range: Range<Anchor>,
1679 pub old_name: Arc<str>,
1680 pub editor: Entity<Editor>,
1681 block_id: CustomBlockId,
1682}
1683
1684struct InvalidationStack<T>(Vec<T>);
1685
1686struct RegisteredEditPredictionDelegate {
1687 provider: Arc<dyn EditPredictionDelegateHandle>,
1688 _subscription: Subscription,
1689}
1690
1691#[derive(Debug, PartialEq, Eq)]
1692pub struct ActiveDiagnosticGroup {
1693 pub active_range: Range<Anchor>,
1694 pub active_message: String,
1695 pub group_id: usize,
1696 pub blocks: HashSet<CustomBlockId>,
1697}
1698
1699#[derive(Debug, PartialEq, Eq)]
1700
1701pub(crate) enum ActiveDiagnostic {
1702 None,
1703 All,
1704 Group(ActiveDiagnosticGroup),
1705}
1706
1707#[derive(Serialize, Deserialize, Clone, Debug)]
1708pub struct ClipboardSelection {
1709 /// The number of bytes in this selection.
1710 pub len: usize,
1711 /// Whether this was a full-line selection.
1712 pub is_entire_line: bool,
1713 /// The indentation of the first line when this content was originally copied.
1714 pub first_line_indent: u32,
1715 #[serde(default)]
1716 pub file_path: Option<PathBuf>,
1717 #[serde(default)]
1718 pub line_range: Option<RangeInclusive<u32>>,
1719}
1720
1721impl ClipboardSelection {
1722 pub fn for_buffer(
1723 len: usize,
1724 is_entire_line: bool,
1725 range: Range<Point>,
1726 buffer: &MultiBufferSnapshot,
1727 project: Option<&Entity<Project>>,
1728 cx: &App,
1729 ) -> Self {
1730 let first_line_indent = buffer
1731 .indent_size_for_line(MultiBufferRow(range.start.row))
1732 .len;
1733
1734 let file_path = util::maybe!({
1735 let project = project?.read(cx);
1736 let file = buffer.file_at(range.start)?;
1737 let project_path = ProjectPath {
1738 worktree_id: file.worktree_id(cx),
1739 path: file.path().clone(),
1740 };
1741 project.absolute_path(&project_path, cx)
1742 });
1743
1744 let line_range = file_path.as_ref().and_then(|_| {
1745 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1746 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1747 if start_excerpt_id == end_excerpt_id {
1748 Some(start_point.row..=end_point.row)
1749 } else {
1750 None
1751 }
1752 });
1753
1754 Self {
1755 len,
1756 is_entire_line,
1757 first_line_indent,
1758 file_path,
1759 line_range,
1760 }
1761 }
1762}
1763
1764// selections, scroll behavior, was newest selection reversed
1765type SelectSyntaxNodeHistoryState = (
1766 Box<[Selection<Anchor>]>,
1767 SelectSyntaxNodeScrollBehavior,
1768 bool,
1769);
1770
1771#[derive(Default)]
1772struct SelectSyntaxNodeHistory {
1773 stack: Vec<SelectSyntaxNodeHistoryState>,
1774 // disable temporarily to allow changing selections without losing the stack
1775 pub disable_clearing: bool,
1776}
1777
1778impl SelectSyntaxNodeHistory {
1779 pub fn try_clear(&mut self) {
1780 if !self.disable_clearing {
1781 self.stack.clear();
1782 }
1783 }
1784
1785 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1786 self.stack.push(selection);
1787 }
1788
1789 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1790 self.stack.pop()
1791 }
1792}
1793
1794enum SelectSyntaxNodeScrollBehavior {
1795 CursorTop,
1796 FitSelection,
1797 CursorBottom,
1798}
1799
1800#[derive(Debug, Clone, Copy)]
1801pub(crate) struct NavigationData {
1802 cursor_anchor: Anchor,
1803 cursor_position: Point,
1804 scroll_anchor: ScrollAnchor,
1805 scroll_top_row: u32,
1806}
1807
1808#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1809pub enum GotoDefinitionKind {
1810 Symbol,
1811 Declaration,
1812 Type,
1813 Implementation,
1814}
1815
1816pub enum FormatTarget {
1817 Buffers(HashSet<Entity<Buffer>>),
1818 Ranges(Vec<Range<MultiBufferPoint>>),
1819}
1820
1821pub(crate) struct FocusedBlock {
1822 id: BlockId,
1823 focus_handle: WeakFocusHandle,
1824}
1825
1826#[derive(Clone, Debug)]
1827pub enum JumpData {
1828 MultiBufferRow {
1829 row: MultiBufferRow,
1830 line_offset_from_top: u32,
1831 },
1832 MultiBufferPoint {
1833 excerpt_id: ExcerptId,
1834 position: Point,
1835 anchor: text::Anchor,
1836 line_offset_from_top: u32,
1837 },
1838}
1839
1840pub enum MultibufferSelectionMode {
1841 First,
1842 All,
1843}
1844
1845#[derive(Clone, Copy, Debug, Default)]
1846pub struct RewrapOptions {
1847 pub override_language_settings: bool,
1848 pub preserve_existing_whitespace: bool,
1849}
1850
1851impl Editor {
1852 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1853 let buffer = cx.new(|cx| Buffer::local("", cx));
1854 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1855 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1856 }
1857
1858 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1859 let buffer = cx.new(|cx| Buffer::local("", cx));
1860 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1861 Self::new(EditorMode::full(), buffer, None, window, cx)
1862 }
1863
1864 pub fn auto_height(
1865 min_lines: usize,
1866 max_lines: usize,
1867 window: &mut Window,
1868 cx: &mut Context<Self>,
1869 ) -> Self {
1870 let buffer = cx.new(|cx| Buffer::local("", cx));
1871 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1872 Self::new(
1873 EditorMode::AutoHeight {
1874 min_lines,
1875 max_lines: Some(max_lines),
1876 },
1877 buffer,
1878 None,
1879 window,
1880 cx,
1881 )
1882 }
1883
1884 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1885 /// The editor grows as tall as needed to fit its content.
1886 pub fn auto_height_unbounded(
1887 min_lines: usize,
1888 window: &mut Window,
1889 cx: &mut Context<Self>,
1890 ) -> Self {
1891 let buffer = cx.new(|cx| Buffer::local("", cx));
1892 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1893 Self::new(
1894 EditorMode::AutoHeight {
1895 min_lines,
1896 max_lines: None,
1897 },
1898 buffer,
1899 None,
1900 window,
1901 cx,
1902 )
1903 }
1904
1905 pub fn for_buffer(
1906 buffer: Entity<Buffer>,
1907 project: Option<Entity<Project>>,
1908 window: &mut Window,
1909 cx: &mut Context<Self>,
1910 ) -> Self {
1911 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1912 Self::new(EditorMode::full(), buffer, project, window, cx)
1913 }
1914
1915 pub fn for_multibuffer(
1916 buffer: Entity<MultiBuffer>,
1917 project: Option<Entity<Project>>,
1918 window: &mut Window,
1919 cx: &mut Context<Self>,
1920 ) -> Self {
1921 Self::new(EditorMode::full(), buffer, project, window, cx)
1922 }
1923
1924 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1925 let mut clone = Self::new(
1926 self.mode.clone(),
1927 self.buffer.clone(),
1928 self.project.clone(),
1929 window,
1930 cx,
1931 );
1932 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1933 let snapshot = display_map.snapshot(cx);
1934 clone.display_map.update(cx, |display_map, cx| {
1935 display_map.set_state(&snapshot, cx);
1936 });
1937 snapshot
1938 });
1939 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1940 clone.folds_did_change(cx);
1941 clone.selections.clone_state(&self.selections);
1942 clone
1943 .scroll_manager
1944 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1945 clone.searchable = self.searchable;
1946 clone.read_only = self.read_only;
1947 clone.buffers_with_disabled_indent_guides =
1948 self.buffers_with_disabled_indent_guides.clone();
1949 clone
1950 }
1951
1952 pub fn new(
1953 mode: EditorMode,
1954 buffer: Entity<MultiBuffer>,
1955 project: Option<Entity<Project>>,
1956 window: &mut Window,
1957 cx: &mut Context<Self>,
1958 ) -> Self {
1959 Editor::new_internal(mode, buffer, project, None, window, cx)
1960 }
1961
1962 pub fn refresh_sticky_headers(
1963 &mut self,
1964 display_snapshot: &DisplaySnapshot,
1965 cx: &mut Context<Editor>,
1966 ) {
1967 if !self.mode.is_full() {
1968 return;
1969 }
1970 let multi_buffer = display_snapshot.buffer_snapshot();
1971 let scroll_anchor = self
1972 .scroll_manager
1973 .native_anchor(display_snapshot, cx)
1974 .anchor;
1975 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
1976 return;
1977 };
1978 let buffer = buffer.clone();
1979
1980 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
1981 let max_row = buffer.max_point().row;
1982 let start_row = buffer_visible_start.row.min(max_row);
1983 let end_row = (buffer_visible_start.row + 10).min(max_row);
1984
1985 let syntax = self.style(cx).syntax.clone();
1986 let background_task = cx.background_spawn(async move {
1987 buffer
1988 .outline_items_containing(
1989 Point::new(start_row, 0)..Point::new(end_row, 0),
1990 true,
1991 Some(syntax.as_ref()),
1992 )
1993 .into_iter()
1994 .map(|outline_item| OutlineItem {
1995 depth: outline_item.depth,
1996 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
1997 source_range_for_text: Anchor::range_in_buffer(
1998 excerpt_id,
1999 outline_item.source_range_for_text,
2000 ),
2001 text: outline_item.text,
2002 highlight_ranges: outline_item.highlight_ranges,
2003 name_ranges: outline_item.name_ranges,
2004 body_range: outline_item
2005 .body_range
2006 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2007 annotation_range: outline_item
2008 .annotation_range
2009 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2010 })
2011 .collect()
2012 });
2013 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2014 let sticky_headers = background_task.await;
2015 this.update(cx, |this, cx| {
2016 this.sticky_headers = Some(sticky_headers);
2017 cx.notify();
2018 })
2019 .ok();
2020 });
2021 }
2022
2023 fn new_internal(
2024 mode: EditorMode,
2025 multi_buffer: Entity<MultiBuffer>,
2026 project: Option<Entity<Project>>,
2027 display_map: Option<Entity<DisplayMap>>,
2028 window: &mut Window,
2029 cx: &mut Context<Self>,
2030 ) -> Self {
2031 debug_assert!(
2032 display_map.is_none() || mode.is_minimap(),
2033 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2034 );
2035
2036 let full_mode = mode.is_full();
2037 let is_minimap = mode.is_minimap();
2038 let diagnostics_max_severity = if full_mode {
2039 EditorSettings::get_global(cx)
2040 .diagnostics_max_severity
2041 .unwrap_or(DiagnosticSeverity::Hint)
2042 } else {
2043 DiagnosticSeverity::Off
2044 };
2045 let style = window.text_style();
2046 let font_size = style.font_size.to_pixels(window.rem_size());
2047 let editor = cx.entity().downgrade();
2048 let fold_placeholder = FoldPlaceholder {
2049 constrain_width: false,
2050 render: Arc::new(move |fold_id, fold_range, cx| {
2051 let editor = editor.clone();
2052 FoldPlaceholder::fold_element(fold_id, cx)
2053 .cursor_pointer()
2054 .child("⋯")
2055 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2056 .on_click(move |_, _window, cx| {
2057 editor
2058 .update(cx, |editor, cx| {
2059 editor.unfold_ranges(
2060 &[fold_range.start..fold_range.end],
2061 true,
2062 false,
2063 cx,
2064 );
2065 cx.stop_propagation();
2066 })
2067 .ok();
2068 })
2069 .into_any()
2070 }),
2071 merge_adjacent: true,
2072 ..FoldPlaceholder::default()
2073 };
2074 let display_map = display_map.unwrap_or_else(|| {
2075 cx.new(|cx| {
2076 DisplayMap::new(
2077 multi_buffer.clone(),
2078 style.font(),
2079 font_size,
2080 None,
2081 FILE_HEADER_HEIGHT,
2082 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2083 fold_placeholder,
2084 diagnostics_max_severity,
2085 cx,
2086 )
2087 })
2088 });
2089
2090 let selections = SelectionsCollection::new();
2091
2092 let blink_manager = cx.new(|cx| {
2093 let mut blink_manager = BlinkManager::new(
2094 CURSOR_BLINK_INTERVAL,
2095 |cx| EditorSettings::get_global(cx).cursor_blink,
2096 cx,
2097 );
2098 if is_minimap {
2099 blink_manager.disable(cx);
2100 }
2101 blink_manager
2102 });
2103
2104 let soft_wrap_mode_override =
2105 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2106
2107 let mut project_subscriptions = Vec::new();
2108 if full_mode && let Some(project) = project.as_ref() {
2109 project_subscriptions.push(cx.subscribe_in(
2110 project,
2111 window,
2112 |editor, _, event, window, cx| match event {
2113 project::Event::RefreshCodeLens => {
2114 // we always query lens with actions, without storing them, always refreshing them
2115 }
2116 project::Event::RefreshInlayHints {
2117 server_id,
2118 request_id,
2119 } => {
2120 editor.refresh_inlay_hints(
2121 InlayHintRefreshReason::RefreshRequested {
2122 server_id: *server_id,
2123 request_id: *request_id,
2124 },
2125 cx,
2126 );
2127 }
2128 project::Event::RefreshSemanticTokens {
2129 server_id,
2130 request_id,
2131 } => {
2132 editor.refresh_semantic_tokens(
2133 None,
2134 Some(RefreshForServer {
2135 server_id: *server_id,
2136 request_id: *request_id,
2137 }),
2138 cx,
2139 );
2140 }
2141 project::Event::LanguageServerRemoved(_) => {
2142 editor.registered_buffers.clear();
2143 editor.register_visible_buffers(cx);
2144 editor.invalidate_semantic_tokens(None);
2145 editor.refresh_runnables(window, cx);
2146 editor.update_lsp_data(None, window, cx);
2147 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2148 }
2149 project::Event::SnippetEdit(id, snippet_edits) => {
2150 // todo(lw): Non singletons
2151 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2152 let snapshot = buffer.read(cx).snapshot();
2153 let focus_handle = editor.focus_handle(cx);
2154 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2155 for (range, snippet) in snippet_edits {
2156 let buffer_range =
2157 language::range_from_lsp(*range).to_offset(&snapshot);
2158 editor
2159 .insert_snippet(
2160 &[MultiBufferOffset(buffer_range.start)
2161 ..MultiBufferOffset(buffer_range.end)],
2162 snippet.clone(),
2163 window,
2164 cx,
2165 )
2166 .ok();
2167 }
2168 }
2169 }
2170 }
2171 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2172 let buffer_id = *buffer_id;
2173 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2174 editor.register_buffer(buffer_id, cx);
2175 editor.refresh_runnables(window, cx);
2176 editor.update_lsp_data(Some(buffer_id), window, cx);
2177 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2178 refresh_linked_ranges(editor, window, cx);
2179 editor.refresh_code_actions(window, cx);
2180 editor.refresh_document_highlights(cx);
2181 }
2182 }
2183
2184 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2185 let Some(workspace) = editor.workspace() else {
2186 return;
2187 };
2188 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2189 else {
2190 return;
2191 };
2192
2193 if active_editor.entity_id() == cx.entity_id() {
2194 let entity_id = cx.entity_id();
2195 workspace.update(cx, |this, cx| {
2196 this.panes_mut()
2197 .iter_mut()
2198 .filter(|pane| pane.entity_id() != entity_id)
2199 .for_each(|p| {
2200 p.update(cx, |pane, _| {
2201 pane.nav_history_mut().rename_item(
2202 entity_id,
2203 project_path.clone(),
2204 abs_path.clone().into(),
2205 );
2206 })
2207 });
2208 });
2209
2210 Self::open_transaction_for_hidden_buffers(
2211 workspace,
2212 transaction.clone(),
2213 "Rename".to_string(),
2214 window,
2215 cx,
2216 );
2217 }
2218 }
2219
2220 project::Event::WorkspaceEditApplied(transaction) => {
2221 let Some(workspace) = editor.workspace() else {
2222 return;
2223 };
2224 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2225 else {
2226 return;
2227 };
2228
2229 if active_editor.entity_id() == cx.entity_id() {
2230 Self::open_transaction_for_hidden_buffers(
2231 workspace,
2232 transaction.clone(),
2233 "LSP Edit".to_string(),
2234 window,
2235 cx,
2236 );
2237 }
2238 }
2239
2240 _ => {}
2241 },
2242 ));
2243 if let Some(task_inventory) = project
2244 .read(cx)
2245 .task_store()
2246 .read(cx)
2247 .task_inventory()
2248 .cloned()
2249 {
2250 project_subscriptions.push(cx.observe_in(
2251 &task_inventory,
2252 window,
2253 |editor, _, window, cx| {
2254 editor.refresh_runnables(window, cx);
2255 },
2256 ));
2257 };
2258
2259 project_subscriptions.push(cx.subscribe_in(
2260 &project.read(cx).breakpoint_store(),
2261 window,
2262 |editor, _, event, window, cx| match event {
2263 BreakpointStoreEvent::ClearDebugLines => {
2264 editor.clear_row_highlights::<ActiveDebugLine>();
2265 editor.refresh_inline_values(cx);
2266 }
2267 BreakpointStoreEvent::SetDebugLine => {
2268 if editor.go_to_active_debug_line(window, cx) {
2269 cx.stop_propagation();
2270 }
2271
2272 editor.refresh_inline_values(cx);
2273 }
2274 _ => {}
2275 },
2276 ));
2277 let git_store = project.read(cx).git_store().clone();
2278 let project = project.clone();
2279 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2280 if let GitStoreEvent::RepositoryAdded = event {
2281 this.load_diff_task = Some(
2282 update_uncommitted_diff_for_buffer(
2283 cx.entity(),
2284 &project,
2285 this.buffer.read(cx).all_buffers(),
2286 this.buffer.clone(),
2287 cx,
2288 )
2289 .shared(),
2290 );
2291 }
2292 }));
2293 }
2294
2295 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2296
2297 let inlay_hint_settings =
2298 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2299 let focus_handle = cx.focus_handle();
2300 if !is_minimap {
2301 cx.on_focus(&focus_handle, window, Self::handle_focus)
2302 .detach();
2303 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2304 .detach();
2305 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2306 .detach();
2307 cx.on_blur(&focus_handle, window, Self::handle_blur)
2308 .detach();
2309 cx.observe_pending_input(window, Self::observe_pending_input)
2310 .detach();
2311 }
2312
2313 let show_indent_guides =
2314 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2315 Some(false)
2316 } else {
2317 None
2318 };
2319
2320 let breakpoint_store = match (&mode, project.as_ref()) {
2321 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2322 _ => None,
2323 };
2324
2325 let mut code_action_providers = Vec::new();
2326 let mut load_uncommitted_diff = None;
2327 if let Some(project) = project.clone() {
2328 load_uncommitted_diff = Some(
2329 update_uncommitted_diff_for_buffer(
2330 cx.entity(),
2331 &project,
2332 multi_buffer.read(cx).all_buffers(),
2333 multi_buffer.clone(),
2334 cx,
2335 )
2336 .shared(),
2337 );
2338 code_action_providers.push(Rc::new(project) as Rc<_>);
2339 }
2340
2341 let mut editor = Self {
2342 focus_handle,
2343 show_cursor_when_unfocused: false,
2344 last_focused_descendant: None,
2345 buffer: multi_buffer.clone(),
2346 display_map: display_map.clone(),
2347 placeholder_display_map: None,
2348 selections,
2349 scroll_manager: ScrollManager::new(cx),
2350 columnar_selection_state: None,
2351 add_selections_state: None,
2352 select_next_state: None,
2353 select_prev_state: None,
2354 selection_history: SelectionHistory::default(),
2355 defer_selection_effects: false,
2356 deferred_selection_effects_state: None,
2357 autoclose_regions: Vec::new(),
2358 snippet_stack: InvalidationStack::default(),
2359 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2360 ime_transaction: None,
2361 active_diagnostics: ActiveDiagnostic::None,
2362 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2363 inline_diagnostics_update: Task::ready(()),
2364 inline_diagnostics: Vec::new(),
2365 soft_wrap_mode_override,
2366 diagnostics_max_severity,
2367 hard_wrap: None,
2368 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2369 semantics_provider: project
2370 .as_ref()
2371 .map(|project| Rc::new(project.downgrade()) as _),
2372 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2373 project,
2374 blink_manager: blink_manager.clone(),
2375 show_local_selections: true,
2376 show_scrollbars: ScrollbarAxes {
2377 horizontal: full_mode,
2378 vertical: full_mode,
2379 },
2380 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2381 offset_content: !matches!(mode, EditorMode::SingleLine),
2382 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2383 show_gutter: full_mode,
2384 show_line_numbers: (!full_mode).then_some(false),
2385 use_relative_line_numbers: None,
2386 disable_expand_excerpt_buttons: !full_mode,
2387 delegate_expand_excerpts: false,
2388 delegate_stage_and_restore: false,
2389 delegate_open_excerpts: false,
2390 enable_lsp_data: true,
2391 enable_runnables: true,
2392 show_git_diff_gutter: None,
2393 show_code_actions: None,
2394 show_runnables: None,
2395 show_breakpoints: None,
2396 show_diff_review_button: false,
2397 show_wrap_guides: None,
2398 show_indent_guides,
2399 buffers_with_disabled_indent_guides: HashSet::default(),
2400 highlight_order: 0,
2401 highlighted_rows: HashMap::default(),
2402 background_highlights: HashMap::default(),
2403 gutter_highlights: HashMap::default(),
2404 scrollbar_marker_state: ScrollbarMarkerState::default(),
2405 active_indent_guides_state: ActiveIndentGuidesState::default(),
2406 nav_history: None,
2407 context_menu: RefCell::new(None),
2408 context_menu_options: None,
2409 mouse_context_menu: None,
2410 completion_tasks: Vec::new(),
2411 inline_blame_popover: None,
2412 inline_blame_popover_show_task: None,
2413 signature_help_state: SignatureHelpState::default(),
2414 auto_signature_help: None,
2415 find_all_references_task_sources: Vec::new(),
2416 next_completion_id: 0,
2417 next_inlay_id: 0,
2418 code_action_providers,
2419 available_code_actions: None,
2420 code_actions_task: None,
2421 quick_selection_highlight_task: None,
2422 debounced_selection_highlight_task: None,
2423 debounced_selection_highlight_complete: false,
2424 document_highlights_task: None,
2425 linked_editing_range_task: None,
2426 pending_rename: None,
2427 searchable: !is_minimap,
2428 cursor_shape: EditorSettings::get_global(cx)
2429 .cursor_shape
2430 .unwrap_or_default(),
2431 cursor_offset_on_selection: false,
2432 current_line_highlight: None,
2433 autoindent_mode: Some(AutoindentMode::EachLine),
2434 collapse_matches: false,
2435 workspace: None,
2436 input_enabled: !is_minimap,
2437 expects_character_input: !is_minimap,
2438 use_modal_editing: full_mode,
2439 read_only: is_minimap,
2440 use_autoclose: true,
2441 use_auto_surround: true,
2442 auto_replace_emoji_shortcode: false,
2443 jsx_tag_auto_close_enabled_in_any_buffer: false,
2444 leader_id: None,
2445 remote_id: None,
2446 hover_state: HoverState::default(),
2447 pending_mouse_down: None,
2448 prev_pressure_stage: None,
2449 hovered_link_state: None,
2450 edit_prediction_provider: None,
2451 active_edit_prediction: None,
2452 stale_edit_prediction_in_menu: None,
2453 edit_prediction_preview: EditPredictionPreview::Inactive {
2454 released_too_fast: false,
2455 },
2456 inline_diagnostics_enabled: full_mode,
2457 diagnostics_enabled: full_mode,
2458 word_completions_enabled: full_mode,
2459 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2460 gutter_hovered: false,
2461 pixel_position_of_newest_cursor: None,
2462 last_bounds: None,
2463 last_position_map: None,
2464 expect_bounds_change: None,
2465 gutter_dimensions: GutterDimensions::default(),
2466 style: None,
2467 show_cursor_names: false,
2468 hovered_cursors: HashMap::default(),
2469 next_editor_action_id: EditorActionId::default(),
2470 editor_actions: Rc::default(),
2471 edit_predictions_hidden_for_vim_mode: false,
2472 show_edit_predictions_override: None,
2473 show_completions_on_input_override: None,
2474 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2475 edit_prediction_settings: EditPredictionSettings::Disabled,
2476 edit_prediction_indent_conflict: false,
2477 edit_prediction_requires_modifier_in_indent_conflict: true,
2478 custom_context_menu: None,
2479 show_git_blame_gutter: false,
2480 show_git_blame_inline: false,
2481 show_selection_menu: None,
2482 show_git_blame_inline_delay_task: None,
2483 git_blame_inline_enabled: full_mode
2484 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2485 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2486 buffer_serialization: is_minimap.not().then(|| {
2487 BufferSerialization::new(
2488 ProjectSettings::get_global(cx)
2489 .session
2490 .restore_unsaved_buffers,
2491 )
2492 }),
2493 blame: None,
2494 blame_subscription: None,
2495
2496 breakpoint_store,
2497 gutter_breakpoint_indicator: (None, None),
2498 gutter_diff_review_indicator: (None, None),
2499 diff_review_drag_state: None,
2500 diff_review_overlays: Vec::new(),
2501 stored_review_comments: Vec::new(),
2502 next_review_comment_id: 0,
2503 hovered_diff_hunk_row: None,
2504 _subscriptions: (!is_minimap)
2505 .then(|| {
2506 vec![
2507 cx.observe(&multi_buffer, Self::on_buffer_changed),
2508 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2509 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2510 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2511 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2512 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2513 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2514 cx.observe_window_activation(window, |editor, window, cx| {
2515 let active = window.is_window_active();
2516 editor.blink_manager.update(cx, |blink_manager, cx| {
2517 if active {
2518 blink_manager.enable(cx);
2519 } else {
2520 blink_manager.disable(cx);
2521 }
2522 });
2523 if active {
2524 editor.show_mouse_cursor(cx);
2525 }
2526 }),
2527 ]
2528 })
2529 .unwrap_or_default(),
2530 runnables: RunnableData::new(),
2531 pull_diagnostics_task: Task::ready(()),
2532 colors: None,
2533 refresh_colors_task: Task::ready(()),
2534 use_document_folding_ranges: false,
2535 refresh_folding_ranges_task: Task::ready(()),
2536 inlay_hints: None,
2537 next_color_inlay_id: 0,
2538 post_scroll_update: Task::ready(()),
2539 linked_edit_ranges: Default::default(),
2540 in_project_search: false,
2541 previous_search_ranges: None,
2542 breadcrumb_header: None,
2543 focused_block: None,
2544 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2545 addons: HashMap::default(),
2546 registered_buffers: HashMap::default(),
2547 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2548 selection_mark_mode: false,
2549 toggle_fold_multiple_buffers: Task::ready(()),
2550 serialize_selections: Task::ready(()),
2551 serialize_folds: Task::ready(()),
2552 text_style_refinement: None,
2553 load_diff_task: load_uncommitted_diff,
2554 temporary_diff_override: false,
2555 mouse_cursor_hidden: false,
2556 minimap: None,
2557 hide_mouse_mode: EditorSettings::get_global(cx)
2558 .hide_mouse
2559 .unwrap_or_default(),
2560 change_list: ChangeList::new(),
2561 mode,
2562 selection_drag_state: SelectionDragState::None,
2563 folding_newlines: Task::ready(()),
2564 lookup_key: None,
2565 select_next_is_case_sensitive: None,
2566 on_local_selections_changed: None,
2567 suppress_selection_callback: false,
2568 applicable_language_settings: HashMap::default(),
2569 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2570 accent_data: None,
2571 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2572 number_deleted_lines: false,
2573 refresh_matching_bracket_highlights_task: Task::ready(()),
2574 refresh_document_symbols_task: Task::ready(()).shared(),
2575 lsp_document_symbols: HashMap::default(),
2576 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2577 outline_symbols_at_cursor: None,
2578 sticky_headers_task: Task::ready(()),
2579 sticky_headers: None,
2580 colorize_brackets_task: Task::ready(()),
2581 };
2582
2583 if is_minimap {
2584 return editor;
2585 }
2586
2587 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2588 editor.accent_data = editor.fetch_accent_data(cx);
2589
2590 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2591 editor
2592 ._subscriptions
2593 .push(cx.observe(breakpoints, |_, _, cx| {
2594 cx.notify();
2595 }));
2596 }
2597 editor._subscriptions.extend(project_subscriptions);
2598
2599 editor._subscriptions.push(cx.subscribe_in(
2600 &cx.entity(),
2601 window,
2602 |editor, _, e: &EditorEvent, window, cx| match e {
2603 EditorEvent::ScrollPositionChanged { local, .. } => {
2604 if *local {
2605 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2606 editor.inline_blame_popover.take();
2607 let snapshot = editor.snapshot(window, cx);
2608 let new_anchor = editor
2609 .scroll_manager
2610 .native_anchor(&snapshot.display_snapshot, cx);
2611 editor.update_restoration_data(cx, move |data| {
2612 data.scroll_position = (
2613 new_anchor.top_row(snapshot.buffer_snapshot()),
2614 new_anchor.offset,
2615 );
2616 });
2617
2618 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2619 cx.background_executor()
2620 .timer(Duration::from_millis(50))
2621 .await;
2622 editor
2623 .update_in(cx, |editor, window, cx| {
2624 editor.register_visible_buffers(cx);
2625 editor.colorize_brackets(false, cx);
2626 editor.refresh_inlay_hints(
2627 InlayHintRefreshReason::NewLinesShown,
2628 cx,
2629 );
2630 if !editor.buffer().read(cx).is_singleton() {
2631 editor.update_lsp_data(None, window, cx);
2632 editor.refresh_runnables(window, cx);
2633 }
2634 })
2635 .ok();
2636 });
2637 }
2638 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2639 }
2640 EditorEvent::Edited { .. } => {
2641 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2642 .map(|vim_mode| vim_mode.0)
2643 .unwrap_or(false);
2644 if !vim_mode {
2645 let display_map = editor.display_snapshot(cx);
2646 let selections = editor.selections.all_adjusted_display(&display_map);
2647 let pop_state = editor
2648 .change_list
2649 .last()
2650 .map(|previous| {
2651 previous.len() == selections.len()
2652 && previous.iter().enumerate().all(|(ix, p)| {
2653 p.to_display_point(&display_map).row()
2654 == selections[ix].head().row()
2655 })
2656 })
2657 .unwrap_or(false);
2658 let new_positions = selections
2659 .into_iter()
2660 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2661 .collect();
2662 editor
2663 .change_list
2664 .push_to_change_list(pop_state, new_positions);
2665 }
2666 }
2667 _ => (),
2668 },
2669 ));
2670
2671 if let Some(dap_store) = editor
2672 .project
2673 .as_ref()
2674 .map(|project| project.read(cx).dap_store())
2675 {
2676 let weak_editor = cx.weak_entity();
2677
2678 editor
2679 ._subscriptions
2680 .push(
2681 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2682 let session_entity = cx.entity();
2683 weak_editor
2684 .update(cx, |editor, cx| {
2685 editor._subscriptions.push(
2686 cx.subscribe(&session_entity, Self::on_debug_session_event),
2687 );
2688 })
2689 .ok();
2690 }),
2691 );
2692
2693 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2694 editor
2695 ._subscriptions
2696 .push(cx.subscribe(&session, Self::on_debug_session_event));
2697 }
2698 }
2699
2700 // skip adding the initial selection to selection history
2701 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2702 editor.end_selection(window, cx);
2703 editor.selection_history.mode = SelectionHistoryMode::Normal;
2704
2705 editor.scroll_manager.show_scrollbars(window, cx);
2706 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2707
2708 if full_mode {
2709 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2710 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2711
2712 if editor.git_blame_inline_enabled {
2713 editor.start_git_blame_inline(false, window, cx);
2714 }
2715
2716 editor.go_to_active_debug_line(window, cx);
2717
2718 editor.minimap =
2719 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2720 editor.colors = Some(LspColorData::new(cx));
2721 editor.use_document_folding_ranges = true;
2722 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2723
2724 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2725 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2726 }
2727 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2728 }
2729
2730 editor
2731 }
2732
2733 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2734 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2735 }
2736
2737 pub fn deploy_mouse_context_menu(
2738 &mut self,
2739 position: gpui::Point<Pixels>,
2740 context_menu: Entity<ContextMenu>,
2741 window: &mut Window,
2742 cx: &mut Context<Self>,
2743 ) {
2744 self.mouse_context_menu = Some(MouseContextMenu::new(
2745 self,
2746 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2747 context_menu,
2748 window,
2749 cx,
2750 ));
2751 }
2752
2753 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2754 self.mouse_context_menu
2755 .as_ref()
2756 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2757 }
2758
2759 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2760 if self
2761 .selections
2762 .pending_anchor()
2763 .is_some_and(|pending_selection| {
2764 let snapshot = self.buffer().read(cx).snapshot(cx);
2765 pending_selection.range().includes(range, &snapshot)
2766 })
2767 {
2768 return true;
2769 }
2770
2771 self.selections
2772 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2773 .into_iter()
2774 .any(|selection| {
2775 // This is needed to cover a corner case, if we just check for an existing
2776 // selection in the fold range, having a cursor at the start of the fold
2777 // marks it as selected. Non-empty selections don't cause this.
2778 let length = selection.end - selection.start;
2779 length > 0
2780 })
2781 }
2782
2783 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2784 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2785 }
2786
2787 fn key_context_internal(
2788 &self,
2789 has_active_edit_prediction: bool,
2790 window: &mut Window,
2791 cx: &mut App,
2792 ) -> KeyContext {
2793 let mut key_context = KeyContext::new_with_defaults();
2794 key_context.add("Editor");
2795 let mode = match self.mode {
2796 EditorMode::SingleLine => "single_line",
2797 EditorMode::AutoHeight { .. } => "auto_height",
2798 EditorMode::Minimap { .. } => "minimap",
2799 EditorMode::Full { .. } => "full",
2800 };
2801
2802 if EditorSettings::jupyter_enabled(cx) {
2803 key_context.add("jupyter");
2804 }
2805
2806 key_context.set("mode", mode);
2807 if self.pending_rename.is_some() {
2808 key_context.add("renaming");
2809 }
2810
2811 if let Some(snippet_stack) = self.snippet_stack.last() {
2812 key_context.add("in_snippet");
2813
2814 if snippet_stack.active_index > 0 {
2815 key_context.add("has_previous_tabstop");
2816 }
2817
2818 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2819 key_context.add("has_next_tabstop");
2820 }
2821 }
2822
2823 match self.context_menu.borrow().as_ref() {
2824 Some(CodeContextMenu::Completions(menu)) => {
2825 if menu.visible() {
2826 key_context.add("menu");
2827 key_context.add("showing_completions");
2828 }
2829 }
2830 Some(CodeContextMenu::CodeActions(menu)) => {
2831 if menu.visible() {
2832 key_context.add("menu");
2833 key_context.add("showing_code_actions")
2834 }
2835 }
2836 None => {}
2837 }
2838
2839 if self.signature_help_state.has_multiple_signatures() {
2840 key_context.add("showing_signature_help");
2841 }
2842
2843 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2844 if !self.focus_handle(cx).contains_focused(window, cx)
2845 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2846 {
2847 for addon in self.addons.values() {
2848 addon.extend_key_context(&mut key_context, cx)
2849 }
2850 }
2851
2852 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2853 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2854 Some(
2855 file.full_path(cx)
2856 .extension()?
2857 .to_string_lossy()
2858 .to_lowercase(),
2859 )
2860 }) {
2861 key_context.set("extension", extension);
2862 }
2863 } else {
2864 key_context.add("multibuffer");
2865 }
2866
2867 if has_active_edit_prediction {
2868 if self.edit_prediction_in_conflict() {
2869 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2870 } else {
2871 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2872 key_context.add("copilot_suggestion");
2873 }
2874 }
2875
2876 if self.selection_mark_mode {
2877 key_context.add("selection_mode");
2878 }
2879
2880 let disjoint = self.selections.disjoint_anchors();
2881 let snapshot = self.snapshot(window, cx);
2882 let snapshot = snapshot.buffer_snapshot();
2883 if self.mode == EditorMode::SingleLine
2884 && let [selection] = disjoint
2885 && selection.start == selection.end
2886 && selection.end.to_offset(snapshot) == snapshot.len()
2887 {
2888 key_context.add("end_of_input");
2889 }
2890
2891 if self.has_any_expanded_diff_hunks(cx) {
2892 key_context.add("diffs_expanded");
2893 }
2894
2895 key_context
2896 }
2897
2898 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2899 self.last_bounds.as_ref()
2900 }
2901
2902 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2903 if self.mouse_cursor_hidden {
2904 self.mouse_cursor_hidden = false;
2905 cx.notify();
2906 }
2907 }
2908
2909 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2910 let hide_mouse_cursor = match origin {
2911 HideMouseCursorOrigin::TypingAction => {
2912 matches!(
2913 self.hide_mouse_mode,
2914 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2915 )
2916 }
2917 HideMouseCursorOrigin::MovementAction => {
2918 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2919 }
2920 };
2921 if self.mouse_cursor_hidden != hide_mouse_cursor {
2922 self.mouse_cursor_hidden = hide_mouse_cursor;
2923 cx.notify();
2924 }
2925 }
2926
2927 pub fn edit_prediction_in_conflict(&self) -> bool {
2928 if !self.show_edit_predictions_in_menu() {
2929 return false;
2930 }
2931
2932 let showing_completions = self
2933 .context_menu
2934 .borrow()
2935 .as_ref()
2936 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2937
2938 showing_completions
2939 || self.edit_prediction_requires_modifier()
2940 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2941 // bindings to insert tab characters.
2942 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2943 }
2944
2945 pub fn accept_edit_prediction_keybind(
2946 &self,
2947 granularity: EditPredictionGranularity,
2948 window: &mut Window,
2949 cx: &mut App,
2950 ) -> AcceptEditPredictionBinding {
2951 let key_context = self.key_context_internal(true, window, cx);
2952 let in_conflict = self.edit_prediction_in_conflict();
2953
2954 let bindings =
2955 match granularity {
2956 EditPredictionGranularity::Word => window
2957 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2958 EditPredictionGranularity::Line => window
2959 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2960 EditPredictionGranularity::Full => {
2961 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2962 }
2963 };
2964
2965 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2966 !in_conflict
2967 || binding
2968 .keystrokes()
2969 .first()
2970 .is_some_and(|keystroke| keystroke.modifiers().modified())
2971 }))
2972 }
2973
2974 pub fn new_file(
2975 workspace: &mut Workspace,
2976 _: &workspace::NewFile,
2977 window: &mut Window,
2978 cx: &mut Context<Workspace>,
2979 ) {
2980 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2981 "Failed to create buffer",
2982 window,
2983 cx,
2984 |e, _, _| match e.error_code() {
2985 ErrorCode::RemoteUpgradeRequired => Some(format!(
2986 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2987 e.error_tag("required").unwrap_or("the latest version")
2988 )),
2989 _ => None,
2990 },
2991 );
2992 }
2993
2994 pub fn new_in_workspace(
2995 workspace: &mut Workspace,
2996 window: &mut Window,
2997 cx: &mut Context<Workspace>,
2998 ) -> Task<Result<Entity<Editor>>> {
2999 let project = workspace.project().clone();
3000 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3001
3002 cx.spawn_in(window, async move |workspace, cx| {
3003 let buffer = create.await?;
3004 workspace.update_in(cx, |workspace, window, cx| {
3005 let editor =
3006 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3007 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3008 editor
3009 })
3010 })
3011 }
3012
3013 fn new_file_vertical(
3014 workspace: &mut Workspace,
3015 _: &workspace::NewFileSplitVertical,
3016 window: &mut Window,
3017 cx: &mut Context<Workspace>,
3018 ) {
3019 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3020 }
3021
3022 fn new_file_horizontal(
3023 workspace: &mut Workspace,
3024 _: &workspace::NewFileSplitHorizontal,
3025 window: &mut Window,
3026 cx: &mut Context<Workspace>,
3027 ) {
3028 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3029 }
3030
3031 fn new_file_split(
3032 workspace: &mut Workspace,
3033 action: &workspace::NewFileSplit,
3034 window: &mut Window,
3035 cx: &mut Context<Workspace>,
3036 ) {
3037 Self::new_file_in_direction(workspace, action.0, window, cx)
3038 }
3039
3040 fn new_file_in_direction(
3041 workspace: &mut Workspace,
3042 direction: SplitDirection,
3043 window: &mut Window,
3044 cx: &mut Context<Workspace>,
3045 ) {
3046 let project = workspace.project().clone();
3047 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3048
3049 cx.spawn_in(window, async move |workspace, cx| {
3050 let buffer = create.await?;
3051 workspace.update_in(cx, move |workspace, window, cx| {
3052 workspace.split_item(
3053 direction,
3054 Box::new(
3055 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3056 ),
3057 window,
3058 cx,
3059 )
3060 })?;
3061 anyhow::Ok(())
3062 })
3063 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3064 match e.error_code() {
3065 ErrorCode::RemoteUpgradeRequired => Some(format!(
3066 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3067 e.error_tag("required").unwrap_or("the latest version")
3068 )),
3069 _ => None,
3070 }
3071 });
3072 }
3073
3074 pub fn leader_id(&self) -> Option<CollaboratorId> {
3075 self.leader_id
3076 }
3077
3078 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3079 &self.buffer
3080 }
3081
3082 pub fn project(&self) -> Option<&Entity<Project>> {
3083 self.project.as_ref()
3084 }
3085
3086 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3087 self.workspace.as_ref()?.0.upgrade()
3088 }
3089
3090 /// Detaches a task and shows an error notification in the workspace if available,
3091 /// otherwise just logs the error.
3092 pub fn detach_and_notify_err<R, E>(
3093 &self,
3094 task: Task<Result<R, E>>,
3095 window: &mut Window,
3096 cx: &mut App,
3097 ) where
3098 E: std::fmt::Debug + std::fmt::Display + 'static,
3099 R: 'static,
3100 {
3101 if let Some(workspace) = self.workspace() {
3102 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3103 } else {
3104 task.detach_and_log_err(cx);
3105 }
3106 }
3107
3108 /// Returns the workspace serialization ID if this editor should be serialized.
3109 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3110 self.workspace
3111 .as_ref()
3112 .filter(|_| self.should_serialize_buffer())
3113 .and_then(|workspace| workspace.1)
3114 }
3115
3116 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3117 self.buffer().read(cx).title(cx)
3118 }
3119
3120 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3121 let git_blame_gutter_max_author_length = self
3122 .render_git_blame_gutter(cx)
3123 .then(|| {
3124 if let Some(blame) = self.blame.as_ref() {
3125 let max_author_length =
3126 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3127 Some(max_author_length)
3128 } else {
3129 None
3130 }
3131 })
3132 .flatten();
3133
3134 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3135
3136 EditorSnapshot {
3137 mode: self.mode.clone(),
3138 show_gutter: self.show_gutter,
3139 offset_content: self.offset_content,
3140 show_line_numbers: self.show_line_numbers,
3141 number_deleted_lines: self.number_deleted_lines,
3142 show_git_diff_gutter: self.show_git_diff_gutter,
3143 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3144 show_code_actions: self.show_code_actions,
3145 show_runnables: self.show_runnables,
3146 show_breakpoints: self.show_breakpoints,
3147 git_blame_gutter_max_author_length,
3148 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3149 display_snapshot,
3150 placeholder_display_snapshot: self
3151 .placeholder_display_map
3152 .as_ref()
3153 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3154 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3155 is_focused: self.focus_handle.is_focused(window),
3156 current_line_highlight: self
3157 .current_line_highlight
3158 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3159 gutter_hovered: self.gutter_hovered,
3160 }
3161 }
3162
3163 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3164 self.buffer.read(cx).language_at(point, cx)
3165 }
3166
3167 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3168 self.buffer.read(cx).read(cx).file_at(point).cloned()
3169 }
3170
3171 pub fn active_excerpt(
3172 &self,
3173 cx: &App,
3174 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3175 self.buffer
3176 .read(cx)
3177 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3178 }
3179
3180 pub fn mode(&self) -> &EditorMode {
3181 &self.mode
3182 }
3183
3184 pub fn set_mode(&mut self, mode: EditorMode) {
3185 self.mode = mode;
3186 }
3187
3188 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3189 self.collaboration_hub.as_deref()
3190 }
3191
3192 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3193 self.collaboration_hub = Some(hub);
3194 }
3195
3196 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3197 self.in_project_search = in_project_search;
3198 }
3199
3200 pub fn set_custom_context_menu(
3201 &mut self,
3202 f: impl 'static
3203 + Fn(
3204 &mut Self,
3205 DisplayPoint,
3206 &mut Window,
3207 &mut Context<Self>,
3208 ) -> Option<Entity<ui::ContextMenu>>,
3209 ) {
3210 self.custom_context_menu = Some(Box::new(f))
3211 }
3212
3213 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3214 self.completion_provider = provider;
3215 }
3216
3217 #[cfg(any(test, feature = "test-support"))]
3218 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3219 self.completion_provider.clone()
3220 }
3221
3222 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3223 self.semantics_provider.clone()
3224 }
3225
3226 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3227 self.semantics_provider = provider;
3228 }
3229
3230 pub fn set_edit_prediction_provider<T>(
3231 &mut self,
3232 provider: Option<Entity<T>>,
3233 window: &mut Window,
3234 cx: &mut Context<Self>,
3235 ) where
3236 T: EditPredictionDelegate,
3237 {
3238 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3239 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3240 if this.focus_handle.is_focused(window) {
3241 this.update_visible_edit_prediction(window, cx);
3242 }
3243 }),
3244 provider: Arc::new(provider),
3245 });
3246 self.update_edit_prediction_settings(cx);
3247 self.refresh_edit_prediction(false, false, window, cx);
3248 }
3249
3250 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3251 self.placeholder_display_map
3252 .as_ref()
3253 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3254 }
3255
3256 pub fn set_placeholder_text(
3257 &mut self,
3258 placeholder_text: &str,
3259 window: &mut Window,
3260 cx: &mut Context<Self>,
3261 ) {
3262 let multibuffer = cx
3263 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3264
3265 let style = window.text_style();
3266
3267 self.placeholder_display_map = Some(cx.new(|cx| {
3268 DisplayMap::new(
3269 multibuffer,
3270 style.font(),
3271 style.font_size.to_pixels(window.rem_size()),
3272 None,
3273 FILE_HEADER_HEIGHT,
3274 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3275 Default::default(),
3276 DiagnosticSeverity::Off,
3277 cx,
3278 )
3279 }));
3280 cx.notify();
3281 }
3282
3283 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3284 self.cursor_shape = cursor_shape;
3285
3286 // Disrupt blink for immediate user feedback that the cursor shape has changed
3287 self.blink_manager.update(cx, BlinkManager::show_cursor);
3288
3289 cx.notify();
3290 }
3291
3292 pub fn cursor_shape(&self) -> CursorShape {
3293 self.cursor_shape
3294 }
3295
3296 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3297 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3298 }
3299
3300 pub fn set_current_line_highlight(
3301 &mut self,
3302 current_line_highlight: Option<CurrentLineHighlight>,
3303 ) {
3304 self.current_line_highlight = current_line_highlight;
3305 }
3306
3307 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3308 self.collapse_matches = collapse_matches;
3309 }
3310
3311 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3312 if self.collapse_matches {
3313 return range.start..range.start;
3314 }
3315 range.clone()
3316 }
3317
3318 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3319 self.display_map.read(cx).clip_at_line_ends
3320 }
3321
3322 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3323 if self.display_map.read(cx).clip_at_line_ends != clip {
3324 self.display_map
3325 .update(cx, |map, _| map.clip_at_line_ends = clip);
3326 }
3327 }
3328
3329 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3330 self.input_enabled = input_enabled;
3331 }
3332
3333 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3334 self.expects_character_input = expects_character_input;
3335 }
3336
3337 pub fn set_edit_predictions_hidden_for_vim_mode(
3338 &mut self,
3339 hidden: bool,
3340 window: &mut Window,
3341 cx: &mut Context<Self>,
3342 ) {
3343 if hidden != self.edit_predictions_hidden_for_vim_mode {
3344 self.edit_predictions_hidden_for_vim_mode = hidden;
3345 if hidden {
3346 self.update_visible_edit_prediction(window, cx);
3347 } else {
3348 self.refresh_edit_prediction(true, false, window, cx);
3349 }
3350 }
3351 }
3352
3353 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3354 self.menu_edit_predictions_policy = value;
3355 }
3356
3357 pub fn set_autoindent(&mut self, autoindent: bool) {
3358 if autoindent {
3359 self.autoindent_mode = Some(AutoindentMode::EachLine);
3360 } else {
3361 self.autoindent_mode = None;
3362 }
3363 }
3364
3365 pub fn capability(&self, cx: &App) -> Capability {
3366 if self.read_only {
3367 Capability::ReadOnly
3368 } else {
3369 self.buffer.read(cx).capability()
3370 }
3371 }
3372
3373 pub fn read_only(&self, cx: &App) -> bool {
3374 self.read_only || self.buffer.read(cx).read_only()
3375 }
3376
3377 pub fn set_read_only(&mut self, read_only: bool) {
3378 self.read_only = read_only;
3379 }
3380
3381 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3382 self.use_autoclose = autoclose;
3383 }
3384
3385 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3386 self.use_auto_surround = auto_surround;
3387 }
3388
3389 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3390 self.auto_replace_emoji_shortcode = auto_replace;
3391 }
3392
3393 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3394 self.buffer_serialization = should_serialize.then(|| {
3395 BufferSerialization::new(
3396 ProjectSettings::get_global(cx)
3397 .session
3398 .restore_unsaved_buffers,
3399 )
3400 })
3401 }
3402
3403 fn should_serialize_buffer(&self) -> bool {
3404 self.buffer_serialization.is_some()
3405 }
3406
3407 pub fn toggle_edit_predictions(
3408 &mut self,
3409 _: &ToggleEditPrediction,
3410 window: &mut Window,
3411 cx: &mut Context<Self>,
3412 ) {
3413 if self.show_edit_predictions_override.is_some() {
3414 self.set_show_edit_predictions(None, window, cx);
3415 } else {
3416 let show_edit_predictions = !self.edit_predictions_enabled();
3417 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3418 }
3419 }
3420
3421 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3422 self.show_completions_on_input_override = show_completions_on_input;
3423 }
3424
3425 pub fn set_show_edit_predictions(
3426 &mut self,
3427 show_edit_predictions: Option<bool>,
3428 window: &mut Window,
3429 cx: &mut Context<Self>,
3430 ) {
3431 self.show_edit_predictions_override = show_edit_predictions;
3432 self.update_edit_prediction_settings(cx);
3433
3434 if let Some(false) = show_edit_predictions {
3435 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3436 } else {
3437 self.refresh_edit_prediction(false, true, window, cx);
3438 }
3439 }
3440
3441 fn edit_predictions_disabled_in_scope(
3442 &self,
3443 buffer: &Entity<Buffer>,
3444 buffer_position: language::Anchor,
3445 cx: &App,
3446 ) -> bool {
3447 let snapshot = buffer.read(cx).snapshot();
3448 let settings = snapshot.settings_at(buffer_position, cx);
3449
3450 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3451 return false;
3452 };
3453
3454 scope.override_name().is_some_and(|scope_name| {
3455 settings
3456 .edit_predictions_disabled_in
3457 .iter()
3458 .any(|s| s == scope_name)
3459 })
3460 }
3461
3462 pub fn set_use_modal_editing(&mut self, to: bool) {
3463 self.use_modal_editing = to;
3464 }
3465
3466 pub fn use_modal_editing(&self) -> bool {
3467 self.use_modal_editing
3468 }
3469
3470 fn selections_did_change(
3471 &mut self,
3472 local: bool,
3473 old_cursor_position: &Anchor,
3474 effects: SelectionEffects,
3475 window: &mut Window,
3476 cx: &mut Context<Self>,
3477 ) {
3478 window.invalidate_character_coordinates();
3479
3480 // Copy selections to primary selection buffer
3481 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3482 if local {
3483 let selections = self
3484 .selections
3485 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3486 let buffer_handle = self.buffer.read(cx).read(cx);
3487
3488 let mut text = String::new();
3489 for (index, selection) in selections.iter().enumerate() {
3490 let text_for_selection = buffer_handle
3491 .text_for_range(selection.start..selection.end)
3492 .collect::<String>();
3493
3494 text.push_str(&text_for_selection);
3495 if index != selections.len() - 1 {
3496 text.push('\n');
3497 }
3498 }
3499
3500 if !text.is_empty() {
3501 cx.write_to_primary(ClipboardItem::new_string(text));
3502 }
3503 }
3504
3505 let selection_anchors = self.selections.disjoint_anchors_arc();
3506
3507 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3508 self.buffer.update(cx, |buffer, cx| {
3509 buffer.set_active_selections(
3510 &selection_anchors,
3511 self.selections.line_mode(),
3512 self.cursor_shape,
3513 cx,
3514 )
3515 });
3516 }
3517 let display_map = self
3518 .display_map
3519 .update(cx, |display_map, cx| display_map.snapshot(cx));
3520 let buffer = display_map.buffer_snapshot();
3521 if self.selections.count() == 1 {
3522 self.add_selections_state = None;
3523 }
3524 self.select_next_state = None;
3525 self.select_prev_state = None;
3526 self.select_syntax_node_history.try_clear();
3527 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3528 self.snippet_stack.invalidate(&selection_anchors, buffer);
3529 self.take_rename(false, window, cx);
3530
3531 let newest_selection = self.selections.newest_anchor();
3532 let new_cursor_position = newest_selection.head();
3533 let selection_start = newest_selection.start;
3534
3535 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3536 self.push_to_nav_history(
3537 *old_cursor_position,
3538 Some(new_cursor_position.to_point(buffer)),
3539 false,
3540 effects.nav_history == Some(true),
3541 cx,
3542 );
3543 }
3544
3545 if local {
3546 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3547 self.register_buffer(buffer_id, cx);
3548 }
3549
3550 let mut context_menu = self.context_menu.borrow_mut();
3551 let completion_menu = match context_menu.as_ref() {
3552 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3553 Some(CodeContextMenu::CodeActions(_)) => {
3554 *context_menu = None;
3555 None
3556 }
3557 None => None,
3558 };
3559 let completion_position = completion_menu.map(|menu| menu.initial_position);
3560 drop(context_menu);
3561
3562 if effects.completions
3563 && let Some(completion_position) = completion_position
3564 {
3565 let start_offset = selection_start.to_offset(buffer);
3566 let position_matches = start_offset == completion_position.to_offset(buffer);
3567 let continue_showing = if let Some((snap, ..)) =
3568 buffer.point_to_buffer_offset(completion_position)
3569 && !snap.capability.editable()
3570 {
3571 false
3572 } else if position_matches {
3573 if self.snippet_stack.is_empty() {
3574 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3575 == Some(CharKind::Word)
3576 } else {
3577 // Snippet choices can be shown even when the cursor is in whitespace.
3578 // Dismissing the menu with actions like backspace is handled by
3579 // invalidation regions.
3580 true
3581 }
3582 } else {
3583 false
3584 };
3585
3586 if continue_showing {
3587 self.open_or_update_completions_menu(None, None, false, window, cx);
3588 } else {
3589 self.hide_context_menu(window, cx);
3590 }
3591 }
3592
3593 hide_hover(self, cx);
3594
3595 if old_cursor_position.to_display_point(&display_map).row()
3596 != new_cursor_position.to_display_point(&display_map).row()
3597 {
3598 self.available_code_actions.take();
3599 }
3600 self.refresh_code_actions(window, cx);
3601 self.refresh_document_highlights(cx);
3602 refresh_linked_ranges(self, window, cx);
3603
3604 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3605 self.refresh_matching_bracket_highlights(&display_map, cx);
3606 self.refresh_outline_symbols_at_cursor(cx);
3607 self.update_visible_edit_prediction(window, cx);
3608 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3609 self.inline_blame_popover.take();
3610 if self.git_blame_inline_enabled {
3611 self.start_inline_blame_timer(window, cx);
3612 }
3613 }
3614
3615 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3616
3617 if local && !self.suppress_selection_callback {
3618 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3619 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3620 callback(cursor_position, window, cx);
3621 }
3622 }
3623
3624 cx.emit(EditorEvent::SelectionsChanged { local });
3625
3626 let selections = &self.selections.disjoint_anchors_arc();
3627 if selections.len() == 1 {
3628 cx.emit(SearchEvent::ActiveMatchChanged)
3629 }
3630 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3631 let inmemory_selections = selections
3632 .iter()
3633 .map(|s| {
3634 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3635 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3636 })
3637 .collect();
3638 self.update_restoration_data(cx, |data| {
3639 data.selections = inmemory_selections;
3640 });
3641
3642 if WorkspaceSettings::get(None, cx).restore_on_startup
3643 != RestoreOnStartupBehavior::EmptyTab
3644 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3645 {
3646 let snapshot = self.buffer().read(cx).snapshot(cx);
3647 let selections = selections.clone();
3648 let background_executor = cx.background_executor().clone();
3649 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3650 self.serialize_selections = cx.background_spawn(async move {
3651 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3652 let db_selections = selections
3653 .iter()
3654 .map(|selection| {
3655 (
3656 selection.start.to_offset(&snapshot).0,
3657 selection.end.to_offset(&snapshot).0,
3658 )
3659 })
3660 .collect();
3661
3662 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3663 .await
3664 .with_context(|| {
3665 format!(
3666 "persisting editor selections for editor {editor_id}, \
3667 workspace {workspace_id:?}"
3668 )
3669 })
3670 .log_err();
3671 });
3672 }
3673 }
3674
3675 cx.notify();
3676 }
3677
3678 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3679 use text::ToOffset as _;
3680 use text::ToPoint as _;
3681
3682 if self.mode.is_minimap()
3683 || WorkspaceSettings::get(None, cx).restore_on_startup
3684 == RestoreOnStartupBehavior::EmptyTab
3685 {
3686 return;
3687 }
3688
3689 if !self.buffer().read(cx).is_singleton() {
3690 return;
3691 }
3692
3693 let display_snapshot = self
3694 .display_map
3695 .update(cx, |display_map, cx| display_map.snapshot(cx));
3696 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3697 return;
3698 };
3699 let inmemory_folds = display_snapshot
3700 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3701 .map(|fold| {
3702 fold.range.start.text_anchor.to_point(&snapshot)
3703 ..fold.range.end.text_anchor.to_point(&snapshot)
3704 })
3705 .collect();
3706 self.update_restoration_data(cx, |data| {
3707 data.folds = inmemory_folds;
3708 });
3709
3710 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3711 return;
3712 };
3713
3714 // Get file path for path-based fold storage (survives tab close)
3715 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3716 project::File::from_dyn(buffer.read(cx).file())
3717 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3718 }) else {
3719 return;
3720 };
3721
3722 let background_executor = cx.background_executor().clone();
3723 const FINGERPRINT_LEN: usize = 32;
3724 let db_folds = display_snapshot
3725 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3726 .map(|fold| {
3727 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3728 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3729
3730 // Extract fingerprints - content at fold boundaries for validation on restore
3731 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3732 // content that might change independently.
3733 // start_fp: first min(32, fold_len) bytes of fold content
3734 // end_fp: last min(32, fold_len) bytes of fold content
3735 // Clip to character boundaries to handle multibyte UTF-8 characters.
3736 let fold_len = end - start;
3737 let start_fp_end = snapshot
3738 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3739 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3740 let end_fp_start = snapshot
3741 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3742 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3743
3744 (start, end, start_fp, end_fp)
3745 })
3746 .collect::<Vec<_>>();
3747 self.serialize_folds = cx.background_spawn(async move {
3748 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3749 if db_folds.is_empty() {
3750 // No folds - delete any persisted folds for this file
3751 DB.delete_file_folds(workspace_id, file_path)
3752 .await
3753 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3754 .log_err();
3755 } else {
3756 DB.save_file_folds(workspace_id, file_path, db_folds)
3757 .await
3758 .with_context(|| {
3759 format!("persisting file folds for workspace {workspace_id:?}")
3760 })
3761 .log_err();
3762 }
3763 });
3764 }
3765
3766 pub fn sync_selections(
3767 &mut self,
3768 other: Entity<Editor>,
3769 cx: &mut Context<Self>,
3770 ) -> gpui::Subscription {
3771 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3772 if !other_selections.is_empty() {
3773 self.selections
3774 .change_with(&self.display_snapshot(cx), |selections| {
3775 selections.select_anchors(other_selections);
3776 });
3777 }
3778
3779 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3780 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3781 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3782 if other_selections.is_empty() {
3783 return;
3784 }
3785 let snapshot = this.display_snapshot(cx);
3786 this.selections.change_with(&snapshot, |selections| {
3787 selections.select_anchors(other_selections);
3788 });
3789 }
3790 });
3791
3792 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3793 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3794 let these_selections = this.selections.disjoint_anchors().to_vec();
3795 if these_selections.is_empty() {
3796 return;
3797 }
3798 other.update(cx, |other_editor, cx| {
3799 let snapshot = other_editor.display_snapshot(cx);
3800 other_editor
3801 .selections
3802 .change_with(&snapshot, |selections| {
3803 selections.select_anchors(these_selections);
3804 })
3805 });
3806 }
3807 });
3808
3809 Subscription::join(other_subscription, this_subscription)
3810 }
3811
3812 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3813 if self.buffer().read(cx).is_singleton() {
3814 return;
3815 }
3816 let snapshot = self.buffer.read(cx).snapshot(cx);
3817 let buffer_ids: HashSet<BufferId> = self
3818 .selections
3819 .disjoint_anchor_ranges()
3820 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3821 .collect();
3822 for buffer_id in buffer_ids {
3823 self.unfold_buffer(buffer_id, cx);
3824 }
3825 }
3826
3827 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3828 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3829 /// effects of selection change occur at the end of the transaction.
3830 pub fn change_selections<R>(
3831 &mut self,
3832 effects: SelectionEffects,
3833 window: &mut Window,
3834 cx: &mut Context<Self>,
3835 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3836 ) -> R {
3837 let snapshot = self.display_snapshot(cx);
3838 if let Some(state) = &mut self.deferred_selection_effects_state {
3839 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3840 state.effects.completions = effects.completions;
3841 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3842 let (changed, result) = self.selections.change_with(&snapshot, change);
3843 state.changed |= changed;
3844 return result;
3845 }
3846 let mut state = DeferredSelectionEffectsState {
3847 changed: false,
3848 effects,
3849 old_cursor_position: self.selections.newest_anchor().head(),
3850 history_entry: SelectionHistoryEntry {
3851 selections: self.selections.disjoint_anchors_arc(),
3852 select_next_state: self.select_next_state.clone(),
3853 select_prev_state: self.select_prev_state.clone(),
3854 add_selections_state: self.add_selections_state.clone(),
3855 },
3856 };
3857 let (changed, result) = self.selections.change_with(&snapshot, change);
3858 state.changed = state.changed || changed;
3859 if self.defer_selection_effects {
3860 self.deferred_selection_effects_state = Some(state);
3861 } else {
3862 self.apply_selection_effects(state, window, cx);
3863 }
3864 result
3865 }
3866
3867 /// Defers the effects of selection change, so that the effects of multiple calls to
3868 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3869 /// to selection history and the state of popovers based on selection position aren't
3870 /// erroneously updated.
3871 pub fn with_selection_effects_deferred<R>(
3872 &mut self,
3873 window: &mut Window,
3874 cx: &mut Context<Self>,
3875 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3876 ) -> R {
3877 let already_deferred = self.defer_selection_effects;
3878 self.defer_selection_effects = true;
3879 let result = update(self, window, cx);
3880 if !already_deferred {
3881 self.defer_selection_effects = false;
3882 if let Some(state) = self.deferred_selection_effects_state.take() {
3883 self.apply_selection_effects(state, window, cx);
3884 }
3885 }
3886 result
3887 }
3888
3889 fn apply_selection_effects(
3890 &mut self,
3891 state: DeferredSelectionEffectsState,
3892 window: &mut Window,
3893 cx: &mut Context<Self>,
3894 ) {
3895 if state.changed {
3896 self.selection_history.push(state.history_entry);
3897
3898 if let Some(autoscroll) = state.effects.scroll {
3899 self.request_autoscroll(autoscroll, cx);
3900 }
3901
3902 let old_cursor_position = &state.old_cursor_position;
3903
3904 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3905
3906 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3907 self.show_signature_help_auto(window, cx);
3908 }
3909 }
3910 }
3911
3912 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3913 where
3914 I: IntoIterator<Item = (Range<S>, T)>,
3915 S: ToOffset,
3916 T: Into<Arc<str>>,
3917 {
3918 if self.read_only(cx) {
3919 return;
3920 }
3921
3922 self.buffer
3923 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3924 }
3925
3926 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3927 where
3928 I: IntoIterator<Item = (Range<S>, T)>,
3929 S: ToOffset,
3930 T: Into<Arc<str>>,
3931 {
3932 if self.read_only(cx) {
3933 return;
3934 }
3935
3936 self.buffer.update(cx, |buffer, cx| {
3937 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3938 });
3939 }
3940
3941 pub fn edit_with_block_indent<I, S, T>(
3942 &mut self,
3943 edits: I,
3944 original_indent_columns: Vec<Option<u32>>,
3945 cx: &mut Context<Self>,
3946 ) where
3947 I: IntoIterator<Item = (Range<S>, T)>,
3948 S: ToOffset,
3949 T: Into<Arc<str>>,
3950 {
3951 if self.read_only(cx) {
3952 return;
3953 }
3954
3955 self.buffer.update(cx, |buffer, cx| {
3956 buffer.edit(
3957 edits,
3958 Some(AutoindentMode::Block {
3959 original_indent_columns,
3960 }),
3961 cx,
3962 )
3963 });
3964 }
3965
3966 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3967 self.hide_context_menu(window, cx);
3968
3969 match phase {
3970 SelectPhase::Begin {
3971 position,
3972 add,
3973 click_count,
3974 } => self.begin_selection(position, add, click_count, window, cx),
3975 SelectPhase::BeginColumnar {
3976 position,
3977 goal_column,
3978 reset,
3979 mode,
3980 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3981 SelectPhase::Extend {
3982 position,
3983 click_count,
3984 } => self.extend_selection(position, click_count, window, cx),
3985 SelectPhase::Update {
3986 position,
3987 goal_column,
3988 scroll_delta,
3989 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3990 SelectPhase::End => self.end_selection(window, cx),
3991 }
3992 }
3993
3994 fn extend_selection(
3995 &mut self,
3996 position: DisplayPoint,
3997 click_count: usize,
3998 window: &mut Window,
3999 cx: &mut Context<Self>,
4000 ) {
4001 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4002 let tail = self
4003 .selections
4004 .newest::<MultiBufferOffset>(&display_map)
4005 .tail();
4006 let click_count = click_count.max(match self.selections.select_mode() {
4007 SelectMode::Character => 1,
4008 SelectMode::Word(_) => 2,
4009 SelectMode::Line(_) => 3,
4010 SelectMode::All => 4,
4011 });
4012 self.begin_selection(position, false, click_count, window, cx);
4013
4014 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4015
4016 let current_selection = match self.selections.select_mode() {
4017 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4018 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4019 };
4020
4021 let mut pending_selection = self
4022 .selections
4023 .pending_anchor()
4024 .cloned()
4025 .expect("extend_selection not called with pending selection");
4026
4027 if pending_selection
4028 .start
4029 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4030 == Ordering::Greater
4031 {
4032 pending_selection.start = current_selection.start;
4033 }
4034 if pending_selection
4035 .end
4036 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4037 == Ordering::Less
4038 {
4039 pending_selection.end = current_selection.end;
4040 pending_selection.reversed = true;
4041 }
4042
4043 let mut pending_mode = self.selections.pending_mode().unwrap();
4044 match &mut pending_mode {
4045 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4046 _ => {}
4047 }
4048
4049 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4050 SelectionEffects::scroll(Autoscroll::fit())
4051 } else {
4052 SelectionEffects::no_scroll()
4053 };
4054
4055 self.change_selections(effects, window, cx, |s| {
4056 s.set_pending(pending_selection.clone(), pending_mode);
4057 s.set_is_extending(true);
4058 });
4059 }
4060
4061 fn begin_selection(
4062 &mut self,
4063 position: DisplayPoint,
4064 add: bool,
4065 click_count: usize,
4066 window: &mut Window,
4067 cx: &mut Context<Self>,
4068 ) {
4069 if !self.focus_handle.is_focused(window) {
4070 self.last_focused_descendant = None;
4071 window.focus(&self.focus_handle, cx);
4072 }
4073
4074 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4075 let buffer = display_map.buffer_snapshot();
4076 let position = display_map.clip_point(position, Bias::Left);
4077
4078 let start;
4079 let end;
4080 let mode;
4081 let mut auto_scroll;
4082 match click_count {
4083 1 => {
4084 start = buffer.anchor_before(position.to_point(&display_map));
4085 end = start;
4086 mode = SelectMode::Character;
4087 auto_scroll = true;
4088 }
4089 2 => {
4090 let position = display_map
4091 .clip_point(position, Bias::Left)
4092 .to_offset(&display_map, Bias::Left);
4093 let (range, _) = buffer.surrounding_word(position, None);
4094 start = buffer.anchor_before(range.start);
4095 end = buffer.anchor_before(range.end);
4096 mode = SelectMode::Word(start..end);
4097 auto_scroll = true;
4098 }
4099 3 => {
4100 let position = display_map
4101 .clip_point(position, Bias::Left)
4102 .to_point(&display_map);
4103 let line_start = display_map.prev_line_boundary(position).0;
4104 let next_line_start = buffer.clip_point(
4105 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4106 Bias::Left,
4107 );
4108 start = buffer.anchor_before(line_start);
4109 end = buffer.anchor_before(next_line_start);
4110 mode = SelectMode::Line(start..end);
4111 auto_scroll = true;
4112 }
4113 _ => {
4114 start = buffer.anchor_before(MultiBufferOffset(0));
4115 end = buffer.anchor_before(buffer.len());
4116 mode = SelectMode::All;
4117 auto_scroll = false;
4118 }
4119 }
4120 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4121
4122 let point_to_delete: Option<usize> = {
4123 let selected_points: Vec<Selection<Point>> =
4124 self.selections.disjoint_in_range(start..end, &display_map);
4125
4126 if !add || click_count > 1 {
4127 None
4128 } else if !selected_points.is_empty() {
4129 Some(selected_points[0].id)
4130 } else {
4131 let clicked_point_already_selected =
4132 self.selections.disjoint_anchors().iter().find(|selection| {
4133 selection.start.to_point(buffer) == start.to_point(buffer)
4134 || selection.end.to_point(buffer) == end.to_point(buffer)
4135 });
4136
4137 clicked_point_already_selected.map(|selection| selection.id)
4138 }
4139 };
4140
4141 let selections_count = self.selections.count();
4142 let effects = if auto_scroll {
4143 SelectionEffects::default()
4144 } else {
4145 SelectionEffects::no_scroll()
4146 };
4147
4148 self.change_selections(effects, window, cx, |s| {
4149 if let Some(point_to_delete) = point_to_delete {
4150 s.delete(point_to_delete);
4151
4152 if selections_count == 1 {
4153 s.set_pending_anchor_range(start..end, mode);
4154 }
4155 } else {
4156 if !add {
4157 s.clear_disjoint();
4158 }
4159
4160 s.set_pending_anchor_range(start..end, mode);
4161 }
4162 });
4163 }
4164
4165 fn begin_columnar_selection(
4166 &mut self,
4167 position: DisplayPoint,
4168 goal_column: u32,
4169 reset: bool,
4170 mode: ColumnarMode,
4171 window: &mut Window,
4172 cx: &mut Context<Self>,
4173 ) {
4174 if !self.focus_handle.is_focused(window) {
4175 self.last_focused_descendant = None;
4176 window.focus(&self.focus_handle, cx);
4177 }
4178
4179 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4180
4181 if reset {
4182 let pointer_position = display_map
4183 .buffer_snapshot()
4184 .anchor_before(position.to_point(&display_map));
4185
4186 self.change_selections(
4187 SelectionEffects::scroll(Autoscroll::newest()),
4188 window,
4189 cx,
4190 |s| {
4191 s.clear_disjoint();
4192 s.set_pending_anchor_range(
4193 pointer_position..pointer_position,
4194 SelectMode::Character,
4195 );
4196 },
4197 );
4198 };
4199
4200 let tail = self.selections.newest::<Point>(&display_map).tail();
4201 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4202 self.columnar_selection_state = match mode {
4203 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4204 selection_tail: selection_anchor,
4205 display_point: if reset {
4206 if position.column() != goal_column {
4207 Some(DisplayPoint::new(position.row(), goal_column))
4208 } else {
4209 None
4210 }
4211 } else {
4212 None
4213 },
4214 }),
4215 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4216 selection_tail: selection_anchor,
4217 }),
4218 };
4219
4220 if !reset {
4221 self.select_columns(position, goal_column, &display_map, window, cx);
4222 }
4223 }
4224
4225 fn update_selection(
4226 &mut self,
4227 position: DisplayPoint,
4228 goal_column: u32,
4229 scroll_delta: gpui::Point<f32>,
4230 window: &mut Window,
4231 cx: &mut Context<Self>,
4232 ) {
4233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4234
4235 if self.columnar_selection_state.is_some() {
4236 self.select_columns(position, goal_column, &display_map, window, cx);
4237 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4238 let buffer = display_map.buffer_snapshot();
4239 let head;
4240 let tail;
4241 let mode = self.selections.pending_mode().unwrap();
4242 match &mode {
4243 SelectMode::Character => {
4244 head = position.to_point(&display_map);
4245 tail = pending.tail().to_point(buffer);
4246 }
4247 SelectMode::Word(original_range) => {
4248 let offset = display_map
4249 .clip_point(position, Bias::Left)
4250 .to_offset(&display_map, Bias::Left);
4251 let original_range = original_range.to_offset(buffer);
4252
4253 let head_offset = if buffer.is_inside_word(offset, None)
4254 || original_range.contains(&offset)
4255 {
4256 let (word_range, _) = buffer.surrounding_word(offset, None);
4257 if word_range.start < original_range.start {
4258 word_range.start
4259 } else {
4260 word_range.end
4261 }
4262 } else {
4263 offset
4264 };
4265
4266 head = head_offset.to_point(buffer);
4267 if head_offset <= original_range.start {
4268 tail = original_range.end.to_point(buffer);
4269 } else {
4270 tail = original_range.start.to_point(buffer);
4271 }
4272 }
4273 SelectMode::Line(original_range) => {
4274 let original_range = original_range.to_point(display_map.buffer_snapshot());
4275
4276 let position = display_map
4277 .clip_point(position, Bias::Left)
4278 .to_point(&display_map);
4279 let line_start = display_map.prev_line_boundary(position).0;
4280 let next_line_start = buffer.clip_point(
4281 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4282 Bias::Left,
4283 );
4284
4285 if line_start < original_range.start {
4286 head = line_start
4287 } else {
4288 head = next_line_start
4289 }
4290
4291 if head <= original_range.start {
4292 tail = original_range.end;
4293 } else {
4294 tail = original_range.start;
4295 }
4296 }
4297 SelectMode::All => {
4298 return;
4299 }
4300 };
4301
4302 if head < tail {
4303 pending.start = buffer.anchor_before(head);
4304 pending.end = buffer.anchor_before(tail);
4305 pending.reversed = true;
4306 } else {
4307 pending.start = buffer.anchor_before(tail);
4308 pending.end = buffer.anchor_before(head);
4309 pending.reversed = false;
4310 }
4311
4312 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4313 s.set_pending(pending.clone(), mode);
4314 });
4315 } else {
4316 log::error!("update_selection dispatched with no pending selection");
4317 return;
4318 }
4319
4320 self.apply_scroll_delta(scroll_delta, window, cx);
4321 cx.notify();
4322 }
4323
4324 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4325 self.columnar_selection_state.take();
4326 if let Some(pending_mode) = self.selections.pending_mode() {
4327 let selections = self
4328 .selections
4329 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4330 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4331 s.select(selections);
4332 s.clear_pending();
4333 if s.is_extending() {
4334 s.set_is_extending(false);
4335 } else {
4336 s.set_select_mode(pending_mode);
4337 }
4338 });
4339 }
4340 }
4341
4342 fn select_columns(
4343 &mut self,
4344 head: DisplayPoint,
4345 goal_column: u32,
4346 display_map: &DisplaySnapshot,
4347 window: &mut Window,
4348 cx: &mut Context<Self>,
4349 ) {
4350 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4351 return;
4352 };
4353
4354 let tail = match columnar_state {
4355 ColumnarSelectionState::FromMouse {
4356 selection_tail,
4357 display_point,
4358 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4359 ColumnarSelectionState::FromSelection { selection_tail } => {
4360 selection_tail.to_display_point(display_map)
4361 }
4362 };
4363
4364 let start_row = cmp::min(tail.row(), head.row());
4365 let end_row = cmp::max(tail.row(), head.row());
4366 let start_column = cmp::min(tail.column(), goal_column);
4367 let end_column = cmp::max(tail.column(), goal_column);
4368 let reversed = start_column < tail.column();
4369
4370 let selection_ranges = (start_row.0..=end_row.0)
4371 .map(DisplayRow)
4372 .filter_map(|row| {
4373 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4374 || start_column <= display_map.line_len(row))
4375 && !display_map.is_block_line(row)
4376 {
4377 let start = display_map
4378 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4379 .to_point(display_map);
4380 let end = display_map
4381 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4382 .to_point(display_map);
4383 if reversed {
4384 Some(end..start)
4385 } else {
4386 Some(start..end)
4387 }
4388 } else {
4389 None
4390 }
4391 })
4392 .collect::<Vec<_>>();
4393 if selection_ranges.is_empty() {
4394 return;
4395 }
4396
4397 let ranges = match columnar_state {
4398 ColumnarSelectionState::FromMouse { .. } => {
4399 let mut non_empty_ranges = selection_ranges
4400 .iter()
4401 .filter(|selection_range| selection_range.start != selection_range.end)
4402 .peekable();
4403 if non_empty_ranges.peek().is_some() {
4404 non_empty_ranges.cloned().collect()
4405 } else {
4406 selection_ranges
4407 }
4408 }
4409 _ => selection_ranges,
4410 };
4411
4412 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4413 s.select_ranges(ranges);
4414 });
4415 cx.notify();
4416 }
4417
4418 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4419 self.selections
4420 .all_adjusted(snapshot)
4421 .iter()
4422 .any(|selection| !selection.is_empty())
4423 }
4424
4425 pub fn has_pending_nonempty_selection(&self) -> bool {
4426 let pending_nonempty_selection = match self.selections.pending_anchor() {
4427 Some(Selection { start, end, .. }) => start != end,
4428 None => false,
4429 };
4430
4431 pending_nonempty_selection
4432 || (self.columnar_selection_state.is_some()
4433 && self.selections.disjoint_anchors().len() > 1)
4434 }
4435
4436 pub fn has_pending_selection(&self) -> bool {
4437 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4438 }
4439
4440 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4441 self.selection_mark_mode = false;
4442 self.selection_drag_state = SelectionDragState::None;
4443
4444 if self.dismiss_menus_and_popups(true, window, cx) {
4445 cx.notify();
4446 return;
4447 }
4448 if self.clear_expanded_diff_hunks(cx) {
4449 cx.notify();
4450 return;
4451 }
4452 if self.show_git_blame_gutter {
4453 self.show_git_blame_gutter = false;
4454 cx.notify();
4455 return;
4456 }
4457
4458 if self.mode.is_full()
4459 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4460 {
4461 cx.notify();
4462 return;
4463 }
4464
4465 cx.propagate();
4466 }
4467
4468 pub fn dismiss_menus_and_popups(
4469 &mut self,
4470 is_user_requested: bool,
4471 window: &mut Window,
4472 cx: &mut Context<Self>,
4473 ) -> bool {
4474 let mut dismissed = false;
4475
4476 dismissed |= self.take_rename(false, window, cx).is_some();
4477 dismissed |= self.hide_blame_popover(true, cx);
4478 dismissed |= hide_hover(self, cx);
4479 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4480 dismissed |= self.hide_context_menu(window, cx).is_some();
4481 dismissed |= self.mouse_context_menu.take().is_some();
4482 dismissed |= is_user_requested
4483 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4484 dismissed |= self.snippet_stack.pop().is_some();
4485 if self.diff_review_drag_state.is_some() {
4486 self.cancel_diff_review_drag(cx);
4487 dismissed = true;
4488 }
4489 if !self.diff_review_overlays.is_empty() {
4490 self.dismiss_all_diff_review_overlays(cx);
4491 dismissed = true;
4492 }
4493
4494 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4495 self.dismiss_diagnostics(cx);
4496 dismissed = true;
4497 }
4498
4499 dismissed
4500 }
4501
4502 fn linked_editing_ranges_for(
4503 &self,
4504 selection: Range<text::Anchor>,
4505 cx: &App,
4506 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4507 if self.linked_edit_ranges.is_empty() {
4508 return None;
4509 }
4510 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4511 selection.end.buffer_id.and_then(|end_buffer_id| {
4512 if selection.start.buffer_id != Some(end_buffer_id) {
4513 return None;
4514 }
4515 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4516 let snapshot = buffer.read(cx).snapshot();
4517 self.linked_edit_ranges
4518 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4519 .map(|ranges| (ranges, snapshot, buffer))
4520 })?;
4521 use text::ToOffset as TO;
4522 // find offset from the start of current range to current cursor position
4523 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4524
4525 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4526 let start_difference = start_offset - start_byte_offset;
4527 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4528 let end_difference = end_offset - start_byte_offset;
4529
4530 // Current range has associated linked ranges.
4531 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4532 for range in linked_ranges.iter() {
4533 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4534 let end_offset = start_offset + end_difference;
4535 let start_offset = start_offset + start_difference;
4536 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4537 continue;
4538 }
4539 if self.selections.disjoint_anchor_ranges().any(|s| {
4540 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4541 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4542 {
4543 return false;
4544 }
4545 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4546 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4547 }) {
4548 continue;
4549 }
4550 let start = buffer_snapshot.anchor_after(start_offset);
4551 let end = buffer_snapshot.anchor_after(end_offset);
4552 linked_edits
4553 .entry(buffer.clone())
4554 .or_default()
4555 .push(start..end);
4556 }
4557 Some(linked_edits)
4558 }
4559
4560 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4561 let text: Arc<str> = text.into();
4562
4563 if self.read_only(cx) {
4564 return;
4565 }
4566
4567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4568
4569 self.unfold_buffers_with_selections(cx);
4570
4571 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4572 let mut bracket_inserted = false;
4573 let mut edits = Vec::new();
4574 let mut linked_edits = LinkedEdits::new();
4575 let mut new_selections = Vec::with_capacity(selections.len());
4576 let mut new_autoclose_regions = Vec::new();
4577 let snapshot = self.buffer.read(cx).read(cx);
4578 let mut clear_linked_edit_ranges = false;
4579 let mut all_selections_read_only = true;
4580 let mut has_adjacent_edits = false;
4581 let mut in_adjacent_group = false;
4582
4583 let mut regions = self
4584 .selections_with_autoclose_regions(selections, &snapshot)
4585 .peekable();
4586
4587 while let Some((selection, autoclose_region)) = regions.next() {
4588 if snapshot
4589 .point_to_buffer_point(selection.head())
4590 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4591 {
4592 continue;
4593 }
4594 if snapshot
4595 .point_to_buffer_point(selection.tail())
4596 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4597 {
4598 // note, ideally we'd clip the tail to the closest writeable region towards the head
4599 continue;
4600 }
4601 all_selections_read_only = false;
4602
4603 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4604 // Determine if the inserted text matches the opening or closing
4605 // bracket of any of this language's bracket pairs.
4606 let mut bracket_pair = None;
4607 let mut is_bracket_pair_start = false;
4608 let mut is_bracket_pair_end = false;
4609 if !text.is_empty() {
4610 let mut bracket_pair_matching_end = None;
4611 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4612 // and they are removing the character that triggered IME popup.
4613 for (pair, enabled) in scope.brackets() {
4614 if !pair.close && !pair.surround {
4615 continue;
4616 }
4617
4618 if enabled && pair.start.ends_with(text.as_ref()) {
4619 let prefix_len = pair.start.len() - text.len();
4620 let preceding_text_matches_prefix = prefix_len == 0
4621 || (selection.start.column >= (prefix_len as u32)
4622 && snapshot.contains_str_at(
4623 Point::new(
4624 selection.start.row,
4625 selection.start.column - (prefix_len as u32),
4626 ),
4627 &pair.start[..prefix_len],
4628 ));
4629 if preceding_text_matches_prefix {
4630 bracket_pair = Some(pair.clone());
4631 is_bracket_pair_start = true;
4632 break;
4633 }
4634 }
4635 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4636 {
4637 // take first bracket pair matching end, but don't break in case a later bracket
4638 // pair matches start
4639 bracket_pair_matching_end = Some(pair.clone());
4640 }
4641 }
4642 if let Some(end) = bracket_pair_matching_end
4643 && bracket_pair.is_none()
4644 {
4645 bracket_pair = Some(end);
4646 is_bracket_pair_end = true;
4647 }
4648 }
4649
4650 if let Some(bracket_pair) = bracket_pair {
4651 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4652 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4653 let auto_surround =
4654 self.use_auto_surround && snapshot_settings.use_auto_surround;
4655 if selection.is_empty() {
4656 if is_bracket_pair_start {
4657 // If the inserted text is a suffix of an opening bracket and the
4658 // selection is preceded by the rest of the opening bracket, then
4659 // insert the closing bracket.
4660 let following_text_allows_autoclose = snapshot
4661 .chars_at(selection.start)
4662 .next()
4663 .is_none_or(|c| scope.should_autoclose_before(c));
4664
4665 let preceding_text_allows_autoclose = selection.start.column == 0
4666 || snapshot
4667 .reversed_chars_at(selection.start)
4668 .next()
4669 .is_none_or(|c| {
4670 bracket_pair.start != bracket_pair.end
4671 || !snapshot
4672 .char_classifier_at(selection.start)
4673 .is_word(c)
4674 });
4675
4676 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4677 && bracket_pair.start.len() == 1
4678 {
4679 let target = bracket_pair.start.chars().next().unwrap();
4680 let mut byte_offset = 0u32;
4681 let current_line_count = snapshot
4682 .reversed_chars_at(selection.start)
4683 .take_while(|&c| c != '\n')
4684 .filter(|c| {
4685 byte_offset += c.len_utf8() as u32;
4686 if *c != target {
4687 return false;
4688 }
4689
4690 let point = Point::new(
4691 selection.start.row,
4692 selection.start.column.saturating_sub(byte_offset),
4693 );
4694
4695 let is_enabled = snapshot
4696 .language_scope_at(point)
4697 .and_then(|scope| {
4698 scope
4699 .brackets()
4700 .find(|(pair, _)| {
4701 pair.start == bracket_pair.start
4702 })
4703 .map(|(_, enabled)| enabled)
4704 })
4705 .unwrap_or(true);
4706
4707 let is_delimiter = snapshot
4708 .language_scope_at(Point::new(
4709 point.row,
4710 point.column + 1,
4711 ))
4712 .and_then(|scope| {
4713 scope
4714 .brackets()
4715 .find(|(pair, _)| {
4716 pair.start == bracket_pair.start
4717 })
4718 .map(|(_, enabled)| !enabled)
4719 })
4720 .unwrap_or(false);
4721
4722 is_enabled && !is_delimiter
4723 })
4724 .count();
4725 current_line_count % 2 == 1
4726 } else {
4727 false
4728 };
4729
4730 if autoclose
4731 && bracket_pair.close
4732 && following_text_allows_autoclose
4733 && preceding_text_allows_autoclose
4734 && !is_closing_quote
4735 {
4736 let anchor = snapshot.anchor_before(selection.end);
4737 new_selections.push((selection.map(|_| anchor), text.len()));
4738 new_autoclose_regions.push((
4739 anchor,
4740 text.len(),
4741 selection.id,
4742 bracket_pair.clone(),
4743 ));
4744 edits.push((
4745 selection.range(),
4746 format!("{}{}", text, bracket_pair.end).into(),
4747 ));
4748 bracket_inserted = true;
4749 continue;
4750 }
4751 }
4752
4753 if let Some(region) = autoclose_region {
4754 // If the selection is followed by an auto-inserted closing bracket,
4755 // then don't insert that closing bracket again; just move the selection
4756 // past the closing bracket.
4757 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4758 && text.as_ref() == region.pair.end.as_str()
4759 && snapshot.contains_str_at(region.range.end, text.as_ref());
4760 if should_skip {
4761 let anchor = snapshot.anchor_after(selection.end);
4762 new_selections
4763 .push((selection.map(|_| anchor), region.pair.end.len()));
4764 continue;
4765 }
4766 }
4767
4768 let always_treat_brackets_as_autoclosed = snapshot
4769 .language_settings_at(selection.start, cx)
4770 .always_treat_brackets_as_autoclosed;
4771 if always_treat_brackets_as_autoclosed
4772 && is_bracket_pair_end
4773 && snapshot.contains_str_at(selection.end, text.as_ref())
4774 {
4775 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4776 // and the inserted text is a closing bracket and the selection is followed
4777 // by the closing bracket then move the selection past the closing bracket.
4778 let anchor = snapshot.anchor_after(selection.end);
4779 new_selections.push((selection.map(|_| anchor), text.len()));
4780 continue;
4781 }
4782 }
4783 // If an opening bracket is 1 character long and is typed while
4784 // text is selected, then surround that text with the bracket pair.
4785 else if auto_surround
4786 && bracket_pair.surround
4787 && is_bracket_pair_start
4788 && bracket_pair.start.chars().count() == 1
4789 {
4790 edits.push((selection.start..selection.start, text.clone()));
4791 edits.push((
4792 selection.end..selection.end,
4793 bracket_pair.end.as_str().into(),
4794 ));
4795 bracket_inserted = true;
4796 new_selections.push((
4797 Selection {
4798 id: selection.id,
4799 start: snapshot.anchor_after(selection.start),
4800 end: snapshot.anchor_before(selection.end),
4801 reversed: selection.reversed,
4802 goal: selection.goal,
4803 },
4804 0,
4805 ));
4806 continue;
4807 }
4808 }
4809 }
4810
4811 if self.auto_replace_emoji_shortcode
4812 && selection.is_empty()
4813 && text.as_ref().ends_with(':')
4814 && let Some(possible_emoji_short_code) =
4815 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4816 && !possible_emoji_short_code.is_empty()
4817 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4818 {
4819 let emoji_shortcode_start = Point::new(
4820 selection.start.row,
4821 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4822 );
4823
4824 // Remove shortcode from buffer
4825 edits.push((
4826 emoji_shortcode_start..selection.start,
4827 "".to_string().into(),
4828 ));
4829 new_selections.push((
4830 Selection {
4831 id: selection.id,
4832 start: snapshot.anchor_after(emoji_shortcode_start),
4833 end: snapshot.anchor_before(selection.start),
4834 reversed: selection.reversed,
4835 goal: selection.goal,
4836 },
4837 0,
4838 ));
4839
4840 // Insert emoji
4841 let selection_start_anchor = snapshot.anchor_after(selection.start);
4842 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4843 edits.push((selection.start..selection.end, emoji.to_string().into()));
4844
4845 continue;
4846 }
4847
4848 let next_is_adjacent = regions
4849 .peek()
4850 .is_some_and(|(next, _)| selection.end == next.start);
4851
4852 // If not handling any auto-close operation, then just replace the selected
4853 // text with the given input and move the selection to the end of the
4854 // newly inserted text.
4855 let anchor = if in_adjacent_group || next_is_adjacent {
4856 // After edits the right bias would shift those anchor to the next visible fragment
4857 // but we want to resolve to the previous one
4858 snapshot.anchor_before(selection.end)
4859 } else {
4860 snapshot.anchor_after(selection.end)
4861 };
4862
4863 if !self.linked_edit_ranges.is_empty() {
4864 let start_anchor = snapshot.anchor_before(selection.start);
4865
4866 let is_word_char = text.chars().next().is_none_or(|char| {
4867 let classifier = snapshot
4868 .char_classifier_at(start_anchor.to_offset(&snapshot))
4869 .scope_context(Some(CharScopeContext::LinkedEdit));
4870 classifier.is_word(char)
4871 });
4872 let is_dot = text.as_ref() == ".";
4873 let should_apply_linked_edit = is_word_char || is_dot;
4874
4875 if should_apply_linked_edit {
4876 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
4877 linked_edits.push(&self, anchor_range, text.clone(), cx);
4878 } else {
4879 clear_linked_edit_ranges = true;
4880 }
4881 }
4882
4883 new_selections.push((selection.map(|_| anchor), 0));
4884 edits.push((selection.start..selection.end, text.clone()));
4885
4886 has_adjacent_edits |= next_is_adjacent;
4887 in_adjacent_group = next_is_adjacent;
4888 }
4889
4890 if all_selections_read_only {
4891 return;
4892 }
4893
4894 drop(regions);
4895 drop(snapshot);
4896
4897 self.transact(window, cx, |this, window, cx| {
4898 if clear_linked_edit_ranges {
4899 this.linked_edit_ranges.clear();
4900 }
4901 let initial_buffer_versions =
4902 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4903
4904 this.buffer.update(cx, |buffer, cx| {
4905 if has_adjacent_edits {
4906 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4907 } else {
4908 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4909 }
4910 });
4911 linked_edits.apply(cx);
4912 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4913 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4914 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4915 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4916 new_anchor_selections,
4917 &map,
4918 )
4919 .zip(new_selection_deltas)
4920 .map(|(selection, delta)| Selection {
4921 id: selection.id,
4922 start: selection.start + delta,
4923 end: selection.end + delta,
4924 reversed: selection.reversed,
4925 goal: SelectionGoal::None,
4926 })
4927 .collect::<Vec<_>>();
4928
4929 let mut i = 0;
4930 for (position, delta, selection_id, pair) in new_autoclose_regions {
4931 let position = position.to_offset(map.buffer_snapshot()) + delta;
4932 let start = map.buffer_snapshot().anchor_before(position);
4933 let end = map.buffer_snapshot().anchor_after(position);
4934 while let Some(existing_state) = this.autoclose_regions.get(i) {
4935 match existing_state
4936 .range
4937 .start
4938 .cmp(&start, map.buffer_snapshot())
4939 {
4940 Ordering::Less => i += 1,
4941 Ordering::Greater => break,
4942 Ordering::Equal => {
4943 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4944 Ordering::Less => i += 1,
4945 Ordering::Equal => break,
4946 Ordering::Greater => break,
4947 }
4948 }
4949 }
4950 }
4951 this.autoclose_regions.insert(
4952 i,
4953 AutocloseRegion {
4954 selection_id,
4955 range: start..end,
4956 pair,
4957 },
4958 );
4959 }
4960
4961 let had_active_edit_prediction = this.has_active_edit_prediction();
4962 this.change_selections(
4963 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4964 window,
4965 cx,
4966 |s| s.select(new_selections),
4967 );
4968
4969 if !bracket_inserted
4970 && let Some(on_type_format_task) =
4971 this.trigger_on_type_formatting(text.to_string(), window, cx)
4972 {
4973 on_type_format_task.detach_and_log_err(cx);
4974 }
4975
4976 let editor_settings = EditorSettings::get_global(cx);
4977 if bracket_inserted
4978 && (editor_settings.auto_signature_help
4979 || editor_settings.show_signature_help_after_edits)
4980 {
4981 this.show_signature_help(&ShowSignatureHelp, window, cx);
4982 }
4983
4984 let trigger_in_words =
4985 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4986 if this.hard_wrap.is_some() {
4987 let latest: Range<Point> = this.selections.newest(&map).range();
4988 if latest.is_empty()
4989 && this
4990 .buffer()
4991 .read(cx)
4992 .snapshot(cx)
4993 .line_len(MultiBufferRow(latest.start.row))
4994 == latest.start.column
4995 {
4996 this.rewrap_impl(
4997 RewrapOptions {
4998 override_language_settings: true,
4999 preserve_existing_whitespace: true,
5000 },
5001 cx,
5002 )
5003 }
5004 }
5005 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5006 refresh_linked_ranges(this, window, cx);
5007 this.refresh_edit_prediction(true, false, window, cx);
5008 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5009 });
5010 }
5011
5012 fn find_possible_emoji_shortcode_at_position(
5013 snapshot: &MultiBufferSnapshot,
5014 position: Point,
5015 ) -> Option<String> {
5016 let mut chars = Vec::new();
5017 let mut found_colon = false;
5018 for char in snapshot.reversed_chars_at(position).take(100) {
5019 // Found a possible emoji shortcode in the middle of the buffer
5020 if found_colon {
5021 if char.is_whitespace() {
5022 chars.reverse();
5023 return Some(chars.iter().collect());
5024 }
5025 // If the previous character is not a whitespace, we are in the middle of a word
5026 // and we only want to complete the shortcode if the word is made up of other emojis
5027 let mut containing_word = String::new();
5028 for ch in snapshot
5029 .reversed_chars_at(position)
5030 .skip(chars.len() + 1)
5031 .take(100)
5032 {
5033 if ch.is_whitespace() {
5034 break;
5035 }
5036 containing_word.push(ch);
5037 }
5038 let containing_word = containing_word.chars().rev().collect::<String>();
5039 if util::word_consists_of_emojis(containing_word.as_str()) {
5040 chars.reverse();
5041 return Some(chars.iter().collect());
5042 }
5043 }
5044
5045 if char.is_whitespace() || !char.is_ascii() {
5046 return None;
5047 }
5048 if char == ':' {
5049 found_colon = true;
5050 } else {
5051 chars.push(char);
5052 }
5053 }
5054 // Found a possible emoji shortcode at the beginning of the buffer
5055 chars.reverse();
5056 Some(chars.iter().collect())
5057 }
5058
5059 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5060 if self.read_only(cx) {
5061 return;
5062 }
5063
5064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5065 self.transact(window, cx, |this, window, cx| {
5066 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5067 let selections = this
5068 .selections
5069 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5070 let multi_buffer = this.buffer.read(cx);
5071 let buffer = multi_buffer.snapshot(cx);
5072 selections
5073 .iter()
5074 .map(|selection| {
5075 let start_point = selection.start.to_point(&buffer);
5076 let mut existing_indent =
5077 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5078 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5079 let start = selection.start;
5080 let end = selection.end;
5081 let selection_is_empty = start == end;
5082 let language_scope = buffer.language_scope_at(start);
5083 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5084 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5085 &buffer,
5086 start..end,
5087 language,
5088 )
5089 || NewlineConfig::insert_extra_newline_tree_sitter(
5090 &buffer,
5091 start..end,
5092 );
5093
5094 let mut newline_config = NewlineConfig::Newline {
5095 additional_indent: IndentSize::spaces(0),
5096 extra_line_additional_indent: if needs_extra_newline {
5097 Some(IndentSize::spaces(0))
5098 } else {
5099 None
5100 },
5101 prevent_auto_indent: false,
5102 };
5103
5104 let comment_delimiter = maybe!({
5105 if !selection_is_empty {
5106 return None;
5107 }
5108
5109 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5110 return None;
5111 }
5112
5113 return comment_delimiter_for_newline(
5114 &start_point,
5115 &buffer,
5116 language,
5117 );
5118 });
5119
5120 let doc_delimiter = maybe!({
5121 if !selection_is_empty {
5122 return None;
5123 }
5124
5125 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5126 return None;
5127 }
5128
5129 return documentation_delimiter_for_newline(
5130 &start_point,
5131 &buffer,
5132 language,
5133 &mut newline_config,
5134 );
5135 });
5136
5137 let list_delimiter = maybe!({
5138 if !selection_is_empty {
5139 return None;
5140 }
5141
5142 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5143 return None;
5144 }
5145
5146 return list_delimiter_for_newline(
5147 &start_point,
5148 &buffer,
5149 language,
5150 &mut newline_config,
5151 );
5152 });
5153
5154 (
5155 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5156 newline_config,
5157 )
5158 } else {
5159 (
5160 None,
5161 NewlineConfig::Newline {
5162 additional_indent: IndentSize::spaces(0),
5163 extra_line_additional_indent: None,
5164 prevent_auto_indent: false,
5165 },
5166 )
5167 };
5168
5169 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5170 NewlineConfig::ClearCurrentLine => {
5171 let row_start =
5172 buffer.point_to_offset(Point::new(start_point.row, 0));
5173 (row_start, String::new(), false)
5174 }
5175 NewlineConfig::UnindentCurrentLine { continuation } => {
5176 let row_start =
5177 buffer.point_to_offset(Point::new(start_point.row, 0));
5178 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5179 let tab_size_indent = IndentSize::spaces(tab_size.get());
5180 let reduced_indent =
5181 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5182 let mut new_text = String::new();
5183 new_text.extend(reduced_indent.chars());
5184 new_text.push_str(continuation);
5185 (row_start, new_text, true)
5186 }
5187 NewlineConfig::Newline {
5188 additional_indent,
5189 extra_line_additional_indent,
5190 prevent_auto_indent,
5191 } => {
5192 let auto_indent_mode =
5193 buffer.language_settings_at(start, cx).auto_indent;
5194 let preserve_indent =
5195 auto_indent_mode != language::AutoIndentMode::None;
5196 let apply_syntax_indent =
5197 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5198 let capacity_for_delimiter =
5199 delimiter.as_deref().map(str::len).unwrap_or_default();
5200 let existing_indent_len = if preserve_indent {
5201 existing_indent.len as usize
5202 } else {
5203 0
5204 };
5205 let extra_line_len = extra_line_additional_indent
5206 .map(|i| 1 + existing_indent_len + i.len as usize)
5207 .unwrap_or(0);
5208 let mut new_text = String::with_capacity(
5209 1 + capacity_for_delimiter
5210 + existing_indent_len
5211 + additional_indent.len as usize
5212 + extra_line_len,
5213 );
5214 new_text.push('\n');
5215 if preserve_indent {
5216 new_text.extend(existing_indent.chars());
5217 }
5218 new_text.extend(additional_indent.chars());
5219 if let Some(delimiter) = &delimiter {
5220 new_text.push_str(delimiter);
5221 }
5222 if let Some(extra_indent) = extra_line_additional_indent {
5223 new_text.push('\n');
5224 if preserve_indent {
5225 new_text.extend(existing_indent.chars());
5226 }
5227 new_text.extend(extra_indent.chars());
5228 }
5229 (
5230 start,
5231 new_text,
5232 *prevent_auto_indent || !apply_syntax_indent,
5233 )
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 if self.read_only(cx) {
5286 return;
5287 }
5288
5289 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5290
5291 let buffer = self.buffer.read(cx);
5292 let snapshot = buffer.snapshot(cx);
5293
5294 let mut edits = Vec::new();
5295 let mut rows = Vec::new();
5296
5297 for (rows_inserted, selection) in self
5298 .selections
5299 .all_adjusted(&self.display_snapshot(cx))
5300 .into_iter()
5301 .enumerate()
5302 {
5303 let cursor = selection.head();
5304 let row = cursor.row;
5305
5306 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5307
5308 let newline = "\n".to_string();
5309 edits.push((start_of_line..start_of_line, newline));
5310
5311 rows.push(row + rows_inserted as u32);
5312 }
5313
5314 self.transact(window, cx, |editor, window, cx| {
5315 editor.edit(edits, cx);
5316
5317 editor.change_selections(Default::default(), window, cx, |s| {
5318 let mut index = 0;
5319 s.move_cursors_with(&mut |map, _, _| {
5320 let row = rows[index];
5321 index += 1;
5322
5323 let point = Point::new(row, 0);
5324 let boundary = map.next_line_boundary(point).1;
5325 let clipped = map.clip_point(boundary, Bias::Left);
5326
5327 (clipped, SelectionGoal::None)
5328 });
5329 });
5330
5331 let mut indent_edits = Vec::new();
5332 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5333 for row in rows {
5334 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5335 for (row, indent) in indents {
5336 if indent.len == 0 {
5337 continue;
5338 }
5339
5340 let text = match indent.kind {
5341 IndentKind::Space => " ".repeat(indent.len as usize),
5342 IndentKind::Tab => "\t".repeat(indent.len as usize),
5343 };
5344 let point = Point::new(row.0, 0);
5345 indent_edits.push((point..point, text));
5346 }
5347 }
5348 editor.edit(indent_edits, cx);
5349 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5350 format.detach_and_log_err(cx);
5351 }
5352 });
5353 }
5354
5355 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5356 if self.read_only(cx) {
5357 return;
5358 }
5359
5360 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5361
5362 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5363 let mut rows = Vec::new();
5364 let mut rows_inserted = 0;
5365
5366 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5367 let cursor = selection.head();
5368 let row = cursor.row;
5369
5370 let point = Point::new(row, 0);
5371 let Some((buffer_handle, buffer_point, _)) =
5372 self.buffer.read(cx).point_to_buffer_point(point, cx)
5373 else {
5374 continue;
5375 };
5376
5377 buffer_edits
5378 .entry(buffer_handle.entity_id())
5379 .or_insert_with(|| (buffer_handle, Vec::new()))
5380 .1
5381 .push(buffer_point);
5382
5383 rows_inserted += 1;
5384 rows.push(row + rows_inserted);
5385 }
5386
5387 self.transact(window, cx, |editor, window, cx| {
5388 for (_, (buffer_handle, points)) in &buffer_edits {
5389 buffer_handle.update(cx, |buffer, cx| {
5390 let edits: Vec<_> = points
5391 .iter()
5392 .map(|point| {
5393 let target = Point::new(point.row + 1, 0);
5394 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5395 (start_of_line..start_of_line, "\n")
5396 })
5397 .collect();
5398 buffer.edit(edits, None, cx);
5399 });
5400 }
5401
5402 editor.change_selections(Default::default(), window, cx, |s| {
5403 let mut index = 0;
5404 s.move_cursors_with(&mut |map, _, _| {
5405 let row = rows[index];
5406 index += 1;
5407
5408 let point = Point::new(row, 0);
5409 let boundary = map.next_line_boundary(point).1;
5410 let clipped = map.clip_point(boundary, Bias::Left);
5411
5412 (clipped, SelectionGoal::None)
5413 });
5414 });
5415
5416 let mut indent_edits = Vec::new();
5417 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5418 for row in rows {
5419 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5420 for (row, indent) in indents {
5421 if indent.len == 0 {
5422 continue;
5423 }
5424
5425 let text = match indent.kind {
5426 IndentKind::Space => " ".repeat(indent.len as usize),
5427 IndentKind::Tab => "\t".repeat(indent.len as usize),
5428 };
5429 let point = Point::new(row.0, 0);
5430 indent_edits.push((point..point, text));
5431 }
5432 }
5433 editor.edit(indent_edits, cx);
5434 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5435 format.detach_and_log_err(cx);
5436 }
5437 });
5438 }
5439
5440 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5441 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5442 original_indent_columns: Vec::new(),
5443 });
5444 self.replace_selections(text, autoindent, window, cx, false);
5445 }
5446
5447 /// Replaces the editor's selections with the provided `text`, applying the
5448 /// given `autoindent_mode` (`None` will skip autoindentation).
5449 ///
5450 /// Early returns if the editor is in read-only mode, without applying any
5451 /// edits.
5452 fn replace_selections(
5453 &mut self,
5454 text: &str,
5455 autoindent_mode: Option<AutoindentMode>,
5456 window: &mut Window,
5457 cx: &mut Context<Self>,
5458 apply_linked_edits: bool,
5459 ) {
5460 if self.read_only(cx) {
5461 return;
5462 }
5463
5464 let text: Arc<str> = text.into();
5465 self.transact(window, cx, |this, window, cx| {
5466 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5467 let linked_edits = if apply_linked_edits {
5468 this.linked_edits_for_selections(text.clone(), cx)
5469 } else {
5470 LinkedEdits::new()
5471 };
5472
5473 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5474 let anchors = {
5475 let snapshot = buffer.read(cx);
5476 old_selections
5477 .iter()
5478 .map(|s| {
5479 let anchor = snapshot.anchor_after(s.head());
5480 s.map(|_| anchor)
5481 })
5482 .collect::<Vec<_>>()
5483 };
5484 buffer.edit(
5485 old_selections
5486 .iter()
5487 .map(|s| (s.start..s.end, text.clone())),
5488 autoindent_mode,
5489 cx,
5490 );
5491 anchors
5492 });
5493
5494 linked_edits.apply(cx);
5495
5496 this.change_selections(Default::default(), window, cx, |s| {
5497 s.select_anchors(selection_anchors);
5498 });
5499
5500 if apply_linked_edits {
5501 refresh_linked_ranges(this, window, cx);
5502 }
5503
5504 cx.notify();
5505 });
5506 }
5507
5508 /// Collects linked edits for the current selections, pairing each linked
5509 /// range with `text`.
5510 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5511 let mut linked_edits = LinkedEdits::new();
5512 if !self.linked_edit_ranges.is_empty() {
5513 for selection in self.selections.disjoint_anchors() {
5514 let start = selection.start.text_anchor;
5515 let end = selection.end.text_anchor;
5516 linked_edits.push(self, start..end, text.clone(), cx);
5517 }
5518 }
5519 linked_edits
5520 }
5521
5522 /// Deletes the content covered by the current selections and applies
5523 /// linked edits.
5524 pub fn delete_selections_with_linked_edits(
5525 &mut self,
5526 window: &mut Window,
5527 cx: &mut Context<Self>,
5528 ) {
5529 self.replace_selections("", None, window, cx, true);
5530 }
5531
5532 #[cfg(any(test, feature = "test-support"))]
5533 pub fn set_linked_edit_ranges_for_testing(
5534 &mut self,
5535 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5536 cx: &mut Context<Self>,
5537 ) -> Option<()> {
5538 let Some((buffer, _)) = self
5539 .buffer
5540 .read(cx)
5541 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5542 else {
5543 return None;
5544 };
5545 let buffer = buffer.read(cx);
5546 let buffer_id = buffer.remote_id();
5547 let mut linked_ranges = Vec::with_capacity(ranges.len());
5548 for (base_range, linked_ranges_points) in ranges {
5549 let base_anchor =
5550 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5551 let linked_anchors = linked_ranges_points
5552 .into_iter()
5553 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5554 .collect();
5555 linked_ranges.push((base_anchor, linked_anchors));
5556 }
5557 let mut map = HashMap::default();
5558 map.insert(buffer_id, linked_ranges);
5559 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5560 Some(())
5561 }
5562
5563 fn trigger_completion_on_input(
5564 &mut self,
5565 text: &str,
5566 trigger_in_words: bool,
5567 window: &mut Window,
5568 cx: &mut Context<Self>,
5569 ) {
5570 let completions_source = self
5571 .context_menu
5572 .borrow()
5573 .as_ref()
5574 .and_then(|menu| match menu {
5575 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5576 CodeContextMenu::CodeActions(_) => None,
5577 });
5578
5579 match completions_source {
5580 Some(CompletionsMenuSource::Words { .. }) => {
5581 self.open_or_update_completions_menu(
5582 Some(CompletionsMenuSource::Words {
5583 ignore_threshold: false,
5584 }),
5585 None,
5586 trigger_in_words,
5587 window,
5588 cx,
5589 );
5590 }
5591 _ => self.open_or_update_completions_menu(
5592 None,
5593 Some(text.to_owned()).filter(|x| !x.is_empty()),
5594 true,
5595 window,
5596 cx,
5597 ),
5598 }
5599 }
5600
5601 /// If any empty selections is touching the start of its innermost containing autoclose
5602 /// region, expand it to select the brackets.
5603 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5604 let selections = self
5605 .selections
5606 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5607 let buffer = self.buffer.read(cx).read(cx);
5608 let new_selections = self
5609 .selections_with_autoclose_regions(selections, &buffer)
5610 .map(|(mut selection, region)| {
5611 if !selection.is_empty() {
5612 return selection;
5613 }
5614
5615 if let Some(region) = region {
5616 let mut range = region.range.to_offset(&buffer);
5617 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5618 range.start -= region.pair.start.len();
5619 if buffer.contains_str_at(range.start, ®ion.pair.start)
5620 && buffer.contains_str_at(range.end, ®ion.pair.end)
5621 {
5622 range.end += region.pair.end.len();
5623 selection.start = range.start;
5624 selection.end = range.end;
5625
5626 return selection;
5627 }
5628 }
5629 }
5630
5631 let always_treat_brackets_as_autoclosed = buffer
5632 .language_settings_at(selection.start, cx)
5633 .always_treat_brackets_as_autoclosed;
5634
5635 if !always_treat_brackets_as_autoclosed {
5636 return selection;
5637 }
5638
5639 if let Some(scope) = buffer.language_scope_at(selection.start) {
5640 for (pair, enabled) in scope.brackets() {
5641 if !enabled || !pair.close {
5642 continue;
5643 }
5644
5645 if buffer.contains_str_at(selection.start, &pair.end) {
5646 let pair_start_len = pair.start.len();
5647 if buffer.contains_str_at(
5648 selection.start.saturating_sub_usize(pair_start_len),
5649 &pair.start,
5650 ) {
5651 selection.start -= pair_start_len;
5652 selection.end += pair.end.len();
5653
5654 return selection;
5655 }
5656 }
5657 }
5658 }
5659
5660 selection
5661 })
5662 .collect();
5663
5664 drop(buffer);
5665 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5666 selections.select(new_selections)
5667 });
5668 }
5669
5670 /// Iterate the given selections, and for each one, find the smallest surrounding
5671 /// autoclose region. This uses the ordering of the selections and the autoclose
5672 /// regions to avoid repeated comparisons.
5673 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5674 &'a self,
5675 selections: impl IntoIterator<Item = Selection<D>>,
5676 buffer: &'a MultiBufferSnapshot,
5677 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5678 let mut i = 0;
5679 let mut regions = self.autoclose_regions.as_slice();
5680 selections.into_iter().map(move |selection| {
5681 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5682
5683 let mut enclosing = None;
5684 while let Some(pair_state) = regions.get(i) {
5685 if pair_state.range.end.to_offset(buffer) < range.start {
5686 regions = ®ions[i + 1..];
5687 i = 0;
5688 } else if pair_state.range.start.to_offset(buffer) > range.end {
5689 break;
5690 } else {
5691 if pair_state.selection_id == selection.id {
5692 enclosing = Some(pair_state);
5693 }
5694 i += 1;
5695 }
5696 }
5697
5698 (selection, enclosing)
5699 })
5700 }
5701
5702 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5703 fn invalidate_autoclose_regions(
5704 &mut self,
5705 mut selections: &[Selection<Anchor>],
5706 buffer: &MultiBufferSnapshot,
5707 ) {
5708 self.autoclose_regions.retain(|state| {
5709 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5710 return false;
5711 }
5712
5713 let mut i = 0;
5714 while let Some(selection) = selections.get(i) {
5715 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5716 selections = &selections[1..];
5717 continue;
5718 }
5719 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5720 break;
5721 }
5722 if selection.id == state.selection_id {
5723 return true;
5724 } else {
5725 i += 1;
5726 }
5727 }
5728 false
5729 });
5730 }
5731
5732 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5733 let offset = position.to_offset(buffer);
5734 let (word_range, kind) =
5735 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5736 if offset > word_range.start && kind == Some(CharKind::Word) {
5737 Some(
5738 buffer
5739 .text_for_range(word_range.start..offset)
5740 .collect::<String>(),
5741 )
5742 } else {
5743 None
5744 }
5745 }
5746
5747 pub fn visible_excerpts(
5748 &self,
5749 lsp_related_only: bool,
5750 cx: &mut Context<Editor>,
5751 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5752 let project = self.project().cloned();
5753 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5754 let multi_buffer = self.buffer().read(cx);
5755 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5756 multi_buffer_snapshot
5757 .range_to_buffer_ranges(
5758 self.multi_buffer_visible_range(&display_snapshot, cx)
5759 .to_inclusive(),
5760 )
5761 .into_iter()
5762 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5763 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5764 if !lsp_related_only {
5765 return Some((
5766 excerpt_id,
5767 (
5768 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5769 buffer.version().clone(),
5770 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5771 ),
5772 ));
5773 }
5774
5775 let project = project.as_ref()?.read(cx);
5776 let buffer_file = project::File::from_dyn(buffer.file())?;
5777 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5778 let worktree_entry = buffer_worktree
5779 .read(cx)
5780 .entry_for_id(buffer_file.project_entry_id()?)?;
5781 if worktree_entry.is_ignored {
5782 None
5783 } else {
5784 Some((
5785 excerpt_id,
5786 (
5787 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5788 buffer.version().clone(),
5789 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5790 ),
5791 ))
5792 }
5793 })
5794 .collect()
5795 }
5796
5797 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5798 TextLayoutDetails {
5799 text_system: window.text_system().clone(),
5800 editor_style: self.style.clone().unwrap(),
5801 rem_size: window.rem_size(),
5802 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5803 visible_rows: self.visible_line_count(),
5804 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5805 }
5806 }
5807
5808 fn trigger_on_type_formatting(
5809 &self,
5810 input: String,
5811 window: &mut Window,
5812 cx: &mut Context<Self>,
5813 ) -> Option<Task<Result<()>>> {
5814 if input.chars().count() != 1 {
5815 return None;
5816 }
5817
5818 let project = self.project()?;
5819 let position = self.selections.newest_anchor().head();
5820 let (buffer, buffer_position) = self
5821 .buffer
5822 .read(cx)
5823 .text_anchor_for_position(position, cx)?;
5824
5825 let settings = language_settings::language_settings(
5826 buffer
5827 .read(cx)
5828 .language_at(buffer_position)
5829 .map(|l| l.name()),
5830 buffer.read(cx).file(),
5831 cx,
5832 );
5833 if !settings.use_on_type_format {
5834 return None;
5835 }
5836
5837 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5838 // hence we do LSP request & edit on host side only — add formats to host's history.
5839 let push_to_lsp_host_history = true;
5840 // If this is not the host, append its history with new edits.
5841 let push_to_client_history = project.read(cx).is_via_collab();
5842
5843 let on_type_formatting = project.update(cx, |project, cx| {
5844 project.on_type_format(
5845 buffer.clone(),
5846 buffer_position,
5847 input,
5848 push_to_lsp_host_history,
5849 cx,
5850 )
5851 });
5852 Some(cx.spawn_in(window, async move |editor, cx| {
5853 if let Some(transaction) = on_type_formatting.await? {
5854 if push_to_client_history {
5855 buffer.update(cx, |buffer, _| {
5856 buffer.push_transaction(transaction, Instant::now());
5857 buffer.finalize_last_transaction();
5858 });
5859 }
5860 editor.update(cx, |editor, cx| {
5861 editor.refresh_document_highlights(cx);
5862 })?;
5863 }
5864 Ok(())
5865 }))
5866 }
5867
5868 pub fn show_word_completions(
5869 &mut self,
5870 _: &ShowWordCompletions,
5871 window: &mut Window,
5872 cx: &mut Context<Self>,
5873 ) {
5874 self.open_or_update_completions_menu(
5875 Some(CompletionsMenuSource::Words {
5876 ignore_threshold: true,
5877 }),
5878 None,
5879 false,
5880 window,
5881 cx,
5882 );
5883 }
5884
5885 pub fn show_completions(
5886 &mut self,
5887 _: &ShowCompletions,
5888 window: &mut Window,
5889 cx: &mut Context<Self>,
5890 ) {
5891 self.open_or_update_completions_menu(None, None, false, window, cx);
5892 }
5893
5894 fn open_or_update_completions_menu(
5895 &mut self,
5896 requested_source: Option<CompletionsMenuSource>,
5897 trigger: Option<String>,
5898 trigger_in_words: bool,
5899 window: &mut Window,
5900 cx: &mut Context<Self>,
5901 ) {
5902 if self.pending_rename.is_some() {
5903 return;
5904 }
5905
5906 let completions_source = self
5907 .context_menu
5908 .borrow()
5909 .as_ref()
5910 .and_then(|menu| match menu {
5911 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5912 CodeContextMenu::CodeActions(_) => None,
5913 });
5914
5915 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5916
5917 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5918 // inserted and selected. To handle that case, the start of the selection is used so that
5919 // the menu starts with all choices.
5920 let position = self
5921 .selections
5922 .newest_anchor()
5923 .start
5924 .bias_right(&multibuffer_snapshot);
5925 if position.diff_base_anchor.is_some() {
5926 return;
5927 }
5928 let buffer_position = multibuffer_snapshot.anchor_before(position);
5929 let Some(buffer) = buffer_position
5930 .text_anchor
5931 .buffer_id
5932 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5933 else {
5934 return;
5935 };
5936 let buffer_snapshot = buffer.read(cx).snapshot();
5937
5938 let menu_is_open = matches!(
5939 self.context_menu.borrow().as_ref(),
5940 Some(CodeContextMenu::Completions(_))
5941 );
5942
5943 let language = buffer_snapshot
5944 .language_at(buffer_position.text_anchor)
5945 .map(|language| language.name());
5946
5947 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5948 let completion_settings = language_settings.completions.clone();
5949
5950 let show_completions_on_input = self
5951 .show_completions_on_input_override
5952 .unwrap_or(language_settings.show_completions_on_input);
5953 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5954 return;
5955 }
5956
5957 let query: Option<Arc<String>> =
5958 Self::completion_query(&multibuffer_snapshot, buffer_position)
5959 .map(|query| query.into());
5960
5961 drop(multibuffer_snapshot);
5962
5963 // Hide the current completions menu when query is empty. Without this, cached
5964 // completions from before the trigger char may be reused (#32774).
5965 if query.is_none() && menu_is_open {
5966 self.hide_context_menu(window, cx);
5967 }
5968
5969 let mut ignore_word_threshold = false;
5970 let provider = match requested_source {
5971 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5972 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5973 ignore_word_threshold = ignore_threshold;
5974 None
5975 }
5976 Some(CompletionsMenuSource::SnippetChoices)
5977 | Some(CompletionsMenuSource::SnippetsOnly) => {
5978 log::error!("bug: SnippetChoices requested_source is not handled");
5979 None
5980 }
5981 };
5982
5983 let sort_completions = provider
5984 .as_ref()
5985 .is_some_and(|provider| provider.sort_completions());
5986
5987 let filter_completions = provider
5988 .as_ref()
5989 .is_none_or(|provider| provider.filter_completions());
5990
5991 let was_snippets_only = matches!(
5992 completions_source,
5993 Some(CompletionsMenuSource::SnippetsOnly)
5994 );
5995
5996 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5997 if filter_completions {
5998 menu.filter(
5999 query.clone().unwrap_or_default(),
6000 buffer_position.text_anchor,
6001 &buffer,
6002 provider.clone(),
6003 window,
6004 cx,
6005 );
6006 }
6007 // When `is_incomplete` is false, no need to re-query completions when the current query
6008 // is a suffix of the initial query.
6009 let was_complete = !menu.is_incomplete;
6010 if was_complete && !was_snippets_only {
6011 // If the new query is a suffix of the old query (typing more characters) and
6012 // the previous result was complete, the existing completions can be filtered.
6013 //
6014 // Note that snippet completions are always complete.
6015 let query_matches = match (&menu.initial_query, &query) {
6016 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6017 (None, _) => true,
6018 _ => false,
6019 };
6020 if query_matches {
6021 let position_matches = if menu.initial_position == position {
6022 true
6023 } else {
6024 let snapshot = self.buffer.read(cx).read(cx);
6025 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6026 };
6027 if position_matches {
6028 return;
6029 }
6030 }
6031 }
6032 };
6033
6034 let Anchor {
6035 excerpt_id: buffer_excerpt_id,
6036 text_anchor: buffer_position,
6037 ..
6038 } = buffer_position;
6039
6040 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6041 buffer_snapshot.surrounding_word(buffer_position, None)
6042 {
6043 let word_to_exclude = buffer_snapshot
6044 .text_for_range(word_range.clone())
6045 .collect::<String>();
6046 (
6047 buffer_snapshot.anchor_before(word_range.start)
6048 ..buffer_snapshot.anchor_after(buffer_position),
6049 Some(word_to_exclude),
6050 )
6051 } else {
6052 (buffer_position..buffer_position, None)
6053 };
6054
6055 let show_completion_documentation = buffer_snapshot
6056 .settings_at(buffer_position, cx)
6057 .show_completion_documentation;
6058
6059 // The document can be large, so stay in reasonable bounds when searching for words,
6060 // otherwise completion pop-up might be slow to appear.
6061 const WORD_LOOKUP_ROWS: u32 = 5_000;
6062 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6063 let min_word_search = buffer_snapshot.clip_point(
6064 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6065 Bias::Left,
6066 );
6067 let max_word_search = buffer_snapshot.clip_point(
6068 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6069 Bias::Right,
6070 );
6071 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6072 ..buffer_snapshot.point_to_offset(max_word_search);
6073
6074 let skip_digits = query
6075 .as_ref()
6076 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6077
6078 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6079 trigger.as_ref().is_none_or(|trigger| {
6080 provider.is_completion_trigger(
6081 &buffer,
6082 position.text_anchor,
6083 trigger,
6084 trigger_in_words,
6085 cx,
6086 )
6087 })
6088 });
6089
6090 let provider_responses = if let Some(provider) = &provider
6091 && load_provider_completions
6092 {
6093 let trigger_character =
6094 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6095 let completion_context = CompletionContext {
6096 trigger_kind: match &trigger_character {
6097 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6098 None => CompletionTriggerKind::INVOKED,
6099 },
6100 trigger_character,
6101 };
6102
6103 provider.completions(
6104 buffer_excerpt_id,
6105 &buffer,
6106 buffer_position,
6107 completion_context,
6108 window,
6109 cx,
6110 )
6111 } else {
6112 Task::ready(Ok(Vec::new()))
6113 };
6114
6115 let load_word_completions = if !self.word_completions_enabled {
6116 false
6117 } else if requested_source
6118 == Some(CompletionsMenuSource::Words {
6119 ignore_threshold: true,
6120 })
6121 {
6122 true
6123 } else {
6124 load_provider_completions
6125 && completion_settings.words != WordsCompletionMode::Disabled
6126 && (ignore_word_threshold || {
6127 let words_min_length = completion_settings.words_min_length;
6128 // check whether word has at least `words_min_length` characters
6129 let query_chars = query.iter().flat_map(|q| q.chars());
6130 query_chars.take(words_min_length).count() == words_min_length
6131 })
6132 };
6133
6134 let mut words = if load_word_completions {
6135 cx.background_spawn({
6136 let buffer_snapshot = buffer_snapshot.clone();
6137 async move {
6138 buffer_snapshot.words_in_range(WordsQuery {
6139 fuzzy_contents: None,
6140 range: word_search_range,
6141 skip_digits,
6142 })
6143 }
6144 })
6145 } else {
6146 Task::ready(BTreeMap::default())
6147 };
6148
6149 let snippets = if let Some(provider) = &provider
6150 && provider.show_snippets()
6151 && let Some(project) = self.project()
6152 {
6153 let char_classifier = buffer_snapshot
6154 .char_classifier_at(buffer_position)
6155 .scope_context(Some(CharScopeContext::Completion));
6156 project.update(cx, |project, cx| {
6157 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6158 })
6159 } else {
6160 Task::ready(Ok(CompletionResponse {
6161 completions: Vec::new(),
6162 display_options: Default::default(),
6163 is_incomplete: false,
6164 }))
6165 };
6166
6167 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6168
6169 let id = post_inc(&mut self.next_completion_id);
6170 let task = cx.spawn_in(window, async move |editor, cx| {
6171 let Ok(()) = editor.update(cx, |this, _| {
6172 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6173 }) else {
6174 return;
6175 };
6176
6177 // TODO: Ideally completions from different sources would be selectively re-queried, so
6178 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6179 let mut completions = Vec::new();
6180 let mut is_incomplete = false;
6181 let mut display_options: Option<CompletionDisplayOptions> = None;
6182 if let Some(provider_responses) = provider_responses.await.log_err()
6183 && !provider_responses.is_empty()
6184 {
6185 for response in provider_responses {
6186 completions.extend(response.completions);
6187 is_incomplete = is_incomplete || response.is_incomplete;
6188 match display_options.as_mut() {
6189 None => {
6190 display_options = Some(response.display_options);
6191 }
6192 Some(options) => options.merge(&response.display_options),
6193 }
6194 }
6195 if completion_settings.words == WordsCompletionMode::Fallback {
6196 words = Task::ready(BTreeMap::default());
6197 }
6198 }
6199 let display_options = display_options.unwrap_or_default();
6200
6201 let mut words = words.await;
6202 if let Some(word_to_exclude) = &word_to_exclude {
6203 words.remove(word_to_exclude);
6204 }
6205 for lsp_completion in &completions {
6206 words.remove(&lsp_completion.new_text);
6207 }
6208 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6209 replace_range: word_replace_range.clone(),
6210 new_text: word.clone(),
6211 label: CodeLabel::plain(word, None),
6212 match_start: None,
6213 snippet_deduplication_key: None,
6214 icon_path: None,
6215 documentation: None,
6216 source: CompletionSource::BufferWord {
6217 word_range,
6218 resolved: false,
6219 },
6220 insert_text_mode: Some(InsertTextMode::AS_IS),
6221 confirm: None,
6222 }));
6223
6224 completions.extend(
6225 snippets
6226 .await
6227 .into_iter()
6228 .flat_map(|response| response.completions),
6229 );
6230
6231 let menu = if completions.is_empty() {
6232 None
6233 } else {
6234 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6235 let languages = editor
6236 .workspace
6237 .as_ref()
6238 .and_then(|(workspace, _)| workspace.upgrade())
6239 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6240 let menu = CompletionsMenu::new(
6241 id,
6242 requested_source.unwrap_or(if load_provider_completions {
6243 CompletionsMenuSource::Normal
6244 } else {
6245 CompletionsMenuSource::SnippetsOnly
6246 }),
6247 sort_completions,
6248 show_completion_documentation,
6249 position,
6250 query.clone(),
6251 is_incomplete,
6252 buffer.clone(),
6253 completions.into(),
6254 editor
6255 .context_menu()
6256 .borrow_mut()
6257 .as_ref()
6258 .map(|menu| menu.primary_scroll_handle()),
6259 display_options,
6260 snippet_sort_order,
6261 languages,
6262 language,
6263 cx,
6264 );
6265
6266 let query = if filter_completions { query } else { None };
6267 let matches_task = menu.do_async_filtering(
6268 query.unwrap_or_default(),
6269 buffer_position,
6270 &buffer,
6271 cx,
6272 );
6273 (menu, matches_task)
6274 }) else {
6275 return;
6276 };
6277
6278 let matches = matches_task.await;
6279
6280 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6281 // Newer menu already set, so exit.
6282 if let Some(CodeContextMenu::Completions(prev_menu)) =
6283 editor.context_menu.borrow().as_ref()
6284 && prev_menu.id > id
6285 {
6286 return;
6287 };
6288
6289 // Only valid to take prev_menu because either the new menu is immediately set
6290 // below, or the menu is hidden.
6291 if let Some(CodeContextMenu::Completions(prev_menu)) =
6292 editor.context_menu.borrow_mut().take()
6293 {
6294 let position_matches =
6295 if prev_menu.initial_position == menu.initial_position {
6296 true
6297 } else {
6298 let snapshot = editor.buffer.read(cx).read(cx);
6299 prev_menu.initial_position.to_offset(&snapshot)
6300 == menu.initial_position.to_offset(&snapshot)
6301 };
6302 if position_matches {
6303 // Preserve markdown cache before `set_filter_results` because it will
6304 // try to populate the documentation cache.
6305 menu.preserve_markdown_cache(prev_menu);
6306 }
6307 };
6308
6309 menu.set_filter_results(matches, provider, window, cx);
6310 }) else {
6311 return;
6312 };
6313
6314 menu.visible().then_some(menu)
6315 };
6316
6317 editor
6318 .update_in(cx, |editor, window, cx| {
6319 if editor.focus_handle.is_focused(window)
6320 && let Some(menu) = menu
6321 {
6322 *editor.context_menu.borrow_mut() =
6323 Some(CodeContextMenu::Completions(menu));
6324
6325 crate::hover_popover::hide_hover(editor, cx);
6326 if editor.show_edit_predictions_in_menu() {
6327 editor.update_visible_edit_prediction(window, cx);
6328 } else {
6329 editor
6330 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6331 }
6332
6333 cx.notify();
6334 return;
6335 }
6336
6337 if editor.completion_tasks.len() <= 1 {
6338 // If there are no more completion tasks and the last menu was empty, we should hide it.
6339 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6340 // If it was already hidden and we don't show edit predictions in the menu,
6341 // we should also show the edit prediction when available.
6342 if was_hidden && editor.show_edit_predictions_in_menu() {
6343 editor.update_visible_edit_prediction(window, cx);
6344 }
6345 }
6346 })
6347 .ok();
6348 });
6349
6350 self.completion_tasks.push((id, task));
6351 }
6352
6353 #[cfg(any(test, feature = "test-support"))]
6354 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6355 let menu = self.context_menu.borrow();
6356 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6357 let completions = menu.completions.borrow();
6358 Some(completions.to_vec())
6359 } else {
6360 None
6361 }
6362 }
6363
6364 pub fn with_completions_menu_matching_id<R>(
6365 &self,
6366 id: CompletionId,
6367 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6368 ) -> R {
6369 let mut context_menu = self.context_menu.borrow_mut();
6370 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6371 return f(None);
6372 };
6373 if completions_menu.id != id {
6374 return f(None);
6375 }
6376 f(Some(completions_menu))
6377 }
6378
6379 pub fn confirm_completion(
6380 &mut self,
6381 action: &ConfirmCompletion,
6382 window: &mut Window,
6383 cx: &mut Context<Self>,
6384 ) -> Option<Task<Result<()>>> {
6385 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6386 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6387 }
6388
6389 pub fn confirm_completion_insert(
6390 &mut self,
6391 _: &ConfirmCompletionInsert,
6392 window: &mut Window,
6393 cx: &mut Context<Self>,
6394 ) -> Option<Task<Result<()>>> {
6395 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6396 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6397 }
6398
6399 pub fn confirm_completion_replace(
6400 &mut self,
6401 _: &ConfirmCompletionReplace,
6402 window: &mut Window,
6403 cx: &mut Context<Self>,
6404 ) -> Option<Task<Result<()>>> {
6405 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6406 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6407 }
6408
6409 pub fn compose_completion(
6410 &mut self,
6411 action: &ComposeCompletion,
6412 window: &mut Window,
6413 cx: &mut Context<Self>,
6414 ) -> Option<Task<Result<()>>> {
6415 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6416 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6417 }
6418
6419 fn do_completion(
6420 &mut self,
6421 item_ix: Option<usize>,
6422 intent: CompletionIntent,
6423 window: &mut Window,
6424 cx: &mut Context<Editor>,
6425 ) -> Option<Task<Result<()>>> {
6426 use language::ToOffset as _;
6427
6428 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6429 else {
6430 return None;
6431 };
6432
6433 let candidate_id = {
6434 let entries = completions_menu.entries.borrow();
6435 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6436 if self.show_edit_predictions_in_menu() {
6437 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6438 }
6439 mat.candidate_id
6440 };
6441
6442 let completion = completions_menu
6443 .completions
6444 .borrow()
6445 .get(candidate_id)?
6446 .clone();
6447 cx.stop_propagation();
6448
6449 let buffer_handle = completions_menu.buffer.clone();
6450
6451 let CompletionEdit {
6452 new_text,
6453 snippet,
6454 replace_range,
6455 } = process_completion_for_edit(
6456 &completion,
6457 intent,
6458 &buffer_handle,
6459 &completions_menu.initial_position.text_anchor,
6460 cx,
6461 );
6462
6463 let buffer = buffer_handle.read(cx);
6464 let snapshot = self.buffer.read(cx).snapshot(cx);
6465 let newest_anchor = self.selections.newest_anchor();
6466 let replace_range_multibuffer = {
6467 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6468 excerpt.map_range_from_buffer(replace_range.clone())
6469 };
6470 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6471 return None;
6472 }
6473
6474 let old_text = buffer
6475 .text_for_range(replace_range.clone())
6476 .collect::<String>();
6477 let lookbehind = newest_anchor
6478 .start
6479 .text_anchor
6480 .to_offset(buffer)
6481 .saturating_sub(replace_range.start.0);
6482 let lookahead = replace_range
6483 .end
6484 .0
6485 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6486 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6487 let suffix = &old_text[lookbehind.min(old_text.len())..];
6488
6489 let selections = self
6490 .selections
6491 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6492 let mut ranges = Vec::new();
6493 let mut linked_edits = LinkedEdits::new();
6494
6495 let text: Arc<str> = new_text.clone().into();
6496 for selection in &selections {
6497 let range = if selection.id == newest_anchor.id {
6498 replace_range_multibuffer.clone()
6499 } else {
6500 let mut range = selection.range();
6501
6502 // if prefix is present, don't duplicate it
6503 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6504 range.start = range.start.saturating_sub_usize(lookbehind);
6505
6506 // if suffix is also present, mimic the newest cursor and replace it
6507 if selection.id != newest_anchor.id
6508 && snapshot.contains_str_at(range.end, suffix)
6509 {
6510 range.end += lookahead;
6511 }
6512 }
6513 range
6514 };
6515
6516 ranges.push(range.clone());
6517
6518 if !self.linked_edit_ranges.is_empty() {
6519 let start_anchor = snapshot.anchor_before(range.start);
6520 let end_anchor = snapshot.anchor_after(range.end);
6521 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6522 linked_edits.push(&self, anchor_range, text.clone(), cx);
6523 }
6524 }
6525
6526 let common_prefix_len = old_text
6527 .chars()
6528 .zip(new_text.chars())
6529 .take_while(|(a, b)| a == b)
6530 .map(|(a, _)| a.len_utf8())
6531 .sum::<usize>();
6532
6533 cx.emit(EditorEvent::InputHandled {
6534 utf16_range_to_replace: None,
6535 text: new_text[common_prefix_len..].into(),
6536 });
6537
6538 self.transact(window, cx, |editor, window, cx| {
6539 if let Some(mut snippet) = snippet {
6540 snippet.text = new_text.to_string();
6541 editor
6542 .insert_snippet(&ranges, snippet, window, cx)
6543 .log_err();
6544 } else {
6545 editor.buffer.update(cx, |multi_buffer, cx| {
6546 let auto_indent = match completion.insert_text_mode {
6547 Some(InsertTextMode::AS_IS) => None,
6548 _ => editor.autoindent_mode.clone(),
6549 };
6550 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6551 multi_buffer.edit(edits, auto_indent, cx);
6552 });
6553 }
6554 linked_edits.apply(cx);
6555 editor.refresh_edit_prediction(true, false, window, cx);
6556 });
6557 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6558
6559 let show_new_completions_on_confirm = completion
6560 .confirm
6561 .as_ref()
6562 .is_some_and(|confirm| confirm(intent, window, cx));
6563 if show_new_completions_on_confirm {
6564 self.open_or_update_completions_menu(None, None, false, window, cx);
6565 }
6566
6567 let provider = self.completion_provider.as_ref()?;
6568
6569 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6570 let command = lsp_store.as_ref().and_then(|lsp_store| {
6571 let CompletionSource::Lsp {
6572 lsp_completion,
6573 server_id,
6574 ..
6575 } = &completion.source
6576 else {
6577 return None;
6578 };
6579 let lsp_command = lsp_completion.command.as_ref()?;
6580 let available_commands = lsp_store
6581 .read(cx)
6582 .lsp_server_capabilities
6583 .get(server_id)
6584 .and_then(|server_capabilities| {
6585 server_capabilities
6586 .execute_command_provider
6587 .as_ref()
6588 .map(|options| options.commands.as_slice())
6589 })?;
6590 if available_commands.contains(&lsp_command.command) {
6591 Some(CodeAction {
6592 server_id: *server_id,
6593 range: language::Anchor::MIN..language::Anchor::MIN,
6594 lsp_action: LspAction::Command(lsp_command.clone()),
6595 resolved: false,
6596 })
6597 } else {
6598 None
6599 }
6600 });
6601
6602 drop(completion);
6603 let apply_edits = provider.apply_additional_edits_for_completion(
6604 buffer_handle.clone(),
6605 completions_menu.completions.clone(),
6606 candidate_id,
6607 true,
6608 cx,
6609 );
6610
6611 let editor_settings = EditorSettings::get_global(cx);
6612 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6613 // After the code completion is finished, users often want to know what signatures are needed.
6614 // so we should automatically call signature_help
6615 self.show_signature_help(&ShowSignatureHelp, window, cx);
6616 }
6617
6618 Some(cx.spawn_in(window, async move |editor, cx| {
6619 apply_edits.await?;
6620
6621 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6622 let title = command.lsp_action.title().to_owned();
6623 let project_transaction = lsp_store
6624 .update(cx, |lsp_store, cx| {
6625 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6626 })
6627 .await
6628 .context("applying post-completion command")?;
6629 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6630 Self::open_project_transaction(
6631 &editor,
6632 workspace.downgrade(),
6633 project_transaction,
6634 title,
6635 cx,
6636 )
6637 .await?;
6638 }
6639 }
6640
6641 Ok(())
6642 }))
6643 }
6644
6645 pub fn toggle_code_actions(
6646 &mut self,
6647 action: &ToggleCodeActions,
6648 window: &mut Window,
6649 cx: &mut Context<Self>,
6650 ) {
6651 let quick_launch = action.quick_launch;
6652 let mut context_menu = self.context_menu.borrow_mut();
6653 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6654 if code_actions.deployed_from == action.deployed_from {
6655 // Toggle if we're selecting the same one
6656 *context_menu = None;
6657 cx.notify();
6658 return;
6659 } else {
6660 // Otherwise, clear it and start a new one
6661 *context_menu = None;
6662 cx.notify();
6663 }
6664 }
6665 drop(context_menu);
6666 let snapshot = self.snapshot(window, cx);
6667 let deployed_from = action.deployed_from.clone();
6668 let action = action.clone();
6669 self.completion_tasks.clear();
6670 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6671
6672 let multibuffer_point = match &action.deployed_from {
6673 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6674 DisplayPoint::new(*row, 0).to_point(&snapshot)
6675 }
6676 _ => self
6677 .selections
6678 .newest::<Point>(&snapshot.display_snapshot)
6679 .head(),
6680 };
6681 let Some((buffer, buffer_row)) = snapshot
6682 .buffer_snapshot()
6683 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6684 .and_then(|(buffer_snapshot, range)| {
6685 self.buffer()
6686 .read(cx)
6687 .buffer(buffer_snapshot.remote_id())
6688 .map(|buffer| (buffer, range.start.row))
6689 })
6690 else {
6691 return;
6692 };
6693 let buffer_id = buffer.read(cx).remote_id();
6694 let tasks = self
6695 .runnables
6696 .runnables((buffer_id, buffer_row))
6697 .map(|t| Arc::new(t.to_owned()));
6698
6699 if !self.focus_handle.is_focused(window) {
6700 return;
6701 }
6702 let project = self.project.clone();
6703
6704 let code_actions_task = match deployed_from {
6705 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6706 _ => self.code_actions(buffer_row, window, cx),
6707 };
6708
6709 let runnable_task = match deployed_from {
6710 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6711 _ => {
6712 let mut task_context_task = Task::ready(None);
6713 if let Some(tasks) = &tasks
6714 && let Some(project) = project
6715 {
6716 task_context_task =
6717 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6718 }
6719
6720 cx.spawn_in(window, {
6721 let buffer = buffer.clone();
6722 async move |editor, cx| {
6723 let task_context = task_context_task.await;
6724
6725 let resolved_tasks =
6726 tasks
6727 .zip(task_context.clone())
6728 .map(|(tasks, task_context)| ResolvedTasks {
6729 templates: tasks.resolve(&task_context).collect(),
6730 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6731 multibuffer_point.row,
6732 tasks.column,
6733 )),
6734 });
6735 let debug_scenarios = editor
6736 .update(cx, |editor, cx| {
6737 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6738 })?
6739 .await;
6740 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6741 }
6742 })
6743 }
6744 };
6745
6746 cx.spawn_in(window, async move |editor, cx| {
6747 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6748 let code_actions = code_actions_task.await;
6749 let spawn_straight_away = quick_launch
6750 && resolved_tasks
6751 .as_ref()
6752 .is_some_and(|tasks| tasks.templates.len() == 1)
6753 && code_actions
6754 .as_ref()
6755 .is_none_or(|actions| actions.is_empty())
6756 && debug_scenarios.is_empty();
6757
6758 editor.update_in(cx, |editor, window, cx| {
6759 crate::hover_popover::hide_hover(editor, cx);
6760 let actions = CodeActionContents::new(
6761 resolved_tasks,
6762 code_actions,
6763 debug_scenarios,
6764 task_context.unwrap_or_default(),
6765 );
6766
6767 // Don't show the menu if there are no actions available
6768 if actions.is_empty() {
6769 cx.notify();
6770 return Task::ready(Ok(()));
6771 }
6772
6773 *editor.context_menu.borrow_mut() =
6774 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6775 buffer,
6776 actions,
6777 selected_item: Default::default(),
6778 scroll_handle: UniformListScrollHandle::default(),
6779 deployed_from,
6780 }));
6781 cx.notify();
6782 if spawn_straight_away
6783 && let Some(task) = editor.confirm_code_action(
6784 &ConfirmCodeAction { item_ix: Some(0) },
6785 window,
6786 cx,
6787 )
6788 {
6789 return task;
6790 }
6791
6792 Task::ready(Ok(()))
6793 })
6794 })
6795 .detach_and_log_err(cx);
6796 }
6797
6798 fn debug_scenarios(
6799 &mut self,
6800 resolved_tasks: &Option<ResolvedTasks>,
6801 buffer: &Entity<Buffer>,
6802 cx: &mut App,
6803 ) -> Task<Vec<task::DebugScenario>> {
6804 maybe!({
6805 let project = self.project()?;
6806 let dap_store = project.read(cx).dap_store();
6807 let mut scenarios = vec![];
6808 let resolved_tasks = resolved_tasks.as_ref()?;
6809 let buffer = buffer.read(cx);
6810 let language = buffer.language()?;
6811 let file = buffer.file();
6812 let debug_adapter = language_settings(language.name().into(), file, cx)
6813 .debuggers
6814 .first()
6815 .map(SharedString::from)
6816 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6817
6818 dap_store.update(cx, |dap_store, cx| {
6819 for (_, task) in &resolved_tasks.templates {
6820 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6821 task.original_task().clone(),
6822 debug_adapter.clone().into(),
6823 task.display_label().to_owned().into(),
6824 cx,
6825 );
6826 scenarios.push(maybe_scenario);
6827 }
6828 });
6829 Some(cx.background_spawn(async move {
6830 futures::future::join_all(scenarios)
6831 .await
6832 .into_iter()
6833 .flatten()
6834 .collect::<Vec<_>>()
6835 }))
6836 })
6837 .unwrap_or_else(|| Task::ready(vec![]))
6838 }
6839
6840 fn code_actions(
6841 &mut self,
6842 buffer_row: u32,
6843 window: &mut Window,
6844 cx: &mut Context<Self>,
6845 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6846 let mut task = self.code_actions_task.take();
6847 cx.spawn_in(window, async move |editor, cx| {
6848 while let Some(prev_task) = task {
6849 prev_task.await.log_err();
6850 task = editor
6851 .update(cx, |this, _| this.code_actions_task.take())
6852 .ok()?;
6853 }
6854
6855 editor
6856 .update(cx, |editor, cx| {
6857 editor
6858 .available_code_actions
6859 .clone()
6860 .and_then(|(location, code_actions)| {
6861 let snapshot = location.buffer.read(cx).snapshot();
6862 let point_range = location.range.to_point(&snapshot);
6863 let point_range = point_range.start.row..=point_range.end.row;
6864 if point_range.contains(&buffer_row) {
6865 Some(code_actions)
6866 } else {
6867 None
6868 }
6869 })
6870 })
6871 .ok()
6872 .flatten()
6873 })
6874 }
6875
6876 pub fn confirm_code_action(
6877 &mut self,
6878 action: &ConfirmCodeAction,
6879 window: &mut Window,
6880 cx: &mut Context<Self>,
6881 ) -> Option<Task<Result<()>>> {
6882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6883
6884 let actions_menu =
6885 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6886 menu
6887 } else {
6888 return None;
6889 };
6890
6891 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6892 let action = actions_menu.actions.get(action_ix)?;
6893 let title = action.label();
6894 let buffer = actions_menu.buffer;
6895 let workspace = self.workspace()?;
6896
6897 match action {
6898 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6899 workspace.update(cx, |workspace, cx| {
6900 workspace.schedule_resolved_task(
6901 task_source_kind,
6902 resolved_task,
6903 false,
6904 window,
6905 cx,
6906 );
6907
6908 Some(Task::ready(Ok(())))
6909 })
6910 }
6911 CodeActionsItem::CodeAction {
6912 excerpt_id,
6913 action,
6914 provider,
6915 } => {
6916 let apply_code_action =
6917 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6918 let workspace = workspace.downgrade();
6919 Some(cx.spawn_in(window, async move |editor, cx| {
6920 let project_transaction = apply_code_action.await?;
6921 Self::open_project_transaction(
6922 &editor,
6923 workspace,
6924 project_transaction,
6925 title,
6926 cx,
6927 )
6928 .await
6929 }))
6930 }
6931 CodeActionsItem::DebugScenario(scenario) => {
6932 let context = actions_menu.actions.context.into();
6933
6934 workspace.update(cx, |workspace, cx| {
6935 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6936 workspace.start_debug_session(
6937 scenario,
6938 context,
6939 Some(buffer),
6940 None,
6941 window,
6942 cx,
6943 );
6944 });
6945 Some(Task::ready(Ok(())))
6946 }
6947 }
6948 }
6949
6950 fn open_transaction_for_hidden_buffers(
6951 workspace: Entity<Workspace>,
6952 transaction: ProjectTransaction,
6953 title: String,
6954 window: &mut Window,
6955 cx: &mut Context<Self>,
6956 ) {
6957 if transaction.0.is_empty() {
6958 return;
6959 }
6960
6961 let edited_buffers_already_open = {
6962 let other_editors: Vec<Entity<Editor>> = workspace
6963 .read(cx)
6964 .panes()
6965 .iter()
6966 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6967 .filter(|editor| editor.entity_id() != cx.entity_id())
6968 .collect();
6969
6970 transaction.0.keys().all(|buffer| {
6971 other_editors.iter().any(|editor| {
6972 let multi_buffer = editor.read(cx).buffer();
6973 multi_buffer.read(cx).is_singleton()
6974 && multi_buffer
6975 .read(cx)
6976 .as_singleton()
6977 .map_or(false, |singleton| {
6978 singleton.entity_id() == buffer.entity_id()
6979 })
6980 })
6981 })
6982 };
6983 if !edited_buffers_already_open {
6984 let workspace = workspace.downgrade();
6985 cx.defer_in(window, move |_, window, cx| {
6986 cx.spawn_in(window, async move |editor, cx| {
6987 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6988 .await
6989 .ok()
6990 })
6991 .detach();
6992 });
6993 }
6994 }
6995
6996 pub async fn open_project_transaction(
6997 editor: &WeakEntity<Editor>,
6998 workspace: WeakEntity<Workspace>,
6999 transaction: ProjectTransaction,
7000 title: String,
7001 cx: &mut AsyncWindowContext,
7002 ) -> Result<()> {
7003 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7004 cx.update(|_, cx| {
7005 entries.sort_unstable_by_key(|(buffer, _)| {
7006 buffer.read(cx).file().map(|f| f.path().clone())
7007 });
7008 })?;
7009 if entries.is_empty() {
7010 return Ok(());
7011 }
7012
7013 // If the project transaction's edits are all contained within this editor, then
7014 // avoid opening a new editor to display them.
7015
7016 if let [(buffer, transaction)] = &*entries {
7017 let excerpt = editor.update(cx, |editor, cx| {
7018 editor
7019 .buffer()
7020 .read(cx)
7021 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7022 })?;
7023 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7024 && excerpted_buffer == *buffer
7025 {
7026 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7027 let excerpt_range = excerpt_range.to_offset(buffer);
7028 buffer
7029 .edited_ranges_for_transaction::<usize>(transaction)
7030 .all(|range| {
7031 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7032 })
7033 });
7034
7035 if all_edits_within_excerpt {
7036 return Ok(());
7037 }
7038 }
7039 }
7040
7041 let mut ranges_to_highlight = Vec::new();
7042 let excerpt_buffer = cx.new(|cx| {
7043 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7044 for (buffer_handle, transaction) in &entries {
7045 let edited_ranges = buffer_handle
7046 .read(cx)
7047 .edited_ranges_for_transaction::<Point>(transaction)
7048 .collect::<Vec<_>>();
7049 let (ranges, _) = multibuffer.set_excerpts_for_path(
7050 PathKey::for_buffer(buffer_handle, cx),
7051 buffer_handle.clone(),
7052 edited_ranges,
7053 multibuffer_context_lines(cx),
7054 cx,
7055 );
7056
7057 ranges_to_highlight.extend(ranges);
7058 }
7059 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7060 multibuffer
7061 });
7062
7063 workspace.update_in(cx, |workspace, window, cx| {
7064 let project = workspace.project().clone();
7065 let editor =
7066 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7067 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7068 editor.update(cx, |editor, cx| {
7069 editor.highlight_background(
7070 HighlightKey::Editor,
7071 &ranges_to_highlight,
7072 |_, theme| theme.colors().editor_highlighted_line_background,
7073 cx,
7074 );
7075 });
7076 })?;
7077
7078 Ok(())
7079 }
7080
7081 pub fn clear_code_action_providers(&mut self) {
7082 self.code_action_providers.clear();
7083 self.available_code_actions.take();
7084 }
7085
7086 pub fn add_code_action_provider(
7087 &mut self,
7088 provider: Rc<dyn CodeActionProvider>,
7089 window: &mut Window,
7090 cx: &mut Context<Self>,
7091 ) {
7092 if self
7093 .code_action_providers
7094 .iter()
7095 .any(|existing_provider| existing_provider.id() == provider.id())
7096 {
7097 return;
7098 }
7099
7100 self.code_action_providers.push(provider);
7101 self.refresh_code_actions(window, cx);
7102 }
7103
7104 pub fn remove_code_action_provider(
7105 &mut self,
7106 id: Arc<str>,
7107 window: &mut Window,
7108 cx: &mut Context<Self>,
7109 ) {
7110 self.code_action_providers
7111 .retain(|provider| provider.id() != id);
7112 self.refresh_code_actions(window, cx);
7113 }
7114
7115 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7116 !self.code_action_providers.is_empty()
7117 && EditorSettings::get_global(cx).toolbar.code_actions
7118 }
7119
7120 pub fn has_available_code_actions(&self) -> bool {
7121 self.available_code_actions
7122 .as_ref()
7123 .is_some_and(|(_, actions)| !actions.is_empty())
7124 }
7125
7126 fn render_inline_code_actions(
7127 &self,
7128 icon_size: ui::IconSize,
7129 display_row: DisplayRow,
7130 is_active: bool,
7131 cx: &mut Context<Self>,
7132 ) -> AnyElement {
7133 let show_tooltip = !self.context_menu_visible();
7134 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7135 .icon_size(icon_size)
7136 .shape(ui::IconButtonShape::Square)
7137 .icon_color(ui::Color::Hidden)
7138 .toggle_state(is_active)
7139 .when(show_tooltip, |this| {
7140 this.tooltip({
7141 let focus_handle = self.focus_handle.clone();
7142 move |_window, cx| {
7143 Tooltip::for_action_in(
7144 "Toggle Code Actions",
7145 &ToggleCodeActions {
7146 deployed_from: None,
7147 quick_launch: false,
7148 },
7149 &focus_handle,
7150 cx,
7151 )
7152 }
7153 })
7154 })
7155 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7156 window.focus(&editor.focus_handle(cx), cx);
7157 editor.toggle_code_actions(
7158 &crate::actions::ToggleCodeActions {
7159 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7160 display_row,
7161 )),
7162 quick_launch: false,
7163 },
7164 window,
7165 cx,
7166 );
7167 }))
7168 .into_any_element()
7169 }
7170
7171 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7172 &self.context_menu
7173 }
7174
7175 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7176 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7177 cx.background_executor()
7178 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7179 .await;
7180
7181 let (start_buffer, start, _, end, newest_selection) = this
7182 .update(cx, |this, cx| {
7183 let newest_selection = this.selections.newest_anchor().clone();
7184 if newest_selection.head().diff_base_anchor.is_some() {
7185 return None;
7186 }
7187 let display_snapshot = this.display_snapshot(cx);
7188 let newest_selection_adjusted =
7189 this.selections.newest_adjusted(&display_snapshot);
7190 let buffer = this.buffer.read(cx);
7191
7192 let (start_buffer, start) =
7193 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7194 let (end_buffer, end) =
7195 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7196
7197 Some((start_buffer, start, end_buffer, end, newest_selection))
7198 })?
7199 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7200 .context(
7201 "Expected selection to lie in a single buffer when refreshing code actions",
7202 )?;
7203 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7204 let providers = this.code_action_providers.clone();
7205 let tasks = this
7206 .code_action_providers
7207 .iter()
7208 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7209 .collect::<Vec<_>>();
7210 (providers, tasks)
7211 })?;
7212
7213 let mut actions = Vec::new();
7214 for (provider, provider_actions) in
7215 providers.into_iter().zip(future::join_all(tasks).await)
7216 {
7217 if let Some(provider_actions) = provider_actions.log_err() {
7218 actions.extend(provider_actions.into_iter().map(|action| {
7219 AvailableCodeAction {
7220 excerpt_id: newest_selection.start.excerpt_id,
7221 action,
7222 provider: provider.clone(),
7223 }
7224 }));
7225 }
7226 }
7227
7228 this.update(cx, |this, cx| {
7229 this.available_code_actions = if actions.is_empty() {
7230 None
7231 } else {
7232 Some((
7233 Location {
7234 buffer: start_buffer,
7235 range: start..end,
7236 },
7237 actions.into(),
7238 ))
7239 };
7240 cx.notify();
7241 })
7242 }));
7243 }
7244
7245 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7246 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7247 self.show_git_blame_inline = false;
7248
7249 self.show_git_blame_inline_delay_task =
7250 Some(cx.spawn_in(window, async move |this, cx| {
7251 cx.background_executor().timer(delay).await;
7252
7253 this.update(cx, |this, cx| {
7254 this.show_git_blame_inline = true;
7255 cx.notify();
7256 })
7257 .log_err();
7258 }));
7259 }
7260 }
7261
7262 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7263 let snapshot = self.snapshot(window, cx);
7264 let cursor = self
7265 .selections
7266 .newest::<Point>(&snapshot.display_snapshot)
7267 .head();
7268 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7269 else {
7270 return;
7271 };
7272
7273 if self.blame.is_none() {
7274 self.start_git_blame(true, window, cx);
7275 }
7276 let Some(blame) = self.blame.as_ref() else {
7277 return;
7278 };
7279
7280 let row_info = RowInfo {
7281 buffer_id: Some(buffer.remote_id()),
7282 buffer_row: Some(point.row),
7283 ..Default::default()
7284 };
7285 let Some((buffer, blame_entry)) = blame
7286 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7287 .flatten()
7288 else {
7289 return;
7290 };
7291
7292 let anchor = self.selections.newest_anchor().head();
7293 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7294 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7295 self.show_blame_popover(
7296 buffer,
7297 &blame_entry,
7298 position + last_bounds.origin,
7299 true,
7300 cx,
7301 );
7302 };
7303 }
7304
7305 fn show_blame_popover(
7306 &mut self,
7307 buffer: BufferId,
7308 blame_entry: &BlameEntry,
7309 position: gpui::Point<Pixels>,
7310 ignore_timeout: bool,
7311 cx: &mut Context<Self>,
7312 ) {
7313 if let Some(state) = &mut self.inline_blame_popover {
7314 state.hide_task.take();
7315 } else {
7316 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7317 let blame_entry = blame_entry.clone();
7318 let show_task = cx.spawn(async move |editor, cx| {
7319 if !ignore_timeout {
7320 cx.background_executor()
7321 .timer(std::time::Duration::from_millis(blame_popover_delay))
7322 .await;
7323 }
7324 editor
7325 .update(cx, |editor, cx| {
7326 editor.inline_blame_popover_show_task.take();
7327 let Some(blame) = editor.blame.as_ref() else {
7328 return;
7329 };
7330 let blame = blame.read(cx);
7331 let details = blame.details_for_entry(buffer, &blame_entry);
7332 let markdown = cx.new(|cx| {
7333 Markdown::new(
7334 details
7335 .as_ref()
7336 .map(|message| message.message.clone())
7337 .unwrap_or_default(),
7338 None,
7339 None,
7340 cx,
7341 )
7342 });
7343 editor.inline_blame_popover = Some(InlineBlamePopover {
7344 position,
7345 hide_task: None,
7346 popover_bounds: None,
7347 popover_state: InlineBlamePopoverState {
7348 scroll_handle: ScrollHandle::new(),
7349 commit_message: details,
7350 markdown,
7351 },
7352 keyboard_grace: ignore_timeout,
7353 });
7354 cx.notify();
7355 })
7356 .ok();
7357 });
7358 self.inline_blame_popover_show_task = Some(show_task);
7359 }
7360 }
7361
7362 pub fn has_mouse_context_menu(&self) -> bool {
7363 self.mouse_context_menu.is_some()
7364 }
7365
7366 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7367 self.inline_blame_popover_show_task.take();
7368 if let Some(state) = &mut self.inline_blame_popover {
7369 let hide_task = cx.spawn(async move |editor, cx| {
7370 if !ignore_timeout {
7371 cx.background_executor()
7372 .timer(std::time::Duration::from_millis(100))
7373 .await;
7374 }
7375 editor
7376 .update(cx, |editor, cx| {
7377 editor.inline_blame_popover.take();
7378 cx.notify();
7379 })
7380 .ok();
7381 });
7382 state.hide_task = Some(hide_task);
7383 true
7384 } else {
7385 false
7386 }
7387 }
7388
7389 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7390 if self.pending_rename.is_some() {
7391 return None;
7392 }
7393
7394 let provider = self.semantics_provider.clone()?;
7395 let buffer = self.buffer.read(cx);
7396 let newest_selection = self.selections.newest_anchor().clone();
7397 let cursor_position = newest_selection.head();
7398 let (cursor_buffer, cursor_buffer_position) =
7399 buffer.text_anchor_for_position(cursor_position, cx)?;
7400 let (tail_buffer, tail_buffer_position) =
7401 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7402 if cursor_buffer != tail_buffer {
7403 return None;
7404 }
7405
7406 let snapshot = cursor_buffer.read(cx).snapshot();
7407 let word_ranges = cx.background_spawn(async move {
7408 // this might look odd to put on the background thread, but
7409 // `surrounding_word` can be quite expensive as it calls into
7410 // tree-sitter language scopes
7411 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7412 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7413 (start_word_range, end_word_range)
7414 });
7415
7416 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7417 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7418 let (start_word_range, end_word_range) = word_ranges.await;
7419 if start_word_range != end_word_range {
7420 this.update(cx, |this, cx| {
7421 this.document_highlights_task.take();
7422 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7423 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7424 })
7425 .ok();
7426 return;
7427 }
7428 cx.background_executor()
7429 .timer(Duration::from_millis(debounce))
7430 .await;
7431
7432 let highlights = if let Some(highlights) = cx.update(|cx| {
7433 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7434 }) {
7435 highlights.await.log_err()
7436 } else {
7437 None
7438 };
7439
7440 if let Some(highlights) = highlights {
7441 this.update(cx, |this, cx| {
7442 if this.pending_rename.is_some() {
7443 return;
7444 }
7445
7446 let buffer = this.buffer.read(cx);
7447 if buffer
7448 .text_anchor_for_position(cursor_position, cx)
7449 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7450 {
7451 return;
7452 }
7453
7454 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7455 let mut write_ranges = Vec::new();
7456 let mut read_ranges = Vec::new();
7457 for highlight in highlights {
7458 let buffer_id = cursor_buffer.read(cx).remote_id();
7459 for (excerpt_id, _, excerpt_range) in
7460 buffer.excerpts_for_buffer(buffer_id, cx)
7461 {
7462 let start = highlight
7463 .range
7464 .start
7465 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7466 let end = highlight
7467 .range
7468 .end
7469 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7470 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7471 continue;
7472 }
7473
7474 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7475 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7476 write_ranges.push(range);
7477 } else {
7478 read_ranges.push(range);
7479 }
7480 }
7481 }
7482
7483 this.highlight_background(
7484 HighlightKey::DocumentHighlightRead,
7485 &read_ranges,
7486 |_, theme| theme.colors().editor_document_highlight_read_background,
7487 cx,
7488 );
7489 this.highlight_background(
7490 HighlightKey::DocumentHighlightWrite,
7491 &write_ranges,
7492 |_, theme| theme.colors().editor_document_highlight_write_background,
7493 cx,
7494 );
7495 cx.notify();
7496 })
7497 .log_err();
7498 }
7499 }));
7500 None
7501 }
7502
7503 fn prepare_highlight_query_from_selection(
7504 &mut self,
7505 snapshot: &DisplaySnapshot,
7506 cx: &mut Context<Editor>,
7507 ) -> Option<(String, Range<Anchor>)> {
7508 if matches!(self.mode, EditorMode::SingleLine) {
7509 return None;
7510 }
7511 if !EditorSettings::get_global(cx).selection_highlight {
7512 return None;
7513 }
7514 if self.selections.count() != 1 || self.selections.line_mode() {
7515 return None;
7516 }
7517 let selection = self.selections.newest::<Point>(&snapshot);
7518 // If the selection spans multiple rows OR it is empty
7519 if selection.start.row != selection.end.row
7520 || selection.start.column == selection.end.column
7521 {
7522 return None;
7523 }
7524 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7525 let query = snapshot
7526 .buffer_snapshot()
7527 .text_for_range(selection_anchor_range.clone())
7528 .collect::<String>();
7529 if query.trim().is_empty() {
7530 return None;
7531 }
7532 Some((query, selection_anchor_range))
7533 }
7534
7535 #[ztracing::instrument(skip_all)]
7536 fn update_selection_occurrence_highlights(
7537 &mut self,
7538 multi_buffer_snapshot: MultiBufferSnapshot,
7539 query_text: String,
7540 query_range: Range<Anchor>,
7541 multi_buffer_range_to_query: Range<Point>,
7542 use_debounce: bool,
7543 window: &mut Window,
7544 cx: &mut Context<Editor>,
7545 ) -> Task<()> {
7546 cx.spawn_in(window, async move |editor, cx| {
7547 if use_debounce {
7548 cx.background_executor()
7549 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7550 .await;
7551 }
7552 let match_task = cx.background_spawn(async move {
7553 let buffer_ranges = multi_buffer_snapshot
7554 .range_to_buffer_ranges(
7555 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7556 )
7557 .into_iter()
7558 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7559 let mut match_ranges = Vec::new();
7560 let Ok(regex) = project::search::SearchQuery::text(
7561 query_text,
7562 false,
7563 false,
7564 false,
7565 Default::default(),
7566 Default::default(),
7567 false,
7568 None,
7569 ) else {
7570 return Vec::default();
7571 };
7572 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7573 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7574 match_ranges.extend(
7575 regex
7576 .search(
7577 buffer_snapshot,
7578 Some(search_range.start.0..search_range.end.0),
7579 )
7580 .await
7581 .into_iter()
7582 .filter_map(|match_range| {
7583 let match_start = buffer_snapshot
7584 .anchor_after(search_range.start + match_range.start);
7585 let match_end = buffer_snapshot
7586 .anchor_before(search_range.start + match_range.end);
7587 let match_anchor_range =
7588 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7589 (match_anchor_range != query_range).then_some(match_anchor_range)
7590 }),
7591 );
7592 }
7593 match_ranges
7594 });
7595 let match_ranges = match_task.await;
7596 editor
7597 .update_in(cx, |editor, _, cx| {
7598 if use_debounce {
7599 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7600 editor.debounced_selection_highlight_complete = true;
7601 } else if editor.debounced_selection_highlight_complete {
7602 return;
7603 }
7604 if !match_ranges.is_empty() {
7605 editor.highlight_background(
7606 HighlightKey::SelectedTextHighlight,
7607 &match_ranges,
7608 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7609 cx,
7610 )
7611 }
7612 })
7613 .log_err();
7614 })
7615 }
7616
7617 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7618 struct NewlineFold;
7619 let type_id = std::any::TypeId::of::<NewlineFold>();
7620 if !self.mode.is_single_line() {
7621 return;
7622 }
7623 let snapshot = self.snapshot(window, cx);
7624 if snapshot.buffer_snapshot().max_point().row == 0 {
7625 return;
7626 }
7627 let task = cx.background_spawn(async move {
7628 let new_newlines = snapshot
7629 .buffer_chars_at(MultiBufferOffset(0))
7630 .filter_map(|(c, i)| {
7631 if c == '\n' {
7632 Some(
7633 snapshot.buffer_snapshot().anchor_after(i)
7634 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7635 )
7636 } else {
7637 None
7638 }
7639 })
7640 .collect::<Vec<_>>();
7641 let existing_newlines = snapshot
7642 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7643 .filter_map(|fold| {
7644 if fold.placeholder.type_tag == Some(type_id) {
7645 Some(fold.range.start..fold.range.end)
7646 } else {
7647 None
7648 }
7649 })
7650 .collect::<Vec<_>>();
7651
7652 (new_newlines, existing_newlines)
7653 });
7654 self.folding_newlines = cx.spawn(async move |this, cx| {
7655 let (new_newlines, existing_newlines) = task.await;
7656 if new_newlines == existing_newlines {
7657 return;
7658 }
7659 let placeholder = FoldPlaceholder {
7660 render: Arc::new(move |_, _, cx| {
7661 div()
7662 .bg(cx.theme().status().hint_background)
7663 .border_b_1()
7664 .size_full()
7665 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7666 .border_color(cx.theme().status().hint)
7667 .child("\\n")
7668 .into_any()
7669 }),
7670 constrain_width: false,
7671 merge_adjacent: false,
7672 type_tag: Some(type_id),
7673 collapsed_text: None,
7674 };
7675 let creases = new_newlines
7676 .into_iter()
7677 .map(|range| Crease::simple(range, placeholder.clone()))
7678 .collect();
7679 this.update(cx, |this, cx| {
7680 this.display_map.update(cx, |display_map, cx| {
7681 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7682 display_map.fold(creases, cx);
7683 });
7684 })
7685 .ok();
7686 });
7687 }
7688
7689 #[ztracing::instrument(skip_all)]
7690 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7691 if !self.lsp_data_enabled() {
7692 return;
7693 }
7694 let cursor = self.selections.newest_anchor().head();
7695 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7696
7697 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7698 self.outline_symbols_at_cursor =
7699 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7700 cx.emit(EditorEvent::OutlineSymbolsChanged);
7701 cx.notify();
7702 } else {
7703 let syntax = cx.theme().syntax().clone();
7704 let background_task = cx.background_spawn(async move {
7705 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7706 });
7707 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7708 cx.spawn(async move |this, cx| {
7709 let symbols = background_task.await;
7710 this.update(cx, |this, cx| {
7711 this.outline_symbols_at_cursor = symbols;
7712 cx.emit(EditorEvent::OutlineSymbolsChanged);
7713 cx.notify();
7714 })
7715 .ok();
7716 });
7717 }
7718 }
7719
7720 #[ztracing::instrument(skip_all)]
7721 fn refresh_selected_text_highlights(
7722 &mut self,
7723 snapshot: &DisplaySnapshot,
7724 on_buffer_edit: bool,
7725 window: &mut Window,
7726 cx: &mut Context<Editor>,
7727 ) {
7728 let Some((query_text, query_range)) =
7729 self.prepare_highlight_query_from_selection(snapshot, cx)
7730 else {
7731 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7732 self.quick_selection_highlight_task.take();
7733 self.debounced_selection_highlight_task.take();
7734 self.debounced_selection_highlight_complete = false;
7735 return;
7736 };
7737 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7738 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7739 let query_changed = self
7740 .quick_selection_highlight_task
7741 .as_ref()
7742 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7743 if query_changed {
7744 self.debounced_selection_highlight_complete = false;
7745 }
7746 if on_buffer_edit || query_changed {
7747 self.quick_selection_highlight_task = Some((
7748 query_range.clone(),
7749 self.update_selection_occurrence_highlights(
7750 snapshot.buffer.clone(),
7751 query_text.clone(),
7752 query_range.clone(),
7753 self.multi_buffer_visible_range(&display_snapshot, cx),
7754 false,
7755 window,
7756 cx,
7757 ),
7758 ));
7759 }
7760 if on_buffer_edit
7761 || self
7762 .debounced_selection_highlight_task
7763 .as_ref()
7764 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7765 {
7766 let multi_buffer_start = multi_buffer_snapshot
7767 .anchor_before(MultiBufferOffset(0))
7768 .to_point(&multi_buffer_snapshot);
7769 let multi_buffer_end = multi_buffer_snapshot
7770 .anchor_after(multi_buffer_snapshot.len())
7771 .to_point(&multi_buffer_snapshot);
7772 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7773 self.debounced_selection_highlight_task = Some((
7774 query_range.clone(),
7775 self.update_selection_occurrence_highlights(
7776 snapshot.buffer.clone(),
7777 query_text,
7778 query_range,
7779 multi_buffer_full_range,
7780 true,
7781 window,
7782 cx,
7783 ),
7784 ));
7785 }
7786 }
7787
7788 pub fn multi_buffer_visible_range(
7789 &self,
7790 display_snapshot: &DisplaySnapshot,
7791 cx: &App,
7792 ) -> Range<Point> {
7793 let visible_start = self
7794 .scroll_manager
7795 .native_anchor(display_snapshot, cx)
7796 .anchor
7797 .to_point(display_snapshot.buffer_snapshot())
7798 .to_display_point(display_snapshot);
7799
7800 let mut target_end = visible_start;
7801 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7802
7803 visible_start.to_point(display_snapshot)
7804 ..display_snapshot
7805 .clip_point(target_end, Bias::Right)
7806 .to_point(display_snapshot)
7807 }
7808
7809 pub fn refresh_edit_prediction(
7810 &mut self,
7811 debounce: bool,
7812 user_requested: bool,
7813 window: &mut Window,
7814 cx: &mut Context<Self>,
7815 ) -> Option<()> {
7816 let provider = self.edit_prediction_provider()?;
7817 let cursor = self.selections.newest_anchor().head();
7818 let (buffer, cursor_buffer_position) =
7819 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7820
7821 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7822 return None;
7823 }
7824
7825 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7826 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7827 return None;
7828 }
7829
7830 self.update_visible_edit_prediction(window, cx);
7831
7832 if !user_requested
7833 && (!self.should_show_edit_predictions()
7834 || !self.is_focused(window)
7835 || buffer.read(cx).is_empty())
7836 {
7837 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7838 return None;
7839 }
7840
7841 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7842 Some(())
7843 }
7844
7845 fn show_edit_predictions_in_menu(&self) -> bool {
7846 match self.edit_prediction_settings {
7847 EditPredictionSettings::Disabled => false,
7848 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7849 }
7850 }
7851
7852 pub fn edit_predictions_enabled(&self) -> bool {
7853 match self.edit_prediction_settings {
7854 EditPredictionSettings::Disabled => false,
7855 EditPredictionSettings::Enabled { .. } => true,
7856 }
7857 }
7858
7859 fn edit_prediction_requires_modifier(&self) -> bool {
7860 match self.edit_prediction_settings {
7861 EditPredictionSettings::Disabled => false,
7862 EditPredictionSettings::Enabled {
7863 preview_requires_modifier,
7864 ..
7865 } => preview_requires_modifier,
7866 }
7867 }
7868
7869 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7870 if self.edit_prediction_provider.is_none() {
7871 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7872 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7873 return;
7874 }
7875
7876 let selection = self.selections.newest_anchor();
7877 let cursor = selection.head();
7878
7879 if let Some((buffer, cursor_buffer_position)) =
7880 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7881 {
7882 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7883 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7884 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7885 return;
7886 }
7887 self.edit_prediction_settings =
7888 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7889 }
7890 }
7891
7892 fn edit_prediction_settings_at_position(
7893 &self,
7894 buffer: &Entity<Buffer>,
7895 buffer_position: language::Anchor,
7896 cx: &App,
7897 ) -> EditPredictionSettings {
7898 if !self.mode.is_full()
7899 || !self.show_edit_predictions_override.unwrap_or(true)
7900 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7901 {
7902 return EditPredictionSettings::Disabled;
7903 }
7904
7905 let buffer = buffer.read(cx);
7906
7907 let file = buffer.file();
7908
7909 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7910 return EditPredictionSettings::Disabled;
7911 };
7912
7913 let by_provider = matches!(
7914 self.menu_edit_predictions_policy,
7915 MenuEditPredictionsPolicy::ByProvider
7916 );
7917
7918 let show_in_menu = by_provider
7919 && self
7920 .edit_prediction_provider
7921 .as_ref()
7922 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7923
7924 let preview_requires_modifier =
7925 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7926
7927 EditPredictionSettings::Enabled {
7928 show_in_menu,
7929 preview_requires_modifier,
7930 }
7931 }
7932
7933 fn should_show_edit_predictions(&self) -> bool {
7934 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7935 }
7936
7937 pub fn edit_prediction_preview_is_active(&self) -> bool {
7938 matches!(
7939 self.edit_prediction_preview,
7940 EditPredictionPreview::Active { .. }
7941 )
7942 }
7943
7944 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7945 let cursor = self.selections.newest_anchor().head();
7946 if let Some((buffer, cursor_position)) =
7947 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7948 {
7949 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7950 } else {
7951 false
7952 }
7953 }
7954
7955 pub fn supports_minimap(&self, cx: &App) -> bool {
7956 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7957 }
7958
7959 fn edit_predictions_enabled_in_buffer(
7960 &self,
7961 buffer: &Entity<Buffer>,
7962 buffer_position: language::Anchor,
7963 cx: &App,
7964 ) -> bool {
7965 maybe!({
7966 if self.read_only(cx) {
7967 return Some(false);
7968 }
7969 let provider = self.edit_prediction_provider()?;
7970 if !provider.is_enabled(buffer, buffer_position, cx) {
7971 return Some(false);
7972 }
7973 let buffer = buffer.read(cx);
7974 let Some(file) = buffer.file() else {
7975 return Some(true);
7976 };
7977 let settings = all_language_settings(Some(file), cx);
7978 Some(settings.edit_predictions_enabled_for_file(file, cx))
7979 })
7980 .unwrap_or(false)
7981 }
7982
7983 pub fn show_edit_prediction(
7984 &mut self,
7985 _: &ShowEditPrediction,
7986 window: &mut Window,
7987 cx: &mut Context<Self>,
7988 ) {
7989 if !self.has_active_edit_prediction() {
7990 self.refresh_edit_prediction(false, true, window, cx);
7991 return;
7992 }
7993
7994 self.update_visible_edit_prediction(window, cx);
7995 }
7996
7997 pub fn display_cursor_names(
7998 &mut self,
7999 _: &DisplayCursorNames,
8000 window: &mut Window,
8001 cx: &mut Context<Self>,
8002 ) {
8003 self.show_cursor_names(window, cx);
8004 }
8005
8006 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8007 self.show_cursor_names = true;
8008 cx.notify();
8009 cx.spawn_in(window, async move |this, cx| {
8010 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8011 this.update(cx, |this, cx| {
8012 this.show_cursor_names = false;
8013 cx.notify()
8014 })
8015 .ok()
8016 })
8017 .detach();
8018 }
8019
8020 pub fn accept_partial_edit_prediction(
8021 &mut self,
8022 granularity: EditPredictionGranularity,
8023 window: &mut Window,
8024 cx: &mut Context<Self>,
8025 ) {
8026 if self.show_edit_predictions_in_menu() {
8027 self.hide_context_menu(window, cx);
8028 }
8029
8030 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8031 return;
8032 };
8033
8034 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8035 return;
8036 }
8037
8038 match &active_edit_prediction.completion {
8039 EditPrediction::MoveWithin { target, .. } => {
8040 let target = *target;
8041
8042 if matches!(granularity, EditPredictionGranularity::Full) {
8043 if let Some(position_map) = &self.last_position_map {
8044 let target_row = target.to_display_point(&position_map.snapshot).row();
8045 let is_visible = position_map.visible_row_range.contains(&target_row);
8046
8047 if is_visible || !self.edit_prediction_requires_modifier() {
8048 self.unfold_ranges(&[target..target], true, false, cx);
8049 self.change_selections(
8050 SelectionEffects::scroll(Autoscroll::newest()),
8051 window,
8052 cx,
8053 |selections| {
8054 selections.select_anchor_ranges([target..target]);
8055 },
8056 );
8057 self.clear_row_highlights::<EditPredictionPreview>();
8058 self.edit_prediction_preview
8059 .set_previous_scroll_position(None);
8060 } else {
8061 // Highlight and request scroll
8062 self.edit_prediction_preview
8063 .set_previous_scroll_position(Some(
8064 position_map.snapshot.scroll_anchor,
8065 ));
8066 self.highlight_rows::<EditPredictionPreview>(
8067 target..target,
8068 cx.theme().colors().editor_highlighted_line_background,
8069 RowHighlightOptions {
8070 autoscroll: true,
8071 ..Default::default()
8072 },
8073 cx,
8074 );
8075 self.request_autoscroll(Autoscroll::fit(), cx);
8076 }
8077 }
8078 } else {
8079 self.change_selections(
8080 SelectionEffects::scroll(Autoscroll::newest()),
8081 window,
8082 cx,
8083 |selections| {
8084 selections.select_anchor_ranges([target..target]);
8085 },
8086 );
8087 }
8088 }
8089 EditPrediction::MoveOutside { snapshot, target } => {
8090 if let Some(workspace) = self.workspace() {
8091 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8092 .detach_and_log_err(cx);
8093 }
8094 }
8095 EditPrediction::Edit {
8096 edits,
8097 cursor_position,
8098 ..
8099 } => {
8100 self.report_edit_prediction_event(
8101 active_edit_prediction.completion_id.clone(),
8102 true,
8103 cx,
8104 );
8105
8106 match granularity {
8107 EditPredictionGranularity::Full => {
8108 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8109
8110 // Compute fallback cursor position BEFORE applying the edit,
8111 // so the anchor tracks through the edit correctly
8112 let fallback_cursor_target = {
8113 let snapshot = self.buffer.read(cx).snapshot(cx);
8114 edits.last().unwrap().0.end.bias_right(&snapshot)
8115 };
8116
8117 self.buffer.update(cx, |buffer, cx| {
8118 buffer.edit(edits.iter().cloned(), None, cx)
8119 });
8120
8121 if let Some(provider) = self.edit_prediction_provider() {
8122 provider.accept(cx);
8123 }
8124
8125 // Resolve cursor position after the edit is applied
8126 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8127 // The anchor tracks through the edit, then we add the offset
8128 let snapshot = self.buffer.read(cx).snapshot(cx);
8129 let base_offset = anchor.to_offset(&snapshot).0;
8130 let target_offset =
8131 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8132 snapshot.anchor_after(target_offset)
8133 } else {
8134 fallback_cursor_target
8135 };
8136
8137 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8138 s.select_anchor_ranges([cursor_target..cursor_target]);
8139 });
8140
8141 let selections = self.selections.disjoint_anchors_arc();
8142 if let Some(transaction_id_now) =
8143 self.buffer.read(cx).last_transaction_id(cx)
8144 {
8145 if transaction_id_prev != Some(transaction_id_now) {
8146 self.selection_history
8147 .insert_transaction(transaction_id_now, selections);
8148 }
8149 }
8150
8151 self.update_visible_edit_prediction(window, cx);
8152 if self.active_edit_prediction.is_none() {
8153 self.refresh_edit_prediction(true, true, window, cx);
8154 }
8155 cx.notify();
8156 }
8157 _ => {
8158 let snapshot = self.buffer.read(cx).snapshot(cx);
8159 let cursor_offset = self
8160 .selections
8161 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8162 .head();
8163
8164 let insertion = edits.iter().find_map(|(range, text)| {
8165 let range = range.to_offset(&snapshot);
8166 if range.is_empty() && range.start == cursor_offset {
8167 Some(text)
8168 } else {
8169 None
8170 }
8171 });
8172
8173 if let Some(text) = insertion {
8174 let text_to_insert = match granularity {
8175 EditPredictionGranularity::Word => {
8176 let mut partial = text
8177 .chars()
8178 .by_ref()
8179 .take_while(|c| c.is_alphabetic())
8180 .collect::<String>();
8181 if partial.is_empty() {
8182 partial = text
8183 .chars()
8184 .by_ref()
8185 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8186 .collect::<String>();
8187 }
8188 partial
8189 }
8190 EditPredictionGranularity::Line => {
8191 if let Some(line) = text.split_inclusive('\n').next() {
8192 line.to_string()
8193 } else {
8194 text.to_string()
8195 }
8196 }
8197 EditPredictionGranularity::Full => unreachable!(),
8198 };
8199
8200 cx.emit(EditorEvent::InputHandled {
8201 utf16_range_to_replace: None,
8202 text: text_to_insert.clone().into(),
8203 });
8204
8205 self.replace_selections(&text_to_insert, None, window, cx, false);
8206 self.refresh_edit_prediction(true, true, window, cx);
8207 cx.notify();
8208 } else {
8209 self.accept_partial_edit_prediction(
8210 EditPredictionGranularity::Full,
8211 window,
8212 cx,
8213 );
8214 }
8215 }
8216 }
8217 }
8218 }
8219
8220 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8221 }
8222
8223 pub fn accept_next_word_edit_prediction(
8224 &mut self,
8225 _: &AcceptNextWordEditPrediction,
8226 window: &mut Window,
8227 cx: &mut Context<Self>,
8228 ) {
8229 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8230 }
8231
8232 pub fn accept_next_line_edit_prediction(
8233 &mut self,
8234 _: &AcceptNextLineEditPrediction,
8235 window: &mut Window,
8236 cx: &mut Context<Self>,
8237 ) {
8238 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8239 }
8240
8241 pub fn accept_edit_prediction(
8242 &mut self,
8243 _: &AcceptEditPrediction,
8244 window: &mut Window,
8245 cx: &mut Context<Self>,
8246 ) {
8247 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8248 }
8249
8250 fn discard_edit_prediction(
8251 &mut self,
8252 reason: EditPredictionDiscardReason,
8253 cx: &mut Context<Self>,
8254 ) -> bool {
8255 if reason == EditPredictionDiscardReason::Rejected {
8256 let completion_id = self
8257 .active_edit_prediction
8258 .as_ref()
8259 .and_then(|active_completion| active_completion.completion_id.clone());
8260
8261 self.report_edit_prediction_event(completion_id, false, cx);
8262 }
8263
8264 if let Some(provider) = self.edit_prediction_provider() {
8265 provider.discard(reason, cx);
8266 }
8267
8268 self.take_active_edit_prediction(cx)
8269 }
8270
8271 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8272 let Some(provider) = self.edit_prediction_provider() else {
8273 return;
8274 };
8275
8276 let Some((_, buffer, _)) = self
8277 .buffer
8278 .read(cx)
8279 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8280 else {
8281 return;
8282 };
8283
8284 let extension = buffer
8285 .read(cx)
8286 .file()
8287 .and_then(|file| Some(file.path().extension()?.to_string()));
8288
8289 let event_type = match accepted {
8290 true => "Edit Prediction Accepted",
8291 false => "Edit Prediction Discarded",
8292 };
8293 telemetry::event!(
8294 event_type,
8295 provider = provider.name(),
8296 prediction_id = id,
8297 suggestion_accepted = accepted,
8298 file_extension = extension,
8299 );
8300 }
8301
8302 fn open_editor_at_anchor(
8303 snapshot: &language::BufferSnapshot,
8304 target: language::Anchor,
8305 workspace: &Entity<Workspace>,
8306 window: &mut Window,
8307 cx: &mut App,
8308 ) -> Task<Result<()>> {
8309 workspace.update(cx, |workspace, cx| {
8310 let path = snapshot.file().map(|file| file.full_path(cx));
8311 let Some(path) =
8312 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8313 else {
8314 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8315 };
8316 let target = text::ToPoint::to_point(&target, snapshot);
8317 let item = workspace.open_path(path, None, true, window, cx);
8318 window.spawn(cx, async move |cx| {
8319 let Some(editor) = item.await?.downcast::<Editor>() else {
8320 return Ok(());
8321 };
8322 editor
8323 .update_in(cx, |editor, window, cx| {
8324 editor.go_to_singleton_buffer_point(target, window, cx);
8325 })
8326 .ok();
8327 anyhow::Ok(())
8328 })
8329 })
8330 }
8331
8332 pub fn has_active_edit_prediction(&self) -> bool {
8333 self.active_edit_prediction.is_some()
8334 }
8335
8336 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8337 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8338 return false;
8339 };
8340
8341 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8342 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8343 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8344 true
8345 }
8346
8347 /// Returns true when we're displaying the edit prediction popover below the cursor
8348 /// like we are not previewing and the LSP autocomplete menu is visible
8349 /// or we are in `when_holding_modifier` mode.
8350 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8351 if self.edit_prediction_preview_is_active()
8352 || !self.show_edit_predictions_in_menu()
8353 || !self.edit_predictions_enabled()
8354 {
8355 return false;
8356 }
8357
8358 if self.has_visible_completions_menu() {
8359 return true;
8360 }
8361
8362 has_completion && self.edit_prediction_requires_modifier()
8363 }
8364
8365 fn handle_modifiers_changed(
8366 &mut self,
8367 modifiers: Modifiers,
8368 position_map: &PositionMap,
8369 window: &mut Window,
8370 cx: &mut Context<Self>,
8371 ) {
8372 // Ensure that the edit prediction preview is updated, even when not
8373 // enabled, if there's an active edit prediction preview.
8374 if self.show_edit_predictions_in_menu()
8375 || matches!(
8376 self.edit_prediction_preview,
8377 EditPredictionPreview::Active { .. }
8378 )
8379 {
8380 self.update_edit_prediction_preview(&modifiers, window, cx);
8381 }
8382
8383 self.update_selection_mode(&modifiers, position_map, window, cx);
8384
8385 let mouse_position = window.mouse_position();
8386 if !position_map.text_hitbox.is_hovered(window) {
8387 return;
8388 }
8389
8390 self.update_hovered_link(
8391 position_map.point_for_position(mouse_position),
8392 &position_map.snapshot,
8393 modifiers,
8394 window,
8395 cx,
8396 )
8397 }
8398
8399 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8400 match EditorSettings::get_global(cx).multi_cursor_modifier {
8401 MultiCursorModifier::Alt => modifiers.secondary(),
8402 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8403 }
8404 }
8405
8406 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8407 match EditorSettings::get_global(cx).multi_cursor_modifier {
8408 MultiCursorModifier::Alt => modifiers.alt,
8409 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8410 }
8411 }
8412
8413 fn columnar_selection_mode(
8414 modifiers: &Modifiers,
8415 cx: &mut Context<Self>,
8416 ) -> Option<ColumnarMode> {
8417 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8418 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8419 Some(ColumnarMode::FromMouse)
8420 } else if Self::is_alt_pressed(modifiers, cx) {
8421 Some(ColumnarMode::FromSelection)
8422 } else {
8423 None
8424 }
8425 } else {
8426 None
8427 }
8428 }
8429
8430 fn update_selection_mode(
8431 &mut self,
8432 modifiers: &Modifiers,
8433 position_map: &PositionMap,
8434 window: &mut Window,
8435 cx: &mut Context<Self>,
8436 ) {
8437 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8438 return;
8439 };
8440 if self.selections.pending_anchor().is_none() {
8441 return;
8442 }
8443
8444 let mouse_position = window.mouse_position();
8445 let point_for_position = position_map.point_for_position(mouse_position);
8446 let position = point_for_position.previous_valid;
8447
8448 self.select(
8449 SelectPhase::BeginColumnar {
8450 position,
8451 reset: false,
8452 mode,
8453 goal_column: point_for_position.exact_unclipped.column(),
8454 },
8455 window,
8456 cx,
8457 );
8458 }
8459
8460 fn update_edit_prediction_preview(
8461 &mut self,
8462 modifiers: &Modifiers,
8463 window: &mut Window,
8464 cx: &mut Context<Self>,
8465 ) {
8466 let mut modifiers_held = false;
8467
8468 // Check bindings for all granularities.
8469 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8470 let granularities = [
8471 EditPredictionGranularity::Full,
8472 EditPredictionGranularity::Line,
8473 EditPredictionGranularity::Word,
8474 ];
8475
8476 for granularity in granularities {
8477 if let Some(keystroke) = self
8478 .accept_edit_prediction_keybind(granularity, window, cx)
8479 .keystroke()
8480 {
8481 modifiers_held = modifiers_held
8482 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8483 }
8484 }
8485
8486 if modifiers_held {
8487 if matches!(
8488 self.edit_prediction_preview,
8489 EditPredictionPreview::Inactive { .. }
8490 ) {
8491 self.edit_prediction_preview = EditPredictionPreview::Active {
8492 previous_scroll_position: None,
8493 since: Instant::now(),
8494 };
8495
8496 self.update_visible_edit_prediction(window, cx);
8497 cx.notify();
8498 }
8499 } else if let EditPredictionPreview::Active {
8500 previous_scroll_position,
8501 since,
8502 } = self.edit_prediction_preview
8503 {
8504 if let (Some(previous_scroll_position), Some(position_map)) =
8505 (previous_scroll_position, self.last_position_map.as_ref())
8506 {
8507 self.set_scroll_position(
8508 previous_scroll_position
8509 .scroll_position(&position_map.snapshot.display_snapshot),
8510 window,
8511 cx,
8512 );
8513 }
8514
8515 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8516 released_too_fast: since.elapsed() < Duration::from_millis(200),
8517 };
8518 self.clear_row_highlights::<EditPredictionPreview>();
8519 self.update_visible_edit_prediction(window, cx);
8520 cx.notify();
8521 }
8522 }
8523
8524 fn update_visible_edit_prediction(
8525 &mut self,
8526 _window: &mut Window,
8527 cx: &mut Context<Self>,
8528 ) -> Option<()> {
8529 if self.ime_transaction.is_some() {
8530 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8531 return None;
8532 }
8533
8534 let selection = self.selections.newest_anchor();
8535 let cursor = selection.head();
8536 let multibuffer = self.buffer.read(cx).snapshot(cx);
8537
8538 // Check project-level disable_ai setting for the current buffer
8539 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8540 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8541 return None;
8542 }
8543 }
8544 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8545 let excerpt_id = cursor.excerpt_id;
8546
8547 let show_in_menu = self.show_edit_predictions_in_menu();
8548 let completions_menu_has_precedence = !show_in_menu
8549 && (self.context_menu.borrow().is_some()
8550 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8551
8552 if completions_menu_has_precedence
8553 || !offset_selection.is_empty()
8554 || self
8555 .active_edit_prediction
8556 .as_ref()
8557 .is_some_and(|completion| {
8558 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8559 return false;
8560 };
8561 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8562 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8563 !invalidation_range.contains(&offset_selection.head())
8564 })
8565 {
8566 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8567 return None;
8568 }
8569
8570 self.take_active_edit_prediction(cx);
8571 let Some(provider) = self.edit_prediction_provider() else {
8572 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8573 return None;
8574 };
8575
8576 let (buffer, cursor_buffer_position) =
8577 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8578
8579 self.edit_prediction_settings =
8580 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8581
8582 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8583
8584 if self.edit_prediction_indent_conflict {
8585 let cursor_point = cursor.to_point(&multibuffer);
8586 let mut suggested_indent = None;
8587 multibuffer.suggested_indents_callback(
8588 cursor_point.row..cursor_point.row + 1,
8589 &mut |_, indent| {
8590 suggested_indent = Some(indent);
8591 ControlFlow::Break(())
8592 },
8593 cx,
8594 );
8595
8596 if let Some(indent) = suggested_indent
8597 && indent.len == cursor_point.column
8598 {
8599 self.edit_prediction_indent_conflict = false;
8600 }
8601 }
8602
8603 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8604
8605 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8606 {
8607 edit_prediction_types::EditPrediction::Local {
8608 id,
8609 edits,
8610 cursor_position,
8611 edit_preview,
8612 } => (id, edits, cursor_position, edit_preview),
8613 edit_prediction_types::EditPrediction::Jump {
8614 id,
8615 snapshot,
8616 target,
8617 } => {
8618 if let Some(provider) = &self.edit_prediction_provider {
8619 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8620 }
8621 self.stale_edit_prediction_in_menu = None;
8622 self.active_edit_prediction = Some(EditPredictionState {
8623 inlay_ids: vec![],
8624 completion: EditPrediction::MoveOutside { snapshot, target },
8625 completion_id: id,
8626 invalidation_range: None,
8627 });
8628 cx.notify();
8629 return Some(());
8630 }
8631 };
8632
8633 let edits = edits
8634 .into_iter()
8635 .flat_map(|(range, new_text)| {
8636 Some((
8637 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8638 new_text,
8639 ))
8640 })
8641 .collect::<Vec<_>>();
8642 if edits.is_empty() {
8643 return None;
8644 }
8645
8646 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8647 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8648 Some((anchor, predicted.offset))
8649 });
8650
8651 let first_edit_start = edits.first().unwrap().0.start;
8652 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8653 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8654
8655 let last_edit_end = edits.last().unwrap().0.end;
8656 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8657 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8658
8659 let cursor_row = cursor.to_point(&multibuffer).row;
8660
8661 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8662
8663 let mut inlay_ids = Vec::new();
8664 let invalidation_row_range;
8665 let move_invalidation_row_range = if cursor_row < edit_start_row {
8666 Some(cursor_row..edit_end_row)
8667 } else if cursor_row > edit_end_row {
8668 Some(edit_start_row..cursor_row)
8669 } else {
8670 None
8671 };
8672 let supports_jump = self
8673 .edit_prediction_provider
8674 .as_ref()
8675 .map(|provider| provider.provider.supports_jump_to_edit())
8676 .unwrap_or(true);
8677
8678 let is_move = supports_jump
8679 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8680 let completion = if is_move {
8681 if let Some(provider) = &self.edit_prediction_provider {
8682 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8683 }
8684 invalidation_row_range =
8685 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8686 let target = first_edit_start;
8687 EditPrediction::MoveWithin { target, snapshot }
8688 } else {
8689 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8690 && !self.edit_predictions_hidden_for_vim_mode;
8691
8692 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8693 if provider.show_tab_accept_marker() {
8694 EditDisplayMode::TabAccept
8695 } else {
8696 EditDisplayMode::Inline
8697 }
8698 } else {
8699 EditDisplayMode::DiffPopover
8700 };
8701
8702 if show_completions_in_buffer {
8703 if let Some(provider) = &self.edit_prediction_provider {
8704 let suggestion_display_type = match display_mode {
8705 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8706 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8707 SuggestionDisplayType::GhostText
8708 }
8709 };
8710 provider.provider.did_show(suggestion_display_type, cx);
8711 }
8712 if edits
8713 .iter()
8714 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8715 {
8716 let mut inlays = Vec::new();
8717 for (range, new_text) in &edits {
8718 let inlay = Inlay::edit_prediction(
8719 post_inc(&mut self.next_inlay_id),
8720 range.start,
8721 new_text.as_ref(),
8722 );
8723 inlay_ids.push(inlay.id);
8724 inlays.push(inlay);
8725 }
8726
8727 self.splice_inlays(&[], inlays, cx);
8728 } else {
8729 let background_color = cx.theme().status().deleted_background;
8730 self.highlight_text(
8731 HighlightKey::EditPredictionHighlight,
8732 edits.iter().map(|(range, _)| range.clone()).collect(),
8733 HighlightStyle {
8734 background_color: Some(background_color),
8735 ..Default::default()
8736 },
8737 cx,
8738 );
8739 }
8740 }
8741
8742 invalidation_row_range = edit_start_row..edit_end_row;
8743
8744 EditPrediction::Edit {
8745 edits,
8746 cursor_position,
8747 edit_preview,
8748 display_mode,
8749 snapshot,
8750 }
8751 };
8752
8753 let invalidation_range = multibuffer
8754 .anchor_before(Point::new(invalidation_row_range.start, 0))
8755 ..multibuffer.anchor_after(Point::new(
8756 invalidation_row_range.end,
8757 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8758 ));
8759
8760 self.stale_edit_prediction_in_menu = None;
8761 self.active_edit_prediction = Some(EditPredictionState {
8762 inlay_ids,
8763 completion,
8764 completion_id,
8765 invalidation_range: Some(invalidation_range),
8766 });
8767
8768 cx.notify();
8769
8770 Some(())
8771 }
8772
8773 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8774 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8775 }
8776
8777 /// Get all display points of breakpoints that will be rendered within editor
8778 ///
8779 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8780 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8781 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8782 fn active_breakpoints(
8783 &self,
8784 range: Range<DisplayRow>,
8785 window: &mut Window,
8786 cx: &mut Context<Self>,
8787 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8788 let mut breakpoint_display_points = HashMap::default();
8789
8790 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8791 return breakpoint_display_points;
8792 };
8793
8794 let snapshot = self.snapshot(window, cx);
8795
8796 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8797 let Some(project) = self.project() else {
8798 return breakpoint_display_points;
8799 };
8800
8801 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8802 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8803
8804 for (buffer_snapshot, range, excerpt_id) in
8805 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8806 {
8807 let Some(buffer) = project
8808 .read(cx)
8809 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8810 else {
8811 continue;
8812 };
8813 let breakpoints = breakpoint_store.read(cx).breakpoints(
8814 &buffer,
8815 Some(
8816 buffer_snapshot.anchor_before(range.start)
8817 ..buffer_snapshot.anchor_after(range.end),
8818 ),
8819 buffer_snapshot,
8820 cx,
8821 );
8822 for (breakpoint, state) in breakpoints {
8823 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8824 let position = multi_buffer_anchor
8825 .to_point(&multi_buffer_snapshot)
8826 .to_display_point(&snapshot);
8827
8828 breakpoint_display_points.insert(
8829 position.row(),
8830 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8831 );
8832 }
8833 }
8834
8835 breakpoint_display_points
8836 }
8837
8838 fn breakpoint_context_menu(
8839 &self,
8840 anchor: Anchor,
8841 window: &mut Window,
8842 cx: &mut Context<Self>,
8843 ) -> Entity<ui::ContextMenu> {
8844 let weak_editor = cx.weak_entity();
8845 let focus_handle = self.focus_handle(cx);
8846
8847 let row = self
8848 .buffer
8849 .read(cx)
8850 .snapshot(cx)
8851 .summary_for_anchor::<Point>(&anchor)
8852 .row;
8853
8854 let breakpoint = self
8855 .breakpoint_at_row(row, window, cx)
8856 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8857
8858 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8859 "Edit Log Breakpoint"
8860 } else {
8861 "Set Log Breakpoint"
8862 };
8863
8864 let condition_breakpoint_msg = if breakpoint
8865 .as_ref()
8866 .is_some_and(|bp| bp.1.condition.is_some())
8867 {
8868 "Edit Condition Breakpoint"
8869 } else {
8870 "Set Condition Breakpoint"
8871 };
8872
8873 let hit_condition_breakpoint_msg = if breakpoint
8874 .as_ref()
8875 .is_some_and(|bp| bp.1.hit_condition.is_some())
8876 {
8877 "Edit Hit Condition Breakpoint"
8878 } else {
8879 "Set Hit Condition Breakpoint"
8880 };
8881
8882 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8883 "Unset Breakpoint"
8884 } else {
8885 "Set Breakpoint"
8886 };
8887
8888 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8889
8890 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8891 BreakpointState::Enabled => Some("Disable"),
8892 BreakpointState::Disabled => Some("Enable"),
8893 });
8894
8895 let (anchor, breakpoint) =
8896 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8897
8898 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8899 menu.on_blur_subscription(Subscription::new(|| {}))
8900 .context(focus_handle)
8901 .when(run_to_cursor, |this| {
8902 let weak_editor = weak_editor.clone();
8903 this.entry("Run to Cursor", None, move |window, cx| {
8904 weak_editor
8905 .update(cx, |editor, cx| {
8906 editor.change_selections(
8907 SelectionEffects::no_scroll(),
8908 window,
8909 cx,
8910 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8911 );
8912 })
8913 .ok();
8914
8915 window.dispatch_action(Box::new(RunToCursor), cx);
8916 })
8917 .separator()
8918 })
8919 .when_some(toggle_state_msg, |this, msg| {
8920 this.entry(msg, None, {
8921 let weak_editor = weak_editor.clone();
8922 let breakpoint = breakpoint.clone();
8923 move |_window, cx| {
8924 weak_editor
8925 .update(cx, |this, cx| {
8926 this.edit_breakpoint_at_anchor(
8927 anchor,
8928 breakpoint.as_ref().clone(),
8929 BreakpointEditAction::InvertState,
8930 cx,
8931 );
8932 })
8933 .log_err();
8934 }
8935 })
8936 })
8937 .entry(set_breakpoint_msg, None, {
8938 let weak_editor = weak_editor.clone();
8939 let breakpoint = breakpoint.clone();
8940 move |_window, cx| {
8941 weak_editor
8942 .update(cx, |this, cx| {
8943 this.edit_breakpoint_at_anchor(
8944 anchor,
8945 breakpoint.as_ref().clone(),
8946 BreakpointEditAction::Toggle,
8947 cx,
8948 );
8949 })
8950 .log_err();
8951 }
8952 })
8953 .entry(log_breakpoint_msg, None, {
8954 let breakpoint = breakpoint.clone();
8955 let weak_editor = weak_editor.clone();
8956 move |window, cx| {
8957 weak_editor
8958 .update(cx, |this, cx| {
8959 this.add_edit_breakpoint_block(
8960 anchor,
8961 breakpoint.as_ref(),
8962 BreakpointPromptEditAction::Log,
8963 window,
8964 cx,
8965 );
8966 })
8967 .log_err();
8968 }
8969 })
8970 .entry(condition_breakpoint_msg, None, {
8971 let breakpoint = breakpoint.clone();
8972 let weak_editor = weak_editor.clone();
8973 move |window, cx| {
8974 weak_editor
8975 .update(cx, |this, cx| {
8976 this.add_edit_breakpoint_block(
8977 anchor,
8978 breakpoint.as_ref(),
8979 BreakpointPromptEditAction::Condition,
8980 window,
8981 cx,
8982 );
8983 })
8984 .log_err();
8985 }
8986 })
8987 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8988 weak_editor
8989 .update(cx, |this, cx| {
8990 this.add_edit_breakpoint_block(
8991 anchor,
8992 breakpoint.as_ref(),
8993 BreakpointPromptEditAction::HitCondition,
8994 window,
8995 cx,
8996 );
8997 })
8998 .log_err();
8999 })
9000 })
9001 }
9002
9003 fn render_breakpoint(
9004 &self,
9005 position: Anchor,
9006 row: DisplayRow,
9007 breakpoint: &Breakpoint,
9008 state: Option<BreakpointSessionState>,
9009 cx: &mut Context<Self>,
9010 ) -> IconButton {
9011 let is_rejected = state.is_some_and(|s| !s.verified);
9012 // Is it a breakpoint that shows up when hovering over gutter?
9013 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9014 (false, false),
9015 |PhantomBreakpointIndicator {
9016 is_active,
9017 display_row,
9018 collides_with_existing_breakpoint,
9019 }| {
9020 (
9021 is_active && display_row == row,
9022 collides_with_existing_breakpoint,
9023 )
9024 },
9025 );
9026
9027 let (color, icon) = {
9028 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9029 (false, false) => ui::IconName::DebugBreakpoint,
9030 (true, false) => ui::IconName::DebugLogBreakpoint,
9031 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9032 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9033 };
9034
9035 let theme_colors = cx.theme().colors();
9036
9037 let color = if is_phantom {
9038 if collides_with_existing {
9039 Color::Custom(
9040 theme_colors
9041 .debugger_accent
9042 .blend(theme_colors.text.opacity(0.6)),
9043 )
9044 } else {
9045 Color::Hint
9046 }
9047 } else if is_rejected {
9048 Color::Disabled
9049 } else {
9050 Color::Debugger
9051 };
9052
9053 (color, icon)
9054 };
9055
9056 let breakpoint = Arc::from(breakpoint.clone());
9057
9058 let alt_as_text = gpui::Keystroke {
9059 modifiers: Modifiers::secondary_key(),
9060 ..Default::default()
9061 };
9062 let primary_action_text = if breakpoint.is_disabled() {
9063 "Enable breakpoint"
9064 } else if is_phantom && !collides_with_existing {
9065 "Set breakpoint"
9066 } else {
9067 "Unset breakpoint"
9068 };
9069 let focus_handle = self.focus_handle.clone();
9070
9071 let meta = if is_rejected {
9072 SharedString::from("No executable code is associated with this line.")
9073 } else if collides_with_existing && !breakpoint.is_disabled() {
9074 SharedString::from(format!(
9075 "{alt_as_text}-click to disable,\nright-click for more options."
9076 ))
9077 } else {
9078 SharedString::from("Right-click for more options.")
9079 };
9080 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9081 .icon_size(IconSize::XSmall)
9082 .size(ui::ButtonSize::None)
9083 .when(is_rejected, |this| {
9084 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9085 })
9086 .icon_color(color)
9087 .style(ButtonStyle::Transparent)
9088 .on_click(cx.listener({
9089 move |editor, event: &ClickEvent, window, cx| {
9090 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9091 BreakpointEditAction::InvertState
9092 } else {
9093 BreakpointEditAction::Toggle
9094 };
9095
9096 window.focus(&editor.focus_handle(cx), cx);
9097 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9098 editor.edit_breakpoint_at_anchor(
9099 position,
9100 breakpoint.as_ref().clone(),
9101 edit_action,
9102 cx,
9103 );
9104 }
9105 }))
9106 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9107 editor.set_breakpoint_context_menu(
9108 row,
9109 Some(position),
9110 event.position(),
9111 window,
9112 cx,
9113 );
9114 }))
9115 .tooltip(move |_window, cx| {
9116 Tooltip::with_meta_in(
9117 primary_action_text,
9118 Some(&ToggleBreakpoint),
9119 meta.clone(),
9120 &focus_handle,
9121 cx,
9122 )
9123 })
9124 }
9125
9126 fn build_tasks_context(
9127 project: &Entity<Project>,
9128 buffer: &Entity<Buffer>,
9129 buffer_row: u32,
9130 tasks: &Arc<RunnableTasks>,
9131 cx: &mut Context<Self>,
9132 ) -> Task<Option<task::TaskContext>> {
9133 let position = Point::new(buffer_row, tasks.column);
9134 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9135 let location = Location {
9136 buffer: buffer.clone(),
9137 range: range_start..range_start,
9138 };
9139 // Fill in the environmental variables from the tree-sitter captures
9140 let mut captured_task_variables = TaskVariables::default();
9141 for (capture_name, value) in tasks.extra_variables.clone() {
9142 captured_task_variables.insert(
9143 task::VariableName::Custom(capture_name.into()),
9144 value.clone(),
9145 );
9146 }
9147 project.update(cx, |project, cx| {
9148 project.task_store().update(cx, |task_store, cx| {
9149 task_store.task_context_for_location(captured_task_variables, location, cx)
9150 })
9151 })
9152 }
9153
9154 pub fn context_menu_visible(&self) -> bool {
9155 !self.edit_prediction_preview_is_active()
9156 && self
9157 .context_menu
9158 .borrow()
9159 .as_ref()
9160 .is_some_and(|menu| menu.visible())
9161 }
9162
9163 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9164 self.context_menu
9165 .borrow()
9166 .as_ref()
9167 .map(|menu| menu.origin())
9168 }
9169
9170 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9171 self.context_menu_options = Some(options);
9172 }
9173
9174 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9175 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9176
9177 fn render_edit_prediction_popover(
9178 &mut self,
9179 text_bounds: &Bounds<Pixels>,
9180 content_origin: gpui::Point<Pixels>,
9181 right_margin: Pixels,
9182 editor_snapshot: &EditorSnapshot,
9183 visible_row_range: Range<DisplayRow>,
9184 scroll_top: ScrollOffset,
9185 scroll_bottom: ScrollOffset,
9186 line_layouts: &[LineWithInvisibles],
9187 line_height: Pixels,
9188 scroll_position: gpui::Point<ScrollOffset>,
9189 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9190 newest_selection_head: Option<DisplayPoint>,
9191 editor_width: Pixels,
9192 style: &EditorStyle,
9193 window: &mut Window,
9194 cx: &mut App,
9195 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9196 if self.mode().is_minimap() {
9197 return None;
9198 }
9199 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9200
9201 if self.edit_prediction_visible_in_cursor_popover(true) {
9202 return None;
9203 }
9204
9205 match &active_edit_prediction.completion {
9206 EditPrediction::MoveWithin { target, .. } => {
9207 let target_display_point = target.to_display_point(editor_snapshot);
9208
9209 if self.edit_prediction_requires_modifier() {
9210 if !self.edit_prediction_preview_is_active() {
9211 return None;
9212 }
9213
9214 self.render_edit_prediction_modifier_jump_popover(
9215 text_bounds,
9216 content_origin,
9217 visible_row_range,
9218 line_layouts,
9219 line_height,
9220 scroll_pixel_position,
9221 newest_selection_head,
9222 target_display_point,
9223 window,
9224 cx,
9225 )
9226 } else {
9227 self.render_edit_prediction_eager_jump_popover(
9228 text_bounds,
9229 content_origin,
9230 editor_snapshot,
9231 visible_row_range,
9232 scroll_top,
9233 scroll_bottom,
9234 line_height,
9235 scroll_pixel_position,
9236 target_display_point,
9237 editor_width,
9238 window,
9239 cx,
9240 )
9241 }
9242 }
9243 EditPrediction::Edit {
9244 display_mode: EditDisplayMode::Inline,
9245 ..
9246 } => None,
9247 EditPrediction::Edit {
9248 display_mode: EditDisplayMode::TabAccept,
9249 edits,
9250 ..
9251 } => {
9252 let range = &edits.first()?.0;
9253 let target_display_point = range.end.to_display_point(editor_snapshot);
9254
9255 self.render_edit_prediction_end_of_line_popover(
9256 "Accept",
9257 editor_snapshot,
9258 visible_row_range,
9259 target_display_point,
9260 line_height,
9261 scroll_pixel_position,
9262 content_origin,
9263 editor_width,
9264 window,
9265 cx,
9266 )
9267 }
9268 EditPrediction::Edit {
9269 edits,
9270 edit_preview,
9271 display_mode: EditDisplayMode::DiffPopover,
9272 snapshot,
9273 ..
9274 } => self.render_edit_prediction_diff_popover(
9275 text_bounds,
9276 content_origin,
9277 right_margin,
9278 editor_snapshot,
9279 visible_row_range,
9280 line_layouts,
9281 line_height,
9282 scroll_position,
9283 scroll_pixel_position,
9284 newest_selection_head,
9285 editor_width,
9286 style,
9287 edits,
9288 edit_preview,
9289 snapshot,
9290 window,
9291 cx,
9292 ),
9293 EditPrediction::MoveOutside { snapshot, .. } => {
9294 let mut element = self
9295 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9296 .into_any();
9297
9298 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9299 let origin_x = text_bounds.size.width - size.width - px(30.);
9300 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9301 element.prepaint_at(origin, window, cx);
9302
9303 Some((element, origin))
9304 }
9305 }
9306 }
9307
9308 fn render_edit_prediction_modifier_jump_popover(
9309 &mut self,
9310 text_bounds: &Bounds<Pixels>,
9311 content_origin: gpui::Point<Pixels>,
9312 visible_row_range: Range<DisplayRow>,
9313 line_layouts: &[LineWithInvisibles],
9314 line_height: Pixels,
9315 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9316 newest_selection_head: Option<DisplayPoint>,
9317 target_display_point: DisplayPoint,
9318 window: &mut Window,
9319 cx: &mut App,
9320 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9321 let scrolled_content_origin =
9322 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9323
9324 const SCROLL_PADDING_Y: Pixels = px(12.);
9325
9326 if target_display_point.row() < visible_row_range.start {
9327 return self.render_edit_prediction_scroll_popover(
9328 &|_| SCROLL_PADDING_Y,
9329 IconName::ArrowUp,
9330 visible_row_range,
9331 line_layouts,
9332 newest_selection_head,
9333 scrolled_content_origin,
9334 window,
9335 cx,
9336 );
9337 } else if target_display_point.row() >= visible_row_range.end {
9338 return self.render_edit_prediction_scroll_popover(
9339 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9340 IconName::ArrowDown,
9341 visible_row_range,
9342 line_layouts,
9343 newest_selection_head,
9344 scrolled_content_origin,
9345 window,
9346 cx,
9347 );
9348 }
9349
9350 const POLE_WIDTH: Pixels = px(2.);
9351
9352 let line_layout =
9353 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9354 let target_column = target_display_point.column() as usize;
9355
9356 let target_x = line_layout.x_for_index(target_column);
9357 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9358 - scroll_pixel_position.y;
9359
9360 let flag_on_right = target_x < text_bounds.size.width / 2.;
9361
9362 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9363 border_color.l += 0.001;
9364
9365 let mut element = v_flex()
9366 .items_end()
9367 .when(flag_on_right, |el| el.items_start())
9368 .child(if flag_on_right {
9369 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9370 .rounded_bl(px(0.))
9371 .rounded_tl(px(0.))
9372 .border_l_2()
9373 .border_color(border_color)
9374 } else {
9375 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9376 .rounded_br(px(0.))
9377 .rounded_tr(px(0.))
9378 .border_r_2()
9379 .border_color(border_color)
9380 })
9381 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9382 .into_any();
9383
9384 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9385
9386 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9387 - point(
9388 if flag_on_right {
9389 POLE_WIDTH
9390 } else {
9391 size.width - POLE_WIDTH
9392 },
9393 size.height - line_height,
9394 );
9395
9396 origin.x = origin.x.max(content_origin.x);
9397
9398 element.prepaint_at(origin, window, cx);
9399
9400 Some((element, origin))
9401 }
9402
9403 fn render_edit_prediction_scroll_popover(
9404 &mut self,
9405 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9406 scroll_icon: IconName,
9407 visible_row_range: Range<DisplayRow>,
9408 line_layouts: &[LineWithInvisibles],
9409 newest_selection_head: Option<DisplayPoint>,
9410 scrolled_content_origin: gpui::Point<Pixels>,
9411 window: &mut Window,
9412 cx: &mut App,
9413 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9414 let mut element = self
9415 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9416 .into_any();
9417
9418 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9419
9420 let cursor = newest_selection_head?;
9421 let cursor_row_layout =
9422 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9423 let cursor_column = cursor.column() as usize;
9424
9425 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9426
9427 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9428
9429 element.prepaint_at(origin, window, cx);
9430 Some((element, origin))
9431 }
9432
9433 fn render_edit_prediction_eager_jump_popover(
9434 &mut self,
9435 text_bounds: &Bounds<Pixels>,
9436 content_origin: gpui::Point<Pixels>,
9437 editor_snapshot: &EditorSnapshot,
9438 visible_row_range: Range<DisplayRow>,
9439 scroll_top: ScrollOffset,
9440 scroll_bottom: ScrollOffset,
9441 line_height: Pixels,
9442 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9443 target_display_point: DisplayPoint,
9444 editor_width: Pixels,
9445 window: &mut Window,
9446 cx: &mut App,
9447 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9448 if target_display_point.row().as_f64() < scroll_top {
9449 let mut element = self
9450 .render_edit_prediction_line_popover(
9451 "Jump to Edit",
9452 Some(IconName::ArrowUp),
9453 window,
9454 cx,
9455 )
9456 .into_any();
9457
9458 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9459 let offset = point(
9460 (text_bounds.size.width - size.width) / 2.,
9461 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9462 );
9463
9464 let origin = text_bounds.origin + offset;
9465 element.prepaint_at(origin, window, cx);
9466 Some((element, origin))
9467 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9468 let mut element = self
9469 .render_edit_prediction_line_popover(
9470 "Jump to Edit",
9471 Some(IconName::ArrowDown),
9472 window,
9473 cx,
9474 )
9475 .into_any();
9476
9477 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9478 let offset = point(
9479 (text_bounds.size.width - size.width) / 2.,
9480 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9481 );
9482
9483 let origin = text_bounds.origin + offset;
9484 element.prepaint_at(origin, window, cx);
9485 Some((element, origin))
9486 } else {
9487 self.render_edit_prediction_end_of_line_popover(
9488 "Jump to Edit",
9489 editor_snapshot,
9490 visible_row_range,
9491 target_display_point,
9492 line_height,
9493 scroll_pixel_position,
9494 content_origin,
9495 editor_width,
9496 window,
9497 cx,
9498 )
9499 }
9500 }
9501
9502 fn render_edit_prediction_end_of_line_popover(
9503 self: &mut Editor,
9504 label: &'static str,
9505 editor_snapshot: &EditorSnapshot,
9506 visible_row_range: Range<DisplayRow>,
9507 target_display_point: DisplayPoint,
9508 line_height: Pixels,
9509 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9510 content_origin: gpui::Point<Pixels>,
9511 editor_width: Pixels,
9512 window: &mut Window,
9513 cx: &mut App,
9514 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9515 let target_line_end = DisplayPoint::new(
9516 target_display_point.row(),
9517 editor_snapshot.line_len(target_display_point.row()),
9518 );
9519
9520 let mut element = self
9521 .render_edit_prediction_line_popover(label, None, window, cx)
9522 .into_any();
9523
9524 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9525
9526 let line_origin =
9527 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9528
9529 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9530 let mut origin = start_point
9531 + line_origin
9532 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9533 origin.x = origin.x.max(content_origin.x);
9534
9535 let max_x = content_origin.x + editor_width - size.width;
9536
9537 if origin.x > max_x {
9538 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9539
9540 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9541 origin.y += offset;
9542 IconName::ArrowUp
9543 } else {
9544 origin.y -= offset;
9545 IconName::ArrowDown
9546 };
9547
9548 element = self
9549 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9550 .into_any();
9551
9552 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9553
9554 origin.x = content_origin.x + editor_width - size.width - px(2.);
9555 }
9556
9557 element.prepaint_at(origin, window, cx);
9558 Some((element, origin))
9559 }
9560
9561 fn render_edit_prediction_diff_popover(
9562 self: &Editor,
9563 text_bounds: &Bounds<Pixels>,
9564 content_origin: gpui::Point<Pixels>,
9565 right_margin: Pixels,
9566 editor_snapshot: &EditorSnapshot,
9567 visible_row_range: Range<DisplayRow>,
9568 line_layouts: &[LineWithInvisibles],
9569 line_height: Pixels,
9570 scroll_position: gpui::Point<ScrollOffset>,
9571 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9572 newest_selection_head: Option<DisplayPoint>,
9573 editor_width: Pixels,
9574 style: &EditorStyle,
9575 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9576 edit_preview: &Option<language::EditPreview>,
9577 snapshot: &language::BufferSnapshot,
9578 window: &mut Window,
9579 cx: &mut App,
9580 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9581 let edit_start = edits
9582 .first()
9583 .unwrap()
9584 .0
9585 .start
9586 .to_display_point(editor_snapshot);
9587 let edit_end = edits
9588 .last()
9589 .unwrap()
9590 .0
9591 .end
9592 .to_display_point(editor_snapshot);
9593
9594 let is_visible = visible_row_range.contains(&edit_start.row())
9595 || visible_row_range.contains(&edit_end.row());
9596 if !is_visible {
9597 return None;
9598 }
9599
9600 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9601 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9602 } else {
9603 // Fallback for providers without edit_preview
9604 crate::edit_prediction_fallback_text(edits, cx)
9605 };
9606
9607 let styled_text = highlighted_edits.to_styled_text(&style.text);
9608 let line_count = highlighted_edits.text.lines().count();
9609
9610 const BORDER_WIDTH: Pixels = px(1.);
9611
9612 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9613 let has_keybind = keybind.is_some();
9614
9615 let mut element = h_flex()
9616 .items_start()
9617 .child(
9618 h_flex()
9619 .bg(cx.theme().colors().editor_background)
9620 .border(BORDER_WIDTH)
9621 .shadow_xs()
9622 .border_color(cx.theme().colors().border)
9623 .rounded_l_lg()
9624 .when(line_count > 1, |el| el.rounded_br_lg())
9625 .pr_1()
9626 .child(styled_text),
9627 )
9628 .child(
9629 h_flex()
9630 .h(line_height + BORDER_WIDTH * 2.)
9631 .px_1p5()
9632 .gap_1()
9633 // Workaround: For some reason, there's a gap if we don't do this
9634 .ml(-BORDER_WIDTH)
9635 .shadow(vec![gpui::BoxShadow {
9636 color: gpui::black().opacity(0.05),
9637 offset: point(px(1.), px(1.)),
9638 blur_radius: px(2.),
9639 spread_radius: px(0.),
9640 }])
9641 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9642 .border(BORDER_WIDTH)
9643 .border_color(cx.theme().colors().border)
9644 .rounded_r_lg()
9645 .id("edit_prediction_diff_popover_keybind")
9646 .when(!has_keybind, |el| {
9647 let status_colors = cx.theme().status();
9648
9649 el.bg(status_colors.error_background)
9650 .border_color(status_colors.error.opacity(0.6))
9651 .child(Icon::new(IconName::Info).color(Color::Error))
9652 .cursor_default()
9653 .hoverable_tooltip(move |_window, cx| {
9654 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9655 })
9656 })
9657 .children(keybind),
9658 )
9659 .into_any();
9660
9661 let longest_row =
9662 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9663 let longest_line_width = if visible_row_range.contains(&longest_row) {
9664 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9665 } else {
9666 layout_line(
9667 longest_row,
9668 editor_snapshot,
9669 style,
9670 editor_width,
9671 |_| false,
9672 window,
9673 cx,
9674 )
9675 .width
9676 };
9677
9678 let viewport_bounds =
9679 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9680 right: -right_margin,
9681 ..Default::default()
9682 });
9683
9684 let x_after_longest = Pixels::from(
9685 ScrollPixelOffset::from(
9686 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9687 ) - scroll_pixel_position.x,
9688 );
9689
9690 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9691
9692 // Fully visible if it can be displayed within the window (allow overlapping other
9693 // panes). However, this is only allowed if the popover starts within text_bounds.
9694 let can_position_to_the_right = x_after_longest < text_bounds.right()
9695 && x_after_longest + element_bounds.width < viewport_bounds.right();
9696
9697 let mut origin = if can_position_to_the_right {
9698 point(
9699 x_after_longest,
9700 text_bounds.origin.y
9701 + Pixels::from(
9702 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9703 - scroll_pixel_position.y,
9704 ),
9705 )
9706 } else {
9707 let cursor_row = newest_selection_head.map(|head| head.row());
9708 let above_edit = edit_start
9709 .row()
9710 .0
9711 .checked_sub(line_count as u32)
9712 .map(DisplayRow);
9713 let below_edit = Some(edit_end.row() + 1);
9714 let above_cursor =
9715 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9716 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9717
9718 // Place the edit popover adjacent to the edit if there is a location
9719 // available that is onscreen and does not obscure the cursor. Otherwise,
9720 // place it adjacent to the cursor.
9721 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9722 .into_iter()
9723 .flatten()
9724 .find(|&start_row| {
9725 let end_row = start_row + line_count as u32;
9726 visible_row_range.contains(&start_row)
9727 && visible_row_range.contains(&end_row)
9728 && cursor_row
9729 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9730 })?;
9731
9732 content_origin
9733 + point(
9734 Pixels::from(-scroll_pixel_position.x),
9735 Pixels::from(
9736 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9737 ),
9738 )
9739 };
9740
9741 origin.x -= BORDER_WIDTH;
9742
9743 window.with_content_mask(
9744 Some(gpui::ContentMask {
9745 bounds: *text_bounds,
9746 }),
9747 |window| {
9748 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9749 },
9750 );
9751
9752 // Do not return an element, since it will already be drawn due to defer_draw.
9753 None
9754 }
9755
9756 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9757 px(30.)
9758 }
9759
9760 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9761 if self.read_only(cx) {
9762 cx.theme().players().read_only()
9763 } else {
9764 self.style.as_ref().unwrap().local_player
9765 }
9766 }
9767
9768 fn render_edit_prediction_accept_keybind(
9769 &self,
9770 window: &mut Window,
9771 cx: &mut App,
9772 ) -> Option<AnyElement> {
9773 let accept_binding =
9774 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9775 let accept_keystroke = accept_binding.keystroke()?;
9776
9777 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9778
9779 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9780 Color::Accent
9781 } else {
9782 Color::Muted
9783 };
9784
9785 h_flex()
9786 .px_0p5()
9787 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9788 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9789 .text_size(TextSize::XSmall.rems(cx))
9790 .child(h_flex().children(ui::render_modifiers(
9791 accept_keystroke.modifiers(),
9792 PlatformStyle::platform(),
9793 Some(modifiers_color),
9794 Some(IconSize::XSmall.rems().into()),
9795 true,
9796 )))
9797 .when(is_platform_style_mac, |parent| {
9798 parent.child(accept_keystroke.key().to_string())
9799 })
9800 .when(!is_platform_style_mac, |parent| {
9801 parent.child(
9802 Key::new(
9803 util::capitalize(accept_keystroke.key()),
9804 Some(Color::Default),
9805 )
9806 .size(Some(IconSize::XSmall.rems().into())),
9807 )
9808 })
9809 .into_any()
9810 .into()
9811 }
9812
9813 fn render_edit_prediction_line_popover(
9814 &self,
9815 label: impl Into<SharedString>,
9816 icon: Option<IconName>,
9817 window: &mut Window,
9818 cx: &mut App,
9819 ) -> Stateful<Div> {
9820 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9821
9822 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9823 let has_keybind = keybind.is_some();
9824 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9825
9826 h_flex()
9827 .id("ep-line-popover")
9828 .py_0p5()
9829 .pl_1()
9830 .pr(padding_right)
9831 .gap_1()
9832 .rounded_md()
9833 .border_1()
9834 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9835 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9836 .shadow_xs()
9837 .when(!has_keybind, |el| {
9838 let status_colors = cx.theme().status();
9839
9840 el.bg(status_colors.error_background)
9841 .border_color(status_colors.error.opacity(0.6))
9842 .pl_2()
9843 .child(Icon::new(icons.error).color(Color::Error))
9844 .cursor_default()
9845 .hoverable_tooltip(move |_window, cx| {
9846 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9847 })
9848 })
9849 .children(keybind)
9850 .child(
9851 Label::new(label)
9852 .size(LabelSize::Small)
9853 .when(!has_keybind, |el| {
9854 el.color(cx.theme().status().error.into()).strikethrough()
9855 }),
9856 )
9857 .when(!has_keybind, |el| {
9858 el.child(
9859 h_flex().ml_1().child(
9860 Icon::new(IconName::Info)
9861 .size(IconSize::Small)
9862 .color(cx.theme().status().error.into()),
9863 ),
9864 )
9865 })
9866 .when_some(icon, |element, icon| {
9867 element.child(
9868 div()
9869 .mt(px(1.5))
9870 .child(Icon::new(icon).size(IconSize::Small)),
9871 )
9872 })
9873 }
9874
9875 fn render_edit_prediction_jump_outside_popover(
9876 &self,
9877 snapshot: &BufferSnapshot,
9878 window: &mut Window,
9879 cx: &mut App,
9880 ) -> Stateful<Div> {
9881 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9882 let has_keybind = keybind.is_some();
9883 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9884
9885 let file_name = snapshot
9886 .file()
9887 .map(|file| SharedString::new(file.file_name(cx)))
9888 .unwrap_or(SharedString::new_static("untitled"));
9889
9890 h_flex()
9891 .id("ep-jump-outside-popover")
9892 .py_1()
9893 .px_2()
9894 .gap_1()
9895 .rounded_md()
9896 .border_1()
9897 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9898 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9899 .shadow_xs()
9900 .when(!has_keybind, |el| {
9901 let status_colors = cx.theme().status();
9902
9903 el.bg(status_colors.error_background)
9904 .border_color(status_colors.error.opacity(0.6))
9905 .pl_2()
9906 .child(Icon::new(icons.error).color(Color::Error))
9907 .cursor_default()
9908 .hoverable_tooltip(move |_window, cx| {
9909 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9910 })
9911 })
9912 .children(keybind)
9913 .child(
9914 Label::new(file_name)
9915 .size(LabelSize::Small)
9916 .buffer_font(cx)
9917 .when(!has_keybind, |el| {
9918 el.color(cx.theme().status().error.into()).strikethrough()
9919 }),
9920 )
9921 .when(!has_keybind, |el| {
9922 el.child(
9923 h_flex().ml_1().child(
9924 Icon::new(IconName::Info)
9925 .size(IconSize::Small)
9926 .color(cx.theme().status().error.into()),
9927 ),
9928 )
9929 })
9930 .child(
9931 div()
9932 .mt(px(1.5))
9933 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9934 )
9935 }
9936
9937 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9938 let accent_color = cx.theme().colors().text_accent;
9939 let editor_bg_color = cx.theme().colors().editor_background;
9940 editor_bg_color.blend(accent_color.opacity(0.1))
9941 }
9942
9943 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9944 let accent_color = cx.theme().colors().text_accent;
9945 let editor_bg_color = cx.theme().colors().editor_background;
9946 editor_bg_color.blend(accent_color.opacity(0.6))
9947 }
9948 fn get_prediction_provider_icons(
9949 provider: &Option<RegisteredEditPredictionDelegate>,
9950 cx: &App,
9951 ) -> edit_prediction_types::EditPredictionIconSet {
9952 match provider {
9953 Some(provider) => provider.provider.icons(cx),
9954 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
9955 }
9956 }
9957
9958 fn render_edit_prediction_cursor_popover(
9959 &self,
9960 min_width: Pixels,
9961 max_width: Pixels,
9962 cursor_point: Point,
9963 style: &EditorStyle,
9964 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9965 _window: &Window,
9966 cx: &mut Context<Editor>,
9967 ) -> Option<AnyElement> {
9968 let provider = self.edit_prediction_provider.as_ref()?;
9969 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9970
9971 let is_refreshing = provider.provider.is_refreshing(cx);
9972
9973 fn pending_completion_container(icon: IconName) -> Div {
9974 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9975 }
9976
9977 let completion = match &self.active_edit_prediction {
9978 Some(prediction) => {
9979 if !self.has_visible_completions_menu() {
9980 const RADIUS: Pixels = px(6.);
9981 const BORDER_WIDTH: Pixels = px(1.);
9982
9983 return Some(
9984 h_flex()
9985 .elevation_2(cx)
9986 .border(BORDER_WIDTH)
9987 .border_color(cx.theme().colors().border)
9988 .when(accept_keystroke.is_none(), |el| {
9989 el.border_color(cx.theme().status().error)
9990 })
9991 .rounded(RADIUS)
9992 .rounded_tl(px(0.))
9993 .overflow_hidden()
9994 .child(div().px_1p5().child(match &prediction.completion {
9995 EditPrediction::MoveWithin { target, snapshot } => {
9996 use text::ToPoint as _;
9997 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9998 {
9999 Icon::new(icons.down)
10000 } else {
10001 Icon::new(icons.up)
10002 }
10003 }
10004 EditPrediction::MoveOutside { .. } => {
10005 // TODO [zeta2] custom icon for external jump?
10006 Icon::new(icons.base)
10007 }
10008 EditPrediction::Edit { .. } => Icon::new(icons.base),
10009 }))
10010 .child(
10011 h_flex()
10012 .gap_1()
10013 .py_1()
10014 .px_2()
10015 .rounded_r(RADIUS - BORDER_WIDTH)
10016 .border_l_1()
10017 .border_color(cx.theme().colors().border)
10018 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10019 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10020 el.child(
10021 Label::new("Hold")
10022 .size(LabelSize::Small)
10023 .when(accept_keystroke.is_none(), |el| {
10024 el.strikethrough()
10025 })
10026 .line_height_style(LineHeightStyle::UiLabel),
10027 )
10028 })
10029 .id("edit_prediction_cursor_popover_keybind")
10030 .when(accept_keystroke.is_none(), |el| {
10031 let status_colors = cx.theme().status();
10032
10033 el.bg(status_colors.error_background)
10034 .border_color(status_colors.error.opacity(0.6))
10035 .child(Icon::new(IconName::Info).color(Color::Error))
10036 .cursor_default()
10037 .hoverable_tooltip(move |_window, cx| {
10038 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10039 .into()
10040 })
10041 })
10042 .when_some(
10043 accept_keystroke.as_ref(),
10044 |el, accept_keystroke| {
10045 el.child(h_flex().children(ui::render_modifiers(
10046 accept_keystroke.modifiers(),
10047 PlatformStyle::platform(),
10048 Some(Color::Default),
10049 Some(IconSize::XSmall.rems().into()),
10050 false,
10051 )))
10052 },
10053 ),
10054 )
10055 .into_any(),
10056 );
10057 }
10058
10059 self.render_edit_prediction_cursor_popover_preview(
10060 prediction,
10061 cursor_point,
10062 style,
10063 cx,
10064 )?
10065 }
10066
10067 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10068 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10069 stale_completion,
10070 cursor_point,
10071 style,
10072 cx,
10073 )?,
10074
10075 None => pending_completion_container(icons.base)
10076 .child(Label::new("...").size(LabelSize::Small)),
10077 },
10078
10079 None => pending_completion_container(icons.base)
10080 .child(Label::new("...").size(LabelSize::Small)),
10081 };
10082
10083 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10084 completion
10085 .with_animation(
10086 "loading-completion",
10087 Animation::new(Duration::from_secs(2))
10088 .repeat()
10089 .with_easing(pulsating_between(0.4, 0.8)),
10090 |label, delta| label.opacity(delta),
10091 )
10092 .into_any_element()
10093 } else {
10094 completion.into_any_element()
10095 };
10096
10097 let has_completion = self.active_edit_prediction.is_some();
10098
10099 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10100 Some(
10101 h_flex()
10102 .min_w(min_width)
10103 .max_w(max_width)
10104 .flex_1()
10105 .elevation_2(cx)
10106 .border_color(cx.theme().colors().border)
10107 .child(
10108 div()
10109 .flex_1()
10110 .py_1()
10111 .px_2()
10112 .overflow_hidden()
10113 .child(completion),
10114 )
10115 .when_some(accept_keystroke, |el, accept_keystroke| {
10116 if !accept_keystroke.modifiers().modified() {
10117 return el;
10118 }
10119
10120 el.child(
10121 h_flex()
10122 .h_full()
10123 .border_l_1()
10124 .rounded_r_lg()
10125 .border_color(cx.theme().colors().border)
10126 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10127 .gap_1()
10128 .py_1()
10129 .px_2()
10130 .child(
10131 h_flex()
10132 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10133 .when(is_platform_style_mac, |parent| parent.gap_1())
10134 .child(h_flex().children(ui::render_modifiers(
10135 accept_keystroke.modifiers(),
10136 PlatformStyle::platform(),
10137 Some(if !has_completion {
10138 Color::Muted
10139 } else {
10140 Color::Default
10141 }),
10142 None,
10143 false,
10144 ))),
10145 )
10146 .child(Label::new("Preview").into_any_element())
10147 .opacity(if has_completion { 1.0 } else { 0.4 }),
10148 )
10149 })
10150 .into_any(),
10151 )
10152 }
10153
10154 fn render_edit_prediction_cursor_popover_preview(
10155 &self,
10156 completion: &EditPredictionState,
10157 cursor_point: Point,
10158 style: &EditorStyle,
10159 cx: &mut Context<Editor>,
10160 ) -> Option<Div> {
10161 use text::ToPoint as _;
10162
10163 fn render_relative_row_jump(
10164 prefix: impl Into<String>,
10165 current_row: u32,
10166 target_row: u32,
10167 ) -> Div {
10168 let (row_diff, arrow) = if target_row < current_row {
10169 (current_row - target_row, IconName::ArrowUp)
10170 } else {
10171 (target_row - current_row, IconName::ArrowDown)
10172 };
10173
10174 h_flex()
10175 .child(
10176 Label::new(format!("{}{}", prefix.into(), row_diff))
10177 .color(Color::Muted)
10178 .size(LabelSize::Small),
10179 )
10180 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10181 }
10182
10183 let supports_jump = self
10184 .edit_prediction_provider
10185 .as_ref()
10186 .map(|provider| provider.provider.supports_jump_to_edit())
10187 .unwrap_or(true);
10188
10189 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10190
10191 match &completion.completion {
10192 EditPrediction::MoveWithin {
10193 target, snapshot, ..
10194 } => {
10195 if !supports_jump {
10196 return None;
10197 }
10198
10199 Some(
10200 h_flex()
10201 .px_2()
10202 .gap_2()
10203 .flex_1()
10204 .child(
10205 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10206 Icon::new(icons.down)
10207 } else {
10208 Icon::new(icons.up)
10209 },
10210 )
10211 .child(Label::new("Jump to Edit")),
10212 )
10213 }
10214 EditPrediction::MoveOutside { snapshot, .. } => {
10215 let file_name = snapshot
10216 .file()
10217 .map(|file| file.file_name(cx))
10218 .unwrap_or("untitled");
10219 Some(
10220 h_flex()
10221 .px_2()
10222 .gap_2()
10223 .flex_1()
10224 .child(Icon::new(icons.base))
10225 .child(Label::new(format!("Jump to {file_name}"))),
10226 )
10227 }
10228 EditPrediction::Edit {
10229 edits,
10230 edit_preview,
10231 snapshot,
10232 ..
10233 } => {
10234 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10235
10236 let (highlighted_edits, has_more_lines) =
10237 if let Some(edit_preview) = edit_preview.as_ref() {
10238 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10239 .first_line_preview()
10240 } else {
10241 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10242 };
10243
10244 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10245 .with_default_highlights(&style.text, highlighted_edits.highlights);
10246
10247 let preview = h_flex()
10248 .gap_1()
10249 .min_w_16()
10250 .child(styled_text)
10251 .when(has_more_lines, |parent| parent.child("…"));
10252
10253 let left = if supports_jump && first_edit_row != cursor_point.row {
10254 render_relative_row_jump("", cursor_point.row, first_edit_row)
10255 .into_any_element()
10256 } else {
10257 Icon::new(icons.base).into_any_element()
10258 };
10259
10260 Some(
10261 h_flex()
10262 .h_full()
10263 .flex_1()
10264 .gap_2()
10265 .pr_1()
10266 .overflow_x_hidden()
10267 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10268 .child(left)
10269 .child(preview),
10270 )
10271 }
10272 }
10273 }
10274
10275 pub fn render_context_menu(
10276 &mut self,
10277 max_height_in_lines: u32,
10278 window: &mut Window,
10279 cx: &mut Context<Editor>,
10280 ) -> Option<AnyElement> {
10281 let menu = self.context_menu.borrow();
10282 let menu = menu.as_ref()?;
10283 if !menu.visible() {
10284 return None;
10285 };
10286 self.style
10287 .as_ref()
10288 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10289 }
10290
10291 fn render_context_menu_aside(
10292 &mut self,
10293 max_size: Size<Pixels>,
10294 window: &mut Window,
10295 cx: &mut Context<Editor>,
10296 ) -> Option<AnyElement> {
10297 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10298 if menu.visible() {
10299 menu.render_aside(max_size, window, cx)
10300 } else {
10301 None
10302 }
10303 })
10304 }
10305
10306 fn hide_context_menu(
10307 &mut self,
10308 window: &mut Window,
10309 cx: &mut Context<Self>,
10310 ) -> Option<CodeContextMenu> {
10311 cx.notify();
10312 self.completion_tasks.clear();
10313 let context_menu = self.context_menu.borrow_mut().take();
10314 self.stale_edit_prediction_in_menu.take();
10315 self.update_visible_edit_prediction(window, cx);
10316 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10317 && let Some(completion_provider) = &self.completion_provider
10318 {
10319 completion_provider.selection_changed(None, window, cx);
10320 }
10321 context_menu
10322 }
10323
10324 fn show_snippet_choices(
10325 &mut self,
10326 choices: &Vec<String>,
10327 selection: Range<Anchor>,
10328 cx: &mut Context<Self>,
10329 ) {
10330 let Some((_, buffer, _)) = self
10331 .buffer()
10332 .read(cx)
10333 .excerpt_containing(selection.start, cx)
10334 else {
10335 return;
10336 };
10337 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10338 else {
10339 return;
10340 };
10341 if buffer != end_buffer {
10342 log::error!("expected anchor range to have matching buffer IDs");
10343 return;
10344 }
10345
10346 let id = post_inc(&mut self.next_completion_id);
10347 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10348 let mut context_menu = self.context_menu.borrow_mut();
10349 let old_menu = context_menu.take();
10350 *context_menu = Some(CodeContextMenu::Completions(
10351 CompletionsMenu::new_snippet_choices(
10352 id,
10353 true,
10354 choices,
10355 selection,
10356 buffer,
10357 old_menu.map(|menu| menu.primary_scroll_handle()),
10358 snippet_sort_order,
10359 ),
10360 ));
10361 }
10362
10363 pub fn insert_snippet(
10364 &mut self,
10365 insertion_ranges: &[Range<MultiBufferOffset>],
10366 snippet: Snippet,
10367 window: &mut Window,
10368 cx: &mut Context<Self>,
10369 ) -> Result<()> {
10370 struct Tabstop<T> {
10371 is_end_tabstop: bool,
10372 ranges: Vec<Range<T>>,
10373 choices: Option<Vec<String>>,
10374 }
10375
10376 let tabstops = self.buffer.update(cx, |buffer, cx| {
10377 let snippet_text: Arc<str> = snippet.text.clone().into();
10378 let edits = insertion_ranges
10379 .iter()
10380 .cloned()
10381 .map(|range| (range, snippet_text.clone()));
10382 let autoindent_mode = AutoindentMode::Block {
10383 original_indent_columns: Vec::new(),
10384 };
10385 buffer.edit(edits, Some(autoindent_mode), cx);
10386
10387 let snapshot = &*buffer.read(cx);
10388 let snippet = &snippet;
10389 snippet
10390 .tabstops
10391 .iter()
10392 .map(|tabstop| {
10393 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10394 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10395 });
10396 let mut tabstop_ranges = tabstop
10397 .ranges
10398 .iter()
10399 .flat_map(|tabstop_range| {
10400 let mut delta = 0_isize;
10401 insertion_ranges.iter().map(move |insertion_range| {
10402 let insertion_start = insertion_range.start + delta;
10403 delta += snippet.text.len() as isize
10404 - (insertion_range.end - insertion_range.start) as isize;
10405
10406 let start =
10407 (insertion_start + tabstop_range.start).min(snapshot.len());
10408 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10409 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10410 })
10411 })
10412 .collect::<Vec<_>>();
10413 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10414
10415 Tabstop {
10416 is_end_tabstop,
10417 ranges: tabstop_ranges,
10418 choices: tabstop.choices.clone(),
10419 }
10420 })
10421 .collect::<Vec<_>>()
10422 });
10423 if let Some(tabstop) = tabstops.first() {
10424 self.change_selections(Default::default(), window, cx, |s| {
10425 // Reverse order so that the first range is the newest created selection.
10426 // Completions will use it and autoscroll will prioritize it.
10427 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10428 });
10429
10430 if let Some(choices) = &tabstop.choices
10431 && let Some(selection) = tabstop.ranges.first()
10432 {
10433 self.show_snippet_choices(choices, selection.clone(), cx)
10434 }
10435
10436 // If we're already at the last tabstop and it's at the end of the snippet,
10437 // we're done, we don't need to keep the state around.
10438 if !tabstop.is_end_tabstop {
10439 let choices = tabstops
10440 .iter()
10441 .map(|tabstop| tabstop.choices.clone())
10442 .collect();
10443
10444 let ranges = tabstops
10445 .into_iter()
10446 .map(|tabstop| tabstop.ranges)
10447 .collect::<Vec<_>>();
10448
10449 self.snippet_stack.push(SnippetState {
10450 active_index: 0,
10451 ranges,
10452 choices,
10453 });
10454 }
10455
10456 // Check whether the just-entered snippet ends with an auto-closable bracket.
10457 if self.autoclose_regions.is_empty() {
10458 let snapshot = self.buffer.read(cx).snapshot(cx);
10459 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10460 let selection_head = selection.head();
10461 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10462 continue;
10463 };
10464
10465 let mut bracket_pair = None;
10466 let max_lookup_length = scope
10467 .brackets()
10468 .map(|(pair, _)| {
10469 pair.start
10470 .as_str()
10471 .chars()
10472 .count()
10473 .max(pair.end.as_str().chars().count())
10474 })
10475 .max();
10476 if let Some(max_lookup_length) = max_lookup_length {
10477 let next_text = snapshot
10478 .chars_at(selection_head)
10479 .take(max_lookup_length)
10480 .collect::<String>();
10481 let prev_text = snapshot
10482 .reversed_chars_at(selection_head)
10483 .take(max_lookup_length)
10484 .collect::<String>();
10485
10486 for (pair, enabled) in scope.brackets() {
10487 if enabled
10488 && pair.close
10489 && prev_text.starts_with(pair.start.as_str())
10490 && next_text.starts_with(pair.end.as_str())
10491 {
10492 bracket_pair = Some(pair.clone());
10493 break;
10494 }
10495 }
10496 }
10497
10498 if let Some(pair) = bracket_pair {
10499 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10500 let autoclose_enabled =
10501 self.use_autoclose && snapshot_settings.use_autoclose;
10502 if autoclose_enabled {
10503 let start = snapshot.anchor_after(selection_head);
10504 let end = snapshot.anchor_after(selection_head);
10505 self.autoclose_regions.push(AutocloseRegion {
10506 selection_id: selection.id,
10507 range: start..end,
10508 pair,
10509 });
10510 }
10511 }
10512 }
10513 }
10514 }
10515 Ok(())
10516 }
10517
10518 pub fn move_to_next_snippet_tabstop(
10519 &mut self,
10520 window: &mut Window,
10521 cx: &mut Context<Self>,
10522 ) -> bool {
10523 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10524 }
10525
10526 pub fn move_to_prev_snippet_tabstop(
10527 &mut self,
10528 window: &mut Window,
10529 cx: &mut Context<Self>,
10530 ) -> bool {
10531 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10532 }
10533
10534 pub fn move_to_snippet_tabstop(
10535 &mut self,
10536 bias: Bias,
10537 window: &mut Window,
10538 cx: &mut Context<Self>,
10539 ) -> bool {
10540 if let Some(mut snippet) = self.snippet_stack.pop() {
10541 match bias {
10542 Bias::Left => {
10543 if snippet.active_index > 0 {
10544 snippet.active_index -= 1;
10545 } else {
10546 self.snippet_stack.push(snippet);
10547 return false;
10548 }
10549 }
10550 Bias::Right => {
10551 if snippet.active_index + 1 < snippet.ranges.len() {
10552 snippet.active_index += 1;
10553 } else {
10554 self.snippet_stack.push(snippet);
10555 return false;
10556 }
10557 }
10558 }
10559 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10560 self.change_selections(Default::default(), window, cx, |s| {
10561 // Reverse order so that the first range is the newest created selection.
10562 // Completions will use it and autoscroll will prioritize it.
10563 s.select_ranges(current_ranges.iter().rev().cloned())
10564 });
10565
10566 if let Some(choices) = &snippet.choices[snippet.active_index]
10567 && let Some(selection) = current_ranges.first()
10568 {
10569 self.show_snippet_choices(choices, selection.clone(), cx);
10570 }
10571
10572 // If snippet state is not at the last tabstop, push it back on the stack
10573 if snippet.active_index + 1 < snippet.ranges.len() {
10574 self.snippet_stack.push(snippet);
10575 }
10576 return true;
10577 }
10578 }
10579
10580 false
10581 }
10582
10583 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10584 self.transact(window, cx, |this, window, cx| {
10585 this.select_all(&SelectAll, window, cx);
10586 this.insert("", window, cx);
10587 });
10588 }
10589
10590 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10591 if self.read_only(cx) {
10592 return;
10593 }
10594 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10595 self.transact(window, cx, |this, window, cx| {
10596 this.select_autoclose_pair(window, cx);
10597
10598 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10599
10600 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10601 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10602 for selection in &mut selections {
10603 if selection.is_empty() {
10604 let old_head = selection.head();
10605 let mut new_head =
10606 movement::left(&display_map, old_head.to_display_point(&display_map))
10607 .to_point(&display_map);
10608 if let Some((buffer, line_buffer_range)) = display_map
10609 .buffer_snapshot()
10610 .buffer_line_for_row(MultiBufferRow(old_head.row))
10611 {
10612 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10613 let indent_len = match indent_size.kind {
10614 IndentKind::Space => {
10615 buffer.settings_at(line_buffer_range.start, cx).tab_size
10616 }
10617 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10618 };
10619 if old_head.column <= indent_size.len && old_head.column > 0 {
10620 let indent_len = indent_len.get();
10621 new_head = cmp::min(
10622 new_head,
10623 MultiBufferPoint::new(
10624 old_head.row,
10625 ((old_head.column - 1) / indent_len) * indent_len,
10626 ),
10627 );
10628 }
10629 }
10630
10631 selection.set_head(new_head, SelectionGoal::None);
10632 }
10633 }
10634
10635 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10636 this.insert("", window, cx);
10637 linked_edits.apply_with_left_expansion(cx);
10638 this.refresh_edit_prediction(true, false, window, cx);
10639 refresh_linked_ranges(this, window, cx);
10640 });
10641 }
10642
10643 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10644 if self.read_only(cx) {
10645 return;
10646 }
10647 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10648 self.transact(window, cx, |this, window, cx| {
10649 this.change_selections(Default::default(), window, cx, |s| {
10650 s.move_with(&mut |map, selection| {
10651 if selection.is_empty() {
10652 let cursor = movement::right(map, selection.head());
10653 selection.end = cursor;
10654 selection.reversed = true;
10655 selection.goal = SelectionGoal::None;
10656 }
10657 })
10658 });
10659 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10660 this.insert("", window, cx);
10661 linked_edits.apply(cx);
10662 this.refresh_edit_prediction(true, false, window, cx);
10663 refresh_linked_ranges(this, window, cx);
10664 });
10665 }
10666
10667 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10668 if self.mode.is_single_line() {
10669 cx.propagate();
10670 return;
10671 }
10672
10673 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10674 if self.move_to_prev_snippet_tabstop(window, cx) {
10675 return;
10676 }
10677 self.outdent(&Outdent, window, cx);
10678 }
10679
10680 pub fn next_snippet_tabstop(
10681 &mut self,
10682 _: &NextSnippetTabstop,
10683 window: &mut Window,
10684 cx: &mut Context<Self>,
10685 ) {
10686 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10687 cx.propagate();
10688 return;
10689 }
10690
10691 if self.move_to_next_snippet_tabstop(window, cx) {
10692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10693 return;
10694 }
10695 cx.propagate();
10696 }
10697
10698 pub fn previous_snippet_tabstop(
10699 &mut self,
10700 _: &PreviousSnippetTabstop,
10701 window: &mut Window,
10702 cx: &mut Context<Self>,
10703 ) {
10704 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10705 cx.propagate();
10706 return;
10707 }
10708
10709 if self.move_to_prev_snippet_tabstop(window, cx) {
10710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10711 return;
10712 }
10713 cx.propagate();
10714 }
10715
10716 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10717 if self.mode.is_single_line() {
10718 cx.propagate();
10719 return;
10720 }
10721
10722 if self.move_to_next_snippet_tabstop(window, cx) {
10723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10724 return;
10725 }
10726 if self.read_only(cx) {
10727 return;
10728 }
10729 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10730 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10731 let buffer = self.buffer.read(cx);
10732 let snapshot = buffer.snapshot(cx);
10733 let rows_iter = selections.iter().map(|s| s.head().row);
10734 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10735
10736 let has_some_cursor_in_whitespace = selections
10737 .iter()
10738 .filter(|selection| selection.is_empty())
10739 .any(|selection| {
10740 let cursor = selection.head();
10741 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10742 cursor.column < current_indent.len
10743 });
10744
10745 let mut edits = Vec::new();
10746 let mut prev_edited_row = 0;
10747 let mut row_delta = 0;
10748 for selection in &mut selections {
10749 if selection.start.row != prev_edited_row {
10750 row_delta = 0;
10751 }
10752 prev_edited_row = selection.end.row;
10753
10754 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10755 if selection.is_empty() {
10756 let cursor = selection.head();
10757 let settings = buffer.language_settings_at(cursor, cx);
10758 if settings.indent_list_on_tab {
10759 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10760 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10761 row_delta = Self::indent_selection(
10762 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10763 );
10764 continue;
10765 }
10766 }
10767 }
10768 }
10769
10770 // If the selection is non-empty, then increase the indentation of the selected lines.
10771 if !selection.is_empty() {
10772 row_delta =
10773 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10774 continue;
10775 }
10776
10777 let cursor = selection.head();
10778 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10779 if let Some(suggested_indent) =
10780 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10781 {
10782 // Don't do anything if already at suggested indent
10783 // and there is any other cursor which is not
10784 if has_some_cursor_in_whitespace
10785 && cursor.column == current_indent.len
10786 && current_indent.len == suggested_indent.len
10787 {
10788 continue;
10789 }
10790
10791 // Adjust line and move cursor to suggested indent
10792 // if cursor is not at suggested indent
10793 if cursor.column < suggested_indent.len
10794 && cursor.column <= current_indent.len
10795 && current_indent.len <= suggested_indent.len
10796 {
10797 selection.start = Point::new(cursor.row, suggested_indent.len);
10798 selection.end = selection.start;
10799 if row_delta == 0 {
10800 edits.extend(Buffer::edit_for_indent_size_adjustment(
10801 cursor.row,
10802 current_indent,
10803 suggested_indent,
10804 ));
10805 row_delta = suggested_indent.len - current_indent.len;
10806 }
10807 continue;
10808 }
10809
10810 // If current indent is more than suggested indent
10811 // only move cursor to current indent and skip indent
10812 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10813 selection.start = Point::new(cursor.row, current_indent.len);
10814 selection.end = selection.start;
10815 continue;
10816 }
10817 }
10818
10819 // Otherwise, insert a hard or soft tab.
10820 let settings = buffer.language_settings_at(cursor, cx);
10821 let tab_size = if settings.hard_tabs {
10822 IndentSize::tab()
10823 } else {
10824 let tab_size = settings.tab_size.get();
10825 let indent_remainder = snapshot
10826 .text_for_range(Point::new(cursor.row, 0)..cursor)
10827 .flat_map(str::chars)
10828 .fold(row_delta % tab_size, |counter: u32, c| {
10829 if c == '\t' {
10830 0
10831 } else {
10832 (counter + 1) % tab_size
10833 }
10834 });
10835
10836 let chars_to_next_tab_stop = tab_size - indent_remainder;
10837 IndentSize::spaces(chars_to_next_tab_stop)
10838 };
10839 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10840 selection.end = selection.start;
10841 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10842 row_delta += tab_size.len;
10843 }
10844
10845 self.transact(window, cx, |this, window, cx| {
10846 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10847 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10848 this.refresh_edit_prediction(true, false, window, cx);
10849 });
10850 }
10851
10852 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10853 if self.read_only(cx) {
10854 return;
10855 }
10856 if self.mode.is_single_line() {
10857 cx.propagate();
10858 return;
10859 }
10860
10861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10862 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10863 let mut prev_edited_row = 0;
10864 let mut row_delta = 0;
10865 let mut edits = Vec::new();
10866 let buffer = self.buffer.read(cx);
10867 let snapshot = buffer.snapshot(cx);
10868 for selection in &mut selections {
10869 if selection.start.row != prev_edited_row {
10870 row_delta = 0;
10871 }
10872 prev_edited_row = selection.end.row;
10873
10874 row_delta =
10875 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10876 }
10877
10878 self.transact(window, cx, |this, window, cx| {
10879 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10880 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10881 });
10882 }
10883
10884 fn indent_selection(
10885 buffer: &MultiBuffer,
10886 snapshot: &MultiBufferSnapshot,
10887 selection: &mut Selection<Point>,
10888 edits: &mut Vec<(Range<Point>, String)>,
10889 delta_for_start_row: u32,
10890 cx: &App,
10891 ) -> u32 {
10892 let settings = buffer.language_settings_at(selection.start, cx);
10893 let tab_size = settings.tab_size.get();
10894 let indent_kind = if settings.hard_tabs {
10895 IndentKind::Tab
10896 } else {
10897 IndentKind::Space
10898 };
10899 let mut start_row = selection.start.row;
10900 let mut end_row = selection.end.row + 1;
10901
10902 // If a selection ends at the beginning of a line, don't indent
10903 // that last line.
10904 if selection.end.column == 0 && selection.end.row > selection.start.row {
10905 end_row -= 1;
10906 }
10907
10908 // Avoid re-indenting a row that has already been indented by a
10909 // previous selection, but still update this selection's column
10910 // to reflect that indentation.
10911 if delta_for_start_row > 0 {
10912 start_row += 1;
10913 selection.start.column += delta_for_start_row;
10914 if selection.end.row == selection.start.row {
10915 selection.end.column += delta_for_start_row;
10916 }
10917 }
10918
10919 let mut delta_for_end_row = 0;
10920 let has_multiple_rows = start_row + 1 != end_row;
10921 for row in start_row..end_row {
10922 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10923 let indent_delta = match (current_indent.kind, indent_kind) {
10924 (IndentKind::Space, IndentKind::Space) => {
10925 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10926 IndentSize::spaces(columns_to_next_tab_stop)
10927 }
10928 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10929 (_, IndentKind::Tab) => IndentSize::tab(),
10930 };
10931
10932 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10933 0
10934 } else {
10935 selection.start.column
10936 };
10937 let row_start = Point::new(row, start);
10938 edits.push((
10939 row_start..row_start,
10940 indent_delta.chars().collect::<String>(),
10941 ));
10942
10943 // Update this selection's endpoints to reflect the indentation.
10944 if row == selection.start.row {
10945 selection.start.column += indent_delta.len;
10946 }
10947 if row == selection.end.row {
10948 selection.end.column += indent_delta.len;
10949 delta_for_end_row = indent_delta.len;
10950 }
10951 }
10952
10953 if selection.start.row == selection.end.row {
10954 delta_for_start_row + delta_for_end_row
10955 } else {
10956 delta_for_end_row
10957 }
10958 }
10959
10960 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10961 if self.read_only(cx) {
10962 return;
10963 }
10964 if self.mode.is_single_line() {
10965 cx.propagate();
10966 return;
10967 }
10968
10969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10970 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10971 let selections = self.selections.all::<Point>(&display_map);
10972 let mut deletion_ranges = Vec::new();
10973 let mut last_outdent = None;
10974 {
10975 let buffer = self.buffer.read(cx);
10976 let snapshot = buffer.snapshot(cx);
10977 for selection in &selections {
10978 let settings = buffer.language_settings_at(selection.start, cx);
10979 let tab_size = settings.tab_size.get();
10980 let mut rows = selection.spanned_rows(false, &display_map);
10981
10982 // Avoid re-outdenting a row that has already been outdented by a
10983 // previous selection.
10984 if let Some(last_row) = last_outdent
10985 && last_row == rows.start
10986 {
10987 rows.start = rows.start.next_row();
10988 }
10989 let has_multiple_rows = rows.len() > 1;
10990 for row in rows.iter_rows() {
10991 let indent_size = snapshot.indent_size_for_line(row);
10992 if indent_size.len > 0 {
10993 let deletion_len = match indent_size.kind {
10994 IndentKind::Space => {
10995 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10996 if columns_to_prev_tab_stop == 0 {
10997 tab_size
10998 } else {
10999 columns_to_prev_tab_stop
11000 }
11001 }
11002 IndentKind::Tab => 1,
11003 };
11004 let start = if has_multiple_rows
11005 || deletion_len > selection.start.column
11006 || indent_size.len < selection.start.column
11007 {
11008 0
11009 } else {
11010 selection.start.column - deletion_len
11011 };
11012 deletion_ranges.push(
11013 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11014 );
11015 last_outdent = Some(row);
11016 }
11017 }
11018 }
11019 }
11020
11021 self.transact(window, cx, |this, window, cx| {
11022 this.buffer.update(cx, |buffer, cx| {
11023 let empty_str: Arc<str> = Arc::default();
11024 buffer.edit(
11025 deletion_ranges
11026 .into_iter()
11027 .map(|range| (range, empty_str.clone())),
11028 None,
11029 cx,
11030 );
11031 });
11032 let selections = this
11033 .selections
11034 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11035 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11036 });
11037 }
11038
11039 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11040 if self.read_only(cx) {
11041 return;
11042 }
11043 if self.mode.is_single_line() {
11044 cx.propagate();
11045 return;
11046 }
11047
11048 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11049 let selections = self
11050 .selections
11051 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11052 .into_iter()
11053 .map(|s| s.range());
11054
11055 self.transact(window, cx, |this, window, cx| {
11056 this.buffer.update(cx, |buffer, cx| {
11057 buffer.autoindent_ranges(selections, cx);
11058 });
11059 let selections = this
11060 .selections
11061 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11062 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11063 });
11064 }
11065
11066 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11068 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11069 let selections = self.selections.all::<Point>(&display_map);
11070
11071 let mut new_cursors = Vec::new();
11072 let mut edit_ranges = Vec::new();
11073 let mut selections = selections.iter().peekable();
11074 while let Some(selection) = selections.next() {
11075 let mut rows = selection.spanned_rows(false, &display_map);
11076
11077 // Accumulate contiguous regions of rows that we want to delete.
11078 while let Some(next_selection) = selections.peek() {
11079 let next_rows = next_selection.spanned_rows(false, &display_map);
11080 if next_rows.start <= rows.end {
11081 rows.end = next_rows.end;
11082 selections.next().unwrap();
11083 } else {
11084 break;
11085 }
11086 }
11087
11088 let buffer = display_map.buffer_snapshot();
11089 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11090 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11091 // If there's a line after the range, delete the \n from the end of the row range
11092 (
11093 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11094 rows.end,
11095 )
11096 } else {
11097 // If there isn't a line after the range, delete the \n from the line before the
11098 // start of the row range
11099 edit_start = edit_start.saturating_sub_usize(1);
11100 (buffer.len(), rows.start.previous_row())
11101 };
11102
11103 let text_layout_details = self.text_layout_details(window, cx);
11104 let x = display_map.x_for_display_point(
11105 selection.head().to_display_point(&display_map),
11106 &text_layout_details,
11107 );
11108 let row = Point::new(target_row.0, 0)
11109 .to_display_point(&display_map)
11110 .row();
11111 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11112
11113 new_cursors.push((
11114 selection.id,
11115 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11116 SelectionGoal::None,
11117 ));
11118 edit_ranges.push(edit_start..edit_end);
11119 }
11120
11121 self.transact(window, cx, |this, window, cx| {
11122 let buffer = this.buffer.update(cx, |buffer, cx| {
11123 let empty_str: Arc<str> = Arc::default();
11124 buffer.edit(
11125 edit_ranges
11126 .into_iter()
11127 .map(|range| (range, empty_str.clone())),
11128 None,
11129 cx,
11130 );
11131 buffer.snapshot(cx)
11132 });
11133 let new_selections = new_cursors
11134 .into_iter()
11135 .map(|(id, cursor, goal)| {
11136 let cursor = cursor.to_point(&buffer);
11137 Selection {
11138 id,
11139 start: cursor,
11140 end: cursor,
11141 reversed: false,
11142 goal,
11143 }
11144 })
11145 .collect();
11146
11147 this.change_selections(Default::default(), window, cx, |s| {
11148 s.select(new_selections);
11149 });
11150 });
11151 }
11152
11153 pub fn join_lines_impl(
11154 &mut self,
11155 insert_whitespace: bool,
11156 window: &mut Window,
11157 cx: &mut Context<Self>,
11158 ) {
11159 if self.read_only(cx) {
11160 return;
11161 }
11162 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11163 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11164 let start = MultiBufferRow(selection.start.row);
11165 // Treat single line selections as if they include the next line. Otherwise this action
11166 // would do nothing for single line selections individual cursors.
11167 let end = if selection.start.row == selection.end.row {
11168 MultiBufferRow(selection.start.row + 1)
11169 } else if selection.end.column == 0 {
11170 // If the selection ends at the start of a line, it's logically at the end of the
11171 // previous line (plus its newline).
11172 // Don't include the end line unless there's only one line selected.
11173 if selection.start.row + 1 == selection.end.row {
11174 MultiBufferRow(selection.end.row)
11175 } else {
11176 MultiBufferRow(selection.end.row - 1)
11177 }
11178 } else {
11179 MultiBufferRow(selection.end.row)
11180 };
11181
11182 if let Some(last_row_range) = row_ranges.last_mut()
11183 && start <= last_row_range.end
11184 {
11185 last_row_range.end = end;
11186 continue;
11187 }
11188 row_ranges.push(start..end);
11189 }
11190
11191 let snapshot = self.buffer.read(cx).snapshot(cx);
11192 let mut cursor_positions = Vec::new();
11193 for row_range in &row_ranges {
11194 let anchor = snapshot.anchor_before(Point::new(
11195 row_range.end.previous_row().0,
11196 snapshot.line_len(row_range.end.previous_row()),
11197 ));
11198 cursor_positions.push(anchor..anchor);
11199 }
11200
11201 self.transact(window, cx, |this, window, cx| {
11202 for row_range in row_ranges.into_iter().rev() {
11203 for row in row_range.iter_rows().rev() {
11204 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11205 let next_line_row = row.next_row();
11206 let indent = snapshot.indent_size_for_line(next_line_row);
11207 let mut join_start_column = indent.len;
11208
11209 if let Some(language_scope) =
11210 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11211 {
11212 let line_end =
11213 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11214 let line_text_after_indent = snapshot
11215 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11216 .collect::<String>();
11217
11218 if !line_text_after_indent.is_empty() {
11219 let block_prefix = language_scope
11220 .block_comment()
11221 .map(|c| c.prefix.as_ref())
11222 .filter(|p| !p.is_empty());
11223 let doc_prefix = language_scope
11224 .documentation_comment()
11225 .map(|c| c.prefix.as_ref())
11226 .filter(|p| !p.is_empty());
11227 let all_prefixes = language_scope
11228 .line_comment_prefixes()
11229 .iter()
11230 .map(|p| p.as_ref())
11231 .chain(block_prefix)
11232 .chain(doc_prefix)
11233 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11234
11235 let mut longest_prefix_len = None;
11236 for prefix in all_prefixes {
11237 let trimmed = prefix.trim_end();
11238 if line_text_after_indent.starts_with(trimmed) {
11239 let candidate_len =
11240 if line_text_after_indent.starts_with(prefix) {
11241 prefix.len()
11242 } else {
11243 trimmed.len()
11244 };
11245 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11246 longest_prefix_len = Some(candidate_len);
11247 }
11248 }
11249 }
11250
11251 if let Some(prefix_len) = longest_prefix_len {
11252 join_start_column =
11253 join_start_column.saturating_add(prefix_len as u32);
11254 }
11255 }
11256 }
11257
11258 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11259
11260 let replace = if snapshot.line_len(next_line_row) > join_start_column
11261 && insert_whitespace
11262 {
11263 " "
11264 } else {
11265 ""
11266 };
11267
11268 this.buffer.update(cx, |buffer, cx| {
11269 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11270 });
11271 }
11272 }
11273
11274 this.change_selections(Default::default(), window, cx, |s| {
11275 s.select_anchor_ranges(cursor_positions)
11276 });
11277 });
11278 }
11279
11280 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11282 self.join_lines_impl(true, window, cx);
11283 }
11284
11285 pub fn sort_lines_case_sensitive(
11286 &mut self,
11287 _: &SortLinesCaseSensitive,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 ) {
11291 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11292 }
11293
11294 pub fn sort_lines_by_length(
11295 &mut self,
11296 _: &SortLinesByLength,
11297 window: &mut Window,
11298 cx: &mut Context<Self>,
11299 ) {
11300 self.manipulate_immutable_lines(window, cx, |lines| {
11301 lines.sort_by_key(|&line| line.chars().count())
11302 })
11303 }
11304
11305 pub fn sort_lines_case_insensitive(
11306 &mut self,
11307 _: &SortLinesCaseInsensitive,
11308 window: &mut Window,
11309 cx: &mut Context<Self>,
11310 ) {
11311 self.manipulate_immutable_lines(window, cx, |lines| {
11312 lines.sort_by_key(|line| line.to_lowercase())
11313 })
11314 }
11315
11316 pub fn unique_lines_case_insensitive(
11317 &mut self,
11318 _: &UniqueLinesCaseInsensitive,
11319 window: &mut Window,
11320 cx: &mut Context<Self>,
11321 ) {
11322 self.manipulate_immutable_lines(window, cx, |lines| {
11323 let mut seen = HashSet::default();
11324 lines.retain(|line| seen.insert(line.to_lowercase()));
11325 })
11326 }
11327
11328 pub fn unique_lines_case_sensitive(
11329 &mut self,
11330 _: &UniqueLinesCaseSensitive,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 ) {
11334 self.manipulate_immutable_lines(window, cx, |lines| {
11335 let mut seen = HashSet::default();
11336 lines.retain(|line| seen.insert(*line));
11337 })
11338 }
11339
11340 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11341 let snapshot = self.buffer.read(cx).snapshot(cx);
11342 for selection in self.selections.disjoint_anchors_arc().iter() {
11343 if snapshot
11344 .language_at(selection.start)
11345 .and_then(|lang| lang.config().wrap_characters.as_ref())
11346 .is_some()
11347 {
11348 return true;
11349 }
11350 }
11351 false
11352 }
11353
11354 fn wrap_selections_in_tag(
11355 &mut self,
11356 _: &WrapSelectionsInTag,
11357 window: &mut Window,
11358 cx: &mut Context<Self>,
11359 ) {
11360 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11361
11362 let snapshot = self.buffer.read(cx).snapshot(cx);
11363
11364 let mut edits = Vec::new();
11365 let mut boundaries = Vec::new();
11366
11367 for selection in self
11368 .selections
11369 .all_adjusted(&self.display_snapshot(cx))
11370 .iter()
11371 {
11372 let Some(wrap_config) = snapshot
11373 .language_at(selection.start)
11374 .and_then(|lang| lang.config().wrap_characters.clone())
11375 else {
11376 continue;
11377 };
11378
11379 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11380 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11381
11382 let start_before = snapshot.anchor_before(selection.start);
11383 let end_after = snapshot.anchor_after(selection.end);
11384
11385 edits.push((start_before..start_before, open_tag));
11386 edits.push((end_after..end_after, close_tag));
11387
11388 boundaries.push((
11389 start_before,
11390 end_after,
11391 wrap_config.start_prefix.len(),
11392 wrap_config.end_suffix.len(),
11393 ));
11394 }
11395
11396 if edits.is_empty() {
11397 return;
11398 }
11399
11400 self.transact(window, cx, |this, window, cx| {
11401 let buffer = this.buffer.update(cx, |buffer, cx| {
11402 buffer.edit(edits, None, cx);
11403 buffer.snapshot(cx)
11404 });
11405
11406 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11407 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11408 boundaries.into_iter()
11409 {
11410 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11411 let close_offset = end_after
11412 .to_offset(&buffer)
11413 .saturating_sub_usize(end_suffix_len);
11414 new_selections.push(open_offset..open_offset);
11415 new_selections.push(close_offset..close_offset);
11416 }
11417
11418 this.change_selections(Default::default(), window, cx, |s| {
11419 s.select_ranges(new_selections);
11420 });
11421
11422 this.request_autoscroll(Autoscroll::fit(), cx);
11423 });
11424 }
11425
11426 pub fn toggle_read_only(
11427 &mut self,
11428 _: &workspace::ToggleReadOnlyFile,
11429 _: &mut Window,
11430 cx: &mut Context<Self>,
11431 ) {
11432 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11433 buffer.update(cx, |buffer, cx| {
11434 buffer.set_capability(
11435 match buffer.capability() {
11436 Capability::ReadWrite => Capability::Read,
11437 Capability::Read => Capability::ReadWrite,
11438 Capability::ReadOnly => Capability::ReadOnly,
11439 },
11440 cx,
11441 );
11442 })
11443 }
11444 }
11445
11446 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11447 let Some(project) = self.project.clone() else {
11448 return;
11449 };
11450 let task = self.reload(project, window, cx);
11451 self.detach_and_notify_err(task, window, cx);
11452 }
11453
11454 pub fn restore_file(
11455 &mut self,
11456 _: &::git::RestoreFile,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11461 let mut buffer_ids = HashSet::default();
11462 let snapshot = self.buffer().read(cx).snapshot(cx);
11463 for selection in self
11464 .selections
11465 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11466 {
11467 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11468 }
11469
11470 let buffer = self.buffer().read(cx);
11471 let ranges = buffer_ids
11472 .into_iter()
11473 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11474 .collect::<Vec<_>>();
11475
11476 self.restore_hunks_in_ranges(ranges, window, cx);
11477 }
11478
11479 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11480 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11481 let selections = self
11482 .selections
11483 .all(&self.display_snapshot(cx))
11484 .into_iter()
11485 .map(|s| s.range())
11486 .collect();
11487 self.restore_hunks_in_ranges(selections, window, cx);
11488 }
11489
11490 /// Restores the diff hunks in the editor's selections and moves the cursor
11491 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11492 /// not all diff hunks are expanded.
11493 pub fn restore_and_next(
11494 &mut self,
11495 _: &::git::RestoreAndNext,
11496 window: &mut Window,
11497 cx: &mut Context<Self>,
11498 ) {
11499 let selections = self
11500 .selections
11501 .all(&self.display_snapshot(cx))
11502 .into_iter()
11503 .map(|selection| selection.range())
11504 .collect();
11505
11506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11507 self.restore_hunks_in_ranges(selections, window, cx);
11508
11509 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11510 let wrap_around = !all_diff_hunks_expanded;
11511 let snapshot = self.snapshot(window, cx);
11512 let position = self
11513 .selections
11514 .newest::<Point>(&snapshot.display_snapshot)
11515 .head();
11516
11517 self.go_to_hunk_before_or_after_position(
11518 &snapshot,
11519 position,
11520 Direction::Next,
11521 wrap_around,
11522 window,
11523 cx,
11524 );
11525 }
11526
11527 pub fn restore_hunks_in_ranges(
11528 &mut self,
11529 ranges: Vec<Range<Point>>,
11530 window: &mut Window,
11531 cx: &mut Context<Editor>,
11532 ) {
11533 if self.delegate_stage_and_restore {
11534 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11535 if !hunks.is_empty() {
11536 cx.emit(EditorEvent::RestoreRequested { hunks });
11537 }
11538 return;
11539 }
11540 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11541 self.transact(window, cx, |editor, window, cx| {
11542 editor.restore_diff_hunks(hunks, cx);
11543 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11544 selections.refresh()
11545 });
11546 });
11547 }
11548
11549 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11550 let mut revert_changes = HashMap::default();
11551 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11552 for (buffer_id, hunks) in &chunk_by {
11553 let hunks = hunks.collect::<Vec<_>>();
11554 for hunk in &hunks {
11555 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11556 }
11557 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11558 }
11559 if !revert_changes.is_empty() {
11560 self.buffer().update(cx, |multi_buffer, cx| {
11561 for (buffer_id, changes) in revert_changes {
11562 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11563 buffer.update(cx, |buffer, cx| {
11564 buffer.edit(
11565 changes
11566 .into_iter()
11567 .map(|(range, text)| (range, text.to_string())),
11568 None,
11569 cx,
11570 );
11571 });
11572 }
11573 }
11574 });
11575 }
11576 }
11577
11578 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11579 if let Some(status) = self
11580 .addons
11581 .iter()
11582 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11583 {
11584 return Some(status);
11585 }
11586 self.project
11587 .as_ref()?
11588 .read(cx)
11589 .status_for_buffer_id(buffer_id, cx)
11590 }
11591
11592 pub fn open_active_item_in_terminal(
11593 &mut self,
11594 _: &OpenInTerminal,
11595 window: &mut Window,
11596 cx: &mut Context<Self>,
11597 ) {
11598 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11599 let project_path = buffer.read(cx).project_path(cx)?;
11600 let project = self.project()?.read(cx);
11601 let entry = project.entry_for_path(&project_path, cx)?;
11602 let parent = match &entry.canonical_path {
11603 Some(canonical_path) => canonical_path.to_path_buf(),
11604 None => project.absolute_path(&project_path, cx)?,
11605 }
11606 .parent()?
11607 .to_path_buf();
11608 Some(parent)
11609 }) {
11610 window.dispatch_action(
11611 OpenTerminal {
11612 working_directory,
11613 local: false,
11614 }
11615 .boxed_clone(),
11616 cx,
11617 );
11618 }
11619 }
11620
11621 fn set_breakpoint_context_menu(
11622 &mut self,
11623 display_row: DisplayRow,
11624 position: Option<Anchor>,
11625 clicked_point: gpui::Point<Pixels>,
11626 window: &mut Window,
11627 cx: &mut Context<Self>,
11628 ) {
11629 let source = self
11630 .buffer
11631 .read(cx)
11632 .snapshot(cx)
11633 .anchor_before(Point::new(display_row.0, 0u32));
11634
11635 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11636
11637 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11638 self,
11639 source,
11640 clicked_point,
11641 context_menu,
11642 window,
11643 cx,
11644 );
11645 }
11646
11647 fn add_edit_breakpoint_block(
11648 &mut self,
11649 anchor: Anchor,
11650 breakpoint: &Breakpoint,
11651 edit_action: BreakpointPromptEditAction,
11652 window: &mut Window,
11653 cx: &mut Context<Self>,
11654 ) {
11655 let weak_editor = cx.weak_entity();
11656 let bp_prompt = cx.new(|cx| {
11657 BreakpointPromptEditor::new(
11658 weak_editor,
11659 anchor,
11660 breakpoint.clone(),
11661 edit_action,
11662 window,
11663 cx,
11664 )
11665 });
11666
11667 let height = bp_prompt.update(cx, |this, cx| {
11668 this.prompt
11669 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11670 });
11671 let cloned_prompt = bp_prompt.clone();
11672 let blocks = vec![BlockProperties {
11673 style: BlockStyle::Sticky,
11674 placement: BlockPlacement::Above(anchor),
11675 height: Some(height),
11676 render: Arc::new(move |cx| {
11677 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11678 cloned_prompt.clone().into_any_element()
11679 }),
11680 priority: 0,
11681 }];
11682
11683 let focus_handle = bp_prompt.focus_handle(cx);
11684 window.focus(&focus_handle, cx);
11685
11686 let block_ids = self.insert_blocks(blocks, None, cx);
11687 bp_prompt.update(cx, |prompt, _| {
11688 prompt.add_block_ids(block_ids);
11689 });
11690 }
11691
11692 pub(crate) fn breakpoint_at_row(
11693 &self,
11694 row: u32,
11695 window: &mut Window,
11696 cx: &mut Context<Self>,
11697 ) -> Option<(Anchor, Breakpoint)> {
11698 let snapshot = self.snapshot(window, cx);
11699 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11700
11701 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11702 }
11703
11704 pub(crate) fn breakpoint_at_anchor(
11705 &self,
11706 breakpoint_position: Anchor,
11707 snapshot: &EditorSnapshot,
11708 cx: &mut Context<Self>,
11709 ) -> Option<(Anchor, Breakpoint)> {
11710 let buffer = self
11711 .buffer
11712 .read(cx)
11713 .buffer_for_anchor(breakpoint_position, cx)?;
11714
11715 let enclosing_excerpt = breakpoint_position.excerpt_id;
11716 let buffer_snapshot = buffer.read(cx).snapshot();
11717
11718 let row = buffer_snapshot
11719 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11720 .row;
11721
11722 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11723 let anchor_end = snapshot
11724 .buffer_snapshot()
11725 .anchor_after(Point::new(row, line_len));
11726
11727 self.breakpoint_store
11728 .as_ref()?
11729 .read_with(cx, |breakpoint_store, cx| {
11730 breakpoint_store
11731 .breakpoints(
11732 &buffer,
11733 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11734 &buffer_snapshot,
11735 cx,
11736 )
11737 .next()
11738 .and_then(|(bp, _)| {
11739 let breakpoint_row = buffer_snapshot
11740 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11741 .row;
11742
11743 if breakpoint_row == row {
11744 snapshot
11745 .buffer_snapshot()
11746 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11747 .map(|position| (position, bp.bp.clone()))
11748 } else {
11749 None
11750 }
11751 })
11752 })
11753 }
11754
11755 pub fn edit_log_breakpoint(
11756 &mut self,
11757 _: &EditLogBreakpoint,
11758 window: &mut Window,
11759 cx: &mut Context<Self>,
11760 ) {
11761 if self.breakpoint_store.is_none() {
11762 return;
11763 }
11764
11765 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11766 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11767 message: None,
11768 state: BreakpointState::Enabled,
11769 condition: None,
11770 hit_condition: None,
11771 });
11772
11773 self.add_edit_breakpoint_block(
11774 anchor,
11775 &breakpoint,
11776 BreakpointPromptEditAction::Log,
11777 window,
11778 cx,
11779 );
11780 }
11781 }
11782
11783 fn breakpoints_at_cursors(
11784 &self,
11785 window: &mut Window,
11786 cx: &mut Context<Self>,
11787 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11788 let snapshot = self.snapshot(window, cx);
11789 let cursors = self
11790 .selections
11791 .disjoint_anchors_arc()
11792 .iter()
11793 .map(|selection| {
11794 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11795
11796 let breakpoint_position = self
11797 .breakpoint_at_row(cursor_position.row, window, cx)
11798 .map(|bp| bp.0)
11799 .unwrap_or_else(|| {
11800 snapshot
11801 .display_snapshot
11802 .buffer_snapshot()
11803 .anchor_after(Point::new(cursor_position.row, 0))
11804 });
11805
11806 let breakpoint = self
11807 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11808 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11809
11810 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11811 })
11812 // 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.
11813 .collect::<HashMap<Anchor, _>>();
11814
11815 cursors.into_iter().collect()
11816 }
11817
11818 pub fn enable_breakpoint(
11819 &mut self,
11820 _: &crate::actions::EnableBreakpoint,
11821 window: &mut Window,
11822 cx: &mut Context<Self>,
11823 ) {
11824 if self.breakpoint_store.is_none() {
11825 return;
11826 }
11827
11828 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11829 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11830 continue;
11831 };
11832 self.edit_breakpoint_at_anchor(
11833 anchor,
11834 breakpoint,
11835 BreakpointEditAction::InvertState,
11836 cx,
11837 );
11838 }
11839 }
11840
11841 pub fn disable_breakpoint(
11842 &mut self,
11843 _: &crate::actions::DisableBreakpoint,
11844 window: &mut Window,
11845 cx: &mut Context<Self>,
11846 ) {
11847 if self.breakpoint_store.is_none() {
11848 return;
11849 }
11850
11851 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11852 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11853 continue;
11854 };
11855 self.edit_breakpoint_at_anchor(
11856 anchor,
11857 breakpoint,
11858 BreakpointEditAction::InvertState,
11859 cx,
11860 );
11861 }
11862 }
11863
11864 pub fn toggle_breakpoint(
11865 &mut self,
11866 _: &crate::actions::ToggleBreakpoint,
11867 window: &mut Window,
11868 cx: &mut Context<Self>,
11869 ) {
11870 if self.breakpoint_store.is_none() {
11871 return;
11872 }
11873
11874 let snapshot = self.snapshot(window, cx);
11875 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11876 if self.gutter_breakpoint_indicator.0.is_some() {
11877 let display_row = anchor
11878 .to_point(snapshot.buffer_snapshot())
11879 .to_display_point(&snapshot.display_snapshot)
11880 .row();
11881 self.update_breakpoint_collision_on_toggle(
11882 display_row,
11883 &BreakpointEditAction::Toggle,
11884 );
11885 }
11886
11887 if let Some(breakpoint) = breakpoint {
11888 self.edit_breakpoint_at_anchor(
11889 anchor,
11890 breakpoint,
11891 BreakpointEditAction::Toggle,
11892 cx,
11893 );
11894 } else {
11895 self.edit_breakpoint_at_anchor(
11896 anchor,
11897 Breakpoint::new_standard(),
11898 BreakpointEditAction::Toggle,
11899 cx,
11900 );
11901 }
11902 }
11903 }
11904
11905 fn update_breakpoint_collision_on_toggle(
11906 &mut self,
11907 display_row: DisplayRow,
11908 edit_action: &BreakpointEditAction,
11909 ) {
11910 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
11911 if breakpoint_indicator.display_row == display_row
11912 && matches!(edit_action, BreakpointEditAction::Toggle)
11913 {
11914 breakpoint_indicator.collides_with_existing_breakpoint =
11915 !breakpoint_indicator.collides_with_existing_breakpoint;
11916 }
11917 }
11918 }
11919
11920 pub fn edit_breakpoint_at_anchor(
11921 &mut self,
11922 breakpoint_position: Anchor,
11923 breakpoint: Breakpoint,
11924 edit_action: BreakpointEditAction,
11925 cx: &mut Context<Self>,
11926 ) {
11927 let Some(breakpoint_store) = &self.breakpoint_store else {
11928 return;
11929 };
11930
11931 let Some(buffer) = self
11932 .buffer
11933 .read(cx)
11934 .buffer_for_anchor(breakpoint_position, cx)
11935 else {
11936 return;
11937 };
11938
11939 breakpoint_store.update(cx, |breakpoint_store, cx| {
11940 breakpoint_store.toggle_breakpoint(
11941 buffer,
11942 BreakpointWithPosition {
11943 position: breakpoint_position.text_anchor,
11944 bp: breakpoint,
11945 },
11946 edit_action,
11947 cx,
11948 );
11949 });
11950
11951 cx.notify();
11952 }
11953
11954 #[cfg(any(test, feature = "test-support"))]
11955 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11956 self.breakpoint_store.clone()
11957 }
11958
11959 pub fn prepare_restore_change(
11960 &self,
11961 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11962 hunk: &MultiBufferDiffHunk,
11963 cx: &mut App,
11964 ) -> Option<()> {
11965 if hunk.is_created_file() {
11966 return None;
11967 }
11968 let buffer = self.buffer.read(cx);
11969 let diff = buffer.diff_for(hunk.buffer_id)?;
11970 let buffer = buffer.buffer(hunk.buffer_id)?;
11971 let buffer = buffer.read(cx);
11972 let original_text = diff
11973 .read(cx)
11974 .base_text(cx)
11975 .as_rope()
11976 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11977 let buffer_snapshot = buffer.snapshot();
11978 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11979 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11980 probe
11981 .0
11982 .start
11983 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11984 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11985 }) {
11986 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11987 Some(())
11988 } else {
11989 None
11990 }
11991 }
11992
11993 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11994 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11995 }
11996
11997 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11998 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11999 }
12000
12001 pub fn rotate_selections_forward(
12002 &mut self,
12003 _: &RotateSelectionsForward,
12004 window: &mut Window,
12005 cx: &mut Context<Self>,
12006 ) {
12007 self.rotate_selections(window, cx, false)
12008 }
12009
12010 pub fn rotate_selections_backward(
12011 &mut self,
12012 _: &RotateSelectionsBackward,
12013 window: &mut Window,
12014 cx: &mut Context<Self>,
12015 ) {
12016 self.rotate_selections(window, cx, true)
12017 }
12018
12019 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12021 let display_snapshot = self.display_snapshot(cx);
12022 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12023
12024 if selections.len() < 2 {
12025 return;
12026 }
12027
12028 let (edits, new_selections) = {
12029 let buffer = self.buffer.read(cx).read(cx);
12030 let has_selections = selections.iter().any(|s| !s.is_empty());
12031 if has_selections {
12032 let mut selected_texts: Vec<String> = selections
12033 .iter()
12034 .map(|selection| {
12035 buffer
12036 .text_for_range(selection.start..selection.end)
12037 .collect()
12038 })
12039 .collect();
12040
12041 if reverse {
12042 selected_texts.rotate_left(1);
12043 } else {
12044 selected_texts.rotate_right(1);
12045 }
12046
12047 let mut offset_delta: i64 = 0;
12048 let mut new_selections = Vec::new();
12049 let edits: Vec<_> = selections
12050 .iter()
12051 .zip(selected_texts.iter())
12052 .map(|(selection, new_text)| {
12053 let old_len = (selection.end.0 - selection.start.0) as i64;
12054 let new_len = new_text.len() as i64;
12055 let adjusted_start =
12056 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12057 let adjusted_end =
12058 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12059
12060 new_selections.push(Selection {
12061 id: selection.id,
12062 start: adjusted_start,
12063 end: adjusted_end,
12064 reversed: selection.reversed,
12065 goal: selection.goal,
12066 });
12067
12068 offset_delta += new_len - old_len;
12069 (selection.start..selection.end, new_text.clone())
12070 })
12071 .collect();
12072 (edits, new_selections)
12073 } else {
12074 let mut all_rows: Vec<u32> = selections
12075 .iter()
12076 .map(|selection| buffer.offset_to_point(selection.start).row)
12077 .collect();
12078 all_rows.sort_unstable();
12079 all_rows.dedup();
12080
12081 if all_rows.len() < 2 {
12082 return;
12083 }
12084
12085 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12086 .iter()
12087 .map(|&row| {
12088 let start = Point::new(row, 0);
12089 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12090 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12091 })
12092 .collect();
12093
12094 let mut line_texts: Vec<String> = line_ranges
12095 .iter()
12096 .map(|range| buffer.text_for_range(range.clone()).collect())
12097 .collect();
12098
12099 if reverse {
12100 line_texts.rotate_left(1);
12101 } else {
12102 line_texts.rotate_right(1);
12103 }
12104
12105 let edits = line_ranges
12106 .iter()
12107 .zip(line_texts.iter())
12108 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12109 .collect();
12110
12111 let num_rows = all_rows.len();
12112 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12113 .iter()
12114 .enumerate()
12115 .map(|(i, &row)| (row, i))
12116 .collect();
12117
12118 // Compute new line start offsets after rotation (handles CRLF)
12119 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12120 let first_line_start = line_ranges[0].start.0;
12121 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12122 for text in line_texts.iter().take(num_rows - 1) {
12123 let prev_start = *new_line_starts.last().unwrap();
12124 new_line_starts.push(prev_start + text.len() + newline_len);
12125 }
12126
12127 let new_selections = selections
12128 .iter()
12129 .map(|selection| {
12130 let point = buffer.offset_to_point(selection.start);
12131 let old_index = row_to_index[&point.row];
12132 let new_index = if reverse {
12133 (old_index + num_rows - 1) % num_rows
12134 } else {
12135 (old_index + 1) % num_rows
12136 };
12137 let new_offset =
12138 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12139 Selection {
12140 id: selection.id,
12141 start: new_offset,
12142 end: new_offset,
12143 reversed: selection.reversed,
12144 goal: selection.goal,
12145 }
12146 })
12147 .collect();
12148
12149 (edits, new_selections)
12150 }
12151 };
12152
12153 self.transact(window, cx, |this, window, cx| {
12154 this.buffer.update(cx, |buffer, cx| {
12155 buffer.edit(edits, None, cx);
12156 });
12157 this.change_selections(Default::default(), window, cx, |s| {
12158 s.select(new_selections);
12159 });
12160 });
12161 }
12162
12163 fn manipulate_lines<M>(
12164 &mut self,
12165 window: &mut Window,
12166 cx: &mut Context<Self>,
12167 mut manipulate: M,
12168 ) where
12169 M: FnMut(&str) -> LineManipulationResult,
12170 {
12171 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12172
12173 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12174 let buffer = self.buffer.read(cx).snapshot(cx);
12175
12176 let mut edits = Vec::new();
12177
12178 let selections = self.selections.all::<Point>(&display_map);
12179 let mut selections = selections.iter().peekable();
12180 let mut contiguous_row_selections = Vec::new();
12181 let mut new_selections = Vec::new();
12182 let mut added_lines = 0;
12183 let mut removed_lines = 0;
12184
12185 while let Some(selection) = selections.next() {
12186 let (start_row, end_row) = consume_contiguous_rows(
12187 &mut contiguous_row_selections,
12188 selection,
12189 &display_map,
12190 &mut selections,
12191 );
12192
12193 let start_point = Point::new(start_row.0, 0);
12194 let end_point = Point::new(
12195 end_row.previous_row().0,
12196 buffer.line_len(end_row.previous_row()),
12197 );
12198 let text = buffer
12199 .text_for_range(start_point..end_point)
12200 .collect::<String>();
12201
12202 let LineManipulationResult {
12203 new_text,
12204 line_count_before,
12205 line_count_after,
12206 } = manipulate(&text);
12207
12208 edits.push((start_point..end_point, new_text));
12209
12210 // Selections must change based on added and removed line count
12211 let start_row =
12212 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12213 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12214 new_selections.push(Selection {
12215 id: selection.id,
12216 start: start_row,
12217 end: end_row,
12218 goal: SelectionGoal::None,
12219 reversed: selection.reversed,
12220 });
12221
12222 if line_count_after > line_count_before {
12223 added_lines += line_count_after - line_count_before;
12224 } else if line_count_before > line_count_after {
12225 removed_lines += line_count_before - line_count_after;
12226 }
12227 }
12228
12229 self.transact(window, cx, |this, window, cx| {
12230 let buffer = this.buffer.update(cx, |buffer, cx| {
12231 buffer.edit(edits, None, cx);
12232 buffer.snapshot(cx)
12233 });
12234
12235 // Recalculate offsets on newly edited buffer
12236 let new_selections = new_selections
12237 .iter()
12238 .map(|s| {
12239 let start_point = Point::new(s.start.0, 0);
12240 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12241 Selection {
12242 id: s.id,
12243 start: buffer.point_to_offset(start_point),
12244 end: buffer.point_to_offset(end_point),
12245 goal: s.goal,
12246 reversed: s.reversed,
12247 }
12248 })
12249 .collect();
12250
12251 this.change_selections(Default::default(), window, cx, |s| {
12252 s.select(new_selections);
12253 });
12254
12255 this.request_autoscroll(Autoscroll::fit(), cx);
12256 });
12257 }
12258
12259 fn manipulate_immutable_lines<Fn>(
12260 &mut self,
12261 window: &mut Window,
12262 cx: &mut Context<Self>,
12263 mut callback: Fn,
12264 ) where
12265 Fn: FnMut(&mut Vec<&str>),
12266 {
12267 self.manipulate_lines(window, cx, |text| {
12268 let mut lines: Vec<&str> = text.split('\n').collect();
12269 let line_count_before = lines.len();
12270
12271 callback(&mut lines);
12272
12273 LineManipulationResult {
12274 new_text: lines.join("\n"),
12275 line_count_before,
12276 line_count_after: lines.len(),
12277 }
12278 });
12279 }
12280
12281 fn manipulate_mutable_lines<Fn>(
12282 &mut self,
12283 window: &mut Window,
12284 cx: &mut Context<Self>,
12285 mut callback: Fn,
12286 ) where
12287 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12288 {
12289 self.manipulate_lines(window, cx, |text| {
12290 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12291 let line_count_before = lines.len();
12292
12293 callback(&mut lines);
12294
12295 LineManipulationResult {
12296 new_text: lines.join("\n"),
12297 line_count_before,
12298 line_count_after: lines.len(),
12299 }
12300 });
12301 }
12302
12303 pub fn convert_indentation_to_spaces(
12304 &mut self,
12305 _: &ConvertIndentationToSpaces,
12306 window: &mut Window,
12307 cx: &mut Context<Self>,
12308 ) {
12309 let settings = self.buffer.read(cx).language_settings(cx);
12310 let tab_size = settings.tab_size.get() as usize;
12311
12312 self.manipulate_mutable_lines(window, cx, |lines| {
12313 // Allocates a reasonably sized scratch buffer once for the whole loop
12314 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12315 // Avoids recomputing spaces that could be inserted many times
12316 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12317 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12318 .collect();
12319
12320 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12321 let mut chars = line.as_ref().chars();
12322 let mut col = 0;
12323 let mut changed = false;
12324
12325 for ch in chars.by_ref() {
12326 match ch {
12327 ' ' => {
12328 reindented_line.push(' ');
12329 col += 1;
12330 }
12331 '\t' => {
12332 // \t are converted to spaces depending on the current column
12333 let spaces_len = tab_size - (col % tab_size);
12334 reindented_line.extend(&space_cache[spaces_len - 1]);
12335 col += spaces_len;
12336 changed = true;
12337 }
12338 _ => {
12339 // If we dont append before break, the character is consumed
12340 reindented_line.push(ch);
12341 break;
12342 }
12343 }
12344 }
12345
12346 if !changed {
12347 reindented_line.clear();
12348 continue;
12349 }
12350 // Append the rest of the line and replace old reference with new one
12351 reindented_line.extend(chars);
12352 *line = Cow::Owned(reindented_line.clone());
12353 reindented_line.clear();
12354 }
12355 });
12356 }
12357
12358 pub fn convert_indentation_to_tabs(
12359 &mut self,
12360 _: &ConvertIndentationToTabs,
12361 window: &mut Window,
12362 cx: &mut Context<Self>,
12363 ) {
12364 let settings = self.buffer.read(cx).language_settings(cx);
12365 let tab_size = settings.tab_size.get() as usize;
12366
12367 self.manipulate_mutable_lines(window, cx, |lines| {
12368 // Allocates a reasonably sized buffer once for the whole loop
12369 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12370 // Avoids recomputing spaces that could be inserted many times
12371 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12372 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12373 .collect();
12374
12375 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12376 let mut chars = line.chars();
12377 let mut spaces_count = 0;
12378 let mut first_non_indent_char = None;
12379 let mut changed = false;
12380
12381 for ch in chars.by_ref() {
12382 match ch {
12383 ' ' => {
12384 // Keep track of spaces. Append \t when we reach tab_size
12385 spaces_count += 1;
12386 changed = true;
12387 if spaces_count == tab_size {
12388 reindented_line.push('\t');
12389 spaces_count = 0;
12390 }
12391 }
12392 '\t' => {
12393 reindented_line.push('\t');
12394 spaces_count = 0;
12395 }
12396 _ => {
12397 // Dont append it yet, we might have remaining spaces
12398 first_non_indent_char = Some(ch);
12399 break;
12400 }
12401 }
12402 }
12403
12404 if !changed {
12405 reindented_line.clear();
12406 continue;
12407 }
12408 // Remaining spaces that didn't make a full tab stop
12409 if spaces_count > 0 {
12410 reindented_line.extend(&space_cache[spaces_count - 1]);
12411 }
12412 // If we consume an extra character that was not indentation, add it back
12413 if let Some(extra_char) = first_non_indent_char {
12414 reindented_line.push(extra_char);
12415 }
12416 // Append the rest of the line and replace old reference with new one
12417 reindented_line.extend(chars);
12418 *line = Cow::Owned(reindented_line.clone());
12419 reindented_line.clear();
12420 }
12421 });
12422 }
12423
12424 pub fn convert_to_upper_case(
12425 &mut self,
12426 _: &ConvertToUpperCase,
12427 window: &mut Window,
12428 cx: &mut Context<Self>,
12429 ) {
12430 self.manipulate_text(window, cx, |text| text.to_uppercase())
12431 }
12432
12433 pub fn convert_to_lower_case(
12434 &mut self,
12435 _: &ConvertToLowerCase,
12436 window: &mut Window,
12437 cx: &mut Context<Self>,
12438 ) {
12439 self.manipulate_text(window, cx, |text| text.to_lowercase())
12440 }
12441
12442 pub fn convert_to_title_case(
12443 &mut self,
12444 _: &ConvertToTitleCase,
12445 window: &mut Window,
12446 cx: &mut Context<Self>,
12447 ) {
12448 self.manipulate_text(window, cx, |text| {
12449 text.split('\n')
12450 .map(|line| line.to_case(Case::Title))
12451 .join("\n")
12452 })
12453 }
12454
12455 pub fn convert_to_snake_case(
12456 &mut self,
12457 _: &ConvertToSnakeCase,
12458 window: &mut Window,
12459 cx: &mut Context<Self>,
12460 ) {
12461 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12462 }
12463
12464 pub fn convert_to_kebab_case(
12465 &mut self,
12466 _: &ConvertToKebabCase,
12467 window: &mut Window,
12468 cx: &mut Context<Self>,
12469 ) {
12470 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12471 }
12472
12473 pub fn convert_to_upper_camel_case(
12474 &mut self,
12475 _: &ConvertToUpperCamelCase,
12476 window: &mut Window,
12477 cx: &mut Context<Self>,
12478 ) {
12479 self.manipulate_text(window, cx, |text| {
12480 text.split('\n')
12481 .map(|line| line.to_case(Case::UpperCamel))
12482 .join("\n")
12483 })
12484 }
12485
12486 pub fn convert_to_lower_camel_case(
12487 &mut self,
12488 _: &ConvertToLowerCamelCase,
12489 window: &mut Window,
12490 cx: &mut Context<Self>,
12491 ) {
12492 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12493 }
12494
12495 pub fn convert_to_opposite_case(
12496 &mut self,
12497 _: &ConvertToOppositeCase,
12498 window: &mut Window,
12499 cx: &mut Context<Self>,
12500 ) {
12501 self.manipulate_text(window, cx, |text| {
12502 text.chars()
12503 .fold(String::with_capacity(text.len()), |mut t, c| {
12504 if c.is_uppercase() {
12505 t.extend(c.to_lowercase());
12506 } else {
12507 t.extend(c.to_uppercase());
12508 }
12509 t
12510 })
12511 })
12512 }
12513
12514 pub fn convert_to_sentence_case(
12515 &mut self,
12516 _: &ConvertToSentenceCase,
12517 window: &mut Window,
12518 cx: &mut Context<Self>,
12519 ) {
12520 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12521 }
12522
12523 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12524 self.manipulate_text(window, cx, |text| {
12525 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12526 if has_upper_case_characters {
12527 text.to_lowercase()
12528 } else {
12529 text.to_uppercase()
12530 }
12531 })
12532 }
12533
12534 pub fn convert_to_rot13(
12535 &mut self,
12536 _: &ConvertToRot13,
12537 window: &mut Window,
12538 cx: &mut Context<Self>,
12539 ) {
12540 self.manipulate_text(window, cx, |text| {
12541 text.chars()
12542 .map(|c| match c {
12543 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12544 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12545 _ => c,
12546 })
12547 .collect()
12548 })
12549 }
12550
12551 pub fn convert_to_rot47(
12552 &mut self,
12553 _: &ConvertToRot47,
12554 window: &mut Window,
12555 cx: &mut Context<Self>,
12556 ) {
12557 self.manipulate_text(window, cx, |text| {
12558 text.chars()
12559 .map(|c| {
12560 let code_point = c as u32;
12561 if code_point >= 33 && code_point <= 126 {
12562 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12563 }
12564 c
12565 })
12566 .collect()
12567 })
12568 }
12569
12570 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12571 where
12572 Fn: FnMut(&str) -> String,
12573 {
12574 let buffer = self.buffer.read(cx).snapshot(cx);
12575
12576 let mut new_selections = Vec::new();
12577 let mut edits = Vec::new();
12578 let mut selection_adjustment = 0isize;
12579
12580 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12581 let selection_is_empty = selection.is_empty();
12582
12583 let (start, end) = if selection_is_empty {
12584 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12585 (word_range.start, word_range.end)
12586 } else {
12587 (
12588 buffer.point_to_offset(selection.start),
12589 buffer.point_to_offset(selection.end),
12590 )
12591 };
12592
12593 let text = buffer.text_for_range(start..end).collect::<String>();
12594 let old_length = text.len() as isize;
12595 let text = callback(&text);
12596
12597 new_selections.push(Selection {
12598 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12599 end: MultiBufferOffset(
12600 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12601 ),
12602 goal: SelectionGoal::None,
12603 id: selection.id,
12604 reversed: selection.reversed,
12605 });
12606
12607 selection_adjustment += old_length - text.len() as isize;
12608
12609 edits.push((start..end, text));
12610 }
12611
12612 self.transact(window, cx, |this, window, cx| {
12613 this.buffer.update(cx, |buffer, cx| {
12614 buffer.edit(edits, None, cx);
12615 });
12616
12617 this.change_selections(Default::default(), window, cx, |s| {
12618 s.select(new_selections);
12619 });
12620
12621 this.request_autoscroll(Autoscroll::fit(), cx);
12622 });
12623 }
12624
12625 pub fn move_selection_on_drop(
12626 &mut self,
12627 selection: &Selection<Anchor>,
12628 target: DisplayPoint,
12629 is_cut: bool,
12630 window: &mut Window,
12631 cx: &mut Context<Self>,
12632 ) {
12633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12634 let buffer = display_map.buffer_snapshot();
12635 let mut edits = Vec::new();
12636 let insert_point = display_map
12637 .clip_point(target, Bias::Left)
12638 .to_point(&display_map);
12639 let text = buffer
12640 .text_for_range(selection.start..selection.end)
12641 .collect::<String>();
12642 if is_cut {
12643 edits.push(((selection.start..selection.end), String::new()));
12644 }
12645 let insert_anchor = buffer.anchor_before(insert_point);
12646 edits.push(((insert_anchor..insert_anchor), text));
12647 let last_edit_start = insert_anchor.bias_left(buffer);
12648 let last_edit_end = insert_anchor.bias_right(buffer);
12649 self.transact(window, cx, |this, window, cx| {
12650 this.buffer.update(cx, |buffer, cx| {
12651 buffer.edit(edits, None, cx);
12652 });
12653 this.change_selections(Default::default(), window, cx, |s| {
12654 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12655 });
12656 });
12657 }
12658
12659 pub fn clear_selection_drag_state(&mut self) {
12660 self.selection_drag_state = SelectionDragState::None;
12661 }
12662
12663 pub fn duplicate(
12664 &mut self,
12665 upwards: bool,
12666 whole_lines: bool,
12667 window: &mut Window,
12668 cx: &mut Context<Self>,
12669 ) {
12670 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12671
12672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12673 let buffer = display_map.buffer_snapshot();
12674 let selections = self.selections.all::<Point>(&display_map);
12675
12676 let mut edits = Vec::new();
12677 let mut selections_iter = selections.iter().peekable();
12678 while let Some(selection) = selections_iter.next() {
12679 let mut rows = selection.spanned_rows(false, &display_map);
12680 // duplicate line-wise
12681 if whole_lines || selection.start == selection.end {
12682 // Avoid duplicating the same lines twice.
12683 while let Some(next_selection) = selections_iter.peek() {
12684 let next_rows = next_selection.spanned_rows(false, &display_map);
12685 if next_rows.start < rows.end {
12686 rows.end = next_rows.end;
12687 selections_iter.next().unwrap();
12688 } else {
12689 break;
12690 }
12691 }
12692
12693 // Copy the text from the selected row region and splice it either at the start
12694 // or end of the region.
12695 let start = Point::new(rows.start.0, 0);
12696 let end = Point::new(
12697 rows.end.previous_row().0,
12698 buffer.line_len(rows.end.previous_row()),
12699 );
12700
12701 let mut text = buffer.text_for_range(start..end).collect::<String>();
12702
12703 let insert_location = if upwards {
12704 // When duplicating upward, we need to insert before the current line.
12705 // If we're on the last line and it doesn't end with a newline,
12706 // we need to add a newline before the duplicated content.
12707 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12708 && buffer.max_point().column > 0
12709 && !text.ends_with('\n');
12710
12711 if needs_leading_newline {
12712 text.insert(0, '\n');
12713 end
12714 } else {
12715 text.push('\n');
12716 Point::new(rows.start.0, 0)
12717 }
12718 } else {
12719 text.push('\n');
12720 start
12721 };
12722 edits.push((insert_location..insert_location, text));
12723 } else {
12724 // duplicate character-wise
12725 let start = selection.start;
12726 let end = selection.end;
12727 let text = buffer.text_for_range(start..end).collect::<String>();
12728 edits.push((selection.end..selection.end, text));
12729 }
12730 }
12731
12732 self.transact(window, cx, |this, window, cx| {
12733 this.buffer.update(cx, |buffer, cx| {
12734 buffer.edit(edits, None, cx);
12735 });
12736
12737 // When duplicating upward with whole lines, move the cursor to the duplicated line
12738 if upwards && whole_lines {
12739 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12740
12741 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12742 let mut new_ranges = Vec::new();
12743 let selections = s.all::<Point>(&display_map);
12744 let mut selections_iter = selections.iter().peekable();
12745
12746 while let Some(first_selection) = selections_iter.next() {
12747 // Group contiguous selections together to find the total row span
12748 let mut group_selections = vec![first_selection];
12749 let mut rows = first_selection.spanned_rows(false, &display_map);
12750
12751 while let Some(next_selection) = selections_iter.peek() {
12752 let next_rows = next_selection.spanned_rows(false, &display_map);
12753 if next_rows.start < rows.end {
12754 rows.end = next_rows.end;
12755 group_selections.push(selections_iter.next().unwrap());
12756 } else {
12757 break;
12758 }
12759 }
12760
12761 let row_count = rows.end.0 - rows.start.0;
12762
12763 // Move all selections in this group up by the total number of duplicated rows
12764 for selection in group_selections {
12765 let new_start = Point::new(
12766 selection.start.row.saturating_sub(row_count),
12767 selection.start.column,
12768 );
12769
12770 let new_end = Point::new(
12771 selection.end.row.saturating_sub(row_count),
12772 selection.end.column,
12773 );
12774
12775 new_ranges.push(new_start..new_end);
12776 }
12777 }
12778
12779 s.select_ranges(new_ranges);
12780 });
12781 }
12782
12783 this.request_autoscroll(Autoscroll::fit(), cx);
12784 });
12785 }
12786
12787 pub fn duplicate_line_up(
12788 &mut self,
12789 _: &DuplicateLineUp,
12790 window: &mut Window,
12791 cx: &mut Context<Self>,
12792 ) {
12793 self.duplicate(true, true, window, cx);
12794 }
12795
12796 pub fn duplicate_line_down(
12797 &mut self,
12798 _: &DuplicateLineDown,
12799 window: &mut Window,
12800 cx: &mut Context<Self>,
12801 ) {
12802 self.duplicate(false, true, window, cx);
12803 }
12804
12805 pub fn duplicate_selection(
12806 &mut self,
12807 _: &DuplicateSelection,
12808 window: &mut Window,
12809 cx: &mut Context<Self>,
12810 ) {
12811 self.duplicate(false, false, window, cx);
12812 }
12813
12814 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12816 if self.mode.is_single_line() {
12817 cx.propagate();
12818 return;
12819 }
12820
12821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12822 let buffer = self.buffer.read(cx).snapshot(cx);
12823
12824 let mut edits = Vec::new();
12825 let mut unfold_ranges = Vec::new();
12826 let mut refold_creases = Vec::new();
12827
12828 let selections = self.selections.all::<Point>(&display_map);
12829 let mut selections = selections.iter().peekable();
12830 let mut contiguous_row_selections = Vec::new();
12831 let mut new_selections = Vec::new();
12832
12833 while let Some(selection) = selections.next() {
12834 // Find all the selections that span a contiguous row range
12835 let (start_row, end_row) = consume_contiguous_rows(
12836 &mut contiguous_row_selections,
12837 selection,
12838 &display_map,
12839 &mut selections,
12840 );
12841
12842 // Move the text spanned by the row range to be before the line preceding the row range
12843 if start_row.0 > 0 {
12844 let range_to_move = Point::new(
12845 start_row.previous_row().0,
12846 buffer.line_len(start_row.previous_row()),
12847 )
12848 ..Point::new(
12849 end_row.previous_row().0,
12850 buffer.line_len(end_row.previous_row()),
12851 );
12852 let insertion_point = display_map
12853 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12854 .0;
12855
12856 // Don't move lines across excerpts
12857 if buffer
12858 .excerpt_containing(insertion_point..range_to_move.end)
12859 .is_some()
12860 {
12861 let text = buffer
12862 .text_for_range(range_to_move.clone())
12863 .flat_map(|s| s.chars())
12864 .skip(1)
12865 .chain(['\n'])
12866 .collect::<String>();
12867
12868 edits.push((
12869 buffer.anchor_after(range_to_move.start)
12870 ..buffer.anchor_before(range_to_move.end),
12871 String::new(),
12872 ));
12873 let insertion_anchor = buffer.anchor_after(insertion_point);
12874 edits.push((insertion_anchor..insertion_anchor, text));
12875
12876 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12877
12878 // Move selections up
12879 new_selections.extend(contiguous_row_selections.drain(..).map(
12880 |mut selection| {
12881 selection.start.row -= row_delta;
12882 selection.end.row -= row_delta;
12883 selection
12884 },
12885 ));
12886
12887 // Move folds up
12888 unfold_ranges.push(range_to_move.clone());
12889 for fold in display_map.folds_in_range(
12890 buffer.anchor_before(range_to_move.start)
12891 ..buffer.anchor_after(range_to_move.end),
12892 ) {
12893 let mut start = fold.range.start.to_point(&buffer);
12894 let mut end = fold.range.end.to_point(&buffer);
12895 start.row -= row_delta;
12896 end.row -= row_delta;
12897 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12898 }
12899 }
12900 }
12901
12902 // If we didn't move line(s), preserve the existing selections
12903 new_selections.append(&mut contiguous_row_selections);
12904 }
12905
12906 self.transact(window, cx, |this, window, cx| {
12907 this.unfold_ranges(&unfold_ranges, true, true, cx);
12908 this.buffer.update(cx, |buffer, cx| {
12909 for (range, text) in edits {
12910 buffer.edit([(range, text)], None, cx);
12911 }
12912 });
12913 this.fold_creases(refold_creases, true, window, cx);
12914 this.change_selections(Default::default(), window, cx, |s| {
12915 s.select(new_selections);
12916 })
12917 });
12918 }
12919
12920 pub fn move_line_down(
12921 &mut self,
12922 _: &MoveLineDown,
12923 window: &mut Window,
12924 cx: &mut Context<Self>,
12925 ) {
12926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12927 if self.mode.is_single_line() {
12928 cx.propagate();
12929 return;
12930 }
12931
12932 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12933 let buffer = self.buffer.read(cx).snapshot(cx);
12934
12935 let mut edits = Vec::new();
12936 let mut unfold_ranges = Vec::new();
12937 let mut refold_creases = Vec::new();
12938
12939 let selections = self.selections.all::<Point>(&display_map);
12940 let mut selections = selections.iter().peekable();
12941 let mut contiguous_row_selections = Vec::new();
12942 let mut new_selections = Vec::new();
12943
12944 while let Some(selection) = selections.next() {
12945 // Find all the selections that span a contiguous row range
12946 let (start_row, end_row) = consume_contiguous_rows(
12947 &mut contiguous_row_selections,
12948 selection,
12949 &display_map,
12950 &mut selections,
12951 );
12952
12953 // Move the text spanned by the row range to be after the last line of the row range
12954 if end_row.0 <= buffer.max_point().row {
12955 let range_to_move =
12956 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12957 let insertion_point = display_map
12958 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12959 .0;
12960
12961 // Don't move lines across excerpt boundaries
12962 if buffer
12963 .excerpt_containing(range_to_move.start..insertion_point)
12964 .is_some()
12965 {
12966 let mut text = String::from("\n");
12967 text.extend(buffer.text_for_range(range_to_move.clone()));
12968 text.pop(); // Drop trailing newline
12969 edits.push((
12970 buffer.anchor_after(range_to_move.start)
12971 ..buffer.anchor_before(range_to_move.end),
12972 String::new(),
12973 ));
12974 let insertion_anchor = buffer.anchor_after(insertion_point);
12975 edits.push((insertion_anchor..insertion_anchor, text));
12976
12977 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12978
12979 // Move selections down
12980 new_selections.extend(contiguous_row_selections.drain(..).map(
12981 |mut selection| {
12982 selection.start.row += row_delta;
12983 selection.end.row += row_delta;
12984 selection
12985 },
12986 ));
12987
12988 // Move folds down
12989 unfold_ranges.push(range_to_move.clone());
12990 for fold in display_map.folds_in_range(
12991 buffer.anchor_before(range_to_move.start)
12992 ..buffer.anchor_after(range_to_move.end),
12993 ) {
12994 let mut start = fold.range.start.to_point(&buffer);
12995 let mut end = fold.range.end.to_point(&buffer);
12996 start.row += row_delta;
12997 end.row += row_delta;
12998 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12999 }
13000 }
13001 }
13002
13003 // If we didn't move line(s), preserve the existing selections
13004 new_selections.append(&mut contiguous_row_selections);
13005 }
13006
13007 self.transact(window, cx, |this, window, cx| {
13008 this.unfold_ranges(&unfold_ranges, true, true, cx);
13009 this.buffer.update(cx, |buffer, cx| {
13010 for (range, text) in edits {
13011 buffer.edit([(range, text)], None, cx);
13012 }
13013 });
13014 this.fold_creases(refold_creases, true, window, cx);
13015 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13016 });
13017 }
13018
13019 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13021 let text_layout_details = &self.text_layout_details(window, cx);
13022 self.transact(window, cx, |this, window, cx| {
13023 let edits = this.change_selections(Default::default(), window, cx, |s| {
13024 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13025 s.move_with(&mut |display_map, selection| {
13026 if !selection.is_empty() {
13027 return;
13028 }
13029
13030 let mut head = selection.head();
13031 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13032 if head.column() == display_map.line_len(head.row()) {
13033 transpose_offset = display_map
13034 .buffer_snapshot()
13035 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13036 }
13037
13038 if transpose_offset == MultiBufferOffset(0) {
13039 return;
13040 }
13041
13042 *head.column_mut() += 1;
13043 head = display_map.clip_point(head, Bias::Right);
13044 let goal = SelectionGoal::HorizontalPosition(
13045 display_map
13046 .x_for_display_point(head, text_layout_details)
13047 .into(),
13048 );
13049 selection.collapse_to(head, goal);
13050
13051 let transpose_start = display_map
13052 .buffer_snapshot()
13053 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13054 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13055 let transpose_end = display_map
13056 .buffer_snapshot()
13057 .clip_offset(transpose_offset + 1usize, Bias::Right);
13058 if let Some(ch) = display_map
13059 .buffer_snapshot()
13060 .chars_at(transpose_start)
13061 .next()
13062 {
13063 edits.push((transpose_start..transpose_offset, String::new()));
13064 edits.push((transpose_end..transpose_end, ch.to_string()));
13065 }
13066 }
13067 });
13068 edits
13069 });
13070 this.buffer
13071 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13072 let selections = this
13073 .selections
13074 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13075 this.change_selections(Default::default(), window, cx, |s| {
13076 s.select(selections);
13077 });
13078 });
13079 }
13080
13081 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13083 if self.mode.is_single_line() {
13084 cx.propagate();
13085 return;
13086 }
13087
13088 self.rewrap_impl(RewrapOptions::default(), cx)
13089 }
13090
13091 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13092 let buffer = self.buffer.read(cx).snapshot(cx);
13093 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13094
13095 #[derive(Clone, Debug, PartialEq)]
13096 enum CommentFormat {
13097 /// single line comment, with prefix for line
13098 Line(String),
13099 /// single line within a block comment, with prefix for line
13100 BlockLine(String),
13101 /// a single line of a block comment that includes the initial delimiter
13102 BlockCommentWithStart(BlockCommentConfig),
13103 /// a single line of a block comment that includes the ending delimiter
13104 BlockCommentWithEnd(BlockCommentConfig),
13105 }
13106
13107 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13108 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13109 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13110 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13111 .peekable();
13112
13113 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13114 row
13115 } else {
13116 return Vec::new();
13117 };
13118
13119 let language_settings = buffer.language_settings_at(selection.head(), cx);
13120 let language_scope = buffer.language_scope_at(selection.head());
13121
13122 let indent_and_prefix_for_row =
13123 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13124 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13125 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13126 &language_scope
13127 {
13128 let indent_end = Point::new(row, indent.len);
13129 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13130 let line_text_after_indent = buffer
13131 .text_for_range(indent_end..line_end)
13132 .collect::<String>();
13133
13134 let is_within_comment_override = buffer
13135 .language_scope_at(indent_end)
13136 .is_some_and(|scope| scope.override_name() == Some("comment"));
13137 let comment_delimiters = if is_within_comment_override {
13138 // we are within a comment syntax node, but we don't
13139 // yet know what kind of comment: block, doc or line
13140 match (
13141 language_scope.documentation_comment(),
13142 language_scope.block_comment(),
13143 ) {
13144 (Some(config), _) | (_, Some(config))
13145 if buffer.contains_str_at(indent_end, &config.start) =>
13146 {
13147 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13148 }
13149 (Some(config), _) | (_, Some(config))
13150 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13151 {
13152 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13153 }
13154 (Some(config), _) | (_, Some(config))
13155 if buffer.contains_str_at(indent_end, &config.prefix) =>
13156 {
13157 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13158 }
13159 (_, _) => language_scope
13160 .line_comment_prefixes()
13161 .iter()
13162 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13163 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13164 }
13165 } else {
13166 // we not in an overridden comment node, but we may
13167 // be within a non-overridden line comment node
13168 language_scope
13169 .line_comment_prefixes()
13170 .iter()
13171 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13172 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13173 };
13174
13175 let rewrap_prefix = language_scope
13176 .rewrap_prefixes()
13177 .iter()
13178 .find_map(|prefix_regex| {
13179 prefix_regex.find(&line_text_after_indent).map(|mat| {
13180 if mat.start() == 0 {
13181 Some(mat.as_str().to_string())
13182 } else {
13183 None
13184 }
13185 })
13186 })
13187 .flatten();
13188 (comment_delimiters, rewrap_prefix)
13189 } else {
13190 (None, None)
13191 };
13192 (indent, comment_prefix, rewrap_prefix)
13193 };
13194
13195 let mut ranges = Vec::new();
13196 let from_empty_selection = selection.is_empty();
13197
13198 let mut current_range_start = first_row;
13199 let mut prev_row = first_row;
13200 let (
13201 mut current_range_indent,
13202 mut current_range_comment_delimiters,
13203 mut current_range_rewrap_prefix,
13204 ) = indent_and_prefix_for_row(first_row);
13205
13206 for row in non_blank_rows_iter.skip(1) {
13207 let has_paragraph_break = row > prev_row + 1;
13208
13209 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13210 indent_and_prefix_for_row(row);
13211
13212 let has_indent_change = row_indent != current_range_indent;
13213 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13214
13215 let has_boundary_change = has_comment_change
13216 || row_rewrap_prefix.is_some()
13217 || (has_indent_change && current_range_comment_delimiters.is_some());
13218
13219 if has_paragraph_break || has_boundary_change {
13220 ranges.push((
13221 language_settings.clone(),
13222 Point::new(current_range_start, 0)
13223 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13224 current_range_indent,
13225 current_range_comment_delimiters.clone(),
13226 current_range_rewrap_prefix.clone(),
13227 from_empty_selection,
13228 ));
13229 current_range_start = row;
13230 current_range_indent = row_indent;
13231 current_range_comment_delimiters = row_comment_delimiters;
13232 current_range_rewrap_prefix = row_rewrap_prefix;
13233 }
13234 prev_row = row;
13235 }
13236
13237 ranges.push((
13238 language_settings.clone(),
13239 Point::new(current_range_start, 0)
13240 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13241 current_range_indent,
13242 current_range_comment_delimiters,
13243 current_range_rewrap_prefix,
13244 from_empty_selection,
13245 ));
13246
13247 ranges
13248 });
13249
13250 let mut edits = Vec::new();
13251 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13252
13253 for (
13254 language_settings,
13255 wrap_range,
13256 mut indent_size,
13257 comment_prefix,
13258 rewrap_prefix,
13259 from_empty_selection,
13260 ) in wrap_ranges
13261 {
13262 let mut start_row = wrap_range.start.row;
13263 let mut end_row = wrap_range.end.row;
13264
13265 // Skip selections that overlap with a range that has already been rewrapped.
13266 let selection_range = start_row..end_row;
13267 if rewrapped_row_ranges
13268 .iter()
13269 .any(|range| range.overlaps(&selection_range))
13270 {
13271 continue;
13272 }
13273
13274 let tab_size = language_settings.tab_size;
13275
13276 let (line_prefix, inside_comment) = match &comment_prefix {
13277 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13278 (Some(prefix.as_str()), true)
13279 }
13280 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13281 (Some(prefix.as_ref()), true)
13282 }
13283 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13284 start: _,
13285 end: _,
13286 prefix,
13287 tab_size,
13288 })) => {
13289 indent_size.len += tab_size;
13290 (Some(prefix.as_ref()), true)
13291 }
13292 None => (None, false),
13293 };
13294 let indent_prefix = indent_size.chars().collect::<String>();
13295 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13296
13297 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13298 RewrapBehavior::InComments => inside_comment,
13299 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13300 RewrapBehavior::Anywhere => true,
13301 };
13302
13303 let should_rewrap = options.override_language_settings
13304 || allow_rewrap_based_on_language
13305 || self.hard_wrap.is_some();
13306 if !should_rewrap {
13307 continue;
13308 }
13309
13310 if from_empty_selection {
13311 'expand_upwards: while start_row > 0 {
13312 let prev_row = start_row - 1;
13313 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13314 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13315 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13316 {
13317 start_row = prev_row;
13318 } else {
13319 break 'expand_upwards;
13320 }
13321 }
13322
13323 'expand_downwards: while end_row < buffer.max_point().row {
13324 let next_row = end_row + 1;
13325 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13326 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13327 && !buffer.is_line_blank(MultiBufferRow(next_row))
13328 {
13329 end_row = next_row;
13330 } else {
13331 break 'expand_downwards;
13332 }
13333 }
13334 }
13335
13336 let start = Point::new(start_row, 0);
13337 let start_offset = ToOffset::to_offset(&start, &buffer);
13338 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13339 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13340 let mut first_line_delimiter = None;
13341 let mut last_line_delimiter = None;
13342 let Some(lines_without_prefixes) = selection_text
13343 .lines()
13344 .enumerate()
13345 .map(|(ix, line)| {
13346 let line_trimmed = line.trim_start();
13347 if rewrap_prefix.is_some() && ix > 0 {
13348 Ok(line_trimmed)
13349 } else if let Some(
13350 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13351 start,
13352 prefix,
13353 end,
13354 tab_size,
13355 })
13356 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13357 start,
13358 prefix,
13359 end,
13360 tab_size,
13361 }),
13362 ) = &comment_prefix
13363 {
13364 let line_trimmed = line_trimmed
13365 .strip_prefix(start.as_ref())
13366 .map(|s| {
13367 let mut indent_size = indent_size;
13368 indent_size.len -= tab_size;
13369 let indent_prefix: String = indent_size.chars().collect();
13370 first_line_delimiter = Some((indent_prefix, start));
13371 s.trim_start()
13372 })
13373 .unwrap_or(line_trimmed);
13374 let line_trimmed = line_trimmed
13375 .strip_suffix(end.as_ref())
13376 .map(|s| {
13377 last_line_delimiter = Some(end);
13378 s.trim_end()
13379 })
13380 .unwrap_or(line_trimmed);
13381 let line_trimmed = line_trimmed
13382 .strip_prefix(prefix.as_ref())
13383 .unwrap_or(line_trimmed);
13384 Ok(line_trimmed)
13385 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13386 line_trimmed.strip_prefix(prefix).with_context(|| {
13387 format!("line did not start with prefix {prefix:?}: {line:?}")
13388 })
13389 } else {
13390 line_trimmed
13391 .strip_prefix(&line_prefix.trim_start())
13392 .with_context(|| {
13393 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13394 })
13395 }
13396 })
13397 .collect::<Result<Vec<_>, _>>()
13398 .log_err()
13399 else {
13400 continue;
13401 };
13402
13403 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13404 buffer
13405 .language_settings_at(Point::new(start_row, 0), cx)
13406 .preferred_line_length as usize
13407 });
13408
13409 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13410 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13411 } else {
13412 line_prefix.clone()
13413 };
13414
13415 let wrapped_text = {
13416 let mut wrapped_text = wrap_with_prefix(
13417 line_prefix,
13418 subsequent_lines_prefix,
13419 lines_without_prefixes.join("\n"),
13420 wrap_column,
13421 tab_size,
13422 options.preserve_existing_whitespace,
13423 );
13424
13425 if let Some((indent, delimiter)) = first_line_delimiter {
13426 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13427 }
13428 if let Some(last_line) = last_line_delimiter {
13429 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13430 }
13431
13432 wrapped_text
13433 };
13434
13435 // TODO: should always use char-based diff while still supporting cursor behavior that
13436 // matches vim.
13437 let mut diff_options = DiffOptions::default();
13438 if options.override_language_settings {
13439 diff_options.max_word_diff_len = 0;
13440 diff_options.max_word_diff_line_count = 0;
13441 } else {
13442 diff_options.max_word_diff_len = usize::MAX;
13443 diff_options.max_word_diff_line_count = usize::MAX;
13444 }
13445
13446 for (old_range, new_text) in
13447 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13448 {
13449 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13450 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13451 edits.push((edit_start..edit_end, new_text));
13452 }
13453
13454 rewrapped_row_ranges.push(start_row..=end_row);
13455 }
13456
13457 self.buffer
13458 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13459 }
13460
13461 pub fn cut_common(
13462 &mut self,
13463 cut_no_selection_line: bool,
13464 window: &mut Window,
13465 cx: &mut Context<Self>,
13466 ) -> ClipboardItem {
13467 let mut text = String::new();
13468 let buffer = self.buffer.read(cx).snapshot(cx);
13469 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13470 let mut clipboard_selections = Vec::with_capacity(selections.len());
13471 {
13472 let max_point = buffer.max_point();
13473 let mut is_first = true;
13474 let mut prev_selection_was_entire_line = false;
13475 for selection in &mut selections {
13476 let is_entire_line =
13477 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13478 if is_entire_line {
13479 selection.start = Point::new(selection.start.row, 0);
13480 if !selection.is_empty() && selection.end.column == 0 {
13481 selection.end = cmp::min(max_point, selection.end);
13482 } else {
13483 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13484 }
13485 selection.goal = SelectionGoal::None;
13486 }
13487 if is_first {
13488 is_first = false;
13489 } else if !prev_selection_was_entire_line {
13490 text += "\n";
13491 }
13492 prev_selection_was_entire_line = is_entire_line;
13493 let mut len = 0;
13494 for chunk in buffer.text_for_range(selection.start..selection.end) {
13495 text.push_str(chunk);
13496 len += chunk.len();
13497 }
13498
13499 clipboard_selections.push(ClipboardSelection::for_buffer(
13500 len,
13501 is_entire_line,
13502 selection.range(),
13503 &buffer,
13504 self.project.as_ref(),
13505 cx,
13506 ));
13507 }
13508 }
13509
13510 self.transact(window, cx, |this, window, cx| {
13511 this.change_selections(Default::default(), window, cx, |s| {
13512 s.select(selections);
13513 });
13514 this.insert("", window, cx);
13515 });
13516 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13517 }
13518
13519 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13521 let item = self.cut_common(true, window, cx);
13522 cx.write_to_clipboard(item);
13523 }
13524
13525 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13527 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13528 s.move_with(&mut |snapshot, sel| {
13529 if sel.is_empty() {
13530 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13531 }
13532 if sel.is_empty() {
13533 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13534 }
13535 });
13536 });
13537 let item = self.cut_common(false, window, cx);
13538 cx.set_global(KillRing(item))
13539 }
13540
13541 pub fn kill_ring_yank(
13542 &mut self,
13543 _: &KillRingYank,
13544 window: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) {
13547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13548 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13549 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13550 (kill_ring.text().to_string(), kill_ring.metadata_json())
13551 } else {
13552 return;
13553 }
13554 } else {
13555 return;
13556 };
13557 self.do_paste(&text, metadata, false, window, cx);
13558 }
13559
13560 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13561 self.do_copy(true, cx);
13562 }
13563
13564 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13565 self.do_copy(false, cx);
13566 }
13567
13568 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13569 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13570 let buffer = self.buffer.read(cx).read(cx);
13571 let mut text = String::new();
13572 let mut clipboard_selections = Vec::with_capacity(selections.len());
13573
13574 let max_point = buffer.max_point();
13575 let mut is_first = true;
13576 for selection in &selections {
13577 let mut start = selection.start;
13578 let mut end = selection.end;
13579 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13580 let mut add_trailing_newline = false;
13581 if is_entire_line {
13582 start = Point::new(start.row, 0);
13583 let next_line_start = Point::new(end.row + 1, 0);
13584 if next_line_start <= max_point {
13585 end = next_line_start;
13586 } else {
13587 // We're on the last line without a trailing newline.
13588 // Copy to the end of the line and add a newline afterwards.
13589 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13590 add_trailing_newline = true;
13591 }
13592 }
13593
13594 let mut trimmed_selections = Vec::new();
13595 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13596 let row = MultiBufferRow(start.row);
13597 let first_indent = buffer.indent_size_for_line(row);
13598 if first_indent.len == 0 || start.column > first_indent.len {
13599 trimmed_selections.push(start..end);
13600 } else {
13601 trimmed_selections.push(
13602 Point::new(row.0, first_indent.len)
13603 ..Point::new(row.0, buffer.line_len(row)),
13604 );
13605 for row in start.row + 1..=end.row {
13606 let mut line_len = buffer.line_len(MultiBufferRow(row));
13607 if row == end.row {
13608 line_len = end.column;
13609 }
13610 if line_len == 0 {
13611 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13612 continue;
13613 }
13614 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13615 if row_indent_size.len >= first_indent.len {
13616 trimmed_selections
13617 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13618 } else {
13619 trimmed_selections.clear();
13620 trimmed_selections.push(start..end);
13621 break;
13622 }
13623 }
13624 }
13625 } else {
13626 trimmed_selections.push(start..end);
13627 }
13628
13629 let is_multiline_trim = trimmed_selections.len() > 1;
13630 let mut selection_len: usize = 0;
13631 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13632
13633 for trimmed_range in trimmed_selections {
13634 if is_first {
13635 is_first = false;
13636 } else if is_multiline_trim || !prev_selection_was_entire_line {
13637 text.push('\n');
13638 if is_multiline_trim {
13639 selection_len += 1;
13640 }
13641 }
13642 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13643 text.push_str(chunk);
13644 selection_len += chunk.len();
13645 }
13646 if add_trailing_newline {
13647 text.push('\n');
13648 selection_len += 1;
13649 }
13650 }
13651
13652 clipboard_selections.push(ClipboardSelection::for_buffer(
13653 selection_len,
13654 is_entire_line,
13655 start..end,
13656 &buffer,
13657 self.project.as_ref(),
13658 cx,
13659 ));
13660 }
13661
13662 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13663 text,
13664 clipboard_selections,
13665 ));
13666 }
13667
13668 pub fn do_paste(
13669 &mut self,
13670 text: &String,
13671 clipboard_selections: Option<Vec<ClipboardSelection>>,
13672 handle_entire_lines: bool,
13673 window: &mut Window,
13674 cx: &mut Context<Self>,
13675 ) {
13676 if self.read_only(cx) {
13677 return;
13678 }
13679
13680 let clipboard_text = Cow::Borrowed(text.as_str());
13681
13682 self.transact(window, cx, |this, window, cx| {
13683 let had_active_edit_prediction = this.has_active_edit_prediction();
13684 let display_map = this.display_snapshot(cx);
13685 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13686 let cursor_offset = this
13687 .selections
13688 .last::<MultiBufferOffset>(&display_map)
13689 .head();
13690
13691 if let Some(mut clipboard_selections) = clipboard_selections {
13692 let all_selections_were_entire_line =
13693 clipboard_selections.iter().all(|s| s.is_entire_line);
13694 let first_selection_indent_column =
13695 clipboard_selections.first().map(|s| s.first_line_indent);
13696 if clipboard_selections.len() != old_selections.len() {
13697 clipboard_selections.drain(..);
13698 }
13699 let mut auto_indent_on_paste = true;
13700
13701 this.buffer.update(cx, |buffer, cx| {
13702 let snapshot = buffer.read(cx);
13703 auto_indent_on_paste = snapshot
13704 .language_settings_at(cursor_offset, cx)
13705 .auto_indent_on_paste;
13706
13707 let mut start_offset = 0;
13708 let mut edits = Vec::new();
13709 let mut original_indent_columns = Vec::new();
13710 for (ix, selection) in old_selections.iter().enumerate() {
13711 let to_insert;
13712 let entire_line;
13713 let original_indent_column;
13714 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13715 let end_offset = start_offset + clipboard_selection.len;
13716 to_insert = &clipboard_text[start_offset..end_offset];
13717 entire_line = clipboard_selection.is_entire_line;
13718 start_offset = if entire_line {
13719 end_offset
13720 } else {
13721 end_offset + 1
13722 };
13723 original_indent_column = Some(clipboard_selection.first_line_indent);
13724 } else {
13725 to_insert = &*clipboard_text;
13726 entire_line = all_selections_were_entire_line;
13727 original_indent_column = first_selection_indent_column
13728 }
13729
13730 let (range, to_insert) =
13731 if selection.is_empty() && handle_entire_lines && entire_line {
13732 // If the corresponding selection was empty when this slice of the
13733 // clipboard text was written, then the entire line containing the
13734 // selection was copied. If this selection is also currently empty,
13735 // then paste the line before the current line of the buffer.
13736 let column = selection.start.to_point(&snapshot).column as usize;
13737 let line_start = selection.start - column;
13738 (line_start..line_start, Cow::Borrowed(to_insert))
13739 } else {
13740 let language = snapshot.language_at(selection.head());
13741 let range = selection.range();
13742 if let Some(language) = language
13743 && language.name() == "Markdown"
13744 {
13745 edit_for_markdown_paste(
13746 &snapshot,
13747 range,
13748 to_insert,
13749 url::Url::parse(to_insert).ok(),
13750 )
13751 } else {
13752 (range, Cow::Borrowed(to_insert))
13753 }
13754 };
13755
13756 edits.push((range, to_insert));
13757 original_indent_columns.push(original_indent_column);
13758 }
13759 drop(snapshot);
13760
13761 buffer.edit(
13762 edits,
13763 if auto_indent_on_paste {
13764 Some(AutoindentMode::Block {
13765 original_indent_columns,
13766 })
13767 } else {
13768 None
13769 },
13770 cx,
13771 );
13772 });
13773
13774 let selections = this
13775 .selections
13776 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13777 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13778 } else {
13779 let url = url::Url::parse(&clipboard_text).ok();
13780
13781 let auto_indent_mode = if !clipboard_text.is_empty() {
13782 Some(AutoindentMode::Block {
13783 original_indent_columns: Vec::new(),
13784 })
13785 } else {
13786 None
13787 };
13788
13789 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13790 let snapshot = buffer.snapshot(cx);
13791
13792 let anchors = old_selections
13793 .iter()
13794 .map(|s| {
13795 let anchor = snapshot.anchor_after(s.head());
13796 s.map(|_| anchor)
13797 })
13798 .collect::<Vec<_>>();
13799
13800 let mut edits = Vec::new();
13801
13802 // When pasting text without metadata (e.g. copied from an
13803 // external editor using multiple cursors) and the number of
13804 // lines matches the number of selections, distribute one
13805 // line per cursor instead of pasting the whole text at each.
13806 let lines: Vec<&str> = clipboard_text.split('\n').collect();
13807 let distribute_lines =
13808 old_selections.len() > 1 && lines.len() == old_selections.len();
13809
13810 for (ix, selection) in old_selections.iter().enumerate() {
13811 let language = snapshot.language_at(selection.head());
13812 let range = selection.range();
13813
13814 let text_for_cursor: &str = if distribute_lines {
13815 lines[ix]
13816 } else {
13817 &clipboard_text
13818 };
13819
13820 let (edit_range, edit_text) = if let Some(language) = language
13821 && language.name() == "Markdown"
13822 {
13823 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
13824 } else {
13825 (range, Cow::Borrowed(text_for_cursor))
13826 };
13827
13828 edits.push((edit_range, edit_text));
13829 }
13830
13831 drop(snapshot);
13832 buffer.edit(edits, auto_indent_mode, cx);
13833
13834 anchors
13835 });
13836
13837 this.change_selections(Default::default(), window, cx, |s| {
13838 s.select_anchors(selection_anchors);
13839 });
13840 }
13841
13842 // 🤔 | .. | show_in_menu |
13843 // | .. | true true
13844 // | had_edit_prediction | false true
13845
13846 let trigger_in_words =
13847 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13848
13849 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13850 });
13851 }
13852
13853 pub fn diff_clipboard_with_selection(
13854 &mut self,
13855 _: &DiffClipboardWithSelection,
13856 window: &mut Window,
13857 cx: &mut Context<Self>,
13858 ) {
13859 let selections = self
13860 .selections
13861 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13862
13863 if selections.is_empty() {
13864 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13865 return;
13866 };
13867
13868 let clipboard_text = match cx.read_from_clipboard() {
13869 Some(item) => match item.entries().first() {
13870 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13871 _ => None,
13872 },
13873 None => None,
13874 };
13875
13876 let Some(clipboard_text) = clipboard_text else {
13877 log::warn!("Clipboard doesn't contain text.");
13878 return;
13879 };
13880
13881 window.dispatch_action(
13882 Box::new(DiffClipboardWithSelectionData {
13883 clipboard_text,
13884 editor: cx.entity(),
13885 }),
13886 cx,
13887 );
13888 }
13889
13890 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13891 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13892 if let Some(item) = cx.read_from_clipboard() {
13893 let entries = item.entries();
13894
13895 match entries.first() {
13896 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13897 // of all the pasted entries.
13898 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13899 .do_paste(
13900 clipboard_string.text(),
13901 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13902 true,
13903 window,
13904 cx,
13905 ),
13906 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13907 }
13908 }
13909 }
13910
13911 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13912 if self.read_only(cx) {
13913 return;
13914 }
13915
13916 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13917
13918 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13919 if let Some((selections, _)) =
13920 self.selection_history.transaction(transaction_id).cloned()
13921 {
13922 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13923 s.select_anchors(selections.to_vec());
13924 });
13925 } else {
13926 log::error!(
13927 "No entry in selection_history found for undo. \
13928 This may correspond to a bug where undo does not update the selection. \
13929 If this is occurring, please add details to \
13930 https://github.com/zed-industries/zed/issues/22692"
13931 );
13932 }
13933 self.request_autoscroll(Autoscroll::fit(), cx);
13934 self.unmark_text(window, cx);
13935 self.refresh_edit_prediction(true, false, window, cx);
13936 cx.emit(EditorEvent::Edited { transaction_id });
13937 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13938 }
13939 }
13940
13941 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13942 if self.read_only(cx) {
13943 return;
13944 }
13945
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13947
13948 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13949 if let Some((_, Some(selections))) =
13950 self.selection_history.transaction(transaction_id).cloned()
13951 {
13952 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13953 s.select_anchors(selections.to_vec());
13954 });
13955 } else {
13956 log::error!(
13957 "No entry in selection_history found for redo. \
13958 This may correspond to a bug where undo does not update the selection. \
13959 If this is occurring, please add details to \
13960 https://github.com/zed-industries/zed/issues/22692"
13961 );
13962 }
13963 self.request_autoscroll(Autoscroll::fit(), cx);
13964 self.unmark_text(window, cx);
13965 self.refresh_edit_prediction(true, false, window, cx);
13966 cx.emit(EditorEvent::Edited { transaction_id });
13967 }
13968 }
13969
13970 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13971 self.buffer
13972 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13973 }
13974
13975 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13976 self.buffer
13977 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13978 }
13979
13980 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13982 self.change_selections(Default::default(), window, cx, |s| {
13983 s.move_with(&mut |map, selection| {
13984 let cursor = if selection.is_empty() {
13985 movement::left(map, selection.start)
13986 } else {
13987 selection.start
13988 };
13989 selection.collapse_to(cursor, SelectionGoal::None);
13990 });
13991 })
13992 }
13993
13994 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 self.change_selections(Default::default(), window, cx, |s| {
13997 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
13998 })
13999 }
14000
14001 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14003 self.change_selections(Default::default(), window, cx, |s| {
14004 s.move_with(&mut |map, selection| {
14005 let cursor = if selection.is_empty() {
14006 movement::right(map, selection.end)
14007 } else {
14008 selection.end
14009 };
14010 selection.collapse_to(cursor, SelectionGoal::None)
14011 });
14012 })
14013 }
14014
14015 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14017 self.change_selections(Default::default(), window, cx, |s| {
14018 s.move_heads_with(&mut |map, head, _| {
14019 (movement::right(map, head), SelectionGoal::None)
14020 });
14021 });
14022 }
14023
14024 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14025 if self.take_rename(true, window, cx).is_some() {
14026 return;
14027 }
14028
14029 if self.mode.is_single_line() {
14030 cx.propagate();
14031 return;
14032 }
14033
14034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14035
14036 let text_layout_details = &self.text_layout_details(window, cx);
14037 let selection_count = self.selections.count();
14038 let first_selection = self.selections.first_anchor();
14039
14040 self.change_selections(Default::default(), window, cx, |s| {
14041 s.move_with(&mut |map, selection| {
14042 if !selection.is_empty() {
14043 selection.goal = SelectionGoal::None;
14044 }
14045 let (cursor, goal) = movement::up(
14046 map,
14047 selection.start,
14048 selection.goal,
14049 false,
14050 text_layout_details,
14051 );
14052 selection.collapse_to(cursor, goal);
14053 });
14054 });
14055
14056 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14057 {
14058 cx.propagate();
14059 }
14060 }
14061
14062 pub fn move_up_by_lines(
14063 &mut self,
14064 action: &MoveUpByLines,
14065 window: &mut Window,
14066 cx: &mut Context<Self>,
14067 ) {
14068 if self.take_rename(true, window, cx).is_some() {
14069 return;
14070 }
14071
14072 if self.mode.is_single_line() {
14073 cx.propagate();
14074 return;
14075 }
14076
14077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14078
14079 let text_layout_details = &self.text_layout_details(window, cx);
14080
14081 self.change_selections(Default::default(), window, cx, |s| {
14082 s.move_with(&mut |map, selection| {
14083 if !selection.is_empty() {
14084 selection.goal = SelectionGoal::None;
14085 }
14086 let (cursor, goal) = movement::up_by_rows(
14087 map,
14088 selection.start,
14089 action.lines,
14090 selection.goal,
14091 false,
14092 text_layout_details,
14093 );
14094 selection.collapse_to(cursor, goal);
14095 });
14096 })
14097 }
14098
14099 pub fn move_down_by_lines(
14100 &mut self,
14101 action: &MoveDownByLines,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) {
14105 if self.take_rename(true, window, cx).is_some() {
14106 return;
14107 }
14108
14109 if self.mode.is_single_line() {
14110 cx.propagate();
14111 return;
14112 }
14113
14114 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14115
14116 let text_layout_details = &self.text_layout_details(window, cx);
14117
14118 self.change_selections(Default::default(), window, cx, |s| {
14119 s.move_with(&mut |map, selection| {
14120 if !selection.is_empty() {
14121 selection.goal = SelectionGoal::None;
14122 }
14123 let (cursor, goal) = movement::down_by_rows(
14124 map,
14125 selection.start,
14126 action.lines,
14127 selection.goal,
14128 false,
14129 text_layout_details,
14130 );
14131 selection.collapse_to(cursor, goal);
14132 });
14133 })
14134 }
14135
14136 pub fn select_down_by_lines(
14137 &mut self,
14138 action: &SelectDownByLines,
14139 window: &mut Window,
14140 cx: &mut Context<Self>,
14141 ) {
14142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14143 let text_layout_details = &self.text_layout_details(window, cx);
14144 self.change_selections(Default::default(), window, cx, |s| {
14145 s.move_heads_with(&mut |map, head, goal| {
14146 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14147 })
14148 })
14149 }
14150
14151 pub fn select_up_by_lines(
14152 &mut self,
14153 action: &SelectUpByLines,
14154 window: &mut Window,
14155 cx: &mut Context<Self>,
14156 ) {
14157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14158 let text_layout_details = &self.text_layout_details(window, cx);
14159 self.change_selections(Default::default(), window, cx, |s| {
14160 s.move_heads_with(&mut |map, head, goal| {
14161 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14162 })
14163 })
14164 }
14165
14166 pub fn select_page_up(
14167 &mut self,
14168 _: &SelectPageUp,
14169 window: &mut Window,
14170 cx: &mut Context<Self>,
14171 ) {
14172 let Some(row_count) = self.visible_row_count() else {
14173 return;
14174 };
14175
14176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14177
14178 let text_layout_details = &self.text_layout_details(window, cx);
14179
14180 self.change_selections(Default::default(), window, cx, |s| {
14181 s.move_heads_with(&mut |map, head, goal| {
14182 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14183 })
14184 })
14185 }
14186
14187 pub fn move_page_up(
14188 &mut self,
14189 action: &MovePageUp,
14190 window: &mut Window,
14191 cx: &mut Context<Self>,
14192 ) {
14193 if self.take_rename(true, window, cx).is_some() {
14194 return;
14195 }
14196
14197 if self
14198 .context_menu
14199 .borrow_mut()
14200 .as_mut()
14201 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14202 .unwrap_or(false)
14203 {
14204 return;
14205 }
14206
14207 if matches!(self.mode, EditorMode::SingleLine) {
14208 cx.propagate();
14209 return;
14210 }
14211
14212 let Some(row_count) = self.visible_row_count() else {
14213 return;
14214 };
14215
14216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14217
14218 let effects = if action.center_cursor {
14219 SelectionEffects::scroll(Autoscroll::center())
14220 } else {
14221 SelectionEffects::default()
14222 };
14223
14224 let text_layout_details = &self.text_layout_details(window, cx);
14225
14226 self.change_selections(effects, window, cx, |s| {
14227 s.move_with(&mut |map, selection| {
14228 if !selection.is_empty() {
14229 selection.goal = SelectionGoal::None;
14230 }
14231 let (cursor, goal) = movement::up_by_rows(
14232 map,
14233 selection.end,
14234 row_count,
14235 selection.goal,
14236 false,
14237 text_layout_details,
14238 );
14239 selection.collapse_to(cursor, goal);
14240 });
14241 });
14242 }
14243
14244 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14246 let text_layout_details = &self.text_layout_details(window, cx);
14247 self.change_selections(Default::default(), window, cx, |s| {
14248 s.move_heads_with(&mut |map, head, goal| {
14249 movement::up(map, head, goal, false, text_layout_details)
14250 })
14251 })
14252 }
14253
14254 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14255 self.take_rename(true, window, cx);
14256
14257 if self.mode.is_single_line() {
14258 cx.propagate();
14259 return;
14260 }
14261
14262 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14263
14264 let text_layout_details = &self.text_layout_details(window, cx);
14265 let selection_count = self.selections.count();
14266 let first_selection = self.selections.first_anchor();
14267
14268 self.change_selections(Default::default(), window, cx, |s| {
14269 s.move_with(&mut |map, selection| {
14270 if !selection.is_empty() {
14271 selection.goal = SelectionGoal::None;
14272 }
14273 let (cursor, goal) = movement::down(
14274 map,
14275 selection.end,
14276 selection.goal,
14277 false,
14278 text_layout_details,
14279 );
14280 selection.collapse_to(cursor, goal);
14281 });
14282 });
14283
14284 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14285 {
14286 cx.propagate();
14287 }
14288 }
14289
14290 pub fn select_page_down(
14291 &mut self,
14292 _: &SelectPageDown,
14293 window: &mut Window,
14294 cx: &mut Context<Self>,
14295 ) {
14296 let Some(row_count) = self.visible_row_count() else {
14297 return;
14298 };
14299
14300 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14301
14302 let text_layout_details = &self.text_layout_details(window, cx);
14303
14304 self.change_selections(Default::default(), window, cx, |s| {
14305 s.move_heads_with(&mut |map, head, goal| {
14306 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14307 })
14308 })
14309 }
14310
14311 pub fn move_page_down(
14312 &mut self,
14313 action: &MovePageDown,
14314 window: &mut Window,
14315 cx: &mut Context<Self>,
14316 ) {
14317 if self.take_rename(true, window, cx).is_some() {
14318 return;
14319 }
14320
14321 if self
14322 .context_menu
14323 .borrow_mut()
14324 .as_mut()
14325 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14326 .unwrap_or(false)
14327 {
14328 return;
14329 }
14330
14331 if matches!(self.mode, EditorMode::SingleLine) {
14332 cx.propagate();
14333 return;
14334 }
14335
14336 let Some(row_count) = self.visible_row_count() else {
14337 return;
14338 };
14339
14340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14341
14342 let effects = if action.center_cursor {
14343 SelectionEffects::scroll(Autoscroll::center())
14344 } else {
14345 SelectionEffects::default()
14346 };
14347
14348 let text_layout_details = &self.text_layout_details(window, cx);
14349 self.change_selections(effects, window, cx, |s| {
14350 s.move_with(&mut |map, selection| {
14351 if !selection.is_empty() {
14352 selection.goal = SelectionGoal::None;
14353 }
14354 let (cursor, goal) = movement::down_by_rows(
14355 map,
14356 selection.end,
14357 row_count,
14358 selection.goal,
14359 false,
14360 text_layout_details,
14361 );
14362 selection.collapse_to(cursor, goal);
14363 });
14364 });
14365 }
14366
14367 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14368 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14369 let text_layout_details = &self.text_layout_details(window, cx);
14370 self.change_selections(Default::default(), window, cx, |s| {
14371 s.move_heads_with(&mut |map, head, goal| {
14372 movement::down(map, head, goal, false, text_layout_details)
14373 })
14374 });
14375 }
14376
14377 pub fn context_menu_first(
14378 &mut self,
14379 _: &ContextMenuFirst,
14380 window: &mut Window,
14381 cx: &mut Context<Self>,
14382 ) {
14383 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14384 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14385 }
14386 }
14387
14388 pub fn context_menu_prev(
14389 &mut self,
14390 _: &ContextMenuPrevious,
14391 window: &mut Window,
14392 cx: &mut Context<Self>,
14393 ) {
14394 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14395 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14396 }
14397 }
14398
14399 pub fn context_menu_next(
14400 &mut self,
14401 _: &ContextMenuNext,
14402 window: &mut Window,
14403 cx: &mut Context<Self>,
14404 ) {
14405 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14406 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14407 }
14408 }
14409
14410 pub fn context_menu_last(
14411 &mut self,
14412 _: &ContextMenuLast,
14413 window: &mut Window,
14414 cx: &mut Context<Self>,
14415 ) {
14416 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14417 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14418 }
14419 }
14420
14421 pub fn signature_help_prev(
14422 &mut self,
14423 _: &SignatureHelpPrevious,
14424 _: &mut Window,
14425 cx: &mut Context<Self>,
14426 ) {
14427 if let Some(popover) = self.signature_help_state.popover_mut() {
14428 if popover.current_signature == 0 {
14429 popover.current_signature = popover.signatures.len() - 1;
14430 } else {
14431 popover.current_signature -= 1;
14432 }
14433 cx.notify();
14434 }
14435 }
14436
14437 pub fn signature_help_next(
14438 &mut self,
14439 _: &SignatureHelpNext,
14440 _: &mut Window,
14441 cx: &mut Context<Self>,
14442 ) {
14443 if let Some(popover) = self.signature_help_state.popover_mut() {
14444 if popover.current_signature + 1 == popover.signatures.len() {
14445 popover.current_signature = 0;
14446 } else {
14447 popover.current_signature += 1;
14448 }
14449 cx.notify();
14450 }
14451 }
14452
14453 pub fn move_to_previous_word_start(
14454 &mut self,
14455 _: &MoveToPreviousWordStart,
14456 window: &mut Window,
14457 cx: &mut Context<Self>,
14458 ) {
14459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14460 self.change_selections(Default::default(), window, cx, |s| {
14461 s.move_cursors_with(&mut |map, head, _| {
14462 (
14463 movement::previous_word_start(map, head),
14464 SelectionGoal::None,
14465 )
14466 });
14467 })
14468 }
14469
14470 pub fn move_to_previous_subword_start(
14471 &mut self,
14472 _: &MoveToPreviousSubwordStart,
14473 window: &mut Window,
14474 cx: &mut Context<Self>,
14475 ) {
14476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14477 self.change_selections(Default::default(), window, cx, |s| {
14478 s.move_cursors_with(&mut |map, head, _| {
14479 (
14480 movement::previous_subword_start(map, head),
14481 SelectionGoal::None,
14482 )
14483 });
14484 })
14485 }
14486
14487 pub fn select_to_previous_word_start(
14488 &mut self,
14489 _: &SelectToPreviousWordStart,
14490 window: &mut Window,
14491 cx: &mut Context<Self>,
14492 ) {
14493 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14494 self.change_selections(Default::default(), window, cx, |s| {
14495 s.move_heads_with(&mut |map, head, _| {
14496 (
14497 movement::previous_word_start(map, head),
14498 SelectionGoal::None,
14499 )
14500 });
14501 })
14502 }
14503
14504 pub fn select_to_previous_subword_start(
14505 &mut self,
14506 _: &SelectToPreviousSubwordStart,
14507 window: &mut Window,
14508 cx: &mut Context<Self>,
14509 ) {
14510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14511 self.change_selections(Default::default(), window, cx, |s| {
14512 s.move_heads_with(&mut |map, head, _| {
14513 (
14514 movement::previous_subword_start(map, head),
14515 SelectionGoal::None,
14516 )
14517 });
14518 })
14519 }
14520
14521 pub fn delete_to_previous_word_start(
14522 &mut self,
14523 action: &DeleteToPreviousWordStart,
14524 window: &mut Window,
14525 cx: &mut Context<Self>,
14526 ) {
14527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14528 self.transact(window, cx, |this, window, cx| {
14529 this.select_autoclose_pair(window, cx);
14530 this.change_selections(Default::default(), window, cx, |s| {
14531 s.move_with(&mut |map, selection| {
14532 if selection.is_empty() {
14533 let mut cursor = if action.ignore_newlines {
14534 movement::previous_word_start(map, selection.head())
14535 } else {
14536 movement::previous_word_start_or_newline(map, selection.head())
14537 };
14538 cursor = movement::adjust_greedy_deletion(
14539 map,
14540 selection.head(),
14541 cursor,
14542 action.ignore_brackets,
14543 );
14544 selection.set_head(cursor, SelectionGoal::None);
14545 }
14546 });
14547 });
14548 this.insert("", window, cx);
14549 });
14550 }
14551
14552 pub fn delete_to_previous_subword_start(
14553 &mut self,
14554 action: &DeleteToPreviousSubwordStart,
14555 window: &mut Window,
14556 cx: &mut Context<Self>,
14557 ) {
14558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14559 self.transact(window, cx, |this, window, cx| {
14560 this.select_autoclose_pair(window, cx);
14561 this.change_selections(Default::default(), window, cx, |s| {
14562 s.move_with(&mut |map, selection| {
14563 if selection.is_empty() {
14564 let mut cursor = if action.ignore_newlines {
14565 movement::previous_subword_start(map, selection.head())
14566 } else {
14567 movement::previous_subword_start_or_newline(map, selection.head())
14568 };
14569 cursor = movement::adjust_greedy_deletion(
14570 map,
14571 selection.head(),
14572 cursor,
14573 action.ignore_brackets,
14574 );
14575 selection.set_head(cursor, SelectionGoal::None);
14576 }
14577 });
14578 });
14579 this.insert("", window, cx);
14580 });
14581 }
14582
14583 pub fn move_to_next_word_end(
14584 &mut self,
14585 _: &MoveToNextWordEnd,
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_cursors_with(&mut |map, head, _| {
14592 (movement::next_word_end(map, head), SelectionGoal::None)
14593 });
14594 })
14595 }
14596
14597 pub fn move_to_next_subword_end(
14598 &mut self,
14599 _: &MoveToNextSubwordEnd,
14600 window: &mut Window,
14601 cx: &mut Context<Self>,
14602 ) {
14603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14604 self.change_selections(Default::default(), window, cx, |s| {
14605 s.move_cursors_with(&mut |map, head, _| {
14606 (movement::next_subword_end(map, head), SelectionGoal::None)
14607 });
14608 })
14609 }
14610
14611 pub fn select_to_next_word_end(
14612 &mut self,
14613 _: &SelectToNextWordEnd,
14614 window: &mut Window,
14615 cx: &mut Context<Self>,
14616 ) {
14617 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14618 self.change_selections(Default::default(), window, cx, |s| {
14619 s.move_heads_with(&mut |map, head, _| {
14620 (movement::next_word_end(map, head), SelectionGoal::None)
14621 });
14622 })
14623 }
14624
14625 pub fn select_to_next_subword_end(
14626 &mut self,
14627 _: &SelectToNextSubwordEnd,
14628 window: &mut Window,
14629 cx: &mut Context<Self>,
14630 ) {
14631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14632 self.change_selections(Default::default(), window, cx, |s| {
14633 s.move_heads_with(&mut |map, head, _| {
14634 (movement::next_subword_end(map, head), SelectionGoal::None)
14635 });
14636 })
14637 }
14638
14639 pub fn delete_to_next_word_end(
14640 &mut self,
14641 action: &DeleteToNextWordEnd,
14642 window: &mut Window,
14643 cx: &mut Context<Self>,
14644 ) {
14645 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14646 self.transact(window, cx, |this, window, cx| {
14647 this.change_selections(Default::default(), window, cx, |s| {
14648 s.move_with(&mut |map, selection| {
14649 if selection.is_empty() {
14650 let mut cursor = if action.ignore_newlines {
14651 movement::next_word_end(map, selection.head())
14652 } else {
14653 movement::next_word_end_or_newline(map, selection.head())
14654 };
14655 cursor = movement::adjust_greedy_deletion(
14656 map,
14657 selection.head(),
14658 cursor,
14659 action.ignore_brackets,
14660 );
14661 selection.set_head(cursor, SelectionGoal::None);
14662 }
14663 });
14664 });
14665 this.insert("", window, cx);
14666 });
14667 }
14668
14669 pub fn delete_to_next_subword_end(
14670 &mut self,
14671 action: &DeleteToNextSubwordEnd,
14672 window: &mut Window,
14673 cx: &mut Context<Self>,
14674 ) {
14675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14676 self.transact(window, cx, |this, window, cx| {
14677 this.change_selections(Default::default(), window, cx, |s| {
14678 s.move_with(&mut |map, selection| {
14679 if selection.is_empty() {
14680 let mut cursor = if action.ignore_newlines {
14681 movement::next_subword_end(map, selection.head())
14682 } else {
14683 movement::next_subword_end_or_newline(map, selection.head())
14684 };
14685 cursor = movement::adjust_greedy_deletion(
14686 map,
14687 selection.head(),
14688 cursor,
14689 action.ignore_brackets,
14690 );
14691 selection.set_head(cursor, SelectionGoal::None);
14692 }
14693 });
14694 });
14695 this.insert("", window, cx);
14696 });
14697 }
14698
14699 pub fn move_to_beginning_of_line(
14700 &mut self,
14701 action: &MoveToBeginningOfLine,
14702 window: &mut Window,
14703 cx: &mut Context<Self>,
14704 ) {
14705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14706 self.change_selections(Default::default(), window, cx, |s| {
14707 s.move_cursors_with(&mut |map, head, _| {
14708 (
14709 movement::indented_line_beginning(
14710 map,
14711 head,
14712 action.stop_at_soft_wraps,
14713 action.stop_at_indent,
14714 ),
14715 SelectionGoal::None,
14716 )
14717 });
14718 })
14719 }
14720
14721 pub fn select_to_beginning_of_line(
14722 &mut self,
14723 action: &SelectToBeginningOfLine,
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 (
14731 movement::indented_line_beginning(
14732 map,
14733 head,
14734 action.stop_at_soft_wraps,
14735 action.stop_at_indent,
14736 ),
14737 SelectionGoal::None,
14738 )
14739 });
14740 });
14741 }
14742
14743 pub fn delete_to_beginning_of_line(
14744 &mut self,
14745 action: &DeleteToBeginningOfLine,
14746 window: &mut Window,
14747 cx: &mut Context<Self>,
14748 ) {
14749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14750 self.transact(window, cx, |this, window, cx| {
14751 this.change_selections(Default::default(), window, cx, |s| {
14752 s.move_with(&mut |_, selection| {
14753 selection.reversed = true;
14754 });
14755 });
14756
14757 this.select_to_beginning_of_line(
14758 &SelectToBeginningOfLine {
14759 stop_at_soft_wraps: false,
14760 stop_at_indent: action.stop_at_indent,
14761 },
14762 window,
14763 cx,
14764 );
14765 this.backspace(&Backspace, window, cx);
14766 });
14767 }
14768
14769 pub fn move_to_end_of_line(
14770 &mut self,
14771 action: &MoveToEndOfLine,
14772 window: &mut Window,
14773 cx: &mut Context<Self>,
14774 ) {
14775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14776 self.change_selections(Default::default(), window, cx, |s| {
14777 s.move_cursors_with(&mut |map, head, _| {
14778 (
14779 movement::line_end(map, head, action.stop_at_soft_wraps),
14780 SelectionGoal::None,
14781 )
14782 });
14783 })
14784 }
14785
14786 pub fn select_to_end_of_line(
14787 &mut self,
14788 action: &SelectToEndOfLine,
14789 window: &mut Window,
14790 cx: &mut Context<Self>,
14791 ) {
14792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14793 self.change_selections(Default::default(), window, cx, |s| {
14794 s.move_heads_with(&mut |map, head, _| {
14795 (
14796 movement::line_end(map, head, action.stop_at_soft_wraps),
14797 SelectionGoal::None,
14798 )
14799 });
14800 })
14801 }
14802
14803 pub fn delete_to_end_of_line(
14804 &mut self,
14805 _: &DeleteToEndOfLine,
14806 window: &mut Window,
14807 cx: &mut Context<Self>,
14808 ) {
14809 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14810 self.transact(window, cx, |this, window, cx| {
14811 this.select_to_end_of_line(
14812 &SelectToEndOfLine {
14813 stop_at_soft_wraps: false,
14814 },
14815 window,
14816 cx,
14817 );
14818 this.delete(&Delete, window, cx);
14819 });
14820 }
14821
14822 pub fn cut_to_end_of_line(
14823 &mut self,
14824 action: &CutToEndOfLine,
14825 window: &mut Window,
14826 cx: &mut Context<Self>,
14827 ) {
14828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14829 self.transact(window, cx, |this, window, cx| {
14830 this.select_to_end_of_line(
14831 &SelectToEndOfLine {
14832 stop_at_soft_wraps: false,
14833 },
14834 window,
14835 cx,
14836 );
14837 if !action.stop_at_newlines {
14838 this.change_selections(Default::default(), window, cx, |s| {
14839 s.move_with(&mut |_, sel| {
14840 if sel.is_empty() {
14841 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14842 }
14843 });
14844 });
14845 }
14846 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14847 let item = this.cut_common(false, window, cx);
14848 cx.write_to_clipboard(item);
14849 });
14850 }
14851
14852 pub fn move_to_start_of_paragraph(
14853 &mut self,
14854 _: &MoveToStartOfParagraph,
14855 window: &mut Window,
14856 cx: &mut Context<Self>,
14857 ) {
14858 if matches!(self.mode, EditorMode::SingleLine) {
14859 cx.propagate();
14860 return;
14861 }
14862 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14863 self.change_selections(Default::default(), window, cx, |s| {
14864 s.move_with(&mut |map, selection| {
14865 selection.collapse_to(
14866 movement::start_of_paragraph(map, selection.head(), 1),
14867 SelectionGoal::None,
14868 )
14869 });
14870 })
14871 }
14872
14873 pub fn move_to_end_of_paragraph(
14874 &mut self,
14875 _: &MoveToEndOfParagraph,
14876 window: &mut Window,
14877 cx: &mut Context<Self>,
14878 ) {
14879 if matches!(self.mode, EditorMode::SingleLine) {
14880 cx.propagate();
14881 return;
14882 }
14883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14884 self.change_selections(Default::default(), window, cx, |s| {
14885 s.move_with(&mut |map, selection| {
14886 selection.collapse_to(
14887 movement::end_of_paragraph(map, selection.head(), 1),
14888 SelectionGoal::None,
14889 )
14890 });
14891 })
14892 }
14893
14894 pub fn select_to_start_of_paragraph(
14895 &mut self,
14896 _: &SelectToStartOfParagraph,
14897 window: &mut Window,
14898 cx: &mut Context<Self>,
14899 ) {
14900 if matches!(self.mode, EditorMode::SingleLine) {
14901 cx.propagate();
14902 return;
14903 }
14904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14905 self.change_selections(Default::default(), window, cx, |s| {
14906 s.move_heads_with(&mut |map, head, _| {
14907 (
14908 movement::start_of_paragraph(map, head, 1),
14909 SelectionGoal::None,
14910 )
14911 });
14912 })
14913 }
14914
14915 pub fn select_to_end_of_paragraph(
14916 &mut self,
14917 _: &SelectToEndOfParagraph,
14918 window: &mut Window,
14919 cx: &mut Context<Self>,
14920 ) {
14921 if matches!(self.mode, EditorMode::SingleLine) {
14922 cx.propagate();
14923 return;
14924 }
14925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14926 self.change_selections(Default::default(), window, cx, |s| {
14927 s.move_heads_with(&mut |map, head, _| {
14928 (
14929 movement::end_of_paragraph(map, head, 1),
14930 SelectionGoal::None,
14931 )
14932 });
14933 })
14934 }
14935
14936 pub fn move_to_start_of_excerpt(
14937 &mut self,
14938 _: &MoveToStartOfExcerpt,
14939 window: &mut Window,
14940 cx: &mut Context<Self>,
14941 ) {
14942 if matches!(self.mode, EditorMode::SingleLine) {
14943 cx.propagate();
14944 return;
14945 }
14946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14947 self.change_selections(Default::default(), window, cx, |s| {
14948 s.move_with(&mut |map, selection| {
14949 selection.collapse_to(
14950 movement::start_of_excerpt(
14951 map,
14952 selection.head(),
14953 workspace::searchable::Direction::Prev,
14954 ),
14955 SelectionGoal::None,
14956 )
14957 });
14958 })
14959 }
14960
14961 pub fn move_to_start_of_next_excerpt(
14962 &mut self,
14963 _: &MoveToStartOfNextExcerpt,
14964 window: &mut Window,
14965 cx: &mut Context<Self>,
14966 ) {
14967 if matches!(self.mode, EditorMode::SingleLine) {
14968 cx.propagate();
14969 return;
14970 }
14971
14972 self.change_selections(Default::default(), window, cx, |s| {
14973 s.move_with(&mut |map, selection| {
14974 selection.collapse_to(
14975 movement::start_of_excerpt(
14976 map,
14977 selection.head(),
14978 workspace::searchable::Direction::Next,
14979 ),
14980 SelectionGoal::None,
14981 )
14982 });
14983 })
14984 }
14985
14986 pub fn move_to_end_of_excerpt(
14987 &mut self,
14988 _: &MoveToEndOfExcerpt,
14989 window: &mut Window,
14990 cx: &mut Context<Self>,
14991 ) {
14992 if matches!(self.mode, EditorMode::SingleLine) {
14993 cx.propagate();
14994 return;
14995 }
14996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14997 self.change_selections(Default::default(), window, cx, |s| {
14998 s.move_with(&mut |map, selection| {
14999 selection.collapse_to(
15000 movement::end_of_excerpt(
15001 map,
15002 selection.head(),
15003 workspace::searchable::Direction::Next,
15004 ),
15005 SelectionGoal::None,
15006 )
15007 });
15008 })
15009 }
15010
15011 pub fn move_to_end_of_previous_excerpt(
15012 &mut self,
15013 _: &MoveToEndOfPreviousExcerpt,
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_with(&mut |map, selection| {
15024 selection.collapse_to(
15025 movement::end_of_excerpt(
15026 map,
15027 selection.head(),
15028 workspace::searchable::Direction::Prev,
15029 ),
15030 SelectionGoal::None,
15031 )
15032 });
15033 })
15034 }
15035
15036 pub fn select_to_start_of_excerpt(
15037 &mut self,
15038 _: &SelectToStartOfExcerpt,
15039 window: &mut Window,
15040 cx: &mut Context<Self>,
15041 ) {
15042 if matches!(self.mode, EditorMode::SingleLine) {
15043 cx.propagate();
15044 return;
15045 }
15046 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15047 self.change_selections(Default::default(), window, cx, |s| {
15048 s.move_heads_with(&mut |map, head, _| {
15049 (
15050 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15051 SelectionGoal::None,
15052 )
15053 });
15054 })
15055 }
15056
15057 pub fn select_to_start_of_next_excerpt(
15058 &mut self,
15059 _: &SelectToStartOfNextExcerpt,
15060 window: &mut Window,
15061 cx: &mut Context<Self>,
15062 ) {
15063 if matches!(self.mode, EditorMode::SingleLine) {
15064 cx.propagate();
15065 return;
15066 }
15067 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15068 self.change_selections(Default::default(), window, cx, |s| {
15069 s.move_heads_with(&mut |map, head, _| {
15070 (
15071 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15072 SelectionGoal::None,
15073 )
15074 });
15075 })
15076 }
15077
15078 pub fn select_to_end_of_excerpt(
15079 &mut self,
15080 _: &SelectToEndOfExcerpt,
15081 window: &mut Window,
15082 cx: &mut Context<Self>,
15083 ) {
15084 if matches!(self.mode, EditorMode::SingleLine) {
15085 cx.propagate();
15086 return;
15087 }
15088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15089 self.change_selections(Default::default(), window, cx, |s| {
15090 s.move_heads_with(&mut |map, head, _| {
15091 (
15092 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15093 SelectionGoal::None,
15094 )
15095 });
15096 })
15097 }
15098
15099 pub fn select_to_end_of_previous_excerpt(
15100 &mut self,
15101 _: &SelectToEndOfPreviousExcerpt,
15102 window: &mut Window,
15103 cx: &mut Context<Self>,
15104 ) {
15105 if matches!(self.mode, EditorMode::SingleLine) {
15106 cx.propagate();
15107 return;
15108 }
15109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15110 self.change_selections(Default::default(), window, cx, |s| {
15111 s.move_heads_with(&mut |map, head, _| {
15112 (
15113 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15114 SelectionGoal::None,
15115 )
15116 });
15117 })
15118 }
15119
15120 pub fn move_to_beginning(
15121 &mut self,
15122 _: &MoveToBeginning,
15123 window: &mut Window,
15124 cx: &mut Context<Self>,
15125 ) {
15126 if matches!(self.mode, EditorMode::SingleLine) {
15127 cx.propagate();
15128 return;
15129 }
15130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15131 self.change_selections(Default::default(), window, cx, |s| {
15132 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15133 });
15134 }
15135
15136 pub fn select_to_beginning(
15137 &mut self,
15138 _: &SelectToBeginning,
15139 window: &mut Window,
15140 cx: &mut Context<Self>,
15141 ) {
15142 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15143 selection.set_head(Point::zero(), SelectionGoal::None);
15144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15145 self.change_selections(Default::default(), window, cx, |s| {
15146 s.select(vec![selection]);
15147 });
15148 }
15149
15150 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15151 if matches!(self.mode, EditorMode::SingleLine) {
15152 cx.propagate();
15153 return;
15154 }
15155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15156 let cursor = self.buffer.read(cx).read(cx).len();
15157 self.change_selections(Default::default(), window, cx, |s| {
15158 s.select_ranges(vec![cursor..cursor])
15159 });
15160 }
15161
15162 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15163 self.nav_history = nav_history;
15164 }
15165
15166 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15167 self.nav_history.as_ref()
15168 }
15169
15170 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15171 self.push_to_nav_history(
15172 self.selections.newest_anchor().head(),
15173 None,
15174 false,
15175 true,
15176 cx,
15177 );
15178 }
15179
15180 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15181 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15182 let buffer = self.buffer.read(cx).read(cx);
15183 let cursor_position = cursor_anchor.to_point(&buffer);
15184 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15185 let scroll_top_row = scroll_anchor.top_row(&buffer);
15186 drop(buffer);
15187
15188 NavigationData {
15189 cursor_anchor,
15190 cursor_position,
15191 scroll_anchor,
15192 scroll_top_row,
15193 }
15194 }
15195
15196 fn navigation_entry(
15197 &self,
15198 cursor_anchor: Anchor,
15199 cx: &mut Context<Self>,
15200 ) -> Option<NavigationEntry> {
15201 let Some(history) = self.nav_history.clone() else {
15202 return None;
15203 };
15204 let data = self.navigation_data(cursor_anchor, cx);
15205 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15206 }
15207
15208 fn push_to_nav_history(
15209 &mut self,
15210 cursor_anchor: Anchor,
15211 new_position: Option<Point>,
15212 is_deactivate: bool,
15213 always: bool,
15214 cx: &mut Context<Self>,
15215 ) {
15216 let data = self.navigation_data(cursor_anchor, cx);
15217 if let Some(nav_history) = self.nav_history.as_mut() {
15218 if let Some(new_position) = new_position {
15219 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15220 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15221 return;
15222 }
15223 }
15224
15225 nav_history.push(Some(data), cx);
15226 cx.emit(EditorEvent::PushedToNavHistory {
15227 anchor: cursor_anchor,
15228 is_deactivate,
15229 })
15230 }
15231 }
15232
15233 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15234 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15235 let buffer = self.buffer.read(cx).snapshot(cx);
15236 let mut selection = self
15237 .selections
15238 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15239 selection.set_head(buffer.len(), SelectionGoal::None);
15240 self.change_selections(Default::default(), window, cx, |s| {
15241 s.select(vec![selection]);
15242 });
15243 }
15244
15245 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15246 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15247 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15248 s.select_ranges([Anchor::min()..Anchor::max()]);
15249 });
15250 }
15251
15252 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15254 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15255 let mut selections = self.selections.all::<Point>(&display_map);
15256 let max_point = display_map.buffer_snapshot().max_point();
15257 for selection in &mut selections {
15258 let rows = selection.spanned_rows(true, &display_map);
15259 selection.start = Point::new(rows.start.0, 0);
15260 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15261 selection.reversed = false;
15262 }
15263 self.change_selections(Default::default(), window, cx, |s| {
15264 s.select(selections);
15265 });
15266 }
15267
15268 pub fn split_selection_into_lines(
15269 &mut self,
15270 action: &SplitSelectionIntoLines,
15271 window: &mut Window,
15272 cx: &mut Context<Self>,
15273 ) {
15274 let selections = self
15275 .selections
15276 .all::<Point>(&self.display_snapshot(cx))
15277 .into_iter()
15278 .map(|selection| selection.start..selection.end)
15279 .collect::<Vec<_>>();
15280 self.unfold_ranges(&selections, true, false, cx);
15281
15282 let mut new_selection_ranges = Vec::new();
15283 {
15284 let buffer = self.buffer.read(cx).read(cx);
15285 for selection in selections {
15286 for row in selection.start.row..selection.end.row {
15287 let line_start = Point::new(row, 0);
15288 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15289
15290 if action.keep_selections {
15291 // Keep the selection range for each line
15292 let selection_start = if row == selection.start.row {
15293 selection.start
15294 } else {
15295 line_start
15296 };
15297 new_selection_ranges.push(selection_start..line_end);
15298 } else {
15299 // Collapse to cursor at end of line
15300 new_selection_ranges.push(line_end..line_end);
15301 }
15302 }
15303
15304 let is_multiline_selection = selection.start.row != selection.end.row;
15305 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15306 // so this action feels more ergonomic when paired with other selection operations
15307 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15308 if !should_skip_last {
15309 if action.keep_selections {
15310 if is_multiline_selection {
15311 let line_start = Point::new(selection.end.row, 0);
15312 new_selection_ranges.push(line_start..selection.end);
15313 } else {
15314 new_selection_ranges.push(selection.start..selection.end);
15315 }
15316 } else {
15317 new_selection_ranges.push(selection.end..selection.end);
15318 }
15319 }
15320 }
15321 }
15322 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15323 s.select_ranges(new_selection_ranges);
15324 });
15325 }
15326
15327 pub fn add_selection_above(
15328 &mut self,
15329 action: &AddSelectionAbove,
15330 window: &mut Window,
15331 cx: &mut Context<Self>,
15332 ) {
15333 self.add_selection(true, action.skip_soft_wrap, window, cx);
15334 }
15335
15336 pub fn add_selection_below(
15337 &mut self,
15338 action: &AddSelectionBelow,
15339 window: &mut Window,
15340 cx: &mut Context<Self>,
15341 ) {
15342 self.add_selection(false, action.skip_soft_wrap, window, cx);
15343 }
15344
15345 fn add_selection(
15346 &mut self,
15347 above: bool,
15348 skip_soft_wrap: bool,
15349 window: &mut Window,
15350 cx: &mut Context<Self>,
15351 ) {
15352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15353
15354 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15355 let all_selections = self.selections.all::<Point>(&display_map);
15356 let text_layout_details = self.text_layout_details(window, cx);
15357
15358 let (mut columnar_selections, new_selections_to_columnarize) = {
15359 if let Some(state) = self.add_selections_state.as_ref() {
15360 let columnar_selection_ids: HashSet<_> = state
15361 .groups
15362 .iter()
15363 .flat_map(|group| group.stack.iter())
15364 .copied()
15365 .collect();
15366
15367 all_selections
15368 .into_iter()
15369 .partition(|s| columnar_selection_ids.contains(&s.id))
15370 } else {
15371 (Vec::new(), all_selections)
15372 }
15373 };
15374
15375 let mut state = self
15376 .add_selections_state
15377 .take()
15378 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15379
15380 for selection in new_selections_to_columnarize {
15381 let range = selection.display_range(&display_map).sorted();
15382 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15383 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15384 let positions = start_x.min(end_x)..start_x.max(end_x);
15385 let mut stack = Vec::new();
15386 for row in range.start.row().0..=range.end.row().0 {
15387 if let Some(selection) = self.selections.build_columnar_selection(
15388 &display_map,
15389 DisplayRow(row),
15390 &positions,
15391 selection.reversed,
15392 &text_layout_details,
15393 ) {
15394 stack.push(selection.id);
15395 columnar_selections.push(selection);
15396 }
15397 }
15398 if !stack.is_empty() {
15399 if above {
15400 stack.reverse();
15401 }
15402 state.groups.push(AddSelectionsGroup { above, stack });
15403 }
15404 }
15405
15406 let mut final_selections = Vec::new();
15407 let end_row = if above {
15408 DisplayRow(0)
15409 } else {
15410 display_map.max_point().row()
15411 };
15412
15413 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15414 // positions to place new selections, so we need to keep track of the
15415 // column range of the oldest selection in each group, because
15416 // intermediate selections may have been clamped to shorter lines.
15417 // selections may have been clamped to shorter lines.
15418 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15419 let mut map = HashMap::default();
15420 for group in state.groups.iter() {
15421 if let Some(oldest_id) = group.stack.first() {
15422 if let Some(oldest_selection) =
15423 columnar_selections.iter().find(|s| s.id == *oldest_id)
15424 {
15425 let start_col = oldest_selection.start.column;
15426 let end_col = oldest_selection.end.column;
15427 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15428 for id in &group.stack {
15429 map.insert(*id, goal_columns.clone());
15430 }
15431 }
15432 }
15433 }
15434 map
15435 } else {
15436 HashMap::default()
15437 };
15438
15439 let mut last_added_item_per_group = HashMap::default();
15440 for group in state.groups.iter_mut() {
15441 if let Some(last_id) = group.stack.last() {
15442 last_added_item_per_group.insert(*last_id, group);
15443 }
15444 }
15445
15446 for selection in columnar_selections {
15447 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15448 if above == group.above {
15449 let range = selection.display_range(&display_map).sorted();
15450 debug_assert_eq!(range.start.row(), range.end.row());
15451 let row = range.start.row();
15452 let positions =
15453 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15454 Pixels::from(start)..Pixels::from(end)
15455 } else {
15456 let start_x =
15457 display_map.x_for_display_point(range.start, &text_layout_details);
15458 let end_x =
15459 display_map.x_for_display_point(range.end, &text_layout_details);
15460 start_x.min(end_x)..start_x.max(end_x)
15461 };
15462
15463 let maybe_new_selection = if skip_soft_wrap {
15464 let goal_columns = goal_columns_by_selection_id
15465 .remove(&selection.id)
15466 .unwrap_or_else(|| {
15467 let start_col = selection.start.column;
15468 let end_col = selection.end.column;
15469 start_col.min(end_col)..start_col.max(end_col)
15470 });
15471 self.selections.find_next_columnar_selection_by_buffer_row(
15472 &display_map,
15473 row,
15474 end_row,
15475 above,
15476 &goal_columns,
15477 selection.reversed,
15478 &text_layout_details,
15479 )
15480 } else {
15481 self.selections.find_next_columnar_selection_by_display_row(
15482 &display_map,
15483 row,
15484 end_row,
15485 above,
15486 &positions,
15487 selection.reversed,
15488 &text_layout_details,
15489 )
15490 };
15491
15492 if let Some(new_selection) = maybe_new_selection {
15493 group.stack.push(new_selection.id);
15494 if above {
15495 final_selections.push(new_selection);
15496 final_selections.push(selection);
15497 } else {
15498 final_selections.push(selection);
15499 final_selections.push(new_selection);
15500 }
15501 } else {
15502 final_selections.push(selection);
15503 }
15504 } else {
15505 group.stack.pop();
15506 }
15507 } else {
15508 final_selections.push(selection);
15509 }
15510 }
15511
15512 self.change_selections(Default::default(), window, cx, |s| {
15513 s.select(final_selections);
15514 });
15515
15516 let final_selection_ids: HashSet<_> = self
15517 .selections
15518 .all::<Point>(&display_map)
15519 .iter()
15520 .map(|s| s.id)
15521 .collect();
15522 state.groups.retain_mut(|group| {
15523 // selections might get merged above so we remove invalid items from stacks
15524 group.stack.retain(|id| final_selection_ids.contains(id));
15525
15526 // single selection in stack can be treated as initial state
15527 group.stack.len() > 1
15528 });
15529
15530 if !state.groups.is_empty() {
15531 self.add_selections_state = Some(state);
15532 }
15533 }
15534
15535 pub fn insert_snippet_at_selections(
15536 &mut self,
15537 action: &InsertSnippet,
15538 window: &mut Window,
15539 cx: &mut Context<Self>,
15540 ) {
15541 self.try_insert_snippet_at_selections(action, window, cx)
15542 .log_err();
15543 }
15544
15545 fn try_insert_snippet_at_selections(
15546 &mut self,
15547 action: &InsertSnippet,
15548 window: &mut Window,
15549 cx: &mut Context<Self>,
15550 ) -> Result<()> {
15551 let insertion_ranges = self
15552 .selections
15553 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15554 .into_iter()
15555 .map(|selection| selection.range())
15556 .collect_vec();
15557
15558 let snippet = if let Some(snippet_body) = &action.snippet {
15559 if action.language.is_none() && action.name.is_none() {
15560 Snippet::parse(snippet_body)?
15561 } else {
15562 bail!("`snippet` is mutually exclusive with `language` and `name`")
15563 }
15564 } else if let Some(name) = &action.name {
15565 let project = self.project().context("no project")?;
15566 let snippet_store = project.read(cx).snippets().read(cx);
15567 let snippet = snippet_store
15568 .snippets_for(action.language.clone(), cx)
15569 .into_iter()
15570 .find(|snippet| snippet.name == *name)
15571 .context("snippet not found")?;
15572 Snippet::parse(&snippet.body)?
15573 } else {
15574 // todo(andrew): open modal to select snippet
15575 bail!("`name` or `snippet` is required")
15576 };
15577
15578 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15579 }
15580
15581 fn select_match_ranges(
15582 &mut self,
15583 range: Range<MultiBufferOffset>,
15584 reversed: bool,
15585 replace_newest: bool,
15586 auto_scroll: Option<Autoscroll>,
15587 window: &mut Window,
15588 cx: &mut Context<Editor>,
15589 ) {
15590 self.unfold_ranges(
15591 std::slice::from_ref(&range),
15592 false,
15593 auto_scroll.is_some(),
15594 cx,
15595 );
15596 let effects = if let Some(scroll) = auto_scroll {
15597 SelectionEffects::scroll(scroll)
15598 } else {
15599 SelectionEffects::no_scroll()
15600 };
15601 self.change_selections(effects, window, cx, |s| {
15602 if replace_newest {
15603 s.delete(s.newest_anchor().id);
15604 }
15605 if reversed {
15606 s.insert_range(range.end..range.start);
15607 } else {
15608 s.insert_range(range);
15609 }
15610 });
15611 }
15612
15613 pub fn select_next_match_internal(
15614 &mut self,
15615 display_map: &DisplaySnapshot,
15616 replace_newest: bool,
15617 autoscroll: Option<Autoscroll>,
15618 window: &mut Window,
15619 cx: &mut Context<Self>,
15620 ) -> Result<()> {
15621 let buffer = display_map.buffer_snapshot();
15622 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15623 if let Some(mut select_next_state) = self.select_next_state.take() {
15624 let query = &select_next_state.query;
15625 if !select_next_state.done {
15626 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15627 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15628 let mut next_selected_range = None;
15629
15630 let bytes_after_last_selection =
15631 buffer.bytes_in_range(last_selection.end..buffer.len());
15632 let bytes_before_first_selection =
15633 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15634 let query_matches = query
15635 .stream_find_iter(bytes_after_last_selection)
15636 .map(|result| (last_selection.end, result))
15637 .chain(
15638 query
15639 .stream_find_iter(bytes_before_first_selection)
15640 .map(|result| (MultiBufferOffset(0), result)),
15641 );
15642
15643 for (start_offset, query_match) in query_matches {
15644 let query_match = query_match.unwrap(); // can only fail due to I/O
15645 let offset_range =
15646 start_offset + query_match.start()..start_offset + query_match.end();
15647
15648 if !select_next_state.wordwise
15649 || (!buffer.is_inside_word(offset_range.start, None)
15650 && !buffer.is_inside_word(offset_range.end, None))
15651 {
15652 let idx = selections
15653 .partition_point(|selection| selection.end <= offset_range.start);
15654 let overlaps = selections
15655 .get(idx)
15656 .map_or(false, |selection| selection.start < offset_range.end);
15657
15658 if !overlaps {
15659 next_selected_range = Some(offset_range);
15660 break;
15661 }
15662 }
15663 }
15664
15665 if let Some(next_selected_range) = next_selected_range {
15666 self.select_match_ranges(
15667 next_selected_range,
15668 last_selection.reversed,
15669 replace_newest,
15670 autoscroll,
15671 window,
15672 cx,
15673 );
15674 } else {
15675 select_next_state.done = true;
15676 }
15677 }
15678
15679 self.select_next_state = Some(select_next_state);
15680 } else {
15681 let mut only_carets = true;
15682 let mut same_text_selected = true;
15683 let mut selected_text = None;
15684
15685 let mut selections_iter = selections.iter().peekable();
15686 while let Some(selection) = selections_iter.next() {
15687 if selection.start != selection.end {
15688 only_carets = false;
15689 }
15690
15691 if same_text_selected {
15692 if selected_text.is_none() {
15693 selected_text =
15694 Some(buffer.text_for_range(selection.range()).collect::<String>());
15695 }
15696
15697 if let Some(next_selection) = selections_iter.peek() {
15698 if next_selection.len() == selection.len() {
15699 let next_selected_text = buffer
15700 .text_for_range(next_selection.range())
15701 .collect::<String>();
15702 if Some(next_selected_text) != selected_text {
15703 same_text_selected = false;
15704 selected_text = None;
15705 }
15706 } else {
15707 same_text_selected = false;
15708 selected_text = None;
15709 }
15710 }
15711 }
15712 }
15713
15714 if only_carets {
15715 for selection in &mut selections {
15716 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15717 selection.start = word_range.start;
15718 selection.end = word_range.end;
15719 selection.goal = SelectionGoal::None;
15720 selection.reversed = false;
15721 self.select_match_ranges(
15722 selection.start..selection.end,
15723 selection.reversed,
15724 replace_newest,
15725 autoscroll,
15726 window,
15727 cx,
15728 );
15729 }
15730
15731 if selections.len() == 1 {
15732 let selection = selections
15733 .last()
15734 .expect("ensured that there's only one selection");
15735 let query = buffer
15736 .text_for_range(selection.start..selection.end)
15737 .collect::<String>();
15738 let is_empty = query.is_empty();
15739 let select_state = SelectNextState {
15740 query: self.build_query(&[query], cx)?,
15741 wordwise: true,
15742 done: is_empty,
15743 };
15744 self.select_next_state = Some(select_state);
15745 } else {
15746 self.select_next_state = None;
15747 }
15748 } else if let Some(selected_text) = selected_text {
15749 self.select_next_state = Some(SelectNextState {
15750 query: self.build_query(&[selected_text], cx)?,
15751 wordwise: false,
15752 done: false,
15753 });
15754 self.select_next_match_internal(
15755 display_map,
15756 replace_newest,
15757 autoscroll,
15758 window,
15759 cx,
15760 )?;
15761 }
15762 }
15763 Ok(())
15764 }
15765
15766 pub fn select_all_matches(
15767 &mut self,
15768 _action: &SelectAllMatches,
15769 window: &mut Window,
15770 cx: &mut Context<Self>,
15771 ) -> Result<()> {
15772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15773
15774 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15775
15776 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15777 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15778 else {
15779 return Ok(());
15780 };
15781
15782 let mut new_selections = Vec::new();
15783
15784 let reversed = self
15785 .selections
15786 .oldest::<MultiBufferOffset>(&display_map)
15787 .reversed;
15788 let buffer = display_map.buffer_snapshot();
15789 let query_matches = select_next_state
15790 .query
15791 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15792
15793 for query_match in query_matches.into_iter() {
15794 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15795 let offset_range = if reversed {
15796 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15797 } else {
15798 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15799 };
15800
15801 if !select_next_state.wordwise
15802 || (!buffer.is_inside_word(offset_range.start, None)
15803 && !buffer.is_inside_word(offset_range.end, None))
15804 {
15805 new_selections.push(offset_range.start..offset_range.end);
15806 }
15807 }
15808
15809 select_next_state.done = true;
15810
15811 if new_selections.is_empty() {
15812 log::error!("bug: new_selections is empty in select_all_matches");
15813 return Ok(());
15814 }
15815
15816 self.unfold_ranges(&new_selections, false, false, cx);
15817 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15818 selections.select_ranges(new_selections)
15819 });
15820
15821 Ok(())
15822 }
15823
15824 pub fn select_next(
15825 &mut self,
15826 action: &SelectNext,
15827 window: &mut Window,
15828 cx: &mut Context<Self>,
15829 ) -> Result<()> {
15830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15832 self.select_next_match_internal(
15833 &display_map,
15834 action.replace_newest,
15835 Some(Autoscroll::newest()),
15836 window,
15837 cx,
15838 )
15839 }
15840
15841 pub fn select_previous(
15842 &mut self,
15843 action: &SelectPrevious,
15844 window: &mut Window,
15845 cx: &mut Context<Self>,
15846 ) -> Result<()> {
15847 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15848 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15849 let buffer = display_map.buffer_snapshot();
15850 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15851 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15852 let query = &select_prev_state.query;
15853 if !select_prev_state.done {
15854 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15855 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15856 let mut next_selected_range = None;
15857 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15858 let bytes_before_last_selection =
15859 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15860 let bytes_after_first_selection =
15861 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15862 let query_matches = query
15863 .stream_find_iter(bytes_before_last_selection)
15864 .map(|result| (last_selection.start, result))
15865 .chain(
15866 query
15867 .stream_find_iter(bytes_after_first_selection)
15868 .map(|result| (buffer.len(), result)),
15869 );
15870 for (end_offset, query_match) in query_matches {
15871 let query_match = query_match.unwrap(); // can only fail due to I/O
15872 let offset_range =
15873 end_offset - query_match.end()..end_offset - query_match.start();
15874
15875 if !select_prev_state.wordwise
15876 || (!buffer.is_inside_word(offset_range.start, None)
15877 && !buffer.is_inside_word(offset_range.end, None))
15878 {
15879 next_selected_range = Some(offset_range);
15880 break;
15881 }
15882 }
15883
15884 if let Some(next_selected_range) = next_selected_range {
15885 self.select_match_ranges(
15886 next_selected_range,
15887 last_selection.reversed,
15888 action.replace_newest,
15889 Some(Autoscroll::newest()),
15890 window,
15891 cx,
15892 );
15893 } else {
15894 select_prev_state.done = true;
15895 }
15896 }
15897
15898 self.select_prev_state = Some(select_prev_state);
15899 } else {
15900 let mut only_carets = true;
15901 let mut same_text_selected = true;
15902 let mut selected_text = None;
15903
15904 let mut selections_iter = selections.iter().peekable();
15905 while let Some(selection) = selections_iter.next() {
15906 if selection.start != selection.end {
15907 only_carets = false;
15908 }
15909
15910 if same_text_selected {
15911 if selected_text.is_none() {
15912 selected_text =
15913 Some(buffer.text_for_range(selection.range()).collect::<String>());
15914 }
15915
15916 if let Some(next_selection) = selections_iter.peek() {
15917 if next_selection.len() == selection.len() {
15918 let next_selected_text = buffer
15919 .text_for_range(next_selection.range())
15920 .collect::<String>();
15921 if Some(next_selected_text) != selected_text {
15922 same_text_selected = false;
15923 selected_text = None;
15924 }
15925 } else {
15926 same_text_selected = false;
15927 selected_text = None;
15928 }
15929 }
15930 }
15931 }
15932
15933 if only_carets {
15934 for selection in &mut selections {
15935 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15936 selection.start = word_range.start;
15937 selection.end = word_range.end;
15938 selection.goal = SelectionGoal::None;
15939 selection.reversed = false;
15940 self.select_match_ranges(
15941 selection.start..selection.end,
15942 selection.reversed,
15943 action.replace_newest,
15944 Some(Autoscroll::newest()),
15945 window,
15946 cx,
15947 );
15948 }
15949 if selections.len() == 1 {
15950 let selection = selections
15951 .last()
15952 .expect("ensured that there's only one selection");
15953 let query = buffer
15954 .text_for_range(selection.start..selection.end)
15955 .collect::<String>();
15956 let is_empty = query.is_empty();
15957 let select_state = SelectNextState {
15958 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15959 wordwise: true,
15960 done: is_empty,
15961 };
15962 self.select_prev_state = Some(select_state);
15963 } else {
15964 self.select_prev_state = None;
15965 }
15966 } else if let Some(selected_text) = selected_text {
15967 self.select_prev_state = Some(SelectNextState {
15968 query: self
15969 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15970 wordwise: false,
15971 done: false,
15972 });
15973 self.select_previous(action, window, cx)?;
15974 }
15975 }
15976 Ok(())
15977 }
15978
15979 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15980 /// setting the case sensitivity based on the global
15981 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15982 /// editor's settings.
15983 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15984 where
15985 I: IntoIterator<Item = P>,
15986 P: AsRef<[u8]>,
15987 {
15988 let case_sensitive = self
15989 .select_next_is_case_sensitive
15990 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15991
15992 let mut builder = AhoCorasickBuilder::new();
15993 builder.ascii_case_insensitive(!case_sensitive);
15994 builder.build(patterns)
15995 }
15996
15997 pub fn find_next_match(
15998 &mut self,
15999 _: &FindNextMatch,
16000 window: &mut Window,
16001 cx: &mut Context<Self>,
16002 ) -> Result<()> {
16003 let selections = self.selections.disjoint_anchors_arc();
16004 match selections.first() {
16005 Some(first) if selections.len() >= 2 => {
16006 self.change_selections(Default::default(), window, cx, |s| {
16007 s.select_ranges([first.range()]);
16008 });
16009 }
16010 _ => self.select_next(
16011 &SelectNext {
16012 replace_newest: true,
16013 },
16014 window,
16015 cx,
16016 )?,
16017 }
16018 Ok(())
16019 }
16020
16021 pub fn find_previous_match(
16022 &mut self,
16023 _: &FindPreviousMatch,
16024 window: &mut Window,
16025 cx: &mut Context<Self>,
16026 ) -> Result<()> {
16027 let selections = self.selections.disjoint_anchors_arc();
16028 match selections.last() {
16029 Some(last) if selections.len() >= 2 => {
16030 self.change_selections(Default::default(), window, cx, |s| {
16031 s.select_ranges([last.range()]);
16032 });
16033 }
16034 _ => self.select_previous(
16035 &SelectPrevious {
16036 replace_newest: true,
16037 },
16038 window,
16039 cx,
16040 )?,
16041 }
16042 Ok(())
16043 }
16044
16045 pub fn toggle_comments(
16046 &mut self,
16047 action: &ToggleComments,
16048 window: &mut Window,
16049 cx: &mut Context<Self>,
16050 ) {
16051 if self.read_only(cx) {
16052 return;
16053 }
16054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16055 let text_layout_details = &self.text_layout_details(window, cx);
16056 self.transact(window, cx, |this, window, cx| {
16057 let mut selections = this
16058 .selections
16059 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16060 let mut edits = Vec::new();
16061 let mut selection_edit_ranges = Vec::new();
16062 let mut last_toggled_row = None;
16063 let snapshot = this.buffer.read(cx).read(cx);
16064 let empty_str: Arc<str> = Arc::default();
16065 let mut suffixes_inserted = Vec::new();
16066 let ignore_indent = action.ignore_indent;
16067
16068 fn comment_prefix_range(
16069 snapshot: &MultiBufferSnapshot,
16070 row: MultiBufferRow,
16071 comment_prefix: &str,
16072 comment_prefix_whitespace: &str,
16073 ignore_indent: bool,
16074 ) -> Range<Point> {
16075 let indent_size = if ignore_indent {
16076 0
16077 } else {
16078 snapshot.indent_size_for_line(row).len
16079 };
16080
16081 let start = Point::new(row.0, indent_size);
16082
16083 let mut line_bytes = snapshot
16084 .bytes_in_range(start..snapshot.max_point())
16085 .flatten()
16086 .copied();
16087
16088 // If this line currently begins with the line comment prefix, then record
16089 // the range containing the prefix.
16090 if line_bytes
16091 .by_ref()
16092 .take(comment_prefix.len())
16093 .eq(comment_prefix.bytes())
16094 {
16095 // Include any whitespace that matches the comment prefix.
16096 let matching_whitespace_len = line_bytes
16097 .zip(comment_prefix_whitespace.bytes())
16098 .take_while(|(a, b)| a == b)
16099 .count() as u32;
16100 let end = Point::new(
16101 start.row,
16102 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16103 );
16104 start..end
16105 } else {
16106 start..start
16107 }
16108 }
16109
16110 fn comment_suffix_range(
16111 snapshot: &MultiBufferSnapshot,
16112 row: MultiBufferRow,
16113 comment_suffix: &str,
16114 comment_suffix_has_leading_space: bool,
16115 ) -> Range<Point> {
16116 let end = Point::new(row.0, snapshot.line_len(row));
16117 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16118
16119 let mut line_end_bytes = snapshot
16120 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16121 .flatten()
16122 .copied();
16123
16124 let leading_space_len = if suffix_start_column > 0
16125 && line_end_bytes.next() == Some(b' ')
16126 && comment_suffix_has_leading_space
16127 {
16128 1
16129 } else {
16130 0
16131 };
16132
16133 // If this line currently begins with the line comment prefix, then record
16134 // the range containing the prefix.
16135 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16136 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16137 start..end
16138 } else {
16139 end..end
16140 }
16141 }
16142
16143 // TODO: Handle selections that cross excerpts
16144 for selection in &mut selections {
16145 let start_column = snapshot
16146 .indent_size_for_line(MultiBufferRow(selection.start.row))
16147 .len;
16148 let language = if let Some(language) =
16149 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16150 {
16151 language
16152 } else {
16153 continue;
16154 };
16155
16156 selection_edit_ranges.clear();
16157
16158 // If multiple selections contain a given row, avoid processing that
16159 // row more than once.
16160 let mut start_row = MultiBufferRow(selection.start.row);
16161 if last_toggled_row == Some(start_row) {
16162 start_row = start_row.next_row();
16163 }
16164 let end_row =
16165 if selection.end.row > selection.start.row && selection.end.column == 0 {
16166 MultiBufferRow(selection.end.row - 1)
16167 } else {
16168 MultiBufferRow(selection.end.row)
16169 };
16170 last_toggled_row = Some(end_row);
16171
16172 if start_row > end_row {
16173 continue;
16174 }
16175
16176 // If the language has line comments, toggle those.
16177 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16178
16179 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16180 if ignore_indent {
16181 full_comment_prefixes = full_comment_prefixes
16182 .into_iter()
16183 .map(|s| Arc::from(s.trim_end()))
16184 .collect();
16185 }
16186
16187 if !full_comment_prefixes.is_empty() {
16188 let first_prefix = full_comment_prefixes
16189 .first()
16190 .expect("prefixes is non-empty");
16191 let prefix_trimmed_lengths = full_comment_prefixes
16192 .iter()
16193 .map(|p| p.trim_end_matches(' ').len())
16194 .collect::<SmallVec<[usize; 4]>>();
16195
16196 let mut all_selection_lines_are_comments = true;
16197
16198 for row in start_row.0..=end_row.0 {
16199 let row = MultiBufferRow(row);
16200 if start_row < end_row && snapshot.is_line_blank(row) {
16201 continue;
16202 }
16203
16204 let prefix_range = full_comment_prefixes
16205 .iter()
16206 .zip(prefix_trimmed_lengths.iter().copied())
16207 .map(|(prefix, trimmed_prefix_len)| {
16208 comment_prefix_range(
16209 snapshot.deref(),
16210 row,
16211 &prefix[..trimmed_prefix_len],
16212 &prefix[trimmed_prefix_len..],
16213 ignore_indent,
16214 )
16215 })
16216 .max_by_key(|range| range.end.column - range.start.column)
16217 .expect("prefixes is non-empty");
16218
16219 if prefix_range.is_empty() {
16220 all_selection_lines_are_comments = false;
16221 }
16222
16223 selection_edit_ranges.push(prefix_range);
16224 }
16225
16226 if all_selection_lines_are_comments {
16227 edits.extend(
16228 selection_edit_ranges
16229 .iter()
16230 .cloned()
16231 .map(|range| (range, empty_str.clone())),
16232 );
16233 } else {
16234 let min_column = selection_edit_ranges
16235 .iter()
16236 .map(|range| range.start.column)
16237 .min()
16238 .unwrap_or(0);
16239 edits.extend(selection_edit_ranges.iter().map(|range| {
16240 let position = Point::new(range.start.row, min_column);
16241 (position..position, first_prefix.clone())
16242 }));
16243 }
16244 } else if let Some(BlockCommentConfig {
16245 start: full_comment_prefix,
16246 end: comment_suffix,
16247 ..
16248 }) = language.block_comment()
16249 {
16250 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16251 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16252 let prefix_range = comment_prefix_range(
16253 snapshot.deref(),
16254 start_row,
16255 comment_prefix,
16256 comment_prefix_whitespace,
16257 ignore_indent,
16258 );
16259 let suffix_range = comment_suffix_range(
16260 snapshot.deref(),
16261 end_row,
16262 comment_suffix.trim_start_matches(' '),
16263 comment_suffix.starts_with(' '),
16264 );
16265
16266 if prefix_range.is_empty() || suffix_range.is_empty() {
16267 edits.push((
16268 prefix_range.start..prefix_range.start,
16269 full_comment_prefix.clone(),
16270 ));
16271 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16272 suffixes_inserted.push((end_row, comment_suffix.len()));
16273 } else {
16274 edits.push((prefix_range, empty_str.clone()));
16275 edits.push((suffix_range, empty_str.clone()));
16276 }
16277 } else {
16278 continue;
16279 }
16280 }
16281
16282 drop(snapshot);
16283 this.buffer.update(cx, |buffer, cx| {
16284 buffer.edit(edits, None, cx);
16285 });
16286
16287 // Adjust selections so that they end before any comment suffixes that
16288 // were inserted.
16289 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16290 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16291 let snapshot = this.buffer.read(cx).read(cx);
16292 for selection in &mut selections {
16293 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16294 match row.cmp(&MultiBufferRow(selection.end.row)) {
16295 Ordering::Less => {
16296 suffixes_inserted.next();
16297 continue;
16298 }
16299 Ordering::Greater => break,
16300 Ordering::Equal => {
16301 if selection.end.column == snapshot.line_len(row) {
16302 if selection.is_empty() {
16303 selection.start.column -= suffix_len as u32;
16304 }
16305 selection.end.column -= suffix_len as u32;
16306 }
16307 break;
16308 }
16309 }
16310 }
16311 }
16312
16313 drop(snapshot);
16314 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16315
16316 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16317 let selections_on_single_row = selections.windows(2).all(|selections| {
16318 selections[0].start.row == selections[1].start.row
16319 && selections[0].end.row == selections[1].end.row
16320 && selections[0].start.row == selections[0].end.row
16321 });
16322 let selections_selecting = selections
16323 .iter()
16324 .any(|selection| selection.start != selection.end);
16325 let advance_downwards = action.advance_downwards
16326 && selections_on_single_row
16327 && !selections_selecting
16328 && !matches!(this.mode, EditorMode::SingleLine);
16329
16330 if advance_downwards {
16331 let snapshot = this.buffer.read(cx).snapshot(cx);
16332
16333 this.change_selections(Default::default(), window, cx, |s| {
16334 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16335 let mut point = display_point.to_point(display_snapshot);
16336 point.row += 1;
16337 point = snapshot.clip_point(point, Bias::Left);
16338 let display_point = point.to_display_point(display_snapshot);
16339 let goal = SelectionGoal::HorizontalPosition(
16340 display_snapshot
16341 .x_for_display_point(display_point, text_layout_details)
16342 .into(),
16343 );
16344 (display_point, goal)
16345 })
16346 });
16347 }
16348 });
16349 }
16350
16351 pub fn select_enclosing_symbol(
16352 &mut self,
16353 _: &SelectEnclosingSymbol,
16354 window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) {
16357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16358
16359 let buffer = self.buffer.read(cx).snapshot(cx);
16360 let old_selections = self
16361 .selections
16362 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16363 .into_boxed_slice();
16364
16365 fn update_selection(
16366 selection: &Selection<MultiBufferOffset>,
16367 buffer_snap: &MultiBufferSnapshot,
16368 ) -> Option<Selection<MultiBufferOffset>> {
16369 let cursor = selection.head();
16370 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16371 for symbol in symbols.iter().rev() {
16372 let start = symbol.range.start.to_offset(buffer_snap);
16373 let end = symbol.range.end.to_offset(buffer_snap);
16374 let new_range = start..end;
16375 if start < selection.start || end > selection.end {
16376 return Some(Selection {
16377 id: selection.id,
16378 start: new_range.start,
16379 end: new_range.end,
16380 goal: SelectionGoal::None,
16381 reversed: selection.reversed,
16382 });
16383 }
16384 }
16385 None
16386 }
16387
16388 let mut selected_larger_symbol = false;
16389 let new_selections = old_selections
16390 .iter()
16391 .map(|selection| match update_selection(selection, &buffer) {
16392 Some(new_selection) => {
16393 if new_selection.range() != selection.range() {
16394 selected_larger_symbol = true;
16395 }
16396 new_selection
16397 }
16398 None => selection.clone(),
16399 })
16400 .collect::<Vec<_>>();
16401
16402 if selected_larger_symbol {
16403 self.change_selections(Default::default(), window, cx, |s| {
16404 s.select(new_selections);
16405 });
16406 }
16407 }
16408
16409 pub fn select_larger_syntax_node(
16410 &mut self,
16411 _: &SelectLargerSyntaxNode,
16412 window: &mut Window,
16413 cx: &mut Context<Self>,
16414 ) {
16415 let Some(visible_row_count) = self.visible_row_count() else {
16416 return;
16417 };
16418 let old_selections: Box<[_]> = self
16419 .selections
16420 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16421 .into();
16422 if old_selections.is_empty() {
16423 return;
16424 }
16425
16426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16427
16428 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16429 let buffer = self.buffer.read(cx).snapshot(cx);
16430
16431 let mut selected_larger_node = false;
16432 let mut new_selections = old_selections
16433 .iter()
16434 .map(|selection| {
16435 let old_range = selection.start..selection.end;
16436
16437 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16438 // manually select word at selection
16439 if ["string_content", "inline"].contains(&node.kind()) {
16440 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16441 // ignore if word is already selected
16442 if !word_range.is_empty() && old_range != word_range {
16443 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16444 // only select word if start and end point belongs to same word
16445 if word_range == last_word_range {
16446 selected_larger_node = true;
16447 return Selection {
16448 id: selection.id,
16449 start: word_range.start,
16450 end: word_range.end,
16451 goal: SelectionGoal::None,
16452 reversed: selection.reversed,
16453 };
16454 }
16455 }
16456 }
16457 }
16458
16459 let mut new_range = old_range.clone();
16460 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16461 new_range = range;
16462 if !node.is_named() {
16463 continue;
16464 }
16465 if !display_map.intersects_fold(new_range.start)
16466 && !display_map.intersects_fold(new_range.end)
16467 {
16468 break;
16469 }
16470 }
16471
16472 selected_larger_node |= new_range != old_range;
16473 Selection {
16474 id: selection.id,
16475 start: new_range.start,
16476 end: new_range.end,
16477 goal: SelectionGoal::None,
16478 reversed: selection.reversed,
16479 }
16480 })
16481 .collect::<Vec<_>>();
16482
16483 if !selected_larger_node {
16484 return; // don't put this call in the history
16485 }
16486
16487 // scroll based on transformation done to the last selection created by the user
16488 let (last_old, last_new) = old_selections
16489 .last()
16490 .zip(new_selections.last().cloned())
16491 .expect("old_selections isn't empty");
16492
16493 // revert selection
16494 let is_selection_reversed = {
16495 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16496 new_selections.last_mut().expect("checked above").reversed =
16497 should_newest_selection_be_reversed;
16498 should_newest_selection_be_reversed
16499 };
16500
16501 if selected_larger_node {
16502 self.select_syntax_node_history.disable_clearing = true;
16503 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16504 s.select(new_selections.clone());
16505 });
16506 self.select_syntax_node_history.disable_clearing = false;
16507 }
16508
16509 let start_row = last_new.start.to_display_point(&display_map).row().0;
16510 let end_row = last_new.end.to_display_point(&display_map).row().0;
16511 let selection_height = end_row - start_row + 1;
16512 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16513
16514 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16515 let scroll_behavior = if fits_on_the_screen {
16516 self.request_autoscroll(Autoscroll::fit(), cx);
16517 SelectSyntaxNodeScrollBehavior::FitSelection
16518 } else if is_selection_reversed {
16519 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16520 SelectSyntaxNodeScrollBehavior::CursorTop
16521 } else {
16522 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16523 SelectSyntaxNodeScrollBehavior::CursorBottom
16524 };
16525
16526 let old_selections: Box<[Selection<Anchor>]> = old_selections
16527 .iter()
16528 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16529 .collect();
16530 self.select_syntax_node_history.push((
16531 old_selections,
16532 scroll_behavior,
16533 is_selection_reversed,
16534 ));
16535 }
16536
16537 pub fn select_smaller_syntax_node(
16538 &mut self,
16539 _: &SelectSmallerSyntaxNode,
16540 window: &mut Window,
16541 cx: &mut Context<Self>,
16542 ) {
16543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16544
16545 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16546 self.select_syntax_node_history.pop()
16547 {
16548 if let Some(selection) = selections.last_mut() {
16549 selection.reversed = is_selection_reversed;
16550 }
16551
16552 let snapshot = self.buffer.read(cx).snapshot(cx);
16553 let selections: Vec<Selection<MultiBufferOffset>> = selections
16554 .iter()
16555 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16556 .collect();
16557
16558 self.select_syntax_node_history.disable_clearing = true;
16559 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16560 s.select(selections);
16561 });
16562 self.select_syntax_node_history.disable_clearing = false;
16563
16564 match scroll_behavior {
16565 SelectSyntaxNodeScrollBehavior::CursorTop => {
16566 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16567 }
16568 SelectSyntaxNodeScrollBehavior::FitSelection => {
16569 self.request_autoscroll(Autoscroll::fit(), cx);
16570 }
16571 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16572 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16573 }
16574 }
16575 }
16576 }
16577
16578 pub fn unwrap_syntax_node(
16579 &mut self,
16580 _: &UnwrapSyntaxNode,
16581 window: &mut Window,
16582 cx: &mut Context<Self>,
16583 ) {
16584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16585
16586 let buffer = self.buffer.read(cx).snapshot(cx);
16587 let selections = self
16588 .selections
16589 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16590 .into_iter()
16591 // subtracting the offset requires sorting
16592 .sorted_by_key(|i| i.start);
16593
16594 let full_edits = selections
16595 .into_iter()
16596 .filter_map(|selection| {
16597 let child = if selection.is_empty()
16598 && let Some((_, ancestor_range)) =
16599 buffer.syntax_ancestor(selection.start..selection.end)
16600 {
16601 ancestor_range
16602 } else {
16603 selection.range()
16604 };
16605
16606 let mut parent = child.clone();
16607 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16608 parent = ancestor_range;
16609 if parent.start < child.start || parent.end > child.end {
16610 break;
16611 }
16612 }
16613
16614 if parent == child {
16615 return None;
16616 }
16617 let text = buffer.text_for_range(child).collect::<String>();
16618 Some((selection.id, parent, text))
16619 })
16620 .collect::<Vec<_>>();
16621 if full_edits.is_empty() {
16622 return;
16623 }
16624
16625 self.transact(window, cx, |this, window, cx| {
16626 this.buffer.update(cx, |buffer, cx| {
16627 buffer.edit(
16628 full_edits
16629 .iter()
16630 .map(|(_, p, t)| (p.clone(), t.clone()))
16631 .collect::<Vec<_>>(),
16632 None,
16633 cx,
16634 );
16635 });
16636 this.change_selections(Default::default(), window, cx, |s| {
16637 let mut offset = 0;
16638 let mut selections = vec![];
16639 for (id, parent, text) in full_edits {
16640 let start = parent.start - offset;
16641 offset += (parent.end - parent.start) - text.len();
16642 selections.push(Selection {
16643 id,
16644 start,
16645 end: start + text.len(),
16646 reversed: false,
16647 goal: Default::default(),
16648 });
16649 }
16650 s.select(selections);
16651 });
16652 });
16653 }
16654
16655 pub fn select_next_syntax_node(
16656 &mut self,
16657 _: &SelectNextSyntaxNode,
16658 window: &mut Window,
16659 cx: &mut Context<Self>,
16660 ) {
16661 let old_selections: Box<[_]> = self
16662 .selections
16663 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16664 .into();
16665 if old_selections.is_empty() {
16666 return;
16667 }
16668
16669 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16670
16671 let buffer = self.buffer.read(cx).snapshot(cx);
16672 let mut selected_sibling = false;
16673
16674 let new_selections = old_selections
16675 .iter()
16676 .map(|selection| {
16677 let old_range = selection.start..selection.end;
16678
16679 let old_range =
16680 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16681 let excerpt = buffer.excerpt_containing(old_range.clone());
16682
16683 if let Some(mut excerpt) = excerpt
16684 && let Some(node) = excerpt
16685 .buffer()
16686 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16687 {
16688 let new_range = excerpt.map_range_from_buffer(
16689 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16690 );
16691 selected_sibling = true;
16692 Selection {
16693 id: selection.id,
16694 start: new_range.start,
16695 end: new_range.end,
16696 goal: SelectionGoal::None,
16697 reversed: selection.reversed,
16698 }
16699 } else {
16700 selection.clone()
16701 }
16702 })
16703 .collect::<Vec<_>>();
16704
16705 if selected_sibling {
16706 self.change_selections(
16707 SelectionEffects::scroll(Autoscroll::fit()),
16708 window,
16709 cx,
16710 |s| {
16711 s.select(new_selections);
16712 },
16713 );
16714 }
16715 }
16716
16717 pub fn select_prev_syntax_node(
16718 &mut self,
16719 _: &SelectPreviousSyntaxNode,
16720 window: &mut Window,
16721 cx: &mut Context<Self>,
16722 ) {
16723 let old_selections: Box<[_]> = self
16724 .selections
16725 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16726 .into();
16727 if old_selections.is_empty() {
16728 return;
16729 }
16730
16731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16732
16733 let buffer = self.buffer.read(cx).snapshot(cx);
16734 let mut selected_sibling = false;
16735
16736 let new_selections = old_selections
16737 .iter()
16738 .map(|selection| {
16739 let old_range = selection.start..selection.end;
16740 let old_range =
16741 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16742 let excerpt = buffer.excerpt_containing(old_range.clone());
16743
16744 if let Some(mut excerpt) = excerpt
16745 && let Some(node) = excerpt
16746 .buffer()
16747 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16748 {
16749 let new_range = excerpt.map_range_from_buffer(
16750 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16751 );
16752 selected_sibling = true;
16753 Selection {
16754 id: selection.id,
16755 start: new_range.start,
16756 end: new_range.end,
16757 goal: SelectionGoal::None,
16758 reversed: selection.reversed,
16759 }
16760 } else {
16761 selection.clone()
16762 }
16763 })
16764 .collect::<Vec<_>>();
16765
16766 if selected_sibling {
16767 self.change_selections(
16768 SelectionEffects::scroll(Autoscroll::fit()),
16769 window,
16770 cx,
16771 |s| {
16772 s.select(new_selections);
16773 },
16774 );
16775 }
16776 }
16777
16778 pub fn move_to_start_of_larger_syntax_node(
16779 &mut self,
16780 _: &MoveToStartOfLargerSyntaxNode,
16781 window: &mut Window,
16782 cx: &mut Context<Self>,
16783 ) {
16784 self.move_cursors_to_syntax_nodes(window, cx, false);
16785 }
16786
16787 pub fn move_to_end_of_larger_syntax_node(
16788 &mut self,
16789 _: &MoveToEndOfLargerSyntaxNode,
16790 window: &mut Window,
16791 cx: &mut Context<Self>,
16792 ) {
16793 self.move_cursors_to_syntax_nodes(window, cx, true);
16794 }
16795
16796 fn find_syntax_node_boundary(
16797 &self,
16798 selection_pos: MultiBufferOffset,
16799 move_to_end: bool,
16800 display_map: &DisplaySnapshot,
16801 buffer: &MultiBufferSnapshot,
16802 ) -> MultiBufferOffset {
16803 let old_range = selection_pos..selection_pos;
16804 let mut new_pos = selection_pos;
16805 let mut search_range = old_range;
16806 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16807 search_range = range.clone();
16808 if !node.is_named()
16809 || display_map.intersects_fold(range.start)
16810 || display_map.intersects_fold(range.end)
16811 // If cursor is already at the end of the syntax node, continue searching
16812 || (move_to_end && range.end == selection_pos)
16813 // If cursor is already at the start of the syntax node, continue searching
16814 || (!move_to_end && range.start == selection_pos)
16815 {
16816 continue;
16817 }
16818
16819 // If we found a string_content node, find the largest parent that is still string_content
16820 // Enables us to skip to the end of strings without taking multiple steps inside the string
16821 let (_, final_range) = if node.kind() == "string_content" {
16822 let mut current_node = node;
16823 let mut current_range = range;
16824 while let Some((parent, parent_range)) =
16825 buffer.syntax_ancestor(current_range.clone())
16826 {
16827 if parent.kind() == "string_content" {
16828 current_node = parent;
16829 current_range = parent_range;
16830 } else {
16831 break;
16832 }
16833 }
16834
16835 (current_node, current_range)
16836 } else {
16837 (node, range)
16838 };
16839
16840 new_pos = if move_to_end {
16841 final_range.end
16842 } else {
16843 final_range.start
16844 };
16845
16846 break;
16847 }
16848
16849 new_pos
16850 }
16851
16852 fn move_cursors_to_syntax_nodes(
16853 &mut self,
16854 window: &mut Window,
16855 cx: &mut Context<Self>,
16856 move_to_end: bool,
16857 ) -> bool {
16858 let old_selections: Box<[_]> = self
16859 .selections
16860 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16861 .into();
16862 if old_selections.is_empty() {
16863 return false;
16864 }
16865
16866 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16867
16868 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16869 let buffer = self.buffer.read(cx).snapshot(cx);
16870
16871 let mut any_cursor_moved = false;
16872 let new_selections = old_selections
16873 .iter()
16874 .map(|selection| {
16875 if !selection.is_empty() {
16876 return selection.clone();
16877 }
16878
16879 let selection_pos = selection.head();
16880 let new_pos = self.find_syntax_node_boundary(
16881 selection_pos,
16882 move_to_end,
16883 &display_map,
16884 &buffer,
16885 );
16886
16887 any_cursor_moved |= new_pos != selection_pos;
16888
16889 Selection {
16890 id: selection.id,
16891 start: new_pos,
16892 end: new_pos,
16893 goal: SelectionGoal::None,
16894 reversed: false,
16895 }
16896 })
16897 .collect::<Vec<_>>();
16898
16899 self.change_selections(Default::default(), window, cx, |s| {
16900 s.select(new_selections);
16901 });
16902 self.request_autoscroll(Autoscroll::newest(), cx);
16903
16904 any_cursor_moved
16905 }
16906
16907 pub fn select_to_start_of_larger_syntax_node(
16908 &mut self,
16909 _: &SelectToStartOfLargerSyntaxNode,
16910 window: &mut Window,
16911 cx: &mut Context<Self>,
16912 ) {
16913 self.select_to_syntax_nodes(window, cx, false);
16914 }
16915
16916 pub fn select_to_end_of_larger_syntax_node(
16917 &mut self,
16918 _: &SelectToEndOfLargerSyntaxNode,
16919 window: &mut Window,
16920 cx: &mut Context<Self>,
16921 ) {
16922 self.select_to_syntax_nodes(window, cx, true);
16923 }
16924
16925 fn select_to_syntax_nodes(
16926 &mut self,
16927 window: &mut Window,
16928 cx: &mut Context<Self>,
16929 move_to_end: bool,
16930 ) {
16931 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16932
16933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16934 let buffer = self.buffer.read(cx).snapshot(cx);
16935 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
16936
16937 let new_selections = old_selections
16938 .iter()
16939 .map(|selection| {
16940 let new_pos = self.find_syntax_node_boundary(
16941 selection.head(),
16942 move_to_end,
16943 &display_map,
16944 &buffer,
16945 );
16946
16947 let mut new_selection = selection.clone();
16948 new_selection.set_head(new_pos, SelectionGoal::None);
16949 new_selection
16950 })
16951 .collect::<Vec<_>>();
16952
16953 self.change_selections(Default::default(), window, cx, |s| {
16954 s.select(new_selections);
16955 });
16956 }
16957
16958 pub fn move_to_enclosing_bracket(
16959 &mut self,
16960 _: &MoveToEnclosingBracket,
16961 window: &mut Window,
16962 cx: &mut Context<Self>,
16963 ) {
16964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16965 self.change_selections(Default::default(), window, cx, |s| {
16966 s.move_offsets_with(&mut |snapshot, selection| {
16967 let Some(enclosing_bracket_ranges) =
16968 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16969 else {
16970 return;
16971 };
16972
16973 let mut best_length = usize::MAX;
16974 let mut best_inside = false;
16975 let mut best_in_bracket_range = false;
16976 let mut best_destination = None;
16977 for (open, close) in enclosing_bracket_ranges {
16978 let close = close.to_inclusive();
16979 let length = *close.end() - open.start;
16980 let inside = selection.start >= open.end && selection.end <= *close.start();
16981 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16982 || close.contains(&selection.head());
16983
16984 // If best is next to a bracket and current isn't, skip
16985 if !in_bracket_range && best_in_bracket_range {
16986 continue;
16987 }
16988
16989 // Prefer smaller lengths unless best is inside and current isn't
16990 if length > best_length && (best_inside || !inside) {
16991 continue;
16992 }
16993
16994 best_length = length;
16995 best_inside = inside;
16996 best_in_bracket_range = in_bracket_range;
16997 best_destination = Some(
16998 if close.contains(&selection.start) && close.contains(&selection.end) {
16999 if inside { open.end } else { open.start }
17000 } else if inside {
17001 *close.start()
17002 } else {
17003 *close.end()
17004 },
17005 );
17006 }
17007
17008 if let Some(destination) = best_destination {
17009 selection.collapse_to(destination, SelectionGoal::None);
17010 }
17011 })
17012 });
17013 }
17014
17015 pub fn undo_selection(
17016 &mut self,
17017 _: &UndoSelection,
17018 window: &mut Window,
17019 cx: &mut Context<Self>,
17020 ) {
17021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17022 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17023 self.selection_history.mode = SelectionHistoryMode::Undoing;
17024 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17025 this.end_selection(window, cx);
17026 this.change_selections(
17027 SelectionEffects::scroll(Autoscroll::newest()),
17028 window,
17029 cx,
17030 |s| s.select_anchors(entry.selections.to_vec()),
17031 );
17032 });
17033 self.selection_history.mode = SelectionHistoryMode::Normal;
17034
17035 self.select_next_state = entry.select_next_state;
17036 self.select_prev_state = entry.select_prev_state;
17037 self.add_selections_state = entry.add_selections_state;
17038 }
17039 }
17040
17041 pub fn redo_selection(
17042 &mut self,
17043 _: &RedoSelection,
17044 window: &mut Window,
17045 cx: &mut Context<Self>,
17046 ) {
17047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17048 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17049 self.selection_history.mode = SelectionHistoryMode::Redoing;
17050 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17051 this.end_selection(window, cx);
17052 this.change_selections(
17053 SelectionEffects::scroll(Autoscroll::newest()),
17054 window,
17055 cx,
17056 |s| s.select_anchors(entry.selections.to_vec()),
17057 );
17058 });
17059 self.selection_history.mode = SelectionHistoryMode::Normal;
17060
17061 self.select_next_state = entry.select_next_state;
17062 self.select_prev_state = entry.select_prev_state;
17063 self.add_selections_state = entry.add_selections_state;
17064 }
17065 }
17066
17067 pub fn expand_excerpts(
17068 &mut self,
17069 action: &ExpandExcerpts,
17070 _: &mut Window,
17071 cx: &mut Context<Self>,
17072 ) {
17073 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17074 }
17075
17076 pub fn expand_excerpts_down(
17077 &mut self,
17078 action: &ExpandExcerptsDown,
17079 _: &mut Window,
17080 cx: &mut Context<Self>,
17081 ) {
17082 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17083 }
17084
17085 pub fn expand_excerpts_up(
17086 &mut self,
17087 action: &ExpandExcerptsUp,
17088 _: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) {
17091 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17092 }
17093
17094 pub fn expand_excerpts_for_direction(
17095 &mut self,
17096 lines: u32,
17097 direction: ExpandExcerptDirection,
17098 cx: &mut Context<Self>,
17099 ) {
17100 let selections = self.selections.disjoint_anchors_arc();
17101
17102 let lines = if lines == 0 {
17103 EditorSettings::get_global(cx).expand_excerpt_lines
17104 } else {
17105 lines
17106 };
17107
17108 let snapshot = self.buffer.read(cx).snapshot(cx);
17109 let excerpt_ids = selections
17110 .iter()
17111 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17112 .unique()
17113 .sorted()
17114 .collect::<Vec<_>>();
17115
17116 if self.delegate_expand_excerpts {
17117 cx.emit(EditorEvent::ExpandExcerptsRequested {
17118 excerpt_ids,
17119 lines,
17120 direction,
17121 });
17122 return;
17123 }
17124
17125 self.buffer.update(cx, |buffer, cx| {
17126 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17127 })
17128 }
17129
17130 pub fn expand_excerpt(
17131 &mut self,
17132 excerpt: ExcerptId,
17133 direction: ExpandExcerptDirection,
17134 window: &mut Window,
17135 cx: &mut Context<Self>,
17136 ) {
17137 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17138
17139 if self.delegate_expand_excerpts {
17140 cx.emit(EditorEvent::ExpandExcerptsRequested {
17141 excerpt_ids: vec![excerpt],
17142 lines: lines_to_expand,
17143 direction,
17144 });
17145 return;
17146 }
17147
17148 let current_scroll_position = self.scroll_position(cx);
17149 let mut scroll = None;
17150
17151 if direction == ExpandExcerptDirection::Down {
17152 let multi_buffer = self.buffer.read(cx);
17153 let snapshot = multi_buffer.snapshot(cx);
17154 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17155 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17156 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17157 {
17158 let buffer_snapshot = buffer.read(cx).snapshot();
17159 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17160 let last_row = buffer_snapshot.max_point().row;
17161 let lines_below = last_row.saturating_sub(excerpt_end_row);
17162 if lines_below >= lines_to_expand {
17163 scroll = Some(
17164 current_scroll_position
17165 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17166 );
17167 }
17168 }
17169 }
17170 if direction == ExpandExcerptDirection::Up
17171 && self
17172 .buffer
17173 .read(cx)
17174 .snapshot(cx)
17175 .excerpt_before(excerpt)
17176 .is_none()
17177 {
17178 scroll = Some(current_scroll_position);
17179 }
17180
17181 self.buffer.update(cx, |buffer, cx| {
17182 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17183 });
17184
17185 if let Some(new_scroll_position) = scroll {
17186 self.set_scroll_position(new_scroll_position, window, cx);
17187 }
17188 }
17189
17190 pub fn go_to_singleton_buffer_point(
17191 &mut self,
17192 point: Point,
17193 window: &mut Window,
17194 cx: &mut Context<Self>,
17195 ) {
17196 self.go_to_singleton_buffer_range(point..point, window, cx);
17197 }
17198
17199 pub fn go_to_singleton_buffer_range(
17200 &mut self,
17201 range: Range<Point>,
17202 window: &mut Window,
17203 cx: &mut Context<Self>,
17204 ) {
17205 let multibuffer = self.buffer().read(cx);
17206 let Some(buffer) = multibuffer.as_singleton() else {
17207 return;
17208 };
17209 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17210 return;
17211 };
17212 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17213 return;
17214 };
17215 self.change_selections(
17216 SelectionEffects::default().nav_history(true),
17217 window,
17218 cx,
17219 |s| s.select_anchor_ranges([start..end]),
17220 );
17221 }
17222
17223 pub fn go_to_diagnostic(
17224 &mut self,
17225 action: &GoToDiagnostic,
17226 window: &mut Window,
17227 cx: &mut Context<Self>,
17228 ) {
17229 if !self.diagnostics_enabled() {
17230 return;
17231 }
17232 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17233 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17234 }
17235
17236 pub fn go_to_prev_diagnostic(
17237 &mut self,
17238 action: &GoToPreviousDiagnostic,
17239 window: &mut Window,
17240 cx: &mut Context<Self>,
17241 ) {
17242 if !self.diagnostics_enabled() {
17243 return;
17244 }
17245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17246 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17247 }
17248
17249 pub fn go_to_diagnostic_impl(
17250 &mut self,
17251 direction: Direction,
17252 severity: GoToDiagnosticSeverityFilter,
17253 window: &mut Window,
17254 cx: &mut Context<Self>,
17255 ) {
17256 let buffer = self.buffer.read(cx).snapshot(cx);
17257 let selection = self
17258 .selections
17259 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17260
17261 let mut active_group_id = None;
17262 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17263 && active_group.active_range.start.to_offset(&buffer) == selection.start
17264 {
17265 active_group_id = Some(active_group.group_id);
17266 }
17267
17268 fn filtered<'a>(
17269 severity: GoToDiagnosticSeverityFilter,
17270 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17271 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17272 diagnostics
17273 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17274 .filter(|entry| entry.range.start != entry.range.end)
17275 .filter(|entry| !entry.diagnostic.is_unnecessary)
17276 }
17277
17278 let before = filtered(
17279 severity,
17280 buffer
17281 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17282 .filter(|entry| entry.range.start <= selection.start),
17283 );
17284 let after = filtered(
17285 severity,
17286 buffer
17287 .diagnostics_in_range(selection.start..buffer.len())
17288 .filter(|entry| entry.range.start >= selection.start),
17289 );
17290
17291 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17292 if direction == Direction::Prev {
17293 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17294 {
17295 for diagnostic in prev_diagnostics.into_iter().rev() {
17296 if diagnostic.range.start != selection.start
17297 || active_group_id
17298 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17299 {
17300 found = Some(diagnostic);
17301 break 'outer;
17302 }
17303 }
17304 }
17305 } else {
17306 for diagnostic in after.chain(before) {
17307 if diagnostic.range.start != selection.start
17308 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17309 {
17310 found = Some(diagnostic);
17311 break;
17312 }
17313 }
17314 }
17315 let Some(next_diagnostic) = found else {
17316 return;
17317 };
17318
17319 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17320 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17321 return;
17322 };
17323 let snapshot = self.snapshot(window, cx);
17324 if snapshot.intersects_fold(next_diagnostic.range.start) {
17325 self.unfold_ranges(
17326 std::slice::from_ref(&next_diagnostic.range),
17327 true,
17328 false,
17329 cx,
17330 );
17331 }
17332 self.change_selections(Default::default(), window, cx, |s| {
17333 s.select_ranges(vec![
17334 next_diagnostic.range.start..next_diagnostic.range.start,
17335 ])
17336 });
17337 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17338 self.refresh_edit_prediction(false, true, window, cx);
17339 }
17340
17341 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17343 let snapshot = self.snapshot(window, cx);
17344 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17345 self.go_to_hunk_before_or_after_position(
17346 &snapshot,
17347 selection.head(),
17348 Direction::Next,
17349 true,
17350 window,
17351 cx,
17352 );
17353 }
17354
17355 pub fn go_to_hunk_before_or_after_position(
17356 &mut self,
17357 snapshot: &EditorSnapshot,
17358 position: Point,
17359 direction: Direction,
17360 wrap_around: bool,
17361 window: &mut Window,
17362 cx: &mut Context<Editor>,
17363 ) {
17364 let row = if direction == Direction::Next {
17365 self.hunk_after_position(snapshot, position, wrap_around)
17366 .map(|hunk| hunk.row_range.start)
17367 } else {
17368 self.hunk_before_position(snapshot, position, wrap_around)
17369 };
17370
17371 if let Some(row) = row {
17372 let destination = Point::new(row.0, 0);
17373 let autoscroll = Autoscroll::center();
17374
17375 self.unfold_ranges(&[destination..destination], false, false, cx);
17376 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17377 s.select_ranges([destination..destination]);
17378 });
17379 }
17380 }
17381
17382 fn hunk_after_position(
17383 &mut self,
17384 snapshot: &EditorSnapshot,
17385 position: Point,
17386 wrap_around: bool,
17387 ) -> Option<MultiBufferDiffHunk> {
17388 let result = snapshot
17389 .buffer_snapshot()
17390 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17391 .find(|hunk| hunk.row_range.start.0 > position.row);
17392
17393 if wrap_around {
17394 result.or_else(|| {
17395 snapshot
17396 .buffer_snapshot()
17397 .diff_hunks_in_range(Point::zero()..position)
17398 .find(|hunk| hunk.row_range.end.0 < position.row)
17399 })
17400 } else {
17401 result
17402 }
17403 }
17404
17405 fn go_to_prev_hunk(
17406 &mut self,
17407 _: &GoToPreviousHunk,
17408 window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) {
17411 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17412 let snapshot = self.snapshot(window, cx);
17413 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17414 self.go_to_hunk_before_or_after_position(
17415 &snapshot,
17416 selection.head(),
17417 Direction::Prev,
17418 true,
17419 window,
17420 cx,
17421 );
17422 }
17423
17424 fn hunk_before_position(
17425 &mut self,
17426 snapshot: &EditorSnapshot,
17427 position: Point,
17428 wrap_around: bool,
17429 ) -> Option<MultiBufferRow> {
17430 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17431
17432 if wrap_around {
17433 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17434 } else {
17435 result
17436 }
17437 }
17438
17439 fn go_to_next_change(
17440 &mut self,
17441 _: &GoToNextChange,
17442 window: &mut Window,
17443 cx: &mut Context<Self>,
17444 ) {
17445 if let Some(selections) = self
17446 .change_list
17447 .next_change(1, Direction::Next)
17448 .map(|s| s.to_vec())
17449 {
17450 self.change_selections(Default::default(), window, cx, |s| {
17451 let map = s.display_snapshot();
17452 s.select_display_ranges(selections.iter().map(|a| {
17453 let point = a.to_display_point(&map);
17454 point..point
17455 }))
17456 })
17457 }
17458 }
17459
17460 fn go_to_previous_change(
17461 &mut self,
17462 _: &GoToPreviousChange,
17463 window: &mut Window,
17464 cx: &mut Context<Self>,
17465 ) {
17466 if let Some(selections) = self
17467 .change_list
17468 .next_change(1, Direction::Prev)
17469 .map(|s| s.to_vec())
17470 {
17471 self.change_selections(Default::default(), window, cx, |s| {
17472 let map = s.display_snapshot();
17473 s.select_display_ranges(selections.iter().map(|a| {
17474 let point = a.to_display_point(&map);
17475 point..point
17476 }))
17477 })
17478 }
17479 }
17480
17481 pub fn go_to_next_document_highlight(
17482 &mut self,
17483 _: &GoToNextDocumentHighlight,
17484 window: &mut Window,
17485 cx: &mut Context<Self>,
17486 ) {
17487 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17488 }
17489
17490 pub fn go_to_prev_document_highlight(
17491 &mut self,
17492 _: &GoToPreviousDocumentHighlight,
17493 window: &mut Window,
17494 cx: &mut Context<Self>,
17495 ) {
17496 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17497 }
17498
17499 pub fn go_to_document_highlight_before_or_after_position(
17500 &mut self,
17501 direction: Direction,
17502 window: &mut Window,
17503 cx: &mut Context<Editor>,
17504 ) {
17505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17506 let snapshot = self.snapshot(window, cx);
17507 let buffer = &snapshot.buffer_snapshot();
17508 let position = self
17509 .selections
17510 .newest::<Point>(&snapshot.display_snapshot)
17511 .head();
17512 let anchor_position = buffer.anchor_after(position);
17513
17514 // Get all document highlights (both read and write)
17515 let mut all_highlights = Vec::new();
17516
17517 if let Some((_, read_highlights)) = self
17518 .background_highlights
17519 .get(&HighlightKey::DocumentHighlightRead)
17520 {
17521 all_highlights.extend(read_highlights.iter());
17522 }
17523
17524 if let Some((_, write_highlights)) = self
17525 .background_highlights
17526 .get(&HighlightKey::DocumentHighlightWrite)
17527 {
17528 all_highlights.extend(write_highlights.iter());
17529 }
17530
17531 if all_highlights.is_empty() {
17532 return;
17533 }
17534
17535 // Sort highlights by position
17536 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17537
17538 let target_highlight = match direction {
17539 Direction::Next => {
17540 // Find the first highlight after the current position
17541 all_highlights
17542 .iter()
17543 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17544 }
17545 Direction::Prev => {
17546 // Find the last highlight before the current position
17547 all_highlights
17548 .iter()
17549 .rev()
17550 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17551 }
17552 };
17553
17554 if let Some(highlight) = target_highlight {
17555 let destination = highlight.start.to_point(buffer);
17556 let autoscroll = Autoscroll::center();
17557
17558 self.unfold_ranges(&[destination..destination], false, false, cx);
17559 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17560 s.select_ranges([destination..destination]);
17561 });
17562 }
17563 }
17564
17565 fn go_to_line<T: 'static>(
17566 &mut self,
17567 position: Anchor,
17568 highlight_color: Option<Hsla>,
17569 window: &mut Window,
17570 cx: &mut Context<Self>,
17571 ) {
17572 let snapshot = self.snapshot(window, cx).display_snapshot;
17573 let position = position.to_point(&snapshot.buffer_snapshot());
17574 let start = snapshot
17575 .buffer_snapshot()
17576 .clip_point(Point::new(position.row, 0), Bias::Left);
17577 let end = start + Point::new(1, 0);
17578 let start = snapshot.buffer_snapshot().anchor_before(start);
17579 let end = snapshot.buffer_snapshot().anchor_before(end);
17580
17581 self.highlight_rows::<T>(
17582 start..end,
17583 highlight_color
17584 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17585 Default::default(),
17586 cx,
17587 );
17588
17589 if self.buffer.read(cx).is_singleton() {
17590 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17591 }
17592 }
17593
17594 pub fn go_to_definition(
17595 &mut self,
17596 _: &GoToDefinition,
17597 window: &mut Window,
17598 cx: &mut Context<Self>,
17599 ) -> Task<Result<Navigated>> {
17600 let definition =
17601 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17602 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17603 cx.spawn_in(window, async move |editor, cx| {
17604 if definition.await? == Navigated::Yes {
17605 return Ok(Navigated::Yes);
17606 }
17607 match fallback_strategy {
17608 GoToDefinitionFallback::None => Ok(Navigated::No),
17609 GoToDefinitionFallback::FindAllReferences => {
17610 match editor.update_in(cx, |editor, window, cx| {
17611 editor.find_all_references(&FindAllReferences::default(), window, cx)
17612 })? {
17613 Some(references) => references.await,
17614 None => Ok(Navigated::No),
17615 }
17616 }
17617 }
17618 })
17619 }
17620
17621 pub fn go_to_declaration(
17622 &mut self,
17623 _: &GoToDeclaration,
17624 window: &mut Window,
17625 cx: &mut Context<Self>,
17626 ) -> Task<Result<Navigated>> {
17627 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17628 }
17629
17630 pub fn go_to_declaration_split(
17631 &mut self,
17632 _: &GoToDeclaration,
17633 window: &mut Window,
17634 cx: &mut Context<Self>,
17635 ) -> Task<Result<Navigated>> {
17636 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17637 }
17638
17639 pub fn go_to_implementation(
17640 &mut self,
17641 _: &GoToImplementation,
17642 window: &mut Window,
17643 cx: &mut Context<Self>,
17644 ) -> Task<Result<Navigated>> {
17645 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17646 }
17647
17648 pub fn go_to_implementation_split(
17649 &mut self,
17650 _: &GoToImplementationSplit,
17651 window: &mut Window,
17652 cx: &mut Context<Self>,
17653 ) -> Task<Result<Navigated>> {
17654 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17655 }
17656
17657 pub fn go_to_type_definition(
17658 &mut self,
17659 _: &GoToTypeDefinition,
17660 window: &mut Window,
17661 cx: &mut Context<Self>,
17662 ) -> Task<Result<Navigated>> {
17663 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17664 }
17665
17666 pub fn go_to_definition_split(
17667 &mut self,
17668 _: &GoToDefinitionSplit,
17669 window: &mut Window,
17670 cx: &mut Context<Self>,
17671 ) -> Task<Result<Navigated>> {
17672 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17673 }
17674
17675 pub fn go_to_type_definition_split(
17676 &mut self,
17677 _: &GoToTypeDefinitionSplit,
17678 window: &mut Window,
17679 cx: &mut Context<Self>,
17680 ) -> Task<Result<Navigated>> {
17681 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17682 }
17683
17684 fn go_to_definition_of_kind(
17685 &mut self,
17686 kind: GotoDefinitionKind,
17687 split: bool,
17688 window: &mut Window,
17689 cx: &mut Context<Self>,
17690 ) -> Task<Result<Navigated>> {
17691 let Some(provider) = self.semantics_provider.clone() else {
17692 return Task::ready(Ok(Navigated::No));
17693 };
17694 let head = self
17695 .selections
17696 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17697 .head();
17698 let buffer = self.buffer.read(cx);
17699 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17700 return Task::ready(Ok(Navigated::No));
17701 };
17702 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17703 return Task::ready(Ok(Navigated::No));
17704 };
17705
17706 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17707
17708 cx.spawn_in(window, async move |editor, cx| {
17709 let Some(definitions) = definitions.await? else {
17710 return Ok(Navigated::No);
17711 };
17712 let navigated = editor
17713 .update_in(cx, |editor, window, cx| {
17714 editor.navigate_to_hover_links(
17715 Some(kind),
17716 definitions
17717 .into_iter()
17718 .filter(|location| {
17719 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17720 })
17721 .map(HoverLink::Text)
17722 .collect::<Vec<_>>(),
17723 nav_entry,
17724 split,
17725 window,
17726 cx,
17727 )
17728 })?
17729 .await?;
17730 anyhow::Ok(navigated)
17731 })
17732 }
17733
17734 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17735 let selection = self.selections.newest_anchor();
17736 let head = selection.head();
17737 let tail = selection.tail();
17738
17739 let Some((buffer, start_position)) =
17740 self.buffer.read(cx).text_anchor_for_position(head, cx)
17741 else {
17742 return;
17743 };
17744
17745 let end_position = if head != tail {
17746 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17747 return;
17748 };
17749 Some(pos)
17750 } else {
17751 None
17752 };
17753
17754 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17755 let url = if let Some(end_pos) = end_position {
17756 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17757 } else {
17758 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17759 };
17760
17761 if let Some(url) = url {
17762 cx.update(|window, cx| {
17763 if parse_zed_link(&url, cx).is_some() {
17764 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17765 } else {
17766 cx.open_url(&url);
17767 }
17768 })?;
17769 }
17770
17771 anyhow::Ok(())
17772 });
17773
17774 url_finder.detach();
17775 }
17776
17777 pub fn open_selected_filename(
17778 &mut self,
17779 _: &OpenSelectedFilename,
17780 window: &mut Window,
17781 cx: &mut Context<Self>,
17782 ) {
17783 let Some(workspace) = self.workspace() else {
17784 return;
17785 };
17786
17787 let position = self.selections.newest_anchor().head();
17788
17789 let Some((buffer, buffer_position)) =
17790 self.buffer.read(cx).text_anchor_for_position(position, cx)
17791 else {
17792 return;
17793 };
17794
17795 let project = self.project.clone();
17796
17797 cx.spawn_in(window, async move |_, cx| {
17798 let result = find_file(&buffer, project, buffer_position, cx).await;
17799
17800 if let Some((_, path)) = result {
17801 workspace
17802 .update_in(cx, |workspace, window, cx| {
17803 workspace.open_resolved_path(path, window, cx)
17804 })?
17805 .await?;
17806 }
17807 anyhow::Ok(())
17808 })
17809 .detach();
17810 }
17811
17812 pub(crate) fn navigate_to_hover_links(
17813 &mut self,
17814 kind: Option<GotoDefinitionKind>,
17815 definitions: Vec<HoverLink>,
17816 origin: Option<NavigationEntry>,
17817 split: bool,
17818 window: &mut Window,
17819 cx: &mut Context<Editor>,
17820 ) -> Task<Result<Navigated>> {
17821 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17822 let mut first_url_or_file = None;
17823 let definitions: Vec<_> = definitions
17824 .into_iter()
17825 .filter_map(|def| match def {
17826 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17827 HoverLink::InlayHint(lsp_location, server_id) => {
17828 let computation =
17829 self.compute_target_location(lsp_location, server_id, window, cx);
17830 Some(cx.background_spawn(computation))
17831 }
17832 HoverLink::Url(url) => {
17833 first_url_or_file = Some(Either::Left(url));
17834 None
17835 }
17836 HoverLink::File(path) => {
17837 first_url_or_file = Some(Either::Right(path));
17838 None
17839 }
17840 })
17841 .collect();
17842
17843 let workspace = self.workspace();
17844
17845 cx.spawn_in(window, async move |editor, cx| {
17846 let locations: Vec<Location> = future::join_all(definitions)
17847 .await
17848 .into_iter()
17849 .filter_map(|location| location.transpose())
17850 .collect::<Result<_>>()
17851 .context("location tasks")?;
17852 let mut locations = cx.update(|_, cx| {
17853 locations
17854 .into_iter()
17855 .map(|location| {
17856 let buffer = location.buffer.read(cx);
17857 (location.buffer, location.range.to_point(buffer))
17858 })
17859 .into_group_map()
17860 })?;
17861 let mut num_locations = 0;
17862 for ranges in locations.values_mut() {
17863 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17864 ranges.dedup();
17865 num_locations += ranges.len();
17866 }
17867
17868 if num_locations > 1 {
17869 let tab_kind = match kind {
17870 Some(GotoDefinitionKind::Implementation) => "Implementations",
17871 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17872 Some(GotoDefinitionKind::Declaration) => "Declarations",
17873 Some(GotoDefinitionKind::Type) => "Types",
17874 };
17875 let title = editor
17876 .update_in(cx, |_, _, cx| {
17877 let target = locations
17878 .iter()
17879 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17880 .map(|(buffer, location)| {
17881 buffer
17882 .read(cx)
17883 .text_for_range(location.clone())
17884 .collect::<String>()
17885 })
17886 .filter(|text| !text.contains('\n'))
17887 .unique()
17888 .take(3)
17889 .join(", ");
17890 if target.is_empty() {
17891 tab_kind.to_owned()
17892 } else {
17893 format!("{tab_kind} for {target}")
17894 }
17895 })
17896 .context("buffer title")?;
17897
17898 let Some(workspace) = workspace else {
17899 return Ok(Navigated::No);
17900 };
17901
17902 let opened = workspace
17903 .update_in(cx, |workspace, window, cx| {
17904 let allow_preview = PreviewTabsSettings::get_global(cx)
17905 .enable_preview_multibuffer_from_code_navigation;
17906 if let Some((target_editor, target_pane)) =
17907 Self::open_locations_in_multibuffer(
17908 workspace,
17909 locations,
17910 title,
17911 split,
17912 allow_preview,
17913 MultibufferSelectionMode::First,
17914 window,
17915 cx,
17916 )
17917 {
17918 // We create our own nav history instead of using
17919 // `target_editor.nav_history` because `nav_history`
17920 // seems to be populated asynchronously when an item
17921 // is added to a pane
17922 let mut nav_history = target_pane
17923 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17924 target_editor.update(cx, |editor, cx| {
17925 let nav_data = editor
17926 .navigation_data(editor.selections.newest_anchor().head(), cx);
17927 let target =
17928 Some(nav_history.navigation_entry(Some(
17929 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17930 )));
17931 nav_history.push_tag(origin, target);
17932 })
17933 }
17934 })
17935 .is_ok();
17936
17937 anyhow::Ok(Navigated::from_bool(opened))
17938 } else if num_locations == 0 {
17939 // If there is one url or file, open it directly
17940 match first_url_or_file {
17941 Some(Either::Left(url)) => {
17942 cx.update(|window, cx| {
17943 if parse_zed_link(&url, cx).is_some() {
17944 window
17945 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17946 } else {
17947 cx.open_url(&url);
17948 }
17949 })?;
17950 Ok(Navigated::Yes)
17951 }
17952 Some(Either::Right(path)) => {
17953 // TODO(andrew): respect preview tab settings
17954 // `enable_keep_preview_on_code_navigation` and
17955 // `enable_preview_file_from_code_navigation`
17956 let Some(workspace) = workspace else {
17957 return Ok(Navigated::No);
17958 };
17959 workspace
17960 .update_in(cx, |workspace, window, cx| {
17961 workspace.open_resolved_path(path, window, cx)
17962 })?
17963 .await?;
17964 Ok(Navigated::Yes)
17965 }
17966 None => Ok(Navigated::No),
17967 }
17968 } else {
17969 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17970 let target_range = target_ranges.first().unwrap().clone();
17971
17972 editor.update_in(cx, |editor, window, cx| {
17973 let range = editor.range_for_match(&target_range);
17974 let range = collapse_multiline_range(range);
17975
17976 if !split
17977 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17978 {
17979 editor.go_to_singleton_buffer_range(range, window, cx);
17980
17981 let target =
17982 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
17983 if let Some(mut nav_history) = editor.nav_history.clone() {
17984 nav_history.push_tag(origin, target);
17985 }
17986 } else {
17987 let Some(workspace) = workspace else {
17988 return Navigated::No;
17989 };
17990 let pane = workspace.read(cx).active_pane().clone();
17991 window.defer(cx, move |window, cx| {
17992 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
17993 workspace.update(cx, |workspace, cx| {
17994 let pane = if split {
17995 workspace.adjacent_pane(window, cx)
17996 } else {
17997 workspace.active_pane().clone()
17998 };
17999
18000 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18001 let keep_old_preview = preview_tabs_settings
18002 .enable_keep_preview_on_code_navigation;
18003 let allow_new_preview = preview_tabs_settings
18004 .enable_preview_file_from_code_navigation;
18005
18006 let editor = workspace.open_project_item(
18007 pane.clone(),
18008 target_buffer.clone(),
18009 true,
18010 true,
18011 keep_old_preview,
18012 allow_new_preview,
18013 window,
18014 cx,
18015 );
18016 (editor, pane)
18017 });
18018 // We create our own nav history instead of using
18019 // `target_editor.nav_history` because `nav_history`
18020 // seems to be populated asynchronously when an item
18021 // is added to a pane
18022 let mut nav_history = target_pane
18023 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18024 target_editor.update(cx, |target_editor, cx| {
18025 // When selecting a definition in a different buffer, disable the nav history
18026 // to avoid creating a history entry at the previous cursor location.
18027 pane.update(cx, |pane, _| pane.disable_history());
18028 target_editor.go_to_singleton_buffer_range(range, window, cx);
18029
18030 let nav_data = target_editor.navigation_data(
18031 target_editor.selections.newest_anchor().head(),
18032 cx,
18033 );
18034 let target =
18035 Some(nav_history.navigation_entry(Some(
18036 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18037 )));
18038 nav_history.push_tag(origin, target);
18039 pane.update(cx, |pane, _| pane.enable_history());
18040 });
18041 });
18042 }
18043 Navigated::Yes
18044 })
18045 }
18046 })
18047 }
18048
18049 fn compute_target_location(
18050 &self,
18051 lsp_location: lsp::Location,
18052 server_id: LanguageServerId,
18053 window: &mut Window,
18054 cx: &mut Context<Self>,
18055 ) -> Task<anyhow::Result<Option<Location>>> {
18056 let Some(project) = self.project.clone() else {
18057 return Task::ready(Ok(None));
18058 };
18059
18060 cx.spawn_in(window, async move |editor, cx| {
18061 let location_task = editor.update(cx, |_, cx| {
18062 project.update(cx, |project, cx| {
18063 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18064 })
18065 })?;
18066 let location = Some({
18067 let target_buffer_handle = location_task.await.context("open local buffer")?;
18068 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18069 let target_start = target_buffer
18070 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18071 let target_end = target_buffer
18072 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18073 target_buffer.anchor_after(target_start)
18074 ..target_buffer.anchor_before(target_end)
18075 });
18076 Location {
18077 buffer: target_buffer_handle,
18078 range,
18079 }
18080 });
18081 Ok(location)
18082 })
18083 }
18084
18085 fn go_to_next_reference(
18086 &mut self,
18087 _: &GoToNextReference,
18088 window: &mut Window,
18089 cx: &mut Context<Self>,
18090 ) {
18091 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18092 if let Some(task) = task {
18093 task.detach();
18094 };
18095 }
18096
18097 fn go_to_prev_reference(
18098 &mut self,
18099 _: &GoToPreviousReference,
18100 window: &mut Window,
18101 cx: &mut Context<Self>,
18102 ) {
18103 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18104 if let Some(task) = task {
18105 task.detach();
18106 };
18107 }
18108
18109 pub fn go_to_reference_before_or_after_position(
18110 &mut self,
18111 direction: Direction,
18112 count: usize,
18113 window: &mut Window,
18114 cx: &mut Context<Self>,
18115 ) -> Option<Task<Result<()>>> {
18116 let selection = self.selections.newest_anchor();
18117 let head = selection.head();
18118
18119 let multi_buffer = self.buffer.read(cx);
18120
18121 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18122 let workspace = self.workspace()?;
18123 let project = workspace.read(cx).project().clone();
18124 let references =
18125 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18126 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18127 let Some(locations) = references.await? else {
18128 return Ok(());
18129 };
18130
18131 if locations.is_empty() {
18132 // totally normal - the cursor may be on something which is not
18133 // a symbol (e.g. a keyword)
18134 log::info!("no references found under cursor");
18135 return Ok(());
18136 }
18137
18138 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18139
18140 let (locations, current_location_index) =
18141 multi_buffer.update(cx, |multi_buffer, cx| {
18142 let mut locations = locations
18143 .into_iter()
18144 .filter_map(|loc| {
18145 let start = multi_buffer.buffer_anchor_to_anchor(
18146 &loc.buffer,
18147 loc.range.start,
18148 cx,
18149 )?;
18150 let end = multi_buffer.buffer_anchor_to_anchor(
18151 &loc.buffer,
18152 loc.range.end,
18153 cx,
18154 )?;
18155 Some(start..end)
18156 })
18157 .collect::<Vec<_>>();
18158
18159 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18160 // There is an O(n) implementation, but given this list will be
18161 // small (usually <100 items), the extra O(log(n)) factor isn't
18162 // worth the (surprisingly large amount of) extra complexity.
18163 locations
18164 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18165
18166 let head_offset = head.to_offset(&multi_buffer_snapshot);
18167
18168 let current_location_index = locations.iter().position(|loc| {
18169 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18170 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18171 });
18172
18173 (locations, current_location_index)
18174 });
18175
18176 let Some(current_location_index) = current_location_index else {
18177 // This indicates something has gone wrong, because we already
18178 // handle the "no references" case above
18179 log::error!(
18180 "failed to find current reference under cursor. Total references: {}",
18181 locations.len()
18182 );
18183 return Ok(());
18184 };
18185
18186 let destination_location_index = match direction {
18187 Direction::Next => (current_location_index + count) % locations.len(),
18188 Direction::Prev => {
18189 (current_location_index + locations.len() - count % locations.len())
18190 % locations.len()
18191 }
18192 };
18193
18194 // TODO(cameron): is this needed?
18195 // the thinking is to avoid "jumping to the current location" (avoid
18196 // polluting "jumplist" in vim terms)
18197 if current_location_index == destination_location_index {
18198 return Ok(());
18199 }
18200
18201 let Range { start, end } = locations[destination_location_index];
18202
18203 editor.update_in(cx, |editor, window, cx| {
18204 let effects = SelectionEffects::default();
18205
18206 editor.unfold_ranges(&[start..end], false, false, cx);
18207 editor.change_selections(effects, window, cx, |s| {
18208 s.select_ranges([start..start]);
18209 });
18210 })?;
18211
18212 Ok(())
18213 }))
18214 }
18215
18216 pub fn find_all_references(
18217 &mut self,
18218 action: &FindAllReferences,
18219 window: &mut Window,
18220 cx: &mut Context<Self>,
18221 ) -> Option<Task<Result<Navigated>>> {
18222 let always_open_multibuffer = action.always_open_multibuffer;
18223 let selection = self.selections.newest_anchor();
18224 let multi_buffer = self.buffer.read(cx);
18225 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18226 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18227 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18228 let head = selection_offset.head();
18229
18230 let head_anchor = multi_buffer_snapshot.anchor_at(
18231 head,
18232 if head < selection_offset.tail() {
18233 Bias::Right
18234 } else {
18235 Bias::Left
18236 },
18237 );
18238
18239 match self
18240 .find_all_references_task_sources
18241 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18242 {
18243 Ok(_) => {
18244 log::info!(
18245 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18246 );
18247 return None;
18248 }
18249 Err(i) => {
18250 self.find_all_references_task_sources.insert(i, head_anchor);
18251 }
18252 }
18253
18254 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18255 let workspace = self.workspace()?;
18256 let project = workspace.read(cx).project().clone();
18257 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18258 Some(cx.spawn_in(window, async move |editor, cx| {
18259 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18260 if let Ok(i) = editor
18261 .find_all_references_task_sources
18262 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18263 {
18264 editor.find_all_references_task_sources.remove(i);
18265 }
18266 });
18267
18268 let Some(locations) = references.await? else {
18269 return anyhow::Ok(Navigated::No);
18270 };
18271 let mut locations = cx.update(|_, cx| {
18272 locations
18273 .into_iter()
18274 .map(|location| {
18275 let buffer = location.buffer.read(cx);
18276 (location.buffer, location.range.to_point(buffer))
18277 })
18278 // if special-casing the single-match case, remove ranges
18279 // that intersect current selection
18280 .filter(|(location_buffer, location)| {
18281 if always_open_multibuffer || &buffer != location_buffer {
18282 return true;
18283 }
18284
18285 !location.contains_inclusive(&selection_point.range())
18286 })
18287 .into_group_map()
18288 })?;
18289 if locations.is_empty() {
18290 return anyhow::Ok(Navigated::No);
18291 }
18292 for ranges in locations.values_mut() {
18293 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18294 ranges.dedup();
18295 }
18296 let mut num_locations = 0;
18297 for ranges in locations.values_mut() {
18298 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18299 ranges.dedup();
18300 num_locations += ranges.len();
18301 }
18302
18303 if num_locations == 1 && !always_open_multibuffer {
18304 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18305 let target_range = target_ranges.first().unwrap().clone();
18306
18307 return editor.update_in(cx, |editor, window, cx| {
18308 let range = target_range.to_point(target_buffer.read(cx));
18309 let range = editor.range_for_match(&range);
18310 let range = range.start..range.start;
18311
18312 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18313 editor.go_to_singleton_buffer_range(range, window, cx);
18314 } else {
18315 let pane = workspace.read(cx).active_pane().clone();
18316 window.defer(cx, move |window, cx| {
18317 let target_editor: Entity<Self> =
18318 workspace.update(cx, |workspace, cx| {
18319 let pane = workspace.active_pane().clone();
18320
18321 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18322 let keep_old_preview = preview_tabs_settings
18323 .enable_keep_preview_on_code_navigation;
18324 let allow_new_preview = preview_tabs_settings
18325 .enable_preview_file_from_code_navigation;
18326
18327 workspace.open_project_item(
18328 pane,
18329 target_buffer.clone(),
18330 true,
18331 true,
18332 keep_old_preview,
18333 allow_new_preview,
18334 window,
18335 cx,
18336 )
18337 });
18338 target_editor.update(cx, |target_editor, cx| {
18339 // When selecting a definition in a different buffer, disable the nav history
18340 // to avoid creating a history entry at the previous cursor location.
18341 pane.update(cx, |pane, _| pane.disable_history());
18342 target_editor.go_to_singleton_buffer_range(range, window, cx);
18343 pane.update(cx, |pane, _| pane.enable_history());
18344 });
18345 });
18346 }
18347 Navigated::No
18348 });
18349 }
18350
18351 workspace.update_in(cx, |workspace, window, cx| {
18352 let target = locations
18353 .iter()
18354 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18355 .map(|(buffer, location)| {
18356 buffer
18357 .read(cx)
18358 .text_for_range(location.clone())
18359 .collect::<String>()
18360 })
18361 .filter(|text| !text.contains('\n'))
18362 .unique()
18363 .take(3)
18364 .join(", ");
18365 let title = if target.is_empty() {
18366 "References".to_owned()
18367 } else {
18368 format!("References to {target}")
18369 };
18370 let allow_preview = PreviewTabsSettings::get_global(cx)
18371 .enable_preview_multibuffer_from_code_navigation;
18372 Self::open_locations_in_multibuffer(
18373 workspace,
18374 locations,
18375 title,
18376 false,
18377 allow_preview,
18378 MultibufferSelectionMode::First,
18379 window,
18380 cx,
18381 );
18382 Navigated::Yes
18383 })
18384 }))
18385 }
18386
18387 /// Opens a multibuffer with the given project locations in it.
18388 pub fn open_locations_in_multibuffer(
18389 workspace: &mut Workspace,
18390 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18391 title: String,
18392 split: bool,
18393 allow_preview: bool,
18394 multibuffer_selection_mode: MultibufferSelectionMode,
18395 window: &mut Window,
18396 cx: &mut Context<Workspace>,
18397 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18398 if locations.is_empty() {
18399 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18400 return None;
18401 }
18402
18403 let capability = workspace.project().read(cx).capability();
18404 let mut ranges = <Vec<Range<Anchor>>>::new();
18405
18406 // a key to find existing multibuffer editors with the same set of locations
18407 // to prevent us from opening more and more multibuffer tabs for searches and the like
18408 let mut key = (title.clone(), vec![]);
18409 let excerpt_buffer = cx.new(|cx| {
18410 let key = &mut key.1;
18411 let mut multibuffer = MultiBuffer::new(capability);
18412 for (buffer, mut ranges_for_buffer) in locations {
18413 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18414 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18415 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18416 PathKey::for_buffer(&buffer, cx),
18417 buffer.clone(),
18418 ranges_for_buffer,
18419 multibuffer_context_lines(cx),
18420 cx,
18421 );
18422 ranges.extend(new_ranges)
18423 }
18424
18425 multibuffer.with_title(title)
18426 });
18427 let existing = workspace.active_pane().update(cx, |pane, cx| {
18428 pane.items()
18429 .filter_map(|item| item.downcast::<Editor>())
18430 .find(|editor| {
18431 editor
18432 .read(cx)
18433 .lookup_key
18434 .as_ref()
18435 .and_then(|it| {
18436 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18437 })
18438 .is_some_and(|it| *it == key)
18439 })
18440 });
18441 let was_existing = existing.is_some();
18442 let editor = existing.unwrap_or_else(|| {
18443 cx.new(|cx| {
18444 let mut editor = Editor::for_multibuffer(
18445 excerpt_buffer,
18446 Some(workspace.project().clone()),
18447 window,
18448 cx,
18449 );
18450 editor.lookup_key = Some(Box::new(key));
18451 editor
18452 })
18453 });
18454 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18455 MultibufferSelectionMode::First => {
18456 if let Some(first_range) = ranges.first() {
18457 editor.change_selections(
18458 SelectionEffects::no_scroll(),
18459 window,
18460 cx,
18461 |selections| {
18462 selections.clear_disjoint();
18463 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18464 },
18465 );
18466 }
18467 editor.highlight_background(
18468 HighlightKey::Editor,
18469 &ranges,
18470 |_, theme| theme.colors().editor_highlighted_line_background,
18471 cx,
18472 );
18473 }
18474 MultibufferSelectionMode::All => {
18475 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18476 selections.clear_disjoint();
18477 selections.select_anchor_ranges(ranges);
18478 });
18479 }
18480 });
18481
18482 let item = Box::new(editor.clone());
18483
18484 let pane = if split {
18485 workspace.adjacent_pane(window, cx)
18486 } else {
18487 workspace.active_pane().clone()
18488 };
18489 let activate_pane = split;
18490
18491 let mut destination_index = None;
18492 pane.update(cx, |pane, cx| {
18493 if allow_preview && !was_existing {
18494 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18495 }
18496 if was_existing && !allow_preview {
18497 pane.unpreview_item_if_preview(item.item_id());
18498 }
18499 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18500 });
18501
18502 Some((editor, pane))
18503 }
18504
18505 pub fn rename(
18506 &mut self,
18507 _: &Rename,
18508 window: &mut Window,
18509 cx: &mut Context<Self>,
18510 ) -> Option<Task<Result<()>>> {
18511 use language::ToOffset as _;
18512
18513 let provider = self.semantics_provider.clone()?;
18514 let selection = self.selections.newest_anchor().clone();
18515 let (cursor_buffer, cursor_buffer_position) = self
18516 .buffer
18517 .read(cx)
18518 .text_anchor_for_position(selection.head(), cx)?;
18519 let (tail_buffer, cursor_buffer_position_end) = self
18520 .buffer
18521 .read(cx)
18522 .text_anchor_for_position(selection.tail(), cx)?;
18523 if tail_buffer != cursor_buffer {
18524 return None;
18525 }
18526
18527 let snapshot = cursor_buffer.read(cx).snapshot();
18528 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18529 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18530 let prepare_rename = provider
18531 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18532 .unwrap_or_else(|| Task::ready(Ok(None)));
18533 drop(snapshot);
18534
18535 Some(cx.spawn_in(window, async move |this, cx| {
18536 let rename_range = if let Some(range) = prepare_rename.await? {
18537 Some(range)
18538 } else {
18539 this.update(cx, |this, cx| {
18540 let buffer = this.buffer.read(cx).snapshot(cx);
18541 let mut buffer_highlights = this
18542 .document_highlights_for_position(selection.head(), &buffer)
18543 .filter(|highlight| {
18544 highlight.start.excerpt_id == selection.head().excerpt_id
18545 && highlight.end.excerpt_id == selection.head().excerpt_id
18546 });
18547 buffer_highlights
18548 .next()
18549 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18550 })?
18551 };
18552 if let Some(rename_range) = rename_range {
18553 this.update_in(cx, |this, window, cx| {
18554 let snapshot = cursor_buffer.read(cx).snapshot();
18555 let rename_buffer_range = rename_range.to_offset(&snapshot);
18556 let cursor_offset_in_rename_range =
18557 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18558 let cursor_offset_in_rename_range_end =
18559 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18560
18561 this.take_rename(false, window, cx);
18562 let buffer = this.buffer.read(cx).read(cx);
18563 let cursor_offset = selection.head().to_offset(&buffer);
18564 let rename_start =
18565 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18566 let rename_end = rename_start + rename_buffer_range.len();
18567 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18568 let mut old_highlight_id = None;
18569 let old_name: Arc<str> = buffer
18570 .chunks(rename_start..rename_end, true)
18571 .map(|chunk| {
18572 if old_highlight_id.is_none() {
18573 old_highlight_id = chunk.syntax_highlight_id;
18574 }
18575 chunk.text
18576 })
18577 .collect::<String>()
18578 .into();
18579
18580 drop(buffer);
18581
18582 // Position the selection in the rename editor so that it matches the current selection.
18583 this.show_local_selections = false;
18584 let rename_editor = cx.new(|cx| {
18585 let mut editor = Editor::single_line(window, cx);
18586 editor.buffer.update(cx, |buffer, cx| {
18587 buffer.edit(
18588 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18589 None,
18590 cx,
18591 )
18592 });
18593 let cursor_offset_in_rename_range =
18594 MultiBufferOffset(cursor_offset_in_rename_range);
18595 let cursor_offset_in_rename_range_end =
18596 MultiBufferOffset(cursor_offset_in_rename_range_end);
18597 let rename_selection_range = match cursor_offset_in_rename_range
18598 .cmp(&cursor_offset_in_rename_range_end)
18599 {
18600 Ordering::Equal => {
18601 editor.select_all(&SelectAll, window, cx);
18602 return editor;
18603 }
18604 Ordering::Less => {
18605 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18606 }
18607 Ordering::Greater => {
18608 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18609 }
18610 };
18611 if rename_selection_range.end.0 > old_name.len() {
18612 editor.select_all(&SelectAll, window, cx);
18613 } else {
18614 editor.change_selections(Default::default(), window, cx, |s| {
18615 s.select_ranges([rename_selection_range]);
18616 });
18617 }
18618 editor
18619 });
18620 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18621 if e == &EditorEvent::Focused {
18622 cx.emit(EditorEvent::FocusedIn)
18623 }
18624 })
18625 .detach();
18626
18627 let write_highlights =
18628 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
18629 let read_highlights =
18630 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
18631 let ranges = write_highlights
18632 .iter()
18633 .flat_map(|(_, ranges)| ranges.iter())
18634 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18635 .cloned()
18636 .collect();
18637
18638 this.highlight_text(
18639 HighlightKey::Rename,
18640 ranges,
18641 HighlightStyle {
18642 fade_out: Some(0.6),
18643 ..Default::default()
18644 },
18645 cx,
18646 );
18647 let rename_focus_handle = rename_editor.focus_handle(cx);
18648 window.focus(&rename_focus_handle, cx);
18649 let block_id = this.insert_blocks(
18650 [BlockProperties {
18651 style: BlockStyle::Flex,
18652 placement: BlockPlacement::Below(range.start),
18653 height: Some(1),
18654 render: Arc::new({
18655 let rename_editor = rename_editor.clone();
18656 move |cx: &mut BlockContext| {
18657 let mut text_style = cx.editor_style.text.clone();
18658 if let Some(highlight_style) = old_highlight_id
18659 .and_then(|h| h.style(&cx.editor_style.syntax))
18660 {
18661 text_style = text_style.highlight(highlight_style);
18662 }
18663 div()
18664 .block_mouse_except_scroll()
18665 .pl(cx.anchor_x)
18666 .child(EditorElement::new(
18667 &rename_editor,
18668 EditorStyle {
18669 background: cx.theme().system().transparent,
18670 local_player: cx.editor_style.local_player,
18671 text: text_style,
18672 scrollbar_width: cx.editor_style.scrollbar_width,
18673 syntax: cx.editor_style.syntax.clone(),
18674 status: cx.editor_style.status.clone(),
18675 inlay_hints_style: HighlightStyle {
18676 font_weight: Some(FontWeight::BOLD),
18677 ..make_inlay_hints_style(cx.app)
18678 },
18679 edit_prediction_styles: make_suggestion_styles(
18680 cx.app,
18681 ),
18682 ..EditorStyle::default()
18683 },
18684 ))
18685 .into_any_element()
18686 }
18687 }),
18688 priority: 0,
18689 }],
18690 Some(Autoscroll::fit()),
18691 cx,
18692 )[0];
18693 this.pending_rename = Some(RenameState {
18694 range,
18695 old_name,
18696 editor: rename_editor,
18697 block_id,
18698 });
18699 })?;
18700 }
18701
18702 Ok(())
18703 }))
18704 }
18705
18706 pub fn confirm_rename(
18707 &mut self,
18708 _: &ConfirmRename,
18709 window: &mut Window,
18710 cx: &mut Context<Self>,
18711 ) -> Option<Task<Result<()>>> {
18712 let rename = self.take_rename(false, window, cx)?;
18713 let workspace = self.workspace()?.downgrade();
18714 let (buffer, start) = self
18715 .buffer
18716 .read(cx)
18717 .text_anchor_for_position(rename.range.start, cx)?;
18718 let (end_buffer, _) = self
18719 .buffer
18720 .read(cx)
18721 .text_anchor_for_position(rename.range.end, cx)?;
18722 if buffer != end_buffer {
18723 return None;
18724 }
18725
18726 let old_name = rename.old_name;
18727 let new_name = rename.editor.read(cx).text(cx);
18728
18729 let rename = self.semantics_provider.as_ref()?.perform_rename(
18730 &buffer,
18731 start,
18732 new_name.clone(),
18733 cx,
18734 )?;
18735
18736 Some(cx.spawn_in(window, async move |editor, cx| {
18737 let project_transaction = rename.await?;
18738 Self::open_project_transaction(
18739 &editor,
18740 workspace,
18741 project_transaction,
18742 format!("Rename: {} → {}", old_name, new_name),
18743 cx,
18744 )
18745 .await?;
18746
18747 editor.update(cx, |editor, cx| {
18748 editor.refresh_document_highlights(cx);
18749 })?;
18750 Ok(())
18751 }))
18752 }
18753
18754 fn take_rename(
18755 &mut self,
18756 moving_cursor: bool,
18757 window: &mut Window,
18758 cx: &mut Context<Self>,
18759 ) -> Option<RenameState> {
18760 let rename = self.pending_rename.take()?;
18761 if rename.editor.focus_handle(cx).is_focused(window) {
18762 window.focus(&self.focus_handle, cx);
18763 }
18764
18765 self.remove_blocks(
18766 [rename.block_id].into_iter().collect(),
18767 Some(Autoscroll::fit()),
18768 cx,
18769 );
18770 self.clear_highlights(HighlightKey::Rename, cx);
18771 self.show_local_selections = true;
18772
18773 if moving_cursor {
18774 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18775 editor
18776 .selections
18777 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18778 .head()
18779 });
18780
18781 // Update the selection to match the position of the selection inside
18782 // the rename editor.
18783 let snapshot = self.buffer.read(cx).read(cx);
18784 let rename_range = rename.range.to_offset(&snapshot);
18785 let cursor_in_editor = snapshot
18786 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18787 .min(rename_range.end);
18788 drop(snapshot);
18789
18790 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18791 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18792 });
18793 } else {
18794 self.refresh_document_highlights(cx);
18795 }
18796
18797 Some(rename)
18798 }
18799
18800 pub fn pending_rename(&self) -> Option<&RenameState> {
18801 self.pending_rename.as_ref()
18802 }
18803
18804 fn format(
18805 &mut self,
18806 _: &Format,
18807 window: &mut Window,
18808 cx: &mut Context<Self>,
18809 ) -> Option<Task<Result<()>>> {
18810 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18811
18812 let project = match &self.project {
18813 Some(project) => project.clone(),
18814 None => return None,
18815 };
18816
18817 Some(self.perform_format(
18818 project,
18819 FormatTrigger::Manual,
18820 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18821 window,
18822 cx,
18823 ))
18824 }
18825
18826 fn format_selections(
18827 &mut self,
18828 _: &FormatSelections,
18829 window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) -> Option<Task<Result<()>>> {
18832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18833
18834 let project = match &self.project {
18835 Some(project) => project.clone(),
18836 None => return None,
18837 };
18838
18839 let ranges = self
18840 .selections
18841 .all_adjusted(&self.display_snapshot(cx))
18842 .into_iter()
18843 .map(|selection| selection.range())
18844 .collect_vec();
18845
18846 Some(self.perform_format(
18847 project,
18848 FormatTrigger::Manual,
18849 FormatTarget::Ranges(ranges),
18850 window,
18851 cx,
18852 ))
18853 }
18854
18855 fn perform_format(
18856 &mut self,
18857 project: Entity<Project>,
18858 trigger: FormatTrigger,
18859 target: FormatTarget,
18860 window: &mut Window,
18861 cx: &mut Context<Self>,
18862 ) -> Task<Result<()>> {
18863 let buffer = self.buffer.clone();
18864 let (buffers, target) = match target {
18865 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18866 FormatTarget::Ranges(selection_ranges) => {
18867 let multi_buffer = buffer.read(cx);
18868 let snapshot = multi_buffer.read(cx);
18869 let mut buffers = HashSet::default();
18870 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18871 BTreeMap::new();
18872 for selection_range in selection_ranges {
18873 for (buffer, buffer_range, _) in
18874 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
18875 {
18876 let buffer_id = buffer.remote_id();
18877 let start = buffer.anchor_before(buffer_range.start);
18878 let end = buffer.anchor_after(buffer_range.end);
18879 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18880 buffer_id_to_ranges
18881 .entry(buffer_id)
18882 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18883 .or_insert_with(|| vec![start..end]);
18884 }
18885 }
18886 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18887 }
18888 };
18889
18890 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18891 let selections_prev = transaction_id_prev
18892 .and_then(|transaction_id_prev| {
18893 // default to selections as they were after the last edit, if we have them,
18894 // instead of how they are now.
18895 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18896 // will take you back to where you made the last edit, instead of staying where you scrolled
18897 self.selection_history
18898 .transaction(transaction_id_prev)
18899 .map(|t| t.0.clone())
18900 })
18901 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18902
18903 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18904 let format = project.update(cx, |project, cx| {
18905 project.format(buffers, target, true, trigger, cx)
18906 });
18907
18908 cx.spawn_in(window, async move |editor, cx| {
18909 let transaction = futures::select_biased! {
18910 transaction = format.log_err().fuse() => transaction,
18911 () = timeout => {
18912 log::warn!("timed out waiting for formatting");
18913 None
18914 }
18915 };
18916
18917 buffer.update(cx, |buffer, cx| {
18918 if let Some(transaction) = transaction
18919 && !buffer.is_singleton()
18920 {
18921 buffer.push_transaction(&transaction.0, cx);
18922 }
18923 cx.notify();
18924 });
18925
18926 if let Some(transaction_id_now) =
18927 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18928 {
18929 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18930 if has_new_transaction {
18931 editor
18932 .update(cx, |editor, _| {
18933 editor
18934 .selection_history
18935 .insert_transaction(transaction_id_now, selections_prev);
18936 })
18937 .ok();
18938 }
18939 }
18940
18941 Ok(())
18942 })
18943 }
18944
18945 fn organize_imports(
18946 &mut self,
18947 _: &OrganizeImports,
18948 window: &mut Window,
18949 cx: &mut Context<Self>,
18950 ) -> Option<Task<Result<()>>> {
18951 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18952 let project = match &self.project {
18953 Some(project) => project.clone(),
18954 None => return None,
18955 };
18956 Some(self.perform_code_action_kind(
18957 project,
18958 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18959 window,
18960 cx,
18961 ))
18962 }
18963
18964 fn perform_code_action_kind(
18965 &mut self,
18966 project: Entity<Project>,
18967 kind: CodeActionKind,
18968 window: &mut Window,
18969 cx: &mut Context<Self>,
18970 ) -> Task<Result<()>> {
18971 let buffer = self.buffer.clone();
18972 let buffers = buffer.read(cx).all_buffers();
18973 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18974 let apply_action = project.update(cx, |project, cx| {
18975 project.apply_code_action_kind(buffers, kind, true, cx)
18976 });
18977 cx.spawn_in(window, async move |_, cx| {
18978 let transaction = futures::select_biased! {
18979 () = timeout => {
18980 log::warn!("timed out waiting for executing code action");
18981 None
18982 }
18983 transaction = apply_action.log_err().fuse() => transaction,
18984 };
18985 buffer.update(cx, |buffer, cx| {
18986 // check if we need this
18987 if let Some(transaction) = transaction
18988 && !buffer.is_singleton()
18989 {
18990 buffer.push_transaction(&transaction.0, cx);
18991 }
18992 cx.notify();
18993 });
18994 Ok(())
18995 })
18996 }
18997
18998 pub fn restart_language_server(
18999 &mut self,
19000 _: &RestartLanguageServer,
19001 _: &mut Window,
19002 cx: &mut Context<Self>,
19003 ) {
19004 if let Some(project) = self.project.clone() {
19005 self.buffer.update(cx, |multi_buffer, cx| {
19006 project.update(cx, |project, cx| {
19007 project.restart_language_servers_for_buffers(
19008 multi_buffer.all_buffers().into_iter().collect(),
19009 HashSet::default(),
19010 cx,
19011 );
19012 });
19013 })
19014 }
19015 }
19016
19017 pub fn stop_language_server(
19018 &mut self,
19019 _: &StopLanguageServer,
19020 _: &mut Window,
19021 cx: &mut Context<Self>,
19022 ) {
19023 if let Some(project) = self.project.clone() {
19024 self.buffer.update(cx, |multi_buffer, cx| {
19025 project.update(cx, |project, cx| {
19026 project.stop_language_servers_for_buffers(
19027 multi_buffer.all_buffers().into_iter().collect(),
19028 HashSet::default(),
19029 cx,
19030 );
19031 });
19032 });
19033 }
19034 }
19035
19036 fn cancel_language_server_work(
19037 workspace: &mut Workspace,
19038 _: &actions::CancelLanguageServerWork,
19039 _: &mut Window,
19040 cx: &mut Context<Workspace>,
19041 ) {
19042 let project = workspace.project();
19043 let buffers = workspace
19044 .active_item(cx)
19045 .and_then(|item| item.act_as::<Editor>(cx))
19046 .map_or(HashSet::default(), |editor| {
19047 editor.read(cx).buffer.read(cx).all_buffers()
19048 });
19049 project.update(cx, |project, cx| {
19050 project.cancel_language_server_work_for_buffers(buffers, cx);
19051 });
19052 }
19053
19054 fn show_character_palette(
19055 &mut self,
19056 _: &ShowCharacterPalette,
19057 window: &mut Window,
19058 _: &mut Context<Self>,
19059 ) {
19060 window.show_character_palette();
19061 }
19062
19063 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19064 if !self.diagnostics_enabled() {
19065 return;
19066 }
19067
19068 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19069 let buffer = self.buffer.read(cx).snapshot(cx);
19070 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19071 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19072 let is_valid = buffer
19073 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19074 .any(|entry| {
19075 entry.diagnostic.is_primary
19076 && !entry.range.is_empty()
19077 && entry.range.start == primary_range_start
19078 && entry.diagnostic.message == active_diagnostics.active_message
19079 });
19080
19081 if !is_valid {
19082 self.dismiss_diagnostics(cx);
19083 }
19084 }
19085 }
19086
19087 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19088 match &self.active_diagnostics {
19089 ActiveDiagnostic::Group(group) => Some(group),
19090 _ => None,
19091 }
19092 }
19093
19094 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19095 if !self.diagnostics_enabled() {
19096 return;
19097 }
19098 self.dismiss_diagnostics(cx);
19099 self.active_diagnostics = ActiveDiagnostic::All;
19100 }
19101
19102 fn activate_diagnostics(
19103 &mut self,
19104 buffer_id: BufferId,
19105 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19106 window: &mut Window,
19107 cx: &mut Context<Self>,
19108 ) {
19109 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19110 return;
19111 }
19112 self.dismiss_diagnostics(cx);
19113 let snapshot = self.snapshot(window, cx);
19114 let buffer = self.buffer.read(cx).snapshot(cx);
19115 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19116 return;
19117 };
19118
19119 let diagnostic_group = buffer
19120 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19121 .collect::<Vec<_>>();
19122
19123 let language_registry = self
19124 .project()
19125 .map(|project| project.read(cx).languages().clone());
19126
19127 let blocks = renderer.render_group(
19128 diagnostic_group,
19129 buffer_id,
19130 snapshot,
19131 cx.weak_entity(),
19132 language_registry,
19133 cx,
19134 );
19135
19136 let blocks = self.display_map.update(cx, |display_map, cx| {
19137 display_map.insert_blocks(blocks, cx).into_iter().collect()
19138 });
19139 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19140 active_range: buffer.anchor_before(diagnostic.range.start)
19141 ..buffer.anchor_after(diagnostic.range.end),
19142 active_message: diagnostic.diagnostic.message.clone(),
19143 group_id: diagnostic.diagnostic.group_id,
19144 blocks,
19145 });
19146 cx.notify();
19147 }
19148
19149 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19150 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19151 return;
19152 };
19153
19154 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19155 if let ActiveDiagnostic::Group(group) = prev {
19156 self.display_map.update(cx, |display_map, cx| {
19157 display_map.remove_blocks(group.blocks, cx);
19158 });
19159 cx.notify();
19160 }
19161 }
19162
19163 /// Disable inline diagnostics rendering for this editor.
19164 pub fn disable_inline_diagnostics(&mut self) {
19165 self.inline_diagnostics_enabled = false;
19166 self.inline_diagnostics_update = Task::ready(());
19167 self.inline_diagnostics.clear();
19168 }
19169
19170 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19171 self.diagnostics_enabled = false;
19172 self.dismiss_diagnostics(cx);
19173 self.inline_diagnostics_update = Task::ready(());
19174 self.inline_diagnostics.clear();
19175 }
19176
19177 pub fn disable_word_completions(&mut self) {
19178 self.word_completions_enabled = false;
19179 }
19180
19181 pub fn diagnostics_enabled(&self) -> bool {
19182 self.diagnostics_enabled && self.lsp_data_enabled()
19183 }
19184
19185 pub fn inline_diagnostics_enabled(&self) -> bool {
19186 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19187 }
19188
19189 pub fn show_inline_diagnostics(&self) -> bool {
19190 self.show_inline_diagnostics
19191 }
19192
19193 pub fn toggle_inline_diagnostics(
19194 &mut self,
19195 _: &ToggleInlineDiagnostics,
19196 window: &mut Window,
19197 cx: &mut Context<Editor>,
19198 ) {
19199 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19200 self.refresh_inline_diagnostics(false, window, cx);
19201 }
19202
19203 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19204 self.diagnostics_max_severity = severity;
19205 self.display_map.update(cx, |display_map, _| {
19206 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19207 });
19208 }
19209
19210 pub fn toggle_diagnostics(
19211 &mut self,
19212 _: &ToggleDiagnostics,
19213 window: &mut Window,
19214 cx: &mut Context<Editor>,
19215 ) {
19216 if !self.diagnostics_enabled() {
19217 return;
19218 }
19219
19220 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19221 EditorSettings::get_global(cx)
19222 .diagnostics_max_severity
19223 .filter(|severity| severity != &DiagnosticSeverity::Off)
19224 .unwrap_or(DiagnosticSeverity::Hint)
19225 } else {
19226 DiagnosticSeverity::Off
19227 };
19228 self.set_max_diagnostics_severity(new_severity, cx);
19229 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19230 self.active_diagnostics = ActiveDiagnostic::None;
19231 self.inline_diagnostics_update = Task::ready(());
19232 self.inline_diagnostics.clear();
19233 } else {
19234 self.refresh_inline_diagnostics(false, window, cx);
19235 }
19236
19237 cx.notify();
19238 }
19239
19240 pub fn toggle_minimap(
19241 &mut self,
19242 _: &ToggleMinimap,
19243 window: &mut Window,
19244 cx: &mut Context<Editor>,
19245 ) {
19246 if self.supports_minimap(cx) {
19247 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19248 }
19249 }
19250
19251 fn refresh_inline_diagnostics(
19252 &mut self,
19253 debounce: bool,
19254 window: &mut Window,
19255 cx: &mut Context<Self>,
19256 ) {
19257 let max_severity = ProjectSettings::get_global(cx)
19258 .diagnostics
19259 .inline
19260 .max_severity
19261 .unwrap_or(self.diagnostics_max_severity);
19262
19263 if !self.inline_diagnostics_enabled()
19264 || !self.diagnostics_enabled()
19265 || !self.show_inline_diagnostics
19266 || max_severity == DiagnosticSeverity::Off
19267 {
19268 self.inline_diagnostics_update = Task::ready(());
19269 self.inline_diagnostics.clear();
19270 return;
19271 }
19272
19273 let debounce_ms = ProjectSettings::get_global(cx)
19274 .diagnostics
19275 .inline
19276 .update_debounce_ms;
19277 let debounce = if debounce && debounce_ms > 0 {
19278 Some(Duration::from_millis(debounce_ms))
19279 } else {
19280 None
19281 };
19282 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19283 if let Some(debounce) = debounce {
19284 cx.background_executor().timer(debounce).await;
19285 }
19286 let Some(snapshot) = editor.upgrade().map(|editor| {
19287 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19288 }) else {
19289 return;
19290 };
19291
19292 let new_inline_diagnostics = cx
19293 .background_spawn(async move {
19294 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19295 for diagnostic_entry in
19296 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19297 {
19298 let message = diagnostic_entry
19299 .diagnostic
19300 .message
19301 .split_once('\n')
19302 .map(|(line, _)| line)
19303 .map(SharedString::new)
19304 .unwrap_or_else(|| {
19305 SharedString::new(&*diagnostic_entry.diagnostic.message)
19306 });
19307 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19308 let (Ok(i) | Err(i)) = inline_diagnostics
19309 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19310 inline_diagnostics.insert(
19311 i,
19312 (
19313 start_anchor,
19314 InlineDiagnostic {
19315 message,
19316 group_id: diagnostic_entry.diagnostic.group_id,
19317 start: diagnostic_entry.range.start.to_point(&snapshot),
19318 is_primary: diagnostic_entry.diagnostic.is_primary,
19319 severity: diagnostic_entry.diagnostic.severity,
19320 },
19321 ),
19322 );
19323 }
19324 inline_diagnostics
19325 })
19326 .await;
19327
19328 editor
19329 .update(cx, |editor, cx| {
19330 editor.inline_diagnostics = new_inline_diagnostics;
19331 cx.notify();
19332 })
19333 .ok();
19334 });
19335 }
19336
19337 fn pull_diagnostics(
19338 &mut self,
19339 buffer_id: BufferId,
19340 _window: &Window,
19341 cx: &mut Context<Self>,
19342 ) -> Option<()> {
19343 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19344 // skip any LSP updates for it.
19345
19346 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19347 return None;
19348 }
19349 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19350 .diagnostics
19351 .lsp_pull_diagnostics;
19352 if !pull_diagnostics_settings.enabled {
19353 return None;
19354 }
19355 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19356 let project = self.project()?.downgrade();
19357 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19358
19359 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19360 cx.background_executor().timer(debounce).await;
19361 if let Ok(task) = project.update(cx, |project, cx| {
19362 project.lsp_store().update(cx, |lsp_store, cx| {
19363 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19364 })
19365 }) {
19366 task.await.log_err();
19367 }
19368 project
19369 .update(cx, |project, cx| {
19370 project.lsp_store().update(cx, |lsp_store, cx| {
19371 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19372 })
19373 })
19374 .log_err();
19375 });
19376
19377 Some(())
19378 }
19379
19380 pub fn set_selections_from_remote(
19381 &mut self,
19382 selections: Vec<Selection<Anchor>>,
19383 pending_selection: Option<Selection<Anchor>>,
19384 window: &mut Window,
19385 cx: &mut Context<Self>,
19386 ) {
19387 let old_cursor_position = self.selections.newest_anchor().head();
19388 self.selections
19389 .change_with(&self.display_snapshot(cx), |s| {
19390 s.select_anchors(selections);
19391 if let Some(pending_selection) = pending_selection {
19392 s.set_pending(pending_selection, SelectMode::Character);
19393 } else {
19394 s.clear_pending();
19395 }
19396 });
19397 self.selections_did_change(
19398 false,
19399 &old_cursor_position,
19400 SelectionEffects::default(),
19401 window,
19402 cx,
19403 );
19404 }
19405
19406 pub fn transact(
19407 &mut self,
19408 window: &mut Window,
19409 cx: &mut Context<Self>,
19410 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19411 ) -> Option<TransactionId> {
19412 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19413 this.start_transaction_at(Instant::now(), window, cx);
19414 update(this, window, cx);
19415 this.end_transaction_at(Instant::now(), cx)
19416 })
19417 }
19418
19419 pub fn start_transaction_at(
19420 &mut self,
19421 now: Instant,
19422 window: &mut Window,
19423 cx: &mut Context<Self>,
19424 ) -> Option<TransactionId> {
19425 self.end_selection(window, cx);
19426 if let Some(tx_id) = self
19427 .buffer
19428 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19429 {
19430 self.selection_history
19431 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19432 cx.emit(EditorEvent::TransactionBegun {
19433 transaction_id: tx_id,
19434 });
19435 Some(tx_id)
19436 } else {
19437 None
19438 }
19439 }
19440
19441 pub fn end_transaction_at(
19442 &mut self,
19443 now: Instant,
19444 cx: &mut Context<Self>,
19445 ) -> Option<TransactionId> {
19446 if let Some(transaction_id) = self
19447 .buffer
19448 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19449 {
19450 if let Some((_, end_selections)) =
19451 self.selection_history.transaction_mut(transaction_id)
19452 {
19453 *end_selections = Some(self.selections.disjoint_anchors_arc());
19454 } else {
19455 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19456 }
19457
19458 cx.emit(EditorEvent::Edited { transaction_id });
19459 Some(transaction_id)
19460 } else {
19461 None
19462 }
19463 }
19464
19465 pub fn modify_transaction_selection_history(
19466 &mut self,
19467 transaction_id: TransactionId,
19468 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19469 ) -> bool {
19470 self.selection_history
19471 .transaction_mut(transaction_id)
19472 .map(modify)
19473 .is_some()
19474 }
19475
19476 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19477 if self.selection_mark_mode {
19478 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19479 s.move_with(&mut |_, sel| {
19480 sel.collapse_to(sel.head(), SelectionGoal::None);
19481 });
19482 })
19483 }
19484 self.selection_mark_mode = true;
19485 cx.notify();
19486 }
19487
19488 pub fn swap_selection_ends(
19489 &mut self,
19490 _: &actions::SwapSelectionEnds,
19491 window: &mut Window,
19492 cx: &mut Context<Self>,
19493 ) {
19494 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19495 s.move_with(&mut |_, sel| {
19496 if sel.start != sel.end {
19497 sel.reversed = !sel.reversed
19498 }
19499 });
19500 });
19501 self.request_autoscroll(Autoscroll::newest(), cx);
19502 cx.notify();
19503 }
19504
19505 pub fn toggle_focus(
19506 workspace: &mut Workspace,
19507 _: &actions::ToggleFocus,
19508 window: &mut Window,
19509 cx: &mut Context<Workspace>,
19510 ) {
19511 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19512 return;
19513 };
19514 workspace.activate_item(&item, true, true, window, cx);
19515 }
19516
19517 pub fn toggle_fold(
19518 &mut self,
19519 _: &actions::ToggleFold,
19520 window: &mut Window,
19521 cx: &mut Context<Self>,
19522 ) {
19523 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19524 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19525 let selection = self.selections.newest::<Point>(&display_map);
19526
19527 let range = if selection.is_empty() {
19528 let point = selection.head().to_display_point(&display_map);
19529 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19530 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19531 .to_point(&display_map);
19532 start..end
19533 } else {
19534 selection.range()
19535 };
19536 if display_map.folds_in_range(range).next().is_some() {
19537 self.unfold_lines(&Default::default(), window, cx)
19538 } else {
19539 self.fold(&Default::default(), window, cx)
19540 }
19541 } else {
19542 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19543 let buffer_ids: HashSet<_> = self
19544 .selections
19545 .disjoint_anchor_ranges()
19546 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19547 .collect();
19548
19549 let should_unfold = buffer_ids
19550 .iter()
19551 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19552
19553 for buffer_id in buffer_ids {
19554 if should_unfold {
19555 self.unfold_buffer(buffer_id, cx);
19556 } else {
19557 self.fold_buffer(buffer_id, cx);
19558 }
19559 }
19560 }
19561 }
19562
19563 pub fn toggle_fold_recursive(
19564 &mut self,
19565 _: &actions::ToggleFoldRecursive,
19566 window: &mut Window,
19567 cx: &mut Context<Self>,
19568 ) {
19569 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19570
19571 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19572 let range = if selection.is_empty() {
19573 let point = selection.head().to_display_point(&display_map);
19574 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19575 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19576 .to_point(&display_map);
19577 start..end
19578 } else {
19579 selection.range()
19580 };
19581 if display_map.folds_in_range(range).next().is_some() {
19582 self.unfold_recursive(&Default::default(), window, cx)
19583 } else {
19584 self.fold_recursive(&Default::default(), window, cx)
19585 }
19586 }
19587
19588 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19589 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19590 let mut to_fold = Vec::new();
19591 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19592 let selections = self.selections.all_adjusted(&display_map);
19593
19594 for selection in selections {
19595 let range = selection.range().sorted();
19596 let buffer_start_row = range.start.row;
19597
19598 if range.start.row != range.end.row {
19599 let mut found = false;
19600 let mut row = range.start.row;
19601 while row <= range.end.row {
19602 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19603 {
19604 found = true;
19605 row = crease.range().end.row + 1;
19606 to_fold.push(crease);
19607 } else {
19608 row += 1
19609 }
19610 }
19611 if found {
19612 continue;
19613 }
19614 }
19615
19616 for row in (0..=range.start.row).rev() {
19617 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19618 && crease.range().end.row >= buffer_start_row
19619 {
19620 to_fold.push(crease);
19621 if row <= range.start.row {
19622 break;
19623 }
19624 }
19625 }
19626 }
19627
19628 self.fold_creases(to_fold, true, window, cx);
19629 } else {
19630 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19631 let buffer_ids = self
19632 .selections
19633 .disjoint_anchor_ranges()
19634 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19635 .collect::<HashSet<_>>();
19636 for buffer_id in buffer_ids {
19637 self.fold_buffer(buffer_id, cx);
19638 }
19639 }
19640 }
19641
19642 pub fn toggle_fold_all(
19643 &mut self,
19644 _: &actions::ToggleFoldAll,
19645 window: &mut Window,
19646 cx: &mut Context<Self>,
19647 ) {
19648 let has_folds = if self.buffer.read(cx).is_singleton() {
19649 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19650 let has_folds = display_map
19651 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19652 .next()
19653 .is_some();
19654 has_folds
19655 } else {
19656 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19657 let has_folds = buffer_ids
19658 .iter()
19659 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19660 has_folds
19661 };
19662
19663 if has_folds {
19664 self.unfold_all(&actions::UnfoldAll, window, cx);
19665 } else {
19666 self.fold_all(&actions::FoldAll, window, cx);
19667 }
19668 }
19669
19670 fn fold_at_level(
19671 &mut self,
19672 fold_at: &FoldAtLevel,
19673 window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 if !self.buffer.read(cx).is_singleton() {
19677 return;
19678 }
19679
19680 let fold_at_level = fold_at.0;
19681 let snapshot = self.buffer.read(cx).snapshot(cx);
19682 let mut to_fold = Vec::new();
19683 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19684
19685 let row_ranges_to_keep: Vec<Range<u32>> = self
19686 .selections
19687 .all::<Point>(&self.display_snapshot(cx))
19688 .into_iter()
19689 .map(|sel| sel.start.row..sel.end.row)
19690 .collect();
19691
19692 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19693 while start_row < end_row {
19694 match self
19695 .snapshot(window, cx)
19696 .crease_for_buffer_row(MultiBufferRow(start_row))
19697 {
19698 Some(crease) => {
19699 let nested_start_row = crease.range().start.row + 1;
19700 let nested_end_row = crease.range().end.row;
19701
19702 if current_level < fold_at_level {
19703 stack.push((nested_start_row, nested_end_row, current_level + 1));
19704 } else if current_level == fold_at_level {
19705 // Fold iff there is no selection completely contained within the fold region
19706 if !row_ranges_to_keep.iter().any(|selection| {
19707 selection.end >= nested_start_row
19708 && selection.start <= nested_end_row
19709 }) {
19710 to_fold.push(crease);
19711 }
19712 }
19713
19714 start_row = nested_end_row + 1;
19715 }
19716 None => start_row += 1,
19717 }
19718 }
19719 }
19720
19721 self.fold_creases(to_fold, true, window, cx);
19722 }
19723
19724 pub fn fold_at_level_1(
19725 &mut self,
19726 _: &actions::FoldAtLevel1,
19727 window: &mut Window,
19728 cx: &mut Context<Self>,
19729 ) {
19730 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19731 }
19732
19733 pub fn fold_at_level_2(
19734 &mut self,
19735 _: &actions::FoldAtLevel2,
19736 window: &mut Window,
19737 cx: &mut Context<Self>,
19738 ) {
19739 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19740 }
19741
19742 pub fn fold_at_level_3(
19743 &mut self,
19744 _: &actions::FoldAtLevel3,
19745 window: &mut Window,
19746 cx: &mut Context<Self>,
19747 ) {
19748 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19749 }
19750
19751 pub fn fold_at_level_4(
19752 &mut self,
19753 _: &actions::FoldAtLevel4,
19754 window: &mut Window,
19755 cx: &mut Context<Self>,
19756 ) {
19757 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19758 }
19759
19760 pub fn fold_at_level_5(
19761 &mut self,
19762 _: &actions::FoldAtLevel5,
19763 window: &mut Window,
19764 cx: &mut Context<Self>,
19765 ) {
19766 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19767 }
19768
19769 pub fn fold_at_level_6(
19770 &mut self,
19771 _: &actions::FoldAtLevel6,
19772 window: &mut Window,
19773 cx: &mut Context<Self>,
19774 ) {
19775 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19776 }
19777
19778 pub fn fold_at_level_7(
19779 &mut self,
19780 _: &actions::FoldAtLevel7,
19781 window: &mut Window,
19782 cx: &mut Context<Self>,
19783 ) {
19784 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19785 }
19786
19787 pub fn fold_at_level_8(
19788 &mut self,
19789 _: &actions::FoldAtLevel8,
19790 window: &mut Window,
19791 cx: &mut Context<Self>,
19792 ) {
19793 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19794 }
19795
19796 pub fn fold_at_level_9(
19797 &mut self,
19798 _: &actions::FoldAtLevel9,
19799 window: &mut Window,
19800 cx: &mut Context<Self>,
19801 ) {
19802 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19803 }
19804
19805 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19806 if self.buffer.read(cx).is_singleton() {
19807 let mut fold_ranges = Vec::new();
19808 let snapshot = self.buffer.read(cx).snapshot(cx);
19809
19810 for row in 0..snapshot.max_row().0 {
19811 if let Some(foldable_range) = self
19812 .snapshot(window, cx)
19813 .crease_for_buffer_row(MultiBufferRow(row))
19814 {
19815 fold_ranges.push(foldable_range);
19816 }
19817 }
19818
19819 self.fold_creases(fold_ranges, true, window, cx);
19820 } else {
19821 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19822 editor
19823 .update_in(cx, |editor, _, cx| {
19824 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19825 editor.fold_buffer(buffer_id, cx);
19826 }
19827 })
19828 .ok();
19829 });
19830 }
19831 }
19832
19833 pub fn fold_function_bodies(
19834 &mut self,
19835 _: &actions::FoldFunctionBodies,
19836 window: &mut Window,
19837 cx: &mut Context<Self>,
19838 ) {
19839 let snapshot = self.buffer.read(cx).snapshot(cx);
19840
19841 let ranges = snapshot
19842 .text_object_ranges(
19843 MultiBufferOffset(0)..snapshot.len(),
19844 TreeSitterOptions::default(),
19845 )
19846 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19847 .collect::<Vec<_>>();
19848
19849 let creases = ranges
19850 .into_iter()
19851 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19852 .collect();
19853
19854 self.fold_creases(creases, true, window, cx);
19855 }
19856
19857 pub fn fold_recursive(
19858 &mut self,
19859 _: &actions::FoldRecursive,
19860 window: &mut Window,
19861 cx: &mut Context<Self>,
19862 ) {
19863 let mut to_fold = Vec::new();
19864 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19865 let selections = self.selections.all_adjusted(&display_map);
19866
19867 for selection in selections {
19868 let range = selection.range().sorted();
19869 let buffer_start_row = range.start.row;
19870
19871 if range.start.row != range.end.row {
19872 let mut found = false;
19873 for row in range.start.row..=range.end.row {
19874 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19875 found = true;
19876 to_fold.push(crease);
19877 }
19878 }
19879 if found {
19880 continue;
19881 }
19882 }
19883
19884 for row in (0..=range.start.row).rev() {
19885 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19886 if crease.range().end.row >= buffer_start_row {
19887 to_fold.push(crease);
19888 } else {
19889 break;
19890 }
19891 }
19892 }
19893 }
19894
19895 self.fold_creases(to_fold, true, window, cx);
19896 }
19897
19898 pub fn fold_at(
19899 &mut self,
19900 buffer_row: MultiBufferRow,
19901 window: &mut Window,
19902 cx: &mut Context<Self>,
19903 ) {
19904 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19905
19906 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19907 let autoscroll = self
19908 .selections
19909 .all::<Point>(&display_map)
19910 .iter()
19911 .any(|selection| crease.range().overlaps(&selection.range()));
19912
19913 self.fold_creases(vec![crease], autoscroll, window, cx);
19914 }
19915 }
19916
19917 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19918 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19919 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19920 let buffer = display_map.buffer_snapshot();
19921 let selections = self.selections.all::<Point>(&display_map);
19922 let ranges = selections
19923 .iter()
19924 .map(|s| {
19925 let range = s.display_range(&display_map).sorted();
19926 let mut start = range.start.to_point(&display_map);
19927 let mut end = range.end.to_point(&display_map);
19928 start.column = 0;
19929 end.column = buffer.line_len(MultiBufferRow(end.row));
19930 start..end
19931 })
19932 .collect::<Vec<_>>();
19933
19934 self.unfold_ranges(&ranges, true, true, cx);
19935 } else {
19936 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19937 let buffer_ids = self
19938 .selections
19939 .disjoint_anchor_ranges()
19940 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19941 .collect::<HashSet<_>>();
19942 for buffer_id in buffer_ids {
19943 self.unfold_buffer(buffer_id, cx);
19944 }
19945 }
19946 }
19947
19948 pub fn unfold_recursive(
19949 &mut self,
19950 _: &UnfoldRecursive,
19951 _window: &mut Window,
19952 cx: &mut Context<Self>,
19953 ) {
19954 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19955 let selections = self.selections.all::<Point>(&display_map);
19956 let ranges = selections
19957 .iter()
19958 .map(|s| {
19959 let mut range = s.display_range(&display_map).sorted();
19960 *range.start.column_mut() = 0;
19961 *range.end.column_mut() = display_map.line_len(range.end.row());
19962 let start = range.start.to_point(&display_map);
19963 let end = range.end.to_point(&display_map);
19964 start..end
19965 })
19966 .collect::<Vec<_>>();
19967
19968 self.unfold_ranges(&ranges, true, true, cx);
19969 }
19970
19971 pub fn unfold_at(
19972 &mut self,
19973 buffer_row: MultiBufferRow,
19974 _window: &mut Window,
19975 cx: &mut Context<Self>,
19976 ) {
19977 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19978
19979 let intersection_range = Point::new(buffer_row.0, 0)
19980 ..Point::new(
19981 buffer_row.0,
19982 display_map.buffer_snapshot().line_len(buffer_row),
19983 );
19984
19985 let autoscroll = self
19986 .selections
19987 .all::<Point>(&display_map)
19988 .iter()
19989 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19990
19991 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19992 }
19993
19994 pub fn unfold_all(
19995 &mut self,
19996 _: &actions::UnfoldAll,
19997 _window: &mut Window,
19998 cx: &mut Context<Self>,
19999 ) {
20000 if self.buffer.read(cx).is_singleton() {
20001 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20002 self.unfold_ranges(
20003 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20004 true,
20005 true,
20006 cx,
20007 );
20008 } else {
20009 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20010 editor
20011 .update(cx, |editor, cx| {
20012 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20013 editor.unfold_buffer(buffer_id, cx);
20014 }
20015 })
20016 .ok();
20017 });
20018 }
20019 }
20020
20021 pub fn fold_selected_ranges(
20022 &mut self,
20023 _: &FoldSelectedRanges,
20024 window: &mut Window,
20025 cx: &mut Context<Self>,
20026 ) {
20027 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20028 let selections = self.selections.all_adjusted(&display_map);
20029 let ranges = selections
20030 .into_iter()
20031 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20032 .collect::<Vec<_>>();
20033 self.fold_creases(ranges, true, window, cx);
20034 }
20035
20036 pub fn fold_ranges<T: ToOffset + Clone>(
20037 &mut self,
20038 ranges: Vec<Range<T>>,
20039 auto_scroll: bool,
20040 window: &mut Window,
20041 cx: &mut Context<Self>,
20042 ) {
20043 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20044 let ranges = ranges
20045 .into_iter()
20046 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20047 .collect::<Vec<_>>();
20048 self.fold_creases(ranges, auto_scroll, window, cx);
20049 }
20050
20051 pub fn fold_creases<T: ToOffset + Clone>(
20052 &mut self,
20053 creases: Vec<Crease<T>>,
20054 auto_scroll: bool,
20055 _window: &mut Window,
20056 cx: &mut Context<Self>,
20057 ) {
20058 if creases.is_empty() {
20059 return;
20060 }
20061
20062 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20063
20064 if auto_scroll {
20065 self.request_autoscroll(Autoscroll::fit(), cx);
20066 }
20067
20068 cx.notify();
20069
20070 self.scrollbar_marker_state.dirty = true;
20071 self.folds_did_change(cx);
20072 }
20073
20074 /// Removes any folds whose ranges intersect any of the given ranges.
20075 pub fn unfold_ranges<T: ToOffset + Clone>(
20076 &mut self,
20077 ranges: &[Range<T>],
20078 inclusive: bool,
20079 auto_scroll: bool,
20080 cx: &mut Context<Self>,
20081 ) {
20082 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20083 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20084 });
20085 self.folds_did_change(cx);
20086 }
20087
20088 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20089 self.fold_buffers([buffer_id], cx);
20090 }
20091
20092 pub fn fold_buffers(
20093 &mut self,
20094 buffer_ids: impl IntoIterator<Item = BufferId>,
20095 cx: &mut Context<Self>,
20096 ) {
20097 if self.buffer().read(cx).is_singleton() {
20098 return;
20099 }
20100
20101 let ids_to_fold: Vec<BufferId> = buffer_ids
20102 .into_iter()
20103 .filter(|id| !self.is_buffer_folded(*id, cx))
20104 .collect();
20105
20106 if ids_to_fold.is_empty() {
20107 return;
20108 }
20109
20110 let mut all_folded_excerpt_ids = Vec::new();
20111 for buffer_id in &ids_to_fold {
20112 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20113 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20114 }
20115
20116 self.display_map.update(cx, |display_map, cx| {
20117 display_map.fold_buffers(ids_to_fold.clone(), cx)
20118 });
20119
20120 let snapshot = self.display_snapshot(cx);
20121 self.selections.change_with(&snapshot, |selections| {
20122 for buffer_id in ids_to_fold {
20123 selections.remove_selections_from_buffer(buffer_id);
20124 }
20125 });
20126
20127 cx.emit(EditorEvent::BufferFoldToggled {
20128 ids: all_folded_excerpt_ids,
20129 folded: true,
20130 });
20131 cx.notify();
20132 }
20133
20134 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20135 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20136 return;
20137 }
20138 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20139 self.display_map.update(cx, |display_map, cx| {
20140 display_map.unfold_buffers([buffer_id], cx);
20141 });
20142 cx.emit(EditorEvent::BufferFoldToggled {
20143 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20144 folded: false,
20145 });
20146 cx.notify();
20147 }
20148
20149 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20150 self.display_map.read(cx).is_buffer_folded(buffer)
20151 }
20152
20153 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20154 if self.buffer().read(cx).is_singleton() {
20155 return false;
20156 }
20157 !self.folded_buffers(cx).is_empty()
20158 }
20159
20160 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20161 self.display_map.read(cx).folded_buffers()
20162 }
20163
20164 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20165 self.display_map.update(cx, |display_map, cx| {
20166 display_map.disable_header_for_buffer(buffer_id, cx);
20167 });
20168 cx.notify();
20169 }
20170
20171 /// Removes any folds with the given ranges.
20172 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20173 &mut self,
20174 ranges: &[Range<T>],
20175 type_id: TypeId,
20176 auto_scroll: bool,
20177 cx: &mut Context<Self>,
20178 ) {
20179 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20180 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20181 });
20182 self.folds_did_change(cx);
20183 }
20184
20185 fn remove_folds_with<T: ToOffset + Clone>(
20186 &mut self,
20187 ranges: &[Range<T>],
20188 auto_scroll: bool,
20189 cx: &mut Context<Self>,
20190 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20191 ) {
20192 if ranges.is_empty() {
20193 return;
20194 }
20195
20196 let mut buffers_affected = HashSet::default();
20197 let multi_buffer = self.buffer().read(cx);
20198 for range in ranges {
20199 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20200 buffers_affected.insert(buffer.read(cx).remote_id());
20201 };
20202 }
20203
20204 self.display_map.update(cx, update);
20205
20206 if auto_scroll {
20207 self.request_autoscroll(Autoscroll::fit(), cx);
20208 }
20209
20210 cx.notify();
20211 self.scrollbar_marker_state.dirty = true;
20212 self.active_indent_guides_state.dirty = true;
20213 }
20214
20215 pub fn update_renderer_widths(
20216 &mut self,
20217 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20218 cx: &mut Context<Self>,
20219 ) -> bool {
20220 self.display_map
20221 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20222 }
20223
20224 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20225 self.display_map.read(cx).fold_placeholder.clone()
20226 }
20227
20228 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20229 self.buffer.update(cx, |buffer, cx| {
20230 buffer.set_all_diff_hunks_expanded(cx);
20231 });
20232 }
20233
20234 pub fn expand_all_diff_hunks(
20235 &mut self,
20236 _: &ExpandAllDiffHunks,
20237 _window: &mut Window,
20238 cx: &mut Context<Self>,
20239 ) {
20240 self.buffer.update(cx, |buffer, cx| {
20241 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20242 });
20243 }
20244
20245 pub fn collapse_all_diff_hunks(
20246 &mut self,
20247 _: &CollapseAllDiffHunks,
20248 _window: &mut Window,
20249 cx: &mut Context<Self>,
20250 ) {
20251 self.buffer.update(cx, |buffer, cx| {
20252 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20253 });
20254 }
20255
20256 pub fn toggle_selected_diff_hunks(
20257 &mut self,
20258 _: &ToggleSelectedDiffHunks,
20259 _window: &mut Window,
20260 cx: &mut Context<Self>,
20261 ) {
20262 let ranges: Vec<_> = self
20263 .selections
20264 .disjoint_anchors()
20265 .iter()
20266 .map(|s| s.range())
20267 .collect();
20268 self.toggle_diff_hunks_in_ranges(ranges, cx);
20269 }
20270
20271 pub fn diff_hunks_in_ranges<'a>(
20272 &'a self,
20273 ranges: &'a [Range<Anchor>],
20274 buffer: &'a MultiBufferSnapshot,
20275 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20276 ranges.iter().flat_map(move |range| {
20277 let end_excerpt_id = range.end.excerpt_id;
20278 let range = range.to_point(buffer);
20279 let mut peek_end = range.end;
20280 if range.end.row < buffer.max_row().0 {
20281 peek_end = Point::new(range.end.row + 1, 0);
20282 }
20283 buffer
20284 .diff_hunks_in_range(range.start..peek_end)
20285 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20286 })
20287 }
20288
20289 pub fn has_stageable_diff_hunks_in_ranges(
20290 &self,
20291 ranges: &[Range<Anchor>],
20292 snapshot: &MultiBufferSnapshot,
20293 ) -> bool {
20294 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20295 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20296 }
20297
20298 pub fn toggle_staged_selected_diff_hunks(
20299 &mut self,
20300 _: &::git::ToggleStaged,
20301 _: &mut Window,
20302 cx: &mut Context<Self>,
20303 ) {
20304 let snapshot = self.buffer.read(cx).snapshot(cx);
20305 let ranges: Vec<_> = self
20306 .selections
20307 .disjoint_anchors()
20308 .iter()
20309 .map(|s| s.range())
20310 .collect();
20311 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20312 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20313 }
20314
20315 pub fn set_render_diff_hunk_controls(
20316 &mut self,
20317 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20318 cx: &mut Context<Self>,
20319 ) {
20320 self.render_diff_hunk_controls = render_diff_hunk_controls;
20321 cx.notify();
20322 }
20323
20324 pub fn stage_and_next(
20325 &mut self,
20326 _: &::git::StageAndNext,
20327 window: &mut Window,
20328 cx: &mut Context<Self>,
20329 ) {
20330 self.do_stage_or_unstage_and_next(true, window, cx);
20331 }
20332
20333 pub fn unstage_and_next(
20334 &mut self,
20335 _: &::git::UnstageAndNext,
20336 window: &mut Window,
20337 cx: &mut Context<Self>,
20338 ) {
20339 self.do_stage_or_unstage_and_next(false, window, cx);
20340 }
20341
20342 pub fn stage_or_unstage_diff_hunks(
20343 &mut self,
20344 stage: bool,
20345 ranges: Vec<Range<Anchor>>,
20346 cx: &mut Context<Self>,
20347 ) {
20348 if self.delegate_stage_and_restore {
20349 let snapshot = self.buffer.read(cx).snapshot(cx);
20350 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20351 if !hunks.is_empty() {
20352 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20353 }
20354 return;
20355 }
20356 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20357 cx.spawn(async move |this, cx| {
20358 task.await?;
20359 this.update(cx, |this, cx| {
20360 let snapshot = this.buffer.read(cx).snapshot(cx);
20361 let chunk_by = this
20362 .diff_hunks_in_ranges(&ranges, &snapshot)
20363 .chunk_by(|hunk| hunk.buffer_id);
20364 for (buffer_id, hunks) in &chunk_by {
20365 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20366 }
20367 })
20368 })
20369 .detach_and_log_err(cx);
20370 }
20371
20372 fn save_buffers_for_ranges_if_needed(
20373 &mut self,
20374 ranges: &[Range<Anchor>],
20375 cx: &mut Context<Editor>,
20376 ) -> Task<Result<()>> {
20377 let multibuffer = self.buffer.read(cx);
20378 let snapshot = multibuffer.read(cx);
20379 let buffer_ids: HashSet<_> = ranges
20380 .iter()
20381 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20382 .collect();
20383 drop(snapshot);
20384
20385 let mut buffers = HashSet::default();
20386 for buffer_id in buffer_ids {
20387 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20388 let buffer = buffer_entity.read(cx);
20389 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20390 {
20391 buffers.insert(buffer_entity);
20392 }
20393 }
20394 }
20395
20396 if let Some(project) = &self.project {
20397 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20398 } else {
20399 Task::ready(Ok(()))
20400 }
20401 }
20402
20403 fn do_stage_or_unstage_and_next(
20404 &mut self,
20405 stage: bool,
20406 window: &mut Window,
20407 cx: &mut Context<Self>,
20408 ) {
20409 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20410
20411 if ranges.iter().any(|range| range.start != range.end) {
20412 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20413 return;
20414 }
20415
20416 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20417
20418 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20419 let wrap_around = !all_diff_hunks_expanded;
20420 let snapshot = self.snapshot(window, cx);
20421 let position = self
20422 .selections
20423 .newest::<Point>(&snapshot.display_snapshot)
20424 .head();
20425
20426 self.go_to_hunk_before_or_after_position(
20427 &snapshot,
20428 position,
20429 Direction::Next,
20430 wrap_around,
20431 window,
20432 cx,
20433 );
20434 }
20435
20436 pub(crate) fn do_stage_or_unstage(
20437 &self,
20438 stage: bool,
20439 buffer_id: BufferId,
20440 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20441 cx: &mut App,
20442 ) -> Option<()> {
20443 let project = self.project()?;
20444 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20445 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20446 let buffer_snapshot = buffer.read(cx).snapshot();
20447 let file_exists = buffer_snapshot
20448 .file()
20449 .is_some_and(|file| file.disk_state().exists());
20450 diff.update(cx, |diff, cx| {
20451 diff.stage_or_unstage_hunks(
20452 stage,
20453 &hunks
20454 .map(|hunk| buffer_diff::DiffHunk {
20455 buffer_range: hunk.buffer_range,
20456 // We don't need to pass in word diffs here because they're only used for rendering and
20457 // this function changes internal state
20458 base_word_diffs: Vec::default(),
20459 buffer_word_diffs: Vec::default(),
20460 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20461 ..hunk.diff_base_byte_range.end.0,
20462 secondary_status: hunk.status.secondary,
20463 range: Point::zero()..Point::zero(), // unused
20464 })
20465 .collect::<Vec<_>>(),
20466 &buffer_snapshot,
20467 file_exists,
20468 cx,
20469 )
20470 });
20471 None
20472 }
20473
20474 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20475 let ranges: Vec<_> = self
20476 .selections
20477 .disjoint_anchors()
20478 .iter()
20479 .map(|s| s.range())
20480 .collect();
20481 self.buffer
20482 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20483 }
20484
20485 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20486 self.buffer.update(cx, |buffer, cx| {
20487 let ranges = vec![Anchor::min()..Anchor::max()];
20488 if !buffer.all_diff_hunks_expanded()
20489 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20490 {
20491 buffer.collapse_diff_hunks(ranges, cx);
20492 true
20493 } else {
20494 false
20495 }
20496 })
20497 }
20498
20499 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20500 if self.buffer.read(cx).all_diff_hunks_expanded() {
20501 return true;
20502 }
20503 let ranges = vec![Anchor::min()..Anchor::max()];
20504 self.buffer
20505 .read(cx)
20506 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20507 }
20508
20509 fn toggle_diff_hunks_in_ranges(
20510 &mut self,
20511 ranges: Vec<Range<Anchor>>,
20512 cx: &mut Context<Editor>,
20513 ) {
20514 self.buffer.update(cx, |buffer, cx| {
20515 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20516 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20517 })
20518 }
20519
20520 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20521 self.buffer.update(cx, |buffer, cx| {
20522 buffer.toggle_single_diff_hunk(range, cx);
20523 })
20524 }
20525
20526 pub(crate) fn apply_all_diff_hunks(
20527 &mut self,
20528 _: &ApplyAllDiffHunks,
20529 window: &mut Window,
20530 cx: &mut Context<Self>,
20531 ) {
20532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20533
20534 let buffers = self.buffer.read(cx).all_buffers();
20535 for branch_buffer in buffers {
20536 branch_buffer.update(cx, |branch_buffer, cx| {
20537 branch_buffer.merge_into_base(Vec::new(), cx);
20538 });
20539 }
20540
20541 if let Some(project) = self.project.clone() {
20542 self.save(
20543 SaveOptions {
20544 format: true,
20545 autosave: false,
20546 },
20547 project,
20548 window,
20549 cx,
20550 )
20551 .detach_and_log_err(cx);
20552 }
20553 }
20554
20555 pub(crate) fn apply_selected_diff_hunks(
20556 &mut self,
20557 _: &ApplyDiffHunk,
20558 window: &mut Window,
20559 cx: &mut Context<Self>,
20560 ) {
20561 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20562 let snapshot = self.snapshot(window, cx);
20563 let hunks = snapshot.hunks_for_ranges(
20564 self.selections
20565 .all(&snapshot.display_snapshot)
20566 .into_iter()
20567 .map(|selection| selection.range()),
20568 );
20569 let mut ranges_by_buffer = HashMap::default();
20570 self.transact(window, cx, |editor, _window, cx| {
20571 for hunk in hunks {
20572 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20573 ranges_by_buffer
20574 .entry(buffer.clone())
20575 .or_insert_with(Vec::new)
20576 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20577 }
20578 }
20579
20580 for (buffer, ranges) in ranges_by_buffer {
20581 buffer.update(cx, |buffer, cx| {
20582 buffer.merge_into_base(ranges, cx);
20583 });
20584 }
20585 });
20586
20587 if let Some(project) = self.project.clone() {
20588 self.save(
20589 SaveOptions {
20590 format: true,
20591 autosave: false,
20592 },
20593 project,
20594 window,
20595 cx,
20596 )
20597 .detach_and_log_err(cx);
20598 }
20599 }
20600
20601 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20602 if hovered != self.gutter_hovered {
20603 self.gutter_hovered = hovered;
20604 cx.notify();
20605 }
20606 }
20607
20608 pub fn insert_blocks(
20609 &mut self,
20610 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20611 autoscroll: Option<Autoscroll>,
20612 cx: &mut Context<Self>,
20613 ) -> Vec<CustomBlockId> {
20614 let blocks = self
20615 .display_map
20616 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20617 if let Some(autoscroll) = autoscroll {
20618 self.request_autoscroll(autoscroll, cx);
20619 }
20620 cx.notify();
20621 blocks
20622 }
20623
20624 pub fn resize_blocks(
20625 &mut self,
20626 heights: HashMap<CustomBlockId, u32>,
20627 autoscroll: Option<Autoscroll>,
20628 cx: &mut Context<Self>,
20629 ) {
20630 self.display_map
20631 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20632 if let Some(autoscroll) = autoscroll {
20633 self.request_autoscroll(autoscroll, cx);
20634 }
20635 cx.notify();
20636 }
20637
20638 pub fn replace_blocks(
20639 &mut self,
20640 renderers: HashMap<CustomBlockId, RenderBlock>,
20641 autoscroll: Option<Autoscroll>,
20642 cx: &mut Context<Self>,
20643 ) {
20644 self.display_map
20645 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20646 if let Some(autoscroll) = autoscroll {
20647 self.request_autoscroll(autoscroll, cx);
20648 }
20649 cx.notify();
20650 }
20651
20652 pub fn remove_blocks(
20653 &mut self,
20654 block_ids: HashSet<CustomBlockId>,
20655 autoscroll: Option<Autoscroll>,
20656 cx: &mut Context<Self>,
20657 ) {
20658 self.display_map.update(cx, |display_map, cx| {
20659 display_map.remove_blocks(block_ids, cx)
20660 });
20661 if let Some(autoscroll) = autoscroll {
20662 self.request_autoscroll(autoscroll, cx);
20663 }
20664 cx.notify();
20665 }
20666
20667 pub fn row_for_block(
20668 &self,
20669 block_id: CustomBlockId,
20670 cx: &mut Context<Self>,
20671 ) -> Option<DisplayRow> {
20672 self.display_map
20673 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20674 }
20675
20676 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20677 self.focused_block = Some(focused_block);
20678 }
20679
20680 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20681 self.focused_block.take()
20682 }
20683
20684 pub fn insert_creases(
20685 &mut self,
20686 creases: impl IntoIterator<Item = Crease<Anchor>>,
20687 cx: &mut Context<Self>,
20688 ) -> Vec<CreaseId> {
20689 self.display_map
20690 .update(cx, |map, cx| map.insert_creases(creases, cx))
20691 }
20692
20693 pub fn remove_creases(
20694 &mut self,
20695 ids: impl IntoIterator<Item = CreaseId>,
20696 cx: &mut Context<Self>,
20697 ) -> Vec<(CreaseId, Range<Anchor>)> {
20698 self.display_map
20699 .update(cx, |map, cx| map.remove_creases(ids, cx))
20700 }
20701
20702 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20703 self.display_map
20704 .update(cx, |map, cx| map.snapshot(cx))
20705 .longest_row()
20706 }
20707
20708 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20709 self.display_map
20710 .update(cx, |map, cx| map.snapshot(cx))
20711 .max_point()
20712 }
20713
20714 pub fn text(&self, cx: &App) -> String {
20715 self.buffer.read(cx).read(cx).text()
20716 }
20717
20718 pub fn is_empty(&self, cx: &App) -> bool {
20719 self.buffer.read(cx).read(cx).is_empty()
20720 }
20721
20722 pub fn text_option(&self, cx: &App) -> Option<String> {
20723 let text = self.text(cx);
20724 let text = text.trim();
20725
20726 if text.is_empty() {
20727 return None;
20728 }
20729
20730 Some(text.to_string())
20731 }
20732
20733 pub fn set_text(
20734 &mut self,
20735 text: impl Into<Arc<str>>,
20736 window: &mut Window,
20737 cx: &mut Context<Self>,
20738 ) {
20739 self.transact(window, cx, |this, _, cx| {
20740 this.buffer
20741 .read(cx)
20742 .as_singleton()
20743 .expect("you can only call set_text on editors for singleton buffers")
20744 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20745 });
20746 }
20747
20748 pub fn display_text(&self, cx: &mut App) -> String {
20749 self.display_map
20750 .update(cx, |map, cx| map.snapshot(cx))
20751 .text()
20752 }
20753
20754 fn create_minimap(
20755 &self,
20756 minimap_settings: MinimapSettings,
20757 window: &mut Window,
20758 cx: &mut Context<Self>,
20759 ) -> Option<Entity<Self>> {
20760 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20761 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20762 }
20763
20764 fn initialize_new_minimap(
20765 &self,
20766 minimap_settings: MinimapSettings,
20767 window: &mut Window,
20768 cx: &mut Context<Self>,
20769 ) -> Entity<Self> {
20770 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20771 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20772
20773 let mut minimap = Editor::new_internal(
20774 EditorMode::Minimap {
20775 parent: cx.weak_entity(),
20776 },
20777 self.buffer.clone(),
20778 None,
20779 Some(self.display_map.clone()),
20780 window,
20781 cx,
20782 );
20783 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20784 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
20785 minimap.scroll_manager.clone_state(
20786 &self.scroll_manager,
20787 &my_snapshot,
20788 &minimap_snapshot,
20789 cx,
20790 );
20791 minimap.set_text_style_refinement(TextStyleRefinement {
20792 font_size: Some(MINIMAP_FONT_SIZE),
20793 font_weight: Some(MINIMAP_FONT_WEIGHT),
20794 font_family: Some(MINIMAP_FONT_FAMILY),
20795 ..Default::default()
20796 });
20797 minimap.update_minimap_configuration(minimap_settings, cx);
20798 cx.new(|_| minimap)
20799 }
20800
20801 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20802 let current_line_highlight = minimap_settings
20803 .current_line_highlight
20804 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20805 self.set_current_line_highlight(Some(current_line_highlight));
20806 }
20807
20808 pub fn minimap(&self) -> Option<&Entity<Self>> {
20809 self.minimap
20810 .as_ref()
20811 .filter(|_| self.minimap_visibility.visible())
20812 }
20813
20814 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20815 let mut wrap_guides = smallvec![];
20816
20817 if self.show_wrap_guides == Some(false) {
20818 return wrap_guides;
20819 }
20820
20821 let settings = self.buffer.read(cx).language_settings(cx);
20822 if settings.show_wrap_guides {
20823 match self.soft_wrap_mode(cx) {
20824 SoftWrap::Column(soft_wrap) => {
20825 wrap_guides.push((soft_wrap as usize, true));
20826 }
20827 SoftWrap::Bounded(soft_wrap) => {
20828 wrap_guides.push((soft_wrap as usize, true));
20829 }
20830 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20831 }
20832 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20833 }
20834
20835 wrap_guides
20836 }
20837
20838 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20839 let settings = self.buffer.read(cx).language_settings(cx);
20840 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20841 match mode {
20842 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20843 SoftWrap::None
20844 }
20845 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20846 language_settings::SoftWrap::PreferredLineLength => {
20847 SoftWrap::Column(settings.preferred_line_length)
20848 }
20849 language_settings::SoftWrap::Bounded => {
20850 SoftWrap::Bounded(settings.preferred_line_length)
20851 }
20852 }
20853 }
20854
20855 pub fn set_soft_wrap_mode(
20856 &mut self,
20857 mode: language_settings::SoftWrap,
20858 cx: &mut Context<Self>,
20859 ) {
20860 self.soft_wrap_mode_override = Some(mode);
20861 cx.notify();
20862 }
20863
20864 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20865 self.hard_wrap = hard_wrap;
20866 cx.notify();
20867 }
20868
20869 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20870 self.text_style_refinement = Some(style);
20871 }
20872
20873 /// called by the Element so we know what style we were most recently rendered with.
20874 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20875 // We intentionally do not inform the display map about the minimap style
20876 // so that wrapping is not recalculated and stays consistent for the editor
20877 // and its linked minimap.
20878 if !self.mode.is_minimap() {
20879 let font = style.text.font();
20880 let font_size = style.text.font_size.to_pixels(window.rem_size());
20881 let display_map = self
20882 .placeholder_display_map
20883 .as_ref()
20884 .filter(|_| self.is_empty(cx))
20885 .unwrap_or(&self.display_map);
20886
20887 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20888 }
20889 self.style = Some(style);
20890 }
20891
20892 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20893 if self.style.is_none() {
20894 self.style = Some(self.create_style(cx));
20895 }
20896 self.style.as_ref().unwrap()
20897 }
20898
20899 // Called by the element. This method is not designed to be called outside of the editor
20900 // element's layout code because it does not notify when rewrapping is computed synchronously.
20901 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20902 if self.is_empty(cx) {
20903 self.placeholder_display_map
20904 .as_ref()
20905 .map_or(false, |display_map| {
20906 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20907 })
20908 } else {
20909 self.display_map
20910 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20911 }
20912 }
20913
20914 pub fn set_soft_wrap(&mut self) {
20915 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20916 }
20917
20918 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20919 if self.soft_wrap_mode_override.is_some() {
20920 self.soft_wrap_mode_override.take();
20921 } else {
20922 let soft_wrap = match self.soft_wrap_mode(cx) {
20923 SoftWrap::GitDiff => return,
20924 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20925 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20926 language_settings::SoftWrap::None
20927 }
20928 };
20929 self.soft_wrap_mode_override = Some(soft_wrap);
20930 }
20931 cx.notify();
20932 }
20933
20934 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20935 let Some(workspace) = self.workspace() else {
20936 return;
20937 };
20938 let fs = workspace.read(cx).app_state().fs.clone();
20939 let current_show = TabBarSettings::get_global(cx).show;
20940 update_settings_file(fs, cx, move |setting, _| {
20941 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20942 });
20943 }
20944
20945 pub fn toggle_indent_guides(
20946 &mut self,
20947 _: &ToggleIndentGuides,
20948 _: &mut Window,
20949 cx: &mut Context<Self>,
20950 ) {
20951 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20952 self.buffer
20953 .read(cx)
20954 .language_settings(cx)
20955 .indent_guides
20956 .enabled
20957 });
20958 self.show_indent_guides = Some(!currently_enabled);
20959 cx.notify();
20960 }
20961
20962 fn should_show_indent_guides(&self) -> Option<bool> {
20963 self.show_indent_guides
20964 }
20965
20966 pub fn disable_indent_guides_for_buffer(
20967 &mut self,
20968 buffer_id: BufferId,
20969 cx: &mut Context<Self>,
20970 ) {
20971 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20972 cx.notify();
20973 }
20974
20975 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20976 self.buffers_with_disabled_indent_guides
20977 .contains(&buffer_id)
20978 }
20979
20980 pub fn toggle_line_numbers(
20981 &mut self,
20982 _: &ToggleLineNumbers,
20983 _: &mut Window,
20984 cx: &mut Context<Self>,
20985 ) {
20986 let mut editor_settings = EditorSettings::get_global(cx).clone();
20987 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20988 EditorSettings::override_global(editor_settings, cx);
20989 }
20990
20991 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20992 if let Some(show_line_numbers) = self.show_line_numbers {
20993 return show_line_numbers;
20994 }
20995 EditorSettings::get_global(cx).gutter.line_numbers
20996 }
20997
20998 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20999 match (
21000 self.use_relative_line_numbers,
21001 EditorSettings::get_global(cx).relative_line_numbers,
21002 ) {
21003 (None, setting) => setting,
21004 (Some(false), _) => RelativeLineNumbers::Disabled,
21005 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21006 (Some(true), _) => RelativeLineNumbers::Enabled,
21007 }
21008 }
21009
21010 pub fn toggle_relative_line_numbers(
21011 &mut self,
21012 _: &ToggleRelativeLineNumbers,
21013 _: &mut Window,
21014 cx: &mut Context<Self>,
21015 ) {
21016 let is_relative = self.relative_line_numbers(cx);
21017 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21018 }
21019
21020 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21021 self.use_relative_line_numbers = is_relative;
21022 cx.notify();
21023 }
21024
21025 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21026 self.show_gutter = show_gutter;
21027 cx.notify();
21028 }
21029
21030 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21031 self.show_scrollbars = ScrollbarAxes {
21032 horizontal: show,
21033 vertical: show,
21034 };
21035 cx.notify();
21036 }
21037
21038 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21039 self.show_scrollbars.vertical = show;
21040 cx.notify();
21041 }
21042
21043 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21044 self.show_scrollbars.horizontal = show;
21045 cx.notify();
21046 }
21047
21048 pub fn set_minimap_visibility(
21049 &mut self,
21050 minimap_visibility: MinimapVisibility,
21051 window: &mut Window,
21052 cx: &mut Context<Self>,
21053 ) {
21054 if self.minimap_visibility != minimap_visibility {
21055 if minimap_visibility.visible() && self.minimap.is_none() {
21056 let minimap_settings = EditorSettings::get_global(cx).minimap;
21057 self.minimap =
21058 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21059 }
21060 self.minimap_visibility = minimap_visibility;
21061 cx.notify();
21062 }
21063 }
21064
21065 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21066 self.set_show_scrollbars(false, cx);
21067 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21068 }
21069
21070 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21071 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21072 }
21073
21074 /// Normally the text in full mode and auto height editors is padded on the
21075 /// left side by roughly half a character width for improved hit testing.
21076 ///
21077 /// Use this method to disable this for cases where this is not wanted (e.g.
21078 /// if you want to align the editor text with some other text above or below)
21079 /// or if you want to add this padding to single-line editors.
21080 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21081 self.offset_content = offset_content;
21082 cx.notify();
21083 }
21084
21085 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21086 self.show_line_numbers = Some(show_line_numbers);
21087 cx.notify();
21088 }
21089
21090 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21091 self.disable_expand_excerpt_buttons = true;
21092 cx.notify();
21093 }
21094
21095 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21096 self.number_deleted_lines = number;
21097 cx.notify();
21098 }
21099
21100 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21101 self.delegate_expand_excerpts = delegate;
21102 }
21103
21104 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21105 self.delegate_stage_and_restore = delegate;
21106 }
21107
21108 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21109 self.delegate_open_excerpts = delegate;
21110 }
21111
21112 pub fn set_on_local_selections_changed(
21113 &mut self,
21114 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21115 ) {
21116 self.on_local_selections_changed = callback;
21117 }
21118
21119 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21120 self.suppress_selection_callback = suppress;
21121 }
21122
21123 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21124 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21125 cx.notify();
21126 }
21127
21128 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21129 self.show_code_actions = Some(show_code_actions);
21130 cx.notify();
21131 }
21132
21133 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21134 self.show_runnables = Some(show_runnables);
21135 cx.notify();
21136 }
21137
21138 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21139 self.show_breakpoints = Some(show_breakpoints);
21140 cx.notify();
21141 }
21142
21143 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21144 self.show_diff_review_button = show;
21145 cx.notify();
21146 }
21147
21148 pub fn show_diff_review_button(&self) -> bool {
21149 self.show_diff_review_button
21150 }
21151
21152 pub fn render_diff_review_button(
21153 &self,
21154 display_row: DisplayRow,
21155 width: Pixels,
21156 cx: &mut Context<Self>,
21157 ) -> impl IntoElement {
21158 let text_color = cx.theme().colors().text;
21159 let icon_color = cx.theme().colors().icon_accent;
21160
21161 h_flex()
21162 .id("diff_review_button")
21163 .cursor_pointer()
21164 .w(width - px(1.))
21165 .h(relative(0.9))
21166 .justify_center()
21167 .rounded_sm()
21168 .border_1()
21169 .border_color(text_color.opacity(0.1))
21170 .bg(text_color.opacity(0.15))
21171 .hover(|s| {
21172 s.bg(icon_color.opacity(0.4))
21173 .border_color(icon_color.opacity(0.5))
21174 })
21175 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21176 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21177 .on_mouse_down(
21178 gpui::MouseButton::Left,
21179 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21180 editor.start_diff_review_drag(display_row, window, cx);
21181 }),
21182 )
21183 }
21184
21185 pub fn start_diff_review_drag(
21186 &mut self,
21187 display_row: DisplayRow,
21188 window: &mut Window,
21189 cx: &mut Context<Self>,
21190 ) {
21191 let snapshot = self.snapshot(window, cx);
21192 let point = snapshot
21193 .display_snapshot
21194 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21195 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21196 self.diff_review_drag_state = Some(DiffReviewDragState {
21197 start_anchor: anchor,
21198 current_anchor: anchor,
21199 });
21200 cx.notify();
21201 }
21202
21203 pub fn update_diff_review_drag(
21204 &mut self,
21205 display_row: DisplayRow,
21206 window: &mut Window,
21207 cx: &mut Context<Self>,
21208 ) {
21209 if self.diff_review_drag_state.is_none() {
21210 return;
21211 }
21212 let snapshot = self.snapshot(window, cx);
21213 let point = snapshot
21214 .display_snapshot
21215 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21216 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21217 if let Some(drag_state) = &mut self.diff_review_drag_state {
21218 drag_state.current_anchor = anchor;
21219 cx.notify();
21220 }
21221 }
21222
21223 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21224 if let Some(drag_state) = self.diff_review_drag_state.take() {
21225 let snapshot = self.snapshot(window, cx);
21226 let range = drag_state.row_range(&snapshot.display_snapshot);
21227 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21228 }
21229 cx.notify();
21230 }
21231
21232 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21233 self.diff_review_drag_state = None;
21234 cx.notify();
21235 }
21236
21237 /// Calculates the appropriate block height for the diff review overlay.
21238 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21239 /// and 2 lines per comment when expanded.
21240 fn calculate_overlay_height(
21241 &self,
21242 hunk_key: &DiffHunkKey,
21243 comments_expanded: bool,
21244 snapshot: &MultiBufferSnapshot,
21245 ) -> u32 {
21246 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21247 let base_height: u32 = 2; // Input row with avatar and buttons
21248
21249 if comment_count == 0 {
21250 base_height
21251 } else if comments_expanded {
21252 // Header (1 line) + 2 lines per comment
21253 base_height + 1 + (comment_count as u32 * 2)
21254 } else {
21255 // Just header when collapsed
21256 base_height + 1
21257 }
21258 }
21259
21260 pub fn show_diff_review_overlay(
21261 &mut self,
21262 display_range: Range<DisplayRow>,
21263 window: &mut Window,
21264 cx: &mut Context<Self>,
21265 ) {
21266 let Range { start, end } = display_range.sorted();
21267
21268 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21269 let editor_snapshot = self.snapshot(window, cx);
21270
21271 // Convert display rows to multibuffer points
21272 let start_point = editor_snapshot
21273 .display_snapshot
21274 .display_point_to_point(start.as_display_point(), Bias::Left);
21275 let end_point = editor_snapshot
21276 .display_snapshot
21277 .display_point_to_point(end.as_display_point(), Bias::Left);
21278 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21279
21280 // Create anchor range for the selected lines (start of first line to end of last line)
21281 let line_end = Point::new(
21282 end_point.row,
21283 buffer_snapshot.line_len(end_multi_buffer_row),
21284 );
21285 let anchor_range =
21286 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21287
21288 // Compute the hunk key for this display row
21289 let file_path = buffer_snapshot
21290 .file_at(start_point)
21291 .map(|file: &Arc<dyn language::File>| file.path().clone())
21292 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21293 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21294 let new_hunk_key = DiffHunkKey {
21295 file_path,
21296 hunk_start_anchor,
21297 };
21298
21299 // Check if we already have an overlay for this hunk
21300 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21301 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21302 }) {
21303 // Just focus the existing overlay's prompt editor
21304 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21305 window.focus(&focus_handle, cx);
21306 return;
21307 }
21308
21309 // Dismiss overlays that have no comments for their hunks
21310 self.dismiss_overlays_without_comments(cx);
21311
21312 // Get the current user's avatar URI from the project's user_store
21313 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21314 let user_store = project.read(cx).user_store();
21315 user_store
21316 .read(cx)
21317 .current_user()
21318 .map(|user| user.avatar_uri.clone())
21319 });
21320
21321 // Create anchor at the end of the last row so the block appears immediately below it
21322 // Use multibuffer coordinates for anchor creation
21323 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21324 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21325
21326 // Use the hunk key we already computed
21327 let hunk_key = new_hunk_key;
21328
21329 // Create the prompt editor for the review input
21330 let prompt_editor = cx.new(|cx| {
21331 let mut editor = Editor::single_line(window, cx);
21332 editor.set_placeholder_text("Add a review comment...", window, cx);
21333 editor
21334 });
21335
21336 // Register the Newline action on the prompt editor to submit the review
21337 let parent_editor = cx.entity().downgrade();
21338 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21339 prompt_editor.register_action({
21340 let parent_editor = parent_editor.clone();
21341 move |_: &crate::actions::Newline, window, cx| {
21342 if let Some(editor) = parent_editor.upgrade() {
21343 editor.update(cx, |editor, cx| {
21344 editor.submit_diff_review_comment(window, cx);
21345 });
21346 }
21347 }
21348 })
21349 });
21350
21351 // Calculate initial height based on existing comments for this hunk
21352 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21353
21354 // Create the overlay block
21355 let prompt_editor_for_render = prompt_editor.clone();
21356 let hunk_key_for_render = hunk_key.clone();
21357 let editor_handle = cx.entity().downgrade();
21358 let block = BlockProperties {
21359 style: BlockStyle::Sticky,
21360 placement: BlockPlacement::Below(anchor),
21361 height: Some(initial_height),
21362 render: Arc::new(move |cx| {
21363 Self::render_diff_review_overlay(
21364 &prompt_editor_for_render,
21365 &hunk_key_for_render,
21366 &editor_handle,
21367 cx,
21368 )
21369 }),
21370 priority: 0,
21371 };
21372
21373 let block_ids = self.insert_blocks([block], None, cx);
21374 let Some(block_id) = block_ids.into_iter().next() else {
21375 log::error!("Failed to insert diff review overlay block");
21376 return;
21377 };
21378
21379 self.diff_review_overlays.push(DiffReviewOverlay {
21380 anchor_range,
21381 block_id,
21382 prompt_editor: prompt_editor.clone(),
21383 hunk_key,
21384 comments_expanded: true,
21385 inline_edit_editors: HashMap::default(),
21386 inline_edit_subscriptions: HashMap::default(),
21387 user_avatar_uri,
21388 _subscription: subscription,
21389 });
21390
21391 // Focus the prompt editor
21392 let focus_handle = prompt_editor.focus_handle(cx);
21393 window.focus(&focus_handle, cx);
21394
21395 cx.notify();
21396 }
21397
21398 /// Dismisses all diff review overlays.
21399 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21400 if self.diff_review_overlays.is_empty() {
21401 return;
21402 }
21403 let block_ids: HashSet<_> = self
21404 .diff_review_overlays
21405 .drain(..)
21406 .map(|overlay| overlay.block_id)
21407 .collect();
21408 self.remove_blocks(block_ids, None, cx);
21409 cx.notify();
21410 }
21411
21412 /// Dismisses overlays that have no comments stored for their hunks.
21413 /// Keeps overlays that have at least one comment.
21414 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21415 let snapshot = self.buffer.read(cx).snapshot(cx);
21416
21417 // First, compute which overlays have comments (to avoid borrow issues with retain)
21418 let overlays_with_comments: Vec<bool> = self
21419 .diff_review_overlays
21420 .iter()
21421 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21422 .collect();
21423
21424 // Now collect block IDs to remove and retain overlays
21425 let mut block_ids_to_remove = HashSet::default();
21426 let mut index = 0;
21427 self.diff_review_overlays.retain(|overlay| {
21428 let has_comments = overlays_with_comments[index];
21429 index += 1;
21430 if !has_comments {
21431 block_ids_to_remove.insert(overlay.block_id);
21432 }
21433 has_comments
21434 });
21435
21436 if !block_ids_to_remove.is_empty() {
21437 self.remove_blocks(block_ids_to_remove, None, cx);
21438 cx.notify();
21439 }
21440 }
21441
21442 /// Refreshes the diff review overlay block to update its height and render function.
21443 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21444 fn refresh_diff_review_overlay_height(
21445 &mut self,
21446 hunk_key: &DiffHunkKey,
21447 _window: &mut Window,
21448 cx: &mut Context<Self>,
21449 ) {
21450 // Extract all needed data from overlay first to avoid borrow conflicts
21451 let snapshot = self.buffer.read(cx).snapshot(cx);
21452 let (comments_expanded, block_id, prompt_editor) = {
21453 let Some(overlay) = self
21454 .diff_review_overlays
21455 .iter()
21456 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21457 else {
21458 return;
21459 };
21460
21461 (
21462 overlay.comments_expanded,
21463 overlay.block_id,
21464 overlay.prompt_editor.clone(),
21465 )
21466 };
21467
21468 // Calculate new height
21469 let snapshot = self.buffer.read(cx).snapshot(cx);
21470 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21471
21472 // Update the block height using resize_blocks (avoids flicker)
21473 let mut heights = HashMap::default();
21474 heights.insert(block_id, new_height);
21475 self.resize_blocks(heights, None, cx);
21476
21477 // Update the render function using replace_blocks (avoids flicker)
21478 let hunk_key_for_render = hunk_key.clone();
21479 let editor_handle = cx.entity().downgrade();
21480 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21481 Arc::new(move |cx| {
21482 Self::render_diff_review_overlay(
21483 &prompt_editor,
21484 &hunk_key_for_render,
21485 &editor_handle,
21486 cx,
21487 )
21488 });
21489
21490 let mut renderers = HashMap::default();
21491 renderers.insert(block_id, render);
21492 self.replace_blocks(renderers, None, cx);
21493 }
21494
21495 /// Action handler for SubmitDiffReviewComment.
21496 pub fn submit_diff_review_comment_action(
21497 &mut self,
21498 _: &SubmitDiffReviewComment,
21499 window: &mut Window,
21500 cx: &mut Context<Self>,
21501 ) {
21502 self.submit_diff_review_comment(window, cx);
21503 }
21504
21505 /// Stores the diff review comment locally.
21506 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21507 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21508 // Find the overlay that currently has focus
21509 let overlay_index = self
21510 .diff_review_overlays
21511 .iter()
21512 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21513 let Some(overlay_index) = overlay_index else {
21514 return;
21515 };
21516 let overlay = &self.diff_review_overlays[overlay_index];
21517
21518 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21519 if comment_text.is_empty() {
21520 return;
21521 }
21522
21523 let anchor_range = overlay.anchor_range.clone();
21524 let hunk_key = overlay.hunk_key.clone();
21525
21526 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21527
21528 // Clear the prompt editor but keep the overlay open
21529 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21530 overlay.prompt_editor.update(cx, |editor, cx| {
21531 editor.clear(window, cx);
21532 });
21533 }
21534
21535 // Refresh the overlay to update the block height for the new comment
21536 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21537
21538 cx.notify();
21539 }
21540
21541 /// Returns the prompt editor for the diff review overlay, if one is active.
21542 /// This is primarily used for testing.
21543 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21544 self.diff_review_overlays
21545 .first()
21546 .map(|overlay| &overlay.prompt_editor)
21547 }
21548
21549 /// Returns the line range for the first diff review overlay, if one is active.
21550 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21551 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21552 let overlay = self.diff_review_overlays.first()?;
21553 let snapshot = self.buffer.read(cx).snapshot(cx);
21554 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21555 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21556 let start_row = snapshot
21557 .point_to_buffer_point(start_point)
21558 .map(|(_, p, _)| p.row)
21559 .unwrap_or(start_point.row);
21560 let end_row = snapshot
21561 .point_to_buffer_point(end_point)
21562 .map(|(_, p, _)| p.row)
21563 .unwrap_or(end_point.row);
21564 Some((start_row, end_row))
21565 }
21566
21567 /// Sets whether the comments section is expanded in the diff review overlay.
21568 /// This is primarily used for testing.
21569 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21570 for overlay in &mut self.diff_review_overlays {
21571 overlay.comments_expanded = expanded;
21572 }
21573 cx.notify();
21574 }
21575
21576 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21577 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21578 a.file_path == b.file_path
21579 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21580 }
21581
21582 /// Returns comments for a specific hunk, ordered by creation time.
21583 pub fn comments_for_hunk<'a>(
21584 &'a self,
21585 key: &DiffHunkKey,
21586 snapshot: &MultiBufferSnapshot,
21587 ) -> &'a [StoredReviewComment] {
21588 let key_point = key.hunk_start_anchor.to_point(snapshot);
21589 self.stored_review_comments
21590 .iter()
21591 .find(|(k, _)| {
21592 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21593 })
21594 .map(|(_, comments)| comments.as_slice())
21595 .unwrap_or(&[])
21596 }
21597
21598 /// Returns the total count of stored review comments across all hunks.
21599 pub fn total_review_comment_count(&self) -> usize {
21600 self.stored_review_comments
21601 .iter()
21602 .map(|(_, v)| v.len())
21603 .sum()
21604 }
21605
21606 /// Returns the count of comments for a specific hunk.
21607 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21608 let key_point = key.hunk_start_anchor.to_point(snapshot);
21609 self.stored_review_comments
21610 .iter()
21611 .find(|(k, _)| {
21612 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21613 })
21614 .map(|(_, v)| v.len())
21615 .unwrap_or(0)
21616 }
21617
21618 /// Adds a new review comment to a specific hunk.
21619 pub fn add_review_comment(
21620 &mut self,
21621 hunk_key: DiffHunkKey,
21622 comment: String,
21623 anchor_range: Range<Anchor>,
21624 cx: &mut Context<Self>,
21625 ) -> usize {
21626 let id = self.next_review_comment_id;
21627 self.next_review_comment_id += 1;
21628
21629 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21630
21631 let snapshot = self.buffer.read(cx).snapshot(cx);
21632 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21633
21634 // Find existing entry for this hunk or add a new one
21635 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21636 k.file_path == hunk_key.file_path
21637 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21638 }) {
21639 comments.push(stored_comment);
21640 } else {
21641 self.stored_review_comments
21642 .push((hunk_key, vec![stored_comment]));
21643 }
21644
21645 cx.emit(EditorEvent::ReviewCommentsChanged {
21646 total_count: self.total_review_comment_count(),
21647 });
21648 cx.notify();
21649 id
21650 }
21651
21652 /// Removes a review comment by ID from any hunk.
21653 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21654 for (_, comments) in self.stored_review_comments.iter_mut() {
21655 if let Some(index) = comments.iter().position(|c| c.id == id) {
21656 comments.remove(index);
21657 cx.emit(EditorEvent::ReviewCommentsChanged {
21658 total_count: self.total_review_comment_count(),
21659 });
21660 cx.notify();
21661 return true;
21662 }
21663 }
21664 false
21665 }
21666
21667 /// Updates a review comment's text by ID.
21668 pub fn update_review_comment(
21669 &mut self,
21670 id: usize,
21671 new_comment: String,
21672 cx: &mut Context<Self>,
21673 ) -> bool {
21674 for (_, comments) in self.stored_review_comments.iter_mut() {
21675 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21676 comment.comment = new_comment;
21677 comment.is_editing = false;
21678 cx.emit(EditorEvent::ReviewCommentsChanged {
21679 total_count: self.total_review_comment_count(),
21680 });
21681 cx.notify();
21682 return true;
21683 }
21684 }
21685 false
21686 }
21687
21688 /// Sets a comment's editing state.
21689 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21690 for (_, comments) in self.stored_review_comments.iter_mut() {
21691 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21692 comment.is_editing = is_editing;
21693 cx.notify();
21694 return;
21695 }
21696 }
21697 }
21698
21699 /// Takes all stored comments from all hunks, clearing the storage.
21700 /// Returns a Vec of (hunk_key, comments) pairs.
21701 pub fn take_all_review_comments(
21702 &mut self,
21703 cx: &mut Context<Self>,
21704 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21705 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21706 self.dismiss_all_diff_review_overlays(cx);
21707 let comments = std::mem::take(&mut self.stored_review_comments);
21708 // Reset the ID counter since all comments have been taken
21709 self.next_review_comment_id = 0;
21710 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21711 cx.notify();
21712 comments
21713 }
21714
21715 /// Removes review comments whose anchors are no longer valid or whose
21716 /// associated diff hunks no longer exist.
21717 ///
21718 /// This should be called when the buffer changes to prevent orphaned comments
21719 /// from accumulating.
21720 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21721 let snapshot = self.buffer.read(cx).snapshot(cx);
21722 let original_count = self.total_review_comment_count();
21723
21724 // Remove comments with invalid hunk anchors
21725 self.stored_review_comments
21726 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21727
21728 // Also clean up individual comments with invalid anchor ranges
21729 for (_, comments) in &mut self.stored_review_comments {
21730 comments.retain(|comment| {
21731 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
21732 });
21733 }
21734
21735 // Remove empty hunk entries
21736 self.stored_review_comments
21737 .retain(|(_, comments)| !comments.is_empty());
21738
21739 let new_count = self.total_review_comment_count();
21740 if new_count != original_count {
21741 cx.emit(EditorEvent::ReviewCommentsChanged {
21742 total_count: new_count,
21743 });
21744 cx.notify();
21745 }
21746 }
21747
21748 /// Toggles the expanded state of the comments section in the overlay.
21749 pub fn toggle_review_comments_expanded(
21750 &mut self,
21751 _: &ToggleReviewCommentsExpanded,
21752 window: &mut Window,
21753 cx: &mut Context<Self>,
21754 ) {
21755 // Find the overlay that currently has focus, or use the first one
21756 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21757 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21758 overlay.comments_expanded = !overlay.comments_expanded;
21759 Some(overlay.hunk_key.clone())
21760 } else {
21761 None
21762 }
21763 });
21764
21765 // If no focused overlay found, toggle the first one
21766 let hunk_key = overlay_info.or_else(|| {
21767 self.diff_review_overlays.first_mut().map(|overlay| {
21768 overlay.comments_expanded = !overlay.comments_expanded;
21769 overlay.hunk_key.clone()
21770 })
21771 });
21772
21773 if let Some(hunk_key) = hunk_key {
21774 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21775 cx.notify();
21776 }
21777 }
21778
21779 /// Handles the EditReviewComment action - sets a comment into editing mode.
21780 pub fn edit_review_comment(
21781 &mut self,
21782 action: &EditReviewComment,
21783 window: &mut Window,
21784 cx: &mut Context<Self>,
21785 ) {
21786 let comment_id = action.id;
21787
21788 // Set the comment to editing mode
21789 self.set_comment_editing(comment_id, true, cx);
21790
21791 // Find the overlay that contains this comment and create an inline editor if needed
21792 // First, find which hunk this comment belongs to
21793 let hunk_key = self
21794 .stored_review_comments
21795 .iter()
21796 .find_map(|(key, comments)| {
21797 if comments.iter().any(|c| c.id == comment_id) {
21798 Some(key.clone())
21799 } else {
21800 None
21801 }
21802 });
21803
21804 let snapshot = self.buffer.read(cx).snapshot(cx);
21805 if let Some(hunk_key) = hunk_key {
21806 if let Some(overlay) = self
21807 .diff_review_overlays
21808 .iter_mut()
21809 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21810 {
21811 if let std::collections::hash_map::Entry::Vacant(entry) =
21812 overlay.inline_edit_editors.entry(comment_id)
21813 {
21814 // Find the comment text
21815 let comment_text = self
21816 .stored_review_comments
21817 .iter()
21818 .flat_map(|(_, comments)| comments)
21819 .find(|c| c.id == comment_id)
21820 .map(|c| c.comment.clone())
21821 .unwrap_or_default();
21822
21823 // Create inline editor
21824 let parent_editor = cx.entity().downgrade();
21825 let inline_editor = cx.new(|cx| {
21826 let mut editor = Editor::single_line(window, cx);
21827 editor.set_text(&*comment_text, window, cx);
21828 // Select all text for easy replacement
21829 editor.select_all(&crate::actions::SelectAll, window, cx);
21830 editor
21831 });
21832
21833 // Register the Newline action to confirm the edit
21834 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
21835 inline_editor.register_action({
21836 let parent_editor = parent_editor.clone();
21837 move |_: &crate::actions::Newline, window, cx| {
21838 if let Some(editor) = parent_editor.upgrade() {
21839 editor.update(cx, |editor, cx| {
21840 editor.confirm_edit_review_comment(comment_id, window, cx);
21841 });
21842 }
21843 }
21844 })
21845 });
21846
21847 // Store the subscription to keep the action handler alive
21848 overlay
21849 .inline_edit_subscriptions
21850 .insert(comment_id, subscription);
21851
21852 // Focus the inline editor
21853 let focus_handle = inline_editor.focus_handle(cx);
21854 window.focus(&focus_handle, cx);
21855
21856 entry.insert(inline_editor);
21857 }
21858 }
21859 }
21860
21861 cx.notify();
21862 }
21863
21864 /// Confirms an inline edit of a review comment.
21865 pub fn confirm_edit_review_comment(
21866 &mut self,
21867 comment_id: usize,
21868 _window: &mut Window,
21869 cx: &mut Context<Self>,
21870 ) {
21871 // Get the new text from the inline editor
21872 // Find the overlay containing this comment's inline editor
21873 let snapshot = self.buffer.read(cx).snapshot(cx);
21874 let hunk_key = self
21875 .stored_review_comments
21876 .iter()
21877 .find_map(|(key, comments)| {
21878 if comments.iter().any(|c| c.id == comment_id) {
21879 Some(key.clone())
21880 } else {
21881 None
21882 }
21883 });
21884
21885 let new_text = hunk_key
21886 .as_ref()
21887 .and_then(|hunk_key| {
21888 self.diff_review_overlays
21889 .iter()
21890 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21891 })
21892 .as_ref()
21893 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
21894 .map(|editor| editor.read(cx).text(cx).trim().to_string());
21895
21896 if let Some(new_text) = new_text {
21897 if !new_text.is_empty() {
21898 self.update_review_comment(comment_id, new_text, cx);
21899 }
21900 }
21901
21902 // Remove the inline editor and its subscription
21903 if let Some(hunk_key) = hunk_key {
21904 if let Some(overlay) = self
21905 .diff_review_overlays
21906 .iter_mut()
21907 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21908 {
21909 overlay.inline_edit_editors.remove(&comment_id);
21910 overlay.inline_edit_subscriptions.remove(&comment_id);
21911 }
21912 }
21913
21914 // Clear editing state
21915 self.set_comment_editing(comment_id, false, cx);
21916 }
21917
21918 /// Cancels an inline edit of a review comment.
21919 pub fn cancel_edit_review_comment(
21920 &mut self,
21921 comment_id: usize,
21922 _window: &mut Window,
21923 cx: &mut Context<Self>,
21924 ) {
21925 // Find which hunk this comment belongs to
21926 let hunk_key = self
21927 .stored_review_comments
21928 .iter()
21929 .find_map(|(key, comments)| {
21930 if comments.iter().any(|c| c.id == comment_id) {
21931 Some(key.clone())
21932 } else {
21933 None
21934 }
21935 });
21936
21937 // Remove the inline editor and its subscription
21938 if let Some(hunk_key) = hunk_key {
21939 let snapshot = self.buffer.read(cx).snapshot(cx);
21940 if let Some(overlay) = self
21941 .diff_review_overlays
21942 .iter_mut()
21943 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21944 {
21945 overlay.inline_edit_editors.remove(&comment_id);
21946 overlay.inline_edit_subscriptions.remove(&comment_id);
21947 }
21948 }
21949
21950 // Clear editing state
21951 self.set_comment_editing(comment_id, false, cx);
21952 }
21953
21954 /// Action handler for ConfirmEditReviewComment.
21955 pub fn confirm_edit_review_comment_action(
21956 &mut self,
21957 action: &ConfirmEditReviewComment,
21958 window: &mut Window,
21959 cx: &mut Context<Self>,
21960 ) {
21961 self.confirm_edit_review_comment(action.id, window, cx);
21962 }
21963
21964 /// Action handler for CancelEditReviewComment.
21965 pub fn cancel_edit_review_comment_action(
21966 &mut self,
21967 action: &CancelEditReviewComment,
21968 window: &mut Window,
21969 cx: &mut Context<Self>,
21970 ) {
21971 self.cancel_edit_review_comment(action.id, window, cx);
21972 }
21973
21974 /// Handles the DeleteReviewComment action - removes a comment.
21975 pub fn delete_review_comment(
21976 &mut self,
21977 action: &DeleteReviewComment,
21978 window: &mut Window,
21979 cx: &mut Context<Self>,
21980 ) {
21981 // Get the hunk key before removing the comment
21982 // Find the hunk key from the comment itself
21983 let comment_id = action.id;
21984 let hunk_key = self
21985 .stored_review_comments
21986 .iter()
21987 .find_map(|(key, comments)| {
21988 if comments.iter().any(|c| c.id == comment_id) {
21989 Some(key.clone())
21990 } else {
21991 None
21992 }
21993 });
21994
21995 // Also get it from the overlay for refresh purposes
21996 let overlay_hunk_key = self
21997 .diff_review_overlays
21998 .first()
21999 .map(|o| o.hunk_key.clone());
22000
22001 self.remove_review_comment(action.id, cx);
22002
22003 // Refresh the overlay height after removing a comment
22004 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22005 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22006 }
22007 }
22008
22009 fn render_diff_review_overlay(
22010 prompt_editor: &Entity<Editor>,
22011 hunk_key: &DiffHunkKey,
22012 editor_handle: &WeakEntity<Editor>,
22013 cx: &mut BlockContext,
22014 ) -> AnyElement {
22015 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22016 if ranges.is_empty() {
22017 return None;
22018 }
22019 let formatted: Vec<String> = ranges
22020 .iter()
22021 .map(|(start, end)| {
22022 let start_line = start + 1;
22023 let end_line = end + 1;
22024 if start_line == end_line {
22025 format!("Line {start_line}")
22026 } else {
22027 format!("Lines {start_line}-{end_line}")
22028 }
22029 })
22030 .collect();
22031 // Don't show label for single line in single excerpt
22032 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22033 return None;
22034 }
22035 Some(formatted.join(" ⋯ "))
22036 }
22037
22038 let theme = cx.theme();
22039 let colors = theme.colors();
22040
22041 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22042 editor_handle
22043 .upgrade()
22044 .map(|editor| {
22045 let editor = editor.read(cx);
22046 let snapshot = editor.buffer().read(cx).snapshot(cx);
22047 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22048 let (expanded, editors, avatar_uri, line_ranges) = editor
22049 .diff_review_overlays
22050 .iter()
22051 .find(|overlay| {
22052 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22053 })
22054 .map(|o| {
22055 let start_point = o.anchor_range.start.to_point(&snapshot);
22056 let end_point = o.anchor_range.end.to_point(&snapshot);
22057 // Get line ranges per excerpt to detect discontinuities
22058 let buffer_ranges =
22059 snapshot.range_to_buffer_ranges(start_point..end_point);
22060 let ranges: Vec<(u32, u32)> = buffer_ranges
22061 .iter()
22062 .map(|(buffer, range, _)| {
22063 let start = buffer.offset_to_point(range.start.0).row;
22064 let end = buffer.offset_to_point(range.end.0).row;
22065 (start, end)
22066 })
22067 .collect();
22068 (
22069 o.comments_expanded,
22070 o.inline_edit_editors.clone(),
22071 o.user_avatar_uri.clone(),
22072 if ranges.is_empty() {
22073 None
22074 } else {
22075 Some(ranges)
22076 },
22077 )
22078 })
22079 .unwrap_or((true, HashMap::default(), None, None));
22080 (comments, expanded, editors, avatar_uri, line_ranges)
22081 })
22082 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22083
22084 let comment_count = comments.len();
22085 let avatar_size = px(20.);
22086 let action_icon_size = IconSize::XSmall;
22087
22088 v_flex()
22089 .w_full()
22090 .bg(colors.editor_background)
22091 .border_b_1()
22092 .border_color(colors.border)
22093 .px_2()
22094 .pb_2()
22095 .gap_2()
22096 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22097 .when_some(line_ranges, |el, ranges| {
22098 let label = format_line_ranges(&ranges);
22099 if let Some(label) = label {
22100 el.child(
22101 h_flex()
22102 .w_full()
22103 .px_2()
22104 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22105 )
22106 } else {
22107 el
22108 }
22109 })
22110 // Top row: editable input with user's avatar
22111 .child(
22112 h_flex()
22113 .w_full()
22114 .items_center()
22115 .gap_2()
22116 .px_2()
22117 .py_1p5()
22118 .rounded_md()
22119 .bg(colors.surface_background)
22120 .child(
22121 div()
22122 .size(avatar_size)
22123 .flex_shrink_0()
22124 .rounded_full()
22125 .overflow_hidden()
22126 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22127 Avatar::new(avatar_uri.clone())
22128 .size(avatar_size)
22129 .into_any_element()
22130 } else {
22131 Icon::new(IconName::Person)
22132 .size(IconSize::Small)
22133 .color(ui::Color::Muted)
22134 .into_any_element()
22135 }),
22136 )
22137 .child(
22138 div()
22139 .flex_1()
22140 .border_1()
22141 .border_color(colors.border)
22142 .rounded_md()
22143 .bg(colors.editor_background)
22144 .px_2()
22145 .py_1()
22146 .child(prompt_editor.clone()),
22147 )
22148 .child(
22149 h_flex()
22150 .flex_shrink_0()
22151 .gap_1()
22152 .child(
22153 IconButton::new("diff-review-close", IconName::Close)
22154 .icon_color(ui::Color::Muted)
22155 .icon_size(action_icon_size)
22156 .tooltip(Tooltip::text("Close"))
22157 .on_click(|_, window, cx| {
22158 window
22159 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22160 }),
22161 )
22162 .child(
22163 IconButton::new("diff-review-add", IconName::Return)
22164 .icon_color(ui::Color::Muted)
22165 .icon_size(action_icon_size)
22166 .tooltip(Tooltip::text("Add comment"))
22167 .on_click(|_, window, cx| {
22168 window.dispatch_action(
22169 Box::new(crate::actions::SubmitDiffReviewComment),
22170 cx,
22171 );
22172 }),
22173 ),
22174 ),
22175 )
22176 // Expandable comments section (only shown when there are comments)
22177 .when(comment_count > 0, |el| {
22178 el.child(Self::render_comments_section(
22179 comments,
22180 comments_expanded,
22181 inline_editors,
22182 user_avatar_uri,
22183 avatar_size,
22184 action_icon_size,
22185 colors,
22186 ))
22187 })
22188 .into_any_element()
22189 }
22190
22191 fn render_comments_section(
22192 comments: Vec<StoredReviewComment>,
22193 expanded: bool,
22194 inline_editors: HashMap<usize, Entity<Editor>>,
22195 user_avatar_uri: Option<SharedUri>,
22196 avatar_size: Pixels,
22197 action_icon_size: IconSize,
22198 colors: &theme::ThemeColors,
22199 ) -> impl IntoElement {
22200 let comment_count = comments.len();
22201
22202 v_flex()
22203 .w_full()
22204 .gap_1()
22205 // Header with expand/collapse toggle
22206 .child(
22207 h_flex()
22208 .id("review-comments-header")
22209 .w_full()
22210 .items_center()
22211 .gap_1()
22212 .px_2()
22213 .py_1()
22214 .cursor_pointer()
22215 .rounded_md()
22216 .hover(|style| style.bg(colors.ghost_element_hover))
22217 .on_click(|_, window: &mut Window, cx| {
22218 window.dispatch_action(
22219 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22220 cx,
22221 );
22222 })
22223 .child(
22224 Icon::new(if expanded {
22225 IconName::ChevronDown
22226 } else {
22227 IconName::ChevronRight
22228 })
22229 .size(IconSize::Small)
22230 .color(ui::Color::Muted),
22231 )
22232 .child(
22233 Label::new(format!(
22234 "{} Comment{}",
22235 comment_count,
22236 if comment_count == 1 { "" } else { "s" }
22237 ))
22238 .size(LabelSize::Small)
22239 .color(Color::Muted),
22240 ),
22241 )
22242 // Comments list (when expanded)
22243 .when(expanded, |el| {
22244 el.children(comments.into_iter().map(|comment| {
22245 let inline_editor = inline_editors.get(&comment.id).cloned();
22246 Self::render_comment_row(
22247 comment,
22248 inline_editor,
22249 user_avatar_uri.clone(),
22250 avatar_size,
22251 action_icon_size,
22252 colors,
22253 )
22254 }))
22255 })
22256 }
22257
22258 fn render_comment_row(
22259 comment: StoredReviewComment,
22260 inline_editor: Option<Entity<Editor>>,
22261 user_avatar_uri: Option<SharedUri>,
22262 avatar_size: Pixels,
22263 action_icon_size: IconSize,
22264 colors: &theme::ThemeColors,
22265 ) -> impl IntoElement {
22266 let comment_id = comment.id;
22267 let is_editing = inline_editor.is_some();
22268
22269 h_flex()
22270 .w_full()
22271 .items_center()
22272 .gap_2()
22273 .px_2()
22274 .py_1p5()
22275 .rounded_md()
22276 .bg(colors.surface_background)
22277 .child(
22278 div()
22279 .size(avatar_size)
22280 .flex_shrink_0()
22281 .rounded_full()
22282 .overflow_hidden()
22283 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22284 Avatar::new(avatar_uri.clone())
22285 .size(avatar_size)
22286 .into_any_element()
22287 } else {
22288 Icon::new(IconName::Person)
22289 .size(IconSize::Small)
22290 .color(ui::Color::Muted)
22291 .into_any_element()
22292 }),
22293 )
22294 .child(if let Some(editor) = inline_editor {
22295 // Inline edit mode: show an editable text field
22296 div()
22297 .flex_1()
22298 .border_1()
22299 .border_color(colors.border)
22300 .rounded_md()
22301 .bg(colors.editor_background)
22302 .px_2()
22303 .py_1()
22304 .child(editor)
22305 .into_any_element()
22306 } else {
22307 // Display mode: show the comment text
22308 div()
22309 .flex_1()
22310 .text_sm()
22311 .text_color(colors.text)
22312 .child(comment.comment)
22313 .into_any_element()
22314 })
22315 .child(if is_editing {
22316 // Editing mode: show close and confirm buttons
22317 h_flex()
22318 .gap_1()
22319 .child(
22320 IconButton::new(
22321 format!("diff-review-cancel-edit-{comment_id}"),
22322 IconName::Close,
22323 )
22324 .icon_color(ui::Color::Muted)
22325 .icon_size(action_icon_size)
22326 .tooltip(Tooltip::text("Cancel"))
22327 .on_click(move |_, window, cx| {
22328 window.dispatch_action(
22329 Box::new(crate::actions::CancelEditReviewComment {
22330 id: comment_id,
22331 }),
22332 cx,
22333 );
22334 }),
22335 )
22336 .child(
22337 IconButton::new(
22338 format!("diff-review-confirm-edit-{comment_id}"),
22339 IconName::Return,
22340 )
22341 .icon_color(ui::Color::Muted)
22342 .icon_size(action_icon_size)
22343 .tooltip(Tooltip::text("Confirm"))
22344 .on_click(move |_, window, cx| {
22345 window.dispatch_action(
22346 Box::new(crate::actions::ConfirmEditReviewComment {
22347 id: comment_id,
22348 }),
22349 cx,
22350 );
22351 }),
22352 )
22353 .into_any_element()
22354 } else {
22355 // Display mode: no action buttons for now (edit/delete not yet implemented)
22356 gpui::Empty.into_any_element()
22357 })
22358 }
22359
22360 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22361 if self.display_map.read(cx).masked != masked {
22362 self.display_map.update(cx, |map, _| map.masked = masked);
22363 }
22364 cx.notify()
22365 }
22366
22367 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22368 self.show_wrap_guides = Some(show_wrap_guides);
22369 cx.notify();
22370 }
22371
22372 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22373 self.show_indent_guides = Some(show_indent_guides);
22374 cx.notify();
22375 }
22376
22377 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22378 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22379 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22380 && let Some(dir) = file.abs_path(cx).parent()
22381 {
22382 return Some(dir.to_owned());
22383 }
22384 }
22385
22386 None
22387 }
22388
22389 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22390 self.active_excerpt(cx)?
22391 .1
22392 .read(cx)
22393 .file()
22394 .and_then(|f| f.as_local())
22395 }
22396
22397 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22398 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22399 let buffer = buffer.read(cx);
22400 if let Some(project_path) = buffer.project_path(cx) {
22401 let project = self.project()?.read(cx);
22402 project.absolute_path(&project_path, cx)
22403 } else {
22404 buffer
22405 .file()
22406 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22407 }
22408 })
22409 }
22410
22411 pub fn reveal_in_finder(
22412 &mut self,
22413 _: &RevealInFileManager,
22414 _window: &mut Window,
22415 cx: &mut Context<Self>,
22416 ) {
22417 if let Some(path) = self.target_file_abs_path(cx) {
22418 if let Some(project) = self.project() {
22419 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22420 } else {
22421 cx.reveal_path(&path);
22422 }
22423 }
22424 }
22425
22426 pub fn copy_path(
22427 &mut self,
22428 _: &zed_actions::workspace::CopyPath,
22429 _window: &mut Window,
22430 cx: &mut Context<Self>,
22431 ) {
22432 if let Some(path) = self.target_file_abs_path(cx)
22433 && let Some(path) = path.to_str()
22434 {
22435 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22436 } else {
22437 cx.propagate();
22438 }
22439 }
22440
22441 pub fn copy_relative_path(
22442 &mut self,
22443 _: &zed_actions::workspace::CopyRelativePath,
22444 _window: &mut Window,
22445 cx: &mut Context<Self>,
22446 ) {
22447 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22448 let project = self.project()?.read(cx);
22449 let path = buffer.read(cx).file()?.path();
22450 let path = path.display(project.path_style(cx));
22451 Some(path)
22452 }) {
22453 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22454 } else {
22455 cx.propagate();
22456 }
22457 }
22458
22459 /// Returns the project path for the editor's buffer, if any buffer is
22460 /// opened in the editor.
22461 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22462 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22463 buffer.read(cx).project_path(cx)
22464 } else {
22465 None
22466 }
22467 }
22468
22469 // Returns true if the editor handled a go-to-line request
22470 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22471 maybe!({
22472 let breakpoint_store = self.breakpoint_store.as_ref()?;
22473
22474 let (active_stack_frame, debug_line_pane_id) = {
22475 let store = breakpoint_store.read(cx);
22476 let active_stack_frame = store.active_position().cloned();
22477 let debug_line_pane_id = store.active_debug_line_pane_id();
22478 (active_stack_frame, debug_line_pane_id)
22479 };
22480
22481 let Some(active_stack_frame) = active_stack_frame else {
22482 self.clear_row_highlights::<ActiveDebugLine>();
22483 return None;
22484 };
22485
22486 if let Some(debug_line_pane_id) = debug_line_pane_id {
22487 if let Some(workspace) = self
22488 .workspace
22489 .as_ref()
22490 .and_then(|(workspace, _)| workspace.upgrade())
22491 {
22492 let editor_pane_id = workspace
22493 .read(cx)
22494 .pane_for_item_id(cx.entity_id())
22495 .map(|pane| pane.entity_id());
22496
22497 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22498 self.clear_row_highlights::<ActiveDebugLine>();
22499 return None;
22500 }
22501 }
22502 }
22503
22504 let position = active_stack_frame.position;
22505 let buffer_id = position.buffer_id?;
22506 let snapshot = self
22507 .project
22508 .as_ref()?
22509 .read(cx)
22510 .buffer_for_id(buffer_id, cx)?
22511 .read(cx)
22512 .snapshot();
22513
22514 let mut handled = false;
22515 for (id, _, ExcerptRange { context, .. }) in
22516 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22517 {
22518 if context.start.cmp(&position, &snapshot).is_ge()
22519 || context.end.cmp(&position, &snapshot).is_lt()
22520 {
22521 continue;
22522 }
22523 let snapshot = self.buffer.read(cx).snapshot(cx);
22524 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22525
22526 handled = true;
22527 self.clear_row_highlights::<ActiveDebugLine>();
22528
22529 self.go_to_line::<ActiveDebugLine>(
22530 multibuffer_anchor,
22531 Some(cx.theme().colors().editor_debugger_active_line_background),
22532 window,
22533 cx,
22534 );
22535
22536 cx.notify();
22537 }
22538
22539 handled.then_some(())
22540 })
22541 .is_some()
22542 }
22543
22544 pub fn copy_file_name_without_extension(
22545 &mut self,
22546 _: &CopyFileNameWithoutExtension,
22547 _: &mut Window,
22548 cx: &mut Context<Self>,
22549 ) {
22550 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22551 let file = buffer.read(cx).file()?;
22552 file.path().file_stem()
22553 }) {
22554 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22555 }
22556 }
22557
22558 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22559 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22560 let file = buffer.read(cx).file()?;
22561 Some(file.file_name(cx))
22562 }) {
22563 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22564 }
22565 }
22566
22567 pub fn toggle_git_blame(
22568 &mut self,
22569 _: &::git::Blame,
22570 window: &mut Window,
22571 cx: &mut Context<Self>,
22572 ) {
22573 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22574
22575 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22576 self.start_git_blame(true, window, cx);
22577 }
22578
22579 cx.notify();
22580 }
22581
22582 pub fn toggle_git_blame_inline(
22583 &mut self,
22584 _: &ToggleGitBlameInline,
22585 window: &mut Window,
22586 cx: &mut Context<Self>,
22587 ) {
22588 self.toggle_git_blame_inline_internal(true, window, cx);
22589 cx.notify();
22590 }
22591
22592 pub fn open_git_blame_commit(
22593 &mut self,
22594 _: &OpenGitBlameCommit,
22595 window: &mut Window,
22596 cx: &mut Context<Self>,
22597 ) {
22598 self.open_git_blame_commit_internal(window, cx);
22599 }
22600
22601 fn open_git_blame_commit_internal(
22602 &mut self,
22603 window: &mut Window,
22604 cx: &mut Context<Self>,
22605 ) -> Option<()> {
22606 let blame = self.blame.as_ref()?;
22607 let snapshot = self.snapshot(window, cx);
22608 let cursor = self
22609 .selections
22610 .newest::<Point>(&snapshot.display_snapshot)
22611 .head();
22612 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22613 let (_, blame_entry) = blame
22614 .update(cx, |blame, cx| {
22615 blame
22616 .blame_for_rows(
22617 &[RowInfo {
22618 buffer_id: Some(buffer.remote_id()),
22619 buffer_row: Some(point.row),
22620 ..Default::default()
22621 }],
22622 cx,
22623 )
22624 .next()
22625 })
22626 .flatten()?;
22627 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22628 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22629 let workspace = self.workspace()?.downgrade();
22630 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22631 None
22632 }
22633
22634 pub fn git_blame_inline_enabled(&self) -> bool {
22635 self.git_blame_inline_enabled
22636 }
22637
22638 pub fn toggle_selection_menu(
22639 &mut self,
22640 _: &ToggleSelectionMenu,
22641 _: &mut Window,
22642 cx: &mut Context<Self>,
22643 ) {
22644 self.show_selection_menu = self
22645 .show_selection_menu
22646 .map(|show_selections_menu| !show_selections_menu)
22647 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22648
22649 cx.notify();
22650 }
22651
22652 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22653 self.show_selection_menu
22654 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22655 }
22656
22657 fn start_git_blame(
22658 &mut self,
22659 user_triggered: bool,
22660 window: &mut Window,
22661 cx: &mut Context<Self>,
22662 ) {
22663 if let Some(project) = self.project() {
22664 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22665 && buffer.read(cx).file().is_none()
22666 {
22667 return;
22668 }
22669
22670 let focused = self.focus_handle(cx).contains_focused(window, cx);
22671
22672 let project = project.clone();
22673 let blame = cx
22674 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22675 self.blame_subscription =
22676 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22677 self.blame = Some(blame);
22678 }
22679 }
22680
22681 fn toggle_git_blame_inline_internal(
22682 &mut self,
22683 user_triggered: bool,
22684 window: &mut Window,
22685 cx: &mut Context<Self>,
22686 ) {
22687 if self.git_blame_inline_enabled {
22688 self.git_blame_inline_enabled = false;
22689 self.show_git_blame_inline = false;
22690 self.show_git_blame_inline_delay_task.take();
22691 } else {
22692 self.git_blame_inline_enabled = true;
22693 self.start_git_blame_inline(user_triggered, window, cx);
22694 }
22695
22696 cx.notify();
22697 }
22698
22699 fn start_git_blame_inline(
22700 &mut self,
22701 user_triggered: bool,
22702 window: &mut Window,
22703 cx: &mut Context<Self>,
22704 ) {
22705 self.start_git_blame(user_triggered, window, cx);
22706
22707 if ProjectSettings::get_global(cx)
22708 .git
22709 .inline_blame_delay()
22710 .is_some()
22711 {
22712 self.start_inline_blame_timer(window, cx);
22713 } else {
22714 self.show_git_blame_inline = true
22715 }
22716 }
22717
22718 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22719 self.blame.as_ref()
22720 }
22721
22722 pub fn show_git_blame_gutter(&self) -> bool {
22723 self.show_git_blame_gutter
22724 }
22725
22726 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22727 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22728 }
22729
22730 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22731 self.show_git_blame_inline
22732 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22733 && !self.newest_selection_head_on_empty_line(cx)
22734 && self.has_blame_entries(cx)
22735 }
22736
22737 fn has_blame_entries(&self, cx: &App) -> bool {
22738 self.blame()
22739 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22740 }
22741
22742 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22743 let cursor_anchor = self.selections.newest_anchor().head();
22744
22745 let snapshot = self.buffer.read(cx).snapshot(cx);
22746 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22747
22748 snapshot.line_len(buffer_row) == 0
22749 }
22750
22751 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22752 let buffer_and_selection = maybe!({
22753 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22754 let selection_range = selection.range();
22755
22756 let multi_buffer = self.buffer().read(cx);
22757 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22758 let buffer_ranges = multi_buffer_snapshot
22759 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
22760
22761 let (buffer, range, _) = if selection.reversed {
22762 buffer_ranges.first()
22763 } else {
22764 buffer_ranges.last()
22765 }?;
22766
22767 let buffer_range = range.to_point(buffer);
22768
22769 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22770 return Some((
22771 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22772 buffer_range.start.row..buffer_range.end.row,
22773 ));
22774 };
22775
22776 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22777 let start =
22778 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
22779 let end =
22780 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
22781
22782 Some((
22783 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22784 start.row..end.row,
22785 ))
22786 });
22787
22788 let Some((buffer, selection)) = buffer_and_selection else {
22789 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22790 };
22791
22792 let Some(project) = self.project() else {
22793 return Task::ready(Err(anyhow!("editor does not have project")));
22794 };
22795
22796 project.update(cx, |project, cx| {
22797 project.get_permalink_to_line(&buffer, selection, cx)
22798 })
22799 }
22800
22801 pub fn copy_permalink_to_line(
22802 &mut self,
22803 _: &CopyPermalinkToLine,
22804 window: &mut Window,
22805 cx: &mut Context<Self>,
22806 ) {
22807 let permalink_task = self.get_permalink_to_line(cx);
22808 let workspace = self.workspace();
22809
22810 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22811 Ok(permalink) => {
22812 cx.update(|_, cx| {
22813 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22814 })
22815 .ok();
22816 }
22817 Err(err) => {
22818 let message = format!("Failed to copy permalink: {err}");
22819
22820 anyhow::Result::<()>::Err(err).log_err();
22821
22822 if let Some(workspace) = workspace {
22823 workspace
22824 .update_in(cx, |workspace, _, cx| {
22825 struct CopyPermalinkToLine;
22826
22827 workspace.show_toast(
22828 Toast::new(
22829 NotificationId::unique::<CopyPermalinkToLine>(),
22830 message,
22831 ),
22832 cx,
22833 )
22834 })
22835 .ok();
22836 }
22837 }
22838 })
22839 .detach();
22840 }
22841
22842 pub fn copy_file_location(
22843 &mut self,
22844 _: &CopyFileLocation,
22845 _: &mut Window,
22846 cx: &mut Context<Self>,
22847 ) {
22848 let selection = self
22849 .selections
22850 .newest::<Point>(&self.display_snapshot(cx))
22851 .start
22852 .row
22853 + 1;
22854 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22855 let project = self.project()?.read(cx);
22856 let file = buffer.read(cx).file()?;
22857 let path = file.path().display(project.path_style(cx));
22858
22859 Some(format!("{path}:{selection}"))
22860 }) {
22861 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
22862 }
22863 }
22864
22865 pub fn open_permalink_to_line(
22866 &mut self,
22867 _: &OpenPermalinkToLine,
22868 window: &mut Window,
22869 cx: &mut Context<Self>,
22870 ) {
22871 let permalink_task = self.get_permalink_to_line(cx);
22872 let workspace = self.workspace();
22873
22874 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22875 Ok(permalink) => {
22876 cx.update(|_, cx| {
22877 cx.open_url(permalink.as_ref());
22878 })
22879 .ok();
22880 }
22881 Err(err) => {
22882 let message = format!("Failed to open permalink: {err}");
22883
22884 anyhow::Result::<()>::Err(err).log_err();
22885
22886 if let Some(workspace) = workspace {
22887 workspace.update(cx, |workspace, cx| {
22888 struct OpenPermalinkToLine;
22889
22890 workspace.show_toast(
22891 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
22892 cx,
22893 )
22894 });
22895 }
22896 }
22897 })
22898 .detach();
22899 }
22900
22901 pub fn insert_uuid_v4(
22902 &mut self,
22903 _: &InsertUuidV4,
22904 window: &mut Window,
22905 cx: &mut Context<Self>,
22906 ) {
22907 self.insert_uuid(UuidVersion::V4, window, cx);
22908 }
22909
22910 pub fn insert_uuid_v7(
22911 &mut self,
22912 _: &InsertUuidV7,
22913 window: &mut Window,
22914 cx: &mut Context<Self>,
22915 ) {
22916 self.insert_uuid(UuidVersion::V7, window, cx);
22917 }
22918
22919 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
22920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
22921 self.transact(window, cx, |this, window, cx| {
22922 let edits = this
22923 .selections
22924 .all::<Point>(&this.display_snapshot(cx))
22925 .into_iter()
22926 .map(|selection| {
22927 let uuid = match version {
22928 UuidVersion::V4 => uuid::Uuid::new_v4(),
22929 UuidVersion::V7 => uuid::Uuid::now_v7(),
22930 };
22931
22932 (selection.range(), uuid.to_string())
22933 });
22934 this.edit(edits, cx);
22935 this.refresh_edit_prediction(true, false, window, cx);
22936 });
22937 }
22938
22939 pub fn open_selections_in_multibuffer(
22940 &mut self,
22941 _: &OpenSelectionsInMultibuffer,
22942 window: &mut Window,
22943 cx: &mut Context<Self>,
22944 ) {
22945 let multibuffer = self.buffer.read(cx);
22946
22947 let Some(buffer) = multibuffer.as_singleton() else {
22948 return;
22949 };
22950
22951 let Some(workspace) = self.workspace() else {
22952 return;
22953 };
22954
22955 let title = multibuffer.title(cx).to_string();
22956
22957 let locations = self
22958 .selections
22959 .all_anchors(&self.display_snapshot(cx))
22960 .iter()
22961 .map(|selection| {
22962 (
22963 buffer.clone(),
22964 (selection.start.text_anchor..selection.end.text_anchor)
22965 .to_point(buffer.read(cx)),
22966 )
22967 })
22968 .into_group_map();
22969
22970 cx.spawn_in(window, async move |_, cx| {
22971 workspace.update_in(cx, |workspace, window, cx| {
22972 Self::open_locations_in_multibuffer(
22973 workspace,
22974 locations,
22975 format!("Selections for '{title}'"),
22976 false,
22977 false,
22978 MultibufferSelectionMode::All,
22979 window,
22980 cx,
22981 );
22982 })
22983 })
22984 .detach();
22985 }
22986
22987 /// Adds a row highlight for the given range. If a row has multiple highlights, the
22988 /// last highlight added will be used.
22989 ///
22990 /// If the range ends at the beginning of a line, then that line will not be highlighted.
22991 pub fn highlight_rows<T: 'static>(
22992 &mut self,
22993 range: Range<Anchor>,
22994 color: Hsla,
22995 options: RowHighlightOptions,
22996 cx: &mut Context<Self>,
22997 ) {
22998 let snapshot = self.buffer().read(cx).snapshot(cx);
22999 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23000 let ix = row_highlights.binary_search_by(|highlight| {
23001 Ordering::Equal
23002 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23003 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23004 });
23005
23006 if let Err(mut ix) = ix {
23007 let index = post_inc(&mut self.highlight_order);
23008
23009 // If this range intersects with the preceding highlight, then merge it with
23010 // the preceding highlight. Otherwise insert a new highlight.
23011 let mut merged = false;
23012 if ix > 0 {
23013 let prev_highlight = &mut row_highlights[ix - 1];
23014 if prev_highlight
23015 .range
23016 .end
23017 .cmp(&range.start, &snapshot)
23018 .is_ge()
23019 {
23020 ix -= 1;
23021 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23022 prev_highlight.range.end = range.end;
23023 }
23024 merged = true;
23025 prev_highlight.index = index;
23026 prev_highlight.color = color;
23027 prev_highlight.options = options;
23028 }
23029 }
23030
23031 if !merged {
23032 row_highlights.insert(
23033 ix,
23034 RowHighlight {
23035 range,
23036 index,
23037 color,
23038 options,
23039 type_id: TypeId::of::<T>(),
23040 },
23041 );
23042 }
23043
23044 // If any of the following highlights intersect with this one, merge them.
23045 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23046 let highlight = &row_highlights[ix];
23047 if next_highlight
23048 .range
23049 .start
23050 .cmp(&highlight.range.end, &snapshot)
23051 .is_le()
23052 {
23053 if next_highlight
23054 .range
23055 .end
23056 .cmp(&highlight.range.end, &snapshot)
23057 .is_gt()
23058 {
23059 row_highlights[ix].range.end = next_highlight.range.end;
23060 }
23061 row_highlights.remove(ix + 1);
23062 } else {
23063 break;
23064 }
23065 }
23066 }
23067 }
23068
23069 /// Remove any highlighted row ranges of the given type that intersect the
23070 /// given ranges.
23071 pub fn remove_highlighted_rows<T: 'static>(
23072 &mut self,
23073 ranges_to_remove: Vec<Range<Anchor>>,
23074 cx: &mut Context<Self>,
23075 ) {
23076 let snapshot = self.buffer().read(cx).snapshot(cx);
23077 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23078 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23079 row_highlights.retain(|highlight| {
23080 while let Some(range_to_remove) = ranges_to_remove.peek() {
23081 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23082 Ordering::Less | Ordering::Equal => {
23083 ranges_to_remove.next();
23084 }
23085 Ordering::Greater => {
23086 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23087 Ordering::Less | Ordering::Equal => {
23088 return false;
23089 }
23090 Ordering::Greater => break,
23091 }
23092 }
23093 }
23094 }
23095
23096 true
23097 })
23098 }
23099
23100 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23101 pub fn clear_row_highlights<T: 'static>(&mut self) {
23102 self.highlighted_rows.remove(&TypeId::of::<T>());
23103 }
23104
23105 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23106 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23107 self.highlighted_rows
23108 .get(&TypeId::of::<T>())
23109 .map_or(&[] as &[_], |vec| vec.as_slice())
23110 .iter()
23111 .map(|highlight| (highlight.range.clone(), highlight.color))
23112 }
23113
23114 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23115 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23116 /// Allows to ignore certain kinds of highlights.
23117 pub fn highlighted_display_rows(
23118 &self,
23119 window: &mut Window,
23120 cx: &mut App,
23121 ) -> BTreeMap<DisplayRow, LineHighlight> {
23122 let snapshot = self.snapshot(window, cx);
23123 let mut used_highlight_orders = HashMap::default();
23124 self.highlighted_rows
23125 .iter()
23126 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23127 .fold(
23128 BTreeMap::<DisplayRow, LineHighlight>::new(),
23129 |mut unique_rows, highlight| {
23130 let start = highlight.range.start.to_display_point(&snapshot);
23131 let end = highlight.range.end.to_display_point(&snapshot);
23132 let start_row = start.row().0;
23133 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23134 {
23135 end.row().0.saturating_sub(1)
23136 } else {
23137 end.row().0
23138 };
23139 for row in start_row..=end_row {
23140 let used_index =
23141 used_highlight_orders.entry(row).or_insert(highlight.index);
23142 if highlight.index >= *used_index {
23143 *used_index = highlight.index;
23144 unique_rows.insert(
23145 DisplayRow(row),
23146 LineHighlight {
23147 include_gutter: highlight.options.include_gutter,
23148 border: None,
23149 background: highlight.color.into(),
23150 type_id: Some(highlight.type_id),
23151 },
23152 );
23153 }
23154 }
23155 unique_rows
23156 },
23157 )
23158 }
23159
23160 pub fn highlighted_display_row_for_autoscroll(
23161 &self,
23162 snapshot: &DisplaySnapshot,
23163 ) -> Option<DisplayRow> {
23164 self.highlighted_rows
23165 .values()
23166 .flat_map(|highlighted_rows| highlighted_rows.iter())
23167 .filter_map(|highlight| {
23168 if highlight.options.autoscroll {
23169 Some(highlight.range.start.to_display_point(snapshot).row())
23170 } else {
23171 None
23172 }
23173 })
23174 .min()
23175 }
23176
23177 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23178 self.highlight_background(
23179 HighlightKey::SearchWithinRange,
23180 ranges,
23181 |_, colors| colors.colors().editor_document_highlight_read_background,
23182 cx,
23183 )
23184 }
23185
23186 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23187 self.breadcrumb_header = Some(new_header);
23188 }
23189
23190 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23191 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23192 }
23193
23194 pub fn highlight_background(
23195 &mut self,
23196 key: HighlightKey,
23197 ranges: &[Range<Anchor>],
23198 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23199 cx: &mut Context<Self>,
23200 ) {
23201 self.background_highlights
23202 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23203 self.scrollbar_marker_state.dirty = true;
23204 cx.notify();
23205 }
23206
23207 pub fn clear_background_highlights(
23208 &mut self,
23209 key: HighlightKey,
23210 cx: &mut Context<Self>,
23211 ) -> Option<BackgroundHighlight> {
23212 let text_highlights = self.background_highlights.remove(&key)?;
23213 if !text_highlights.1.is_empty() {
23214 self.scrollbar_marker_state.dirty = true;
23215 cx.notify();
23216 }
23217 Some(text_highlights)
23218 }
23219
23220 pub fn highlight_gutter<T: 'static>(
23221 &mut self,
23222 ranges: impl Into<Vec<Range<Anchor>>>,
23223 color_fetcher: fn(&App) -> Hsla,
23224 cx: &mut Context<Self>,
23225 ) {
23226 self.gutter_highlights
23227 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23228 cx.notify();
23229 }
23230
23231 pub fn clear_gutter_highlights<T: 'static>(
23232 &mut self,
23233 cx: &mut Context<Self>,
23234 ) -> Option<GutterHighlight> {
23235 cx.notify();
23236 self.gutter_highlights.remove(&TypeId::of::<T>())
23237 }
23238
23239 pub fn insert_gutter_highlight<T: 'static>(
23240 &mut self,
23241 range: Range<Anchor>,
23242 color_fetcher: fn(&App) -> Hsla,
23243 cx: &mut Context<Self>,
23244 ) {
23245 let snapshot = self.buffer().read(cx).snapshot(cx);
23246 let mut highlights = self
23247 .gutter_highlights
23248 .remove(&TypeId::of::<T>())
23249 .map(|(_, highlights)| highlights)
23250 .unwrap_or_default();
23251 let ix = highlights.binary_search_by(|highlight| {
23252 Ordering::Equal
23253 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23254 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23255 });
23256 if let Err(ix) = ix {
23257 highlights.insert(ix, range);
23258 }
23259 self.gutter_highlights
23260 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23261 }
23262
23263 pub fn remove_gutter_highlights<T: 'static>(
23264 &mut self,
23265 ranges_to_remove: Vec<Range<Anchor>>,
23266 cx: &mut Context<Self>,
23267 ) {
23268 let snapshot = self.buffer().read(cx).snapshot(cx);
23269 let Some((color_fetcher, mut gutter_highlights)) =
23270 self.gutter_highlights.remove(&TypeId::of::<T>())
23271 else {
23272 return;
23273 };
23274 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23275 gutter_highlights.retain(|highlight| {
23276 while let Some(range_to_remove) = ranges_to_remove.peek() {
23277 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23278 Ordering::Less | Ordering::Equal => {
23279 ranges_to_remove.next();
23280 }
23281 Ordering::Greater => {
23282 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23283 Ordering::Less | Ordering::Equal => {
23284 return false;
23285 }
23286 Ordering::Greater => break,
23287 }
23288 }
23289 }
23290 }
23291
23292 true
23293 });
23294 self.gutter_highlights
23295 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23296 }
23297
23298 #[cfg(any(test, feature = "test-support"))]
23299 pub fn all_text_highlights(
23300 &self,
23301 window: &mut Window,
23302 cx: &mut Context<Self>,
23303 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23304 let snapshot = self.snapshot(window, cx);
23305 self.display_map.update(cx, |display_map, _| {
23306 display_map
23307 .all_text_highlights()
23308 .map(|(_, highlight)| {
23309 let (style, ranges) = highlight.as_ref();
23310 (
23311 *style,
23312 ranges
23313 .iter()
23314 .map(|range| range.clone().to_display_points(&snapshot))
23315 .collect(),
23316 )
23317 })
23318 .collect()
23319 })
23320 }
23321
23322 #[cfg(any(test, feature = "test-support"))]
23323 pub fn all_text_background_highlights(
23324 &self,
23325 window: &mut Window,
23326 cx: &mut Context<Self>,
23327 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23328 let snapshot = self.snapshot(window, cx);
23329 let buffer = &snapshot.buffer_snapshot();
23330 let start = buffer.anchor_before(MultiBufferOffset(0));
23331 let end = buffer.anchor_after(buffer.len());
23332 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23333 }
23334
23335 #[cfg(any(test, feature = "test-support"))]
23336 pub fn sorted_background_highlights_in_range(
23337 &self,
23338 search_range: Range<Anchor>,
23339 display_snapshot: &DisplaySnapshot,
23340 theme: &Theme,
23341 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23342 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23343 res.sort_by(|a, b| {
23344 a.0.start
23345 .cmp(&b.0.start)
23346 .then_with(|| a.0.end.cmp(&b.0.end))
23347 .then_with(|| a.1.cmp(&b.1))
23348 });
23349 res
23350 }
23351
23352 #[cfg(any(test, feature = "test-support"))]
23353 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23354 let snapshot = self.buffer().read(cx).snapshot(cx);
23355
23356 let highlights = self
23357 .background_highlights
23358 .get(&HighlightKey::BufferSearchHighlights);
23359
23360 if let Some((_color, ranges)) = highlights {
23361 ranges
23362 .iter()
23363 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23364 .collect_vec()
23365 } else {
23366 vec![]
23367 }
23368 }
23369
23370 fn document_highlights_for_position<'a>(
23371 &'a self,
23372 position: Anchor,
23373 buffer: &'a MultiBufferSnapshot,
23374 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23375 let read_highlights = self
23376 .background_highlights
23377 .get(&HighlightKey::DocumentHighlightRead)
23378 .map(|h| &h.1);
23379 let write_highlights = self
23380 .background_highlights
23381 .get(&HighlightKey::DocumentHighlightWrite)
23382 .map(|h| &h.1);
23383 let left_position = position.bias_left(buffer);
23384 let right_position = position.bias_right(buffer);
23385 read_highlights
23386 .into_iter()
23387 .chain(write_highlights)
23388 .flat_map(move |ranges| {
23389 let start_ix = match ranges.binary_search_by(|probe| {
23390 let cmp = probe.end.cmp(&left_position, buffer);
23391 if cmp.is_ge() {
23392 Ordering::Greater
23393 } else {
23394 Ordering::Less
23395 }
23396 }) {
23397 Ok(i) | Err(i) => i,
23398 };
23399
23400 ranges[start_ix..]
23401 .iter()
23402 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23403 })
23404 }
23405
23406 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23407 self.background_highlights
23408 .get(&key)
23409 .is_some_and(|(_, highlights)| !highlights.is_empty())
23410 }
23411
23412 /// Returns all background highlights for a given range.
23413 ///
23414 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23415 pub fn background_highlights_in_range(
23416 &self,
23417 search_range: Range<Anchor>,
23418 display_snapshot: &DisplaySnapshot,
23419 theme: &Theme,
23420 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23421 let mut results = Vec::new();
23422 for (color_fetcher, ranges) in self.background_highlights.values() {
23423 let start_ix = match ranges.binary_search_by(|probe| {
23424 let cmp = probe
23425 .end
23426 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23427 if cmp.is_gt() {
23428 Ordering::Greater
23429 } else {
23430 Ordering::Less
23431 }
23432 }) {
23433 Ok(i) | Err(i) => i,
23434 };
23435 for (index, range) in ranges[start_ix..].iter().enumerate() {
23436 if range
23437 .start
23438 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23439 .is_ge()
23440 {
23441 break;
23442 }
23443
23444 let color = color_fetcher(&(start_ix + index), theme);
23445 let start = range.start.to_display_point(display_snapshot);
23446 let end = range.end.to_display_point(display_snapshot);
23447 results.push((start..end, color))
23448 }
23449 }
23450 results
23451 }
23452
23453 pub fn gutter_highlights_in_range(
23454 &self,
23455 search_range: Range<Anchor>,
23456 display_snapshot: &DisplaySnapshot,
23457 cx: &App,
23458 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23459 let mut results = Vec::new();
23460 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23461 let color = color_fetcher(cx);
23462 let start_ix = match ranges.binary_search_by(|probe| {
23463 let cmp = probe
23464 .end
23465 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23466 if cmp.is_gt() {
23467 Ordering::Greater
23468 } else {
23469 Ordering::Less
23470 }
23471 }) {
23472 Ok(i) | Err(i) => i,
23473 };
23474 for range in &ranges[start_ix..] {
23475 if range
23476 .start
23477 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23478 .is_ge()
23479 {
23480 break;
23481 }
23482
23483 let start = range.start.to_display_point(display_snapshot);
23484 let end = range.end.to_display_point(display_snapshot);
23485 results.push((start..end, color))
23486 }
23487 }
23488 results
23489 }
23490
23491 /// Get the text ranges corresponding to the redaction query
23492 pub fn redacted_ranges(
23493 &self,
23494 search_range: Range<Anchor>,
23495 display_snapshot: &DisplaySnapshot,
23496 cx: &App,
23497 ) -> Vec<Range<DisplayPoint>> {
23498 display_snapshot
23499 .buffer_snapshot()
23500 .redacted_ranges(search_range, |file| {
23501 if let Some(file) = file {
23502 file.is_private()
23503 && EditorSettings::get(
23504 Some(SettingsLocation {
23505 worktree_id: file.worktree_id(cx),
23506 path: file.path().as_ref(),
23507 }),
23508 cx,
23509 )
23510 .redact_private_values
23511 } else {
23512 false
23513 }
23514 })
23515 .map(|range| {
23516 range.start.to_display_point(display_snapshot)
23517 ..range.end.to_display_point(display_snapshot)
23518 })
23519 .collect()
23520 }
23521
23522 pub fn highlight_text_key(
23523 &mut self,
23524 key: HighlightKey,
23525 ranges: Vec<Range<Anchor>>,
23526 style: HighlightStyle,
23527 merge: bool,
23528 cx: &mut Context<Self>,
23529 ) {
23530 self.display_map.update(cx, |map, cx| {
23531 map.highlight_text(key, ranges, style, merge, cx);
23532 });
23533 cx.notify();
23534 }
23535
23536 pub fn highlight_text(
23537 &mut self,
23538 key: HighlightKey,
23539 ranges: Vec<Range<Anchor>>,
23540 style: HighlightStyle,
23541 cx: &mut Context<Self>,
23542 ) {
23543 self.display_map.update(cx, |map, cx| {
23544 map.highlight_text(key, ranges, style, false, cx)
23545 });
23546 cx.notify();
23547 }
23548
23549 pub fn text_highlights<'a>(
23550 &'a self,
23551 key: HighlightKey,
23552 cx: &'a App,
23553 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23554 self.display_map.read(cx).text_highlights(key)
23555 }
23556
23557 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23558 let cleared = self
23559 .display_map
23560 .update(cx, |map, _| map.clear_highlights(key));
23561 if cleared {
23562 cx.notify();
23563 }
23564 }
23565
23566 pub fn clear_highlights_with(
23567 &mut self,
23568 f: &mut dyn FnMut(&HighlightKey) -> bool,
23569 cx: &mut Context<Self>,
23570 ) {
23571 let cleared = self
23572 .display_map
23573 .update(cx, |map, _| map.clear_highlights_with(f));
23574 if cleared {
23575 cx.notify();
23576 }
23577 }
23578
23579 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23580 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23581 && self.focus_handle.is_focused(window)
23582 }
23583
23584 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23585 self.show_cursor_when_unfocused = is_enabled;
23586 cx.notify();
23587 }
23588
23589 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23590 cx.notify();
23591 }
23592
23593 fn on_debug_session_event(
23594 &mut self,
23595 _session: Entity<Session>,
23596 event: &SessionEvent,
23597 cx: &mut Context<Self>,
23598 ) {
23599 if let SessionEvent::InvalidateInlineValue = event {
23600 self.refresh_inline_values(cx);
23601 }
23602 }
23603
23604 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23605 let Some(semantics) = self.semantics_provider.clone() else {
23606 return;
23607 };
23608
23609 if !self.inline_value_cache.enabled {
23610 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23611 self.splice_inlays(&inlays, Vec::new(), cx);
23612 return;
23613 }
23614
23615 let current_execution_position = self
23616 .highlighted_rows
23617 .get(&TypeId::of::<ActiveDebugLine>())
23618 .and_then(|lines| lines.last().map(|line| line.range.end));
23619
23620 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23621 let inline_values = editor
23622 .update(cx, |editor, cx| {
23623 let Some(current_execution_position) = current_execution_position else {
23624 return Some(Task::ready(Ok(Vec::new())));
23625 };
23626
23627 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23628 let snapshot = buffer.snapshot(cx);
23629
23630 let excerpt = snapshot.excerpt_containing(
23631 current_execution_position..current_execution_position,
23632 )?;
23633
23634 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23635 })?;
23636
23637 let range =
23638 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23639
23640 semantics.inline_values(buffer, range, cx)
23641 })
23642 .ok()
23643 .flatten()?
23644 .await
23645 .context("refreshing debugger inlays")
23646 .log_err()?;
23647
23648 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23649
23650 for (buffer_id, inline_value) in inline_values
23651 .into_iter()
23652 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23653 {
23654 buffer_inline_values
23655 .entry(buffer_id)
23656 .or_default()
23657 .push(inline_value);
23658 }
23659
23660 editor
23661 .update(cx, |editor, cx| {
23662 let snapshot = editor.buffer.read(cx).snapshot(cx);
23663 let mut new_inlays = Vec::default();
23664
23665 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23666 let buffer_id = buffer_snapshot.remote_id();
23667 buffer_inline_values
23668 .get(&buffer_id)
23669 .into_iter()
23670 .flatten()
23671 .for_each(|hint| {
23672 let inlay = Inlay::debugger(
23673 post_inc(&mut editor.next_inlay_id),
23674 Anchor::in_buffer(excerpt_id, hint.position),
23675 hint.text(),
23676 );
23677 if !inlay.text().chars().contains(&'\n') {
23678 new_inlays.push(inlay);
23679 }
23680 });
23681 }
23682
23683 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23684 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23685
23686 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23687 })
23688 .ok()?;
23689 Some(())
23690 });
23691 }
23692
23693 fn on_buffer_event(
23694 &mut self,
23695 multibuffer: &Entity<MultiBuffer>,
23696 event: &multi_buffer::Event,
23697 window: &mut Window,
23698 cx: &mut Context<Self>,
23699 ) {
23700 match event {
23701 multi_buffer::Event::Edited {
23702 edited_buffer,
23703 is_local,
23704 } => {
23705 self.scrollbar_marker_state.dirty = true;
23706 self.active_indent_guides_state.dirty = true;
23707 self.refresh_active_diagnostics(cx);
23708 self.refresh_code_actions(window, cx);
23709 self.refresh_single_line_folds(window, cx);
23710 let snapshot = self.snapshot(window, cx);
23711 self.refresh_matching_bracket_highlights(&snapshot, cx);
23712 self.refresh_outline_symbols_at_cursor(cx);
23713 self.refresh_sticky_headers(&snapshot, cx);
23714 if *is_local && self.has_active_edit_prediction() {
23715 self.update_visible_edit_prediction(window, cx);
23716 }
23717
23718 // Clean up orphaned review comments after edits
23719 self.cleanup_orphaned_review_comments(cx);
23720
23721 if let Some(buffer) = edited_buffer {
23722 if buffer.read(cx).file().is_none() {
23723 cx.emit(EditorEvent::TitleChanged);
23724 }
23725
23726 if self.project.is_some() {
23727 let buffer_id = buffer.read(cx).remote_id();
23728 self.register_buffer(buffer_id, cx);
23729 self.update_lsp_data(Some(buffer_id), window, cx);
23730 self.refresh_inlay_hints(
23731 InlayHintRefreshReason::BufferEdited(buffer_id),
23732 cx,
23733 );
23734 }
23735 }
23736
23737 cx.emit(EditorEvent::BufferEdited);
23738 cx.emit(SearchEvent::MatchesInvalidated);
23739
23740 let Some(project) = &self.project else { return };
23741 let (telemetry, is_via_ssh) = {
23742 let project = project.read(cx);
23743 let telemetry = project.client().telemetry().clone();
23744 let is_via_ssh = project.is_via_remote_server();
23745 (telemetry, is_via_ssh)
23746 };
23747 telemetry.log_edit_event("editor", is_via_ssh);
23748 }
23749 multi_buffer::Event::ExcerptsAdded {
23750 buffer,
23751 predecessor,
23752 excerpts,
23753 } => {
23754 let buffer_id = buffer.read(cx).remote_id();
23755 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23756 && let Some(project) = &self.project
23757 {
23758 update_uncommitted_diff_for_buffer(
23759 cx.entity(),
23760 project,
23761 [buffer.clone()],
23762 self.buffer.clone(),
23763 cx,
23764 )
23765 .detach();
23766 }
23767 self.semantic_token_state
23768 .invalidate_buffer(&buffer.read(cx).remote_id());
23769 self.update_lsp_data(Some(buffer_id), window, cx);
23770 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23771 self.refresh_runnables(window, cx);
23772 self.colorize_brackets(false, cx);
23773 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
23774 cx.emit(EditorEvent::ExcerptsAdded {
23775 buffer: buffer.clone(),
23776 predecessor: *predecessor,
23777 excerpts: excerpts.clone(),
23778 });
23779 }
23780 multi_buffer::Event::ExcerptsRemoved {
23781 ids,
23782 removed_buffer_ids,
23783 } => {
23784 if let Some(inlay_hints) = &mut self.inlay_hints {
23785 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23786 }
23787 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23788 for buffer_id in removed_buffer_ids {
23789 self.registered_buffers.remove(buffer_id);
23790 self.clear_runnables(Some(*buffer_id));
23791 self.semantic_token_state.invalidate_buffer(buffer_id);
23792 self.display_map.update(cx, |display_map, cx| {
23793 display_map.invalidate_semantic_highlights(*buffer_id);
23794 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
23795 });
23796 }
23797
23798 self.display_map.update(cx, |display_map, cx| {
23799 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
23800 });
23801
23802 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23803 cx.emit(EditorEvent::ExcerptsRemoved {
23804 ids: ids.clone(),
23805 removed_buffer_ids: removed_buffer_ids.clone(),
23806 });
23807 }
23808 multi_buffer::Event::ExcerptsEdited {
23809 excerpt_ids,
23810 buffer_ids,
23811 } => {
23812 self.display_map.update(cx, |map, cx| {
23813 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23814 });
23815 cx.emit(EditorEvent::ExcerptsEdited {
23816 ids: excerpt_ids.clone(),
23817 });
23818 }
23819 multi_buffer::Event::ExcerptsExpanded { ids } => {
23820 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23821 self.refresh_document_highlights(cx);
23822 let snapshot = multibuffer.read(cx).snapshot(cx);
23823 for id in ids {
23824 self.bracket_fetched_tree_sitter_chunks.remove(id);
23825 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
23826 self.semantic_token_state
23827 .invalidate_buffer(&buffer.remote_id());
23828 }
23829 }
23830 self.colorize_brackets(false, cx);
23831 self.update_lsp_data(None, window, cx);
23832 self.refresh_runnables(window, cx);
23833 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23834 }
23835 multi_buffer::Event::Reparsed(buffer_id) => {
23836 self.clear_runnables(Some(*buffer_id));
23837 self.refresh_runnables(window, cx);
23838 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
23839 self.colorize_brackets(true, cx);
23840 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23841
23842 cx.emit(EditorEvent::Reparsed(*buffer_id));
23843 }
23844 multi_buffer::Event::DiffHunksToggled => {
23845 self.refresh_runnables(window, cx);
23846 }
23847 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23848 if !is_fresh_language {
23849 self.registered_buffers.remove(&buffer_id);
23850 }
23851 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23852 cx.emit(EditorEvent::Reparsed(*buffer_id));
23853 cx.notify();
23854 }
23855 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
23856 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
23857 multi_buffer::Event::FileHandleChanged
23858 | multi_buffer::Event::Reloaded
23859 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
23860 multi_buffer::Event::DiagnosticsUpdated => {
23861 self.update_diagnostics_state(window, cx);
23862 }
23863 _ => {}
23864 };
23865 }
23866
23867 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
23868 if !self.diagnostics_enabled() {
23869 return;
23870 }
23871 self.refresh_active_diagnostics(cx);
23872 self.refresh_inline_diagnostics(true, window, cx);
23873 self.scrollbar_marker_state.dirty = true;
23874 cx.notify();
23875 }
23876
23877 pub fn start_temporary_diff_override(&mut self) {
23878 self.load_diff_task.take();
23879 self.temporary_diff_override = true;
23880 }
23881
23882 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
23883 self.temporary_diff_override = false;
23884 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
23885 self.buffer.update(cx, |buffer, cx| {
23886 buffer.set_all_diff_hunks_collapsed(cx);
23887 });
23888
23889 if let Some(project) = self.project.clone() {
23890 self.load_diff_task = Some(
23891 update_uncommitted_diff_for_buffer(
23892 cx.entity(),
23893 &project,
23894 self.buffer.read(cx).all_buffers(),
23895 self.buffer.clone(),
23896 cx,
23897 )
23898 .shared(),
23899 );
23900 }
23901 }
23902
23903 fn on_display_map_changed(
23904 &mut self,
23905 _: Entity<DisplayMap>,
23906 _: &mut Window,
23907 cx: &mut Context<Self>,
23908 ) {
23909 cx.notify();
23910 }
23911
23912 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
23913 if !self.mode.is_full() {
23914 return None;
23915 }
23916
23917 let theme_settings = theme::ThemeSettings::get_global(cx);
23918 let theme = cx.theme();
23919 let accent_colors = theme.accents().clone();
23920
23921 let accent_overrides = theme_settings
23922 .theme_overrides
23923 .get(theme.name.as_ref())
23924 .map(|theme_style| &theme_style.accents)
23925 .into_iter()
23926 .flatten()
23927 .chain(
23928 theme_settings
23929 .experimental_theme_overrides
23930 .as_ref()
23931 .map(|overrides| &overrides.accents)
23932 .into_iter()
23933 .flatten(),
23934 )
23935 .flat_map(|accent| accent.0.clone().map(SharedString::from))
23936 .collect();
23937
23938 Some(AccentData {
23939 colors: accent_colors,
23940 overrides: accent_overrides,
23941 })
23942 }
23943
23944 fn fetch_applicable_language_settings(
23945 &self,
23946 cx: &App,
23947 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
23948 if !self.mode.is_full() {
23949 return HashMap::default();
23950 }
23951
23952 self.buffer().read(cx).all_buffers().into_iter().fold(
23953 HashMap::default(),
23954 |mut acc, buffer| {
23955 let buffer = buffer.read(cx);
23956 let language = buffer.language().map(|language| language.name());
23957 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
23958 let file = buffer.file();
23959 v.insert(language_settings(language, file, cx).into_owned());
23960 }
23961 acc
23962 },
23963 )
23964 }
23965
23966 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23967 let new_language_settings = self.fetch_applicable_language_settings(cx);
23968 let language_settings_changed = new_language_settings != self.applicable_language_settings;
23969 self.applicable_language_settings = new_language_settings;
23970
23971 let new_accents = self.fetch_accent_data(cx);
23972 let accents_changed = new_accents != self.accent_data;
23973 self.accent_data = new_accents;
23974
23975 if self.diagnostics_enabled() {
23976 let new_severity = EditorSettings::get_global(cx)
23977 .diagnostics_max_severity
23978 .unwrap_or(DiagnosticSeverity::Hint);
23979 self.set_max_diagnostics_severity(new_severity, cx);
23980 }
23981 self.refresh_runnables(window, cx);
23982 self.update_edit_prediction_settings(cx);
23983 self.refresh_edit_prediction(true, false, window, cx);
23984 self.refresh_inline_values(cx);
23985
23986 let old_cursor_shape = self.cursor_shape;
23987 let old_show_breadcrumbs = self.show_breadcrumbs;
23988
23989 {
23990 let editor_settings = EditorSettings::get_global(cx);
23991 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
23992 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
23993 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
23994 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
23995 }
23996
23997 if old_cursor_shape != self.cursor_shape {
23998 cx.emit(EditorEvent::CursorShapeChanged);
23999 }
24000
24001 if old_show_breadcrumbs != self.show_breadcrumbs {
24002 cx.emit(EditorEvent::BreadcrumbsChanged);
24003 }
24004
24005 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24006 let project_settings = ProjectSettings::get_global(cx);
24007 (
24008 project_settings.session.restore_unsaved_buffers,
24009 project_settings.diagnostics.inline.enabled,
24010 project_settings.git.inline_blame.enabled,
24011 )
24012 };
24013 self.buffer_serialization = self
24014 .should_serialize_buffer()
24015 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24016
24017 if self.mode.is_full() {
24018 if self.show_inline_diagnostics != show_inline_diagnostics {
24019 self.show_inline_diagnostics = show_inline_diagnostics;
24020 self.refresh_inline_diagnostics(false, window, cx);
24021 }
24022
24023 if self.git_blame_inline_enabled != inline_blame_enabled {
24024 self.toggle_git_blame_inline_internal(false, window, cx);
24025 }
24026
24027 let minimap_settings = EditorSettings::get_global(cx).minimap;
24028 if self.minimap_visibility != MinimapVisibility::Disabled {
24029 if self.minimap_visibility.settings_visibility()
24030 != minimap_settings.minimap_enabled()
24031 {
24032 self.set_minimap_visibility(
24033 MinimapVisibility::for_mode(self.mode(), cx),
24034 window,
24035 cx,
24036 );
24037 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24038 minimap_entity.update(cx, |minimap_editor, cx| {
24039 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24040 })
24041 }
24042 }
24043
24044 if language_settings_changed || accents_changed {
24045 self.colorize_brackets(true, cx);
24046 }
24047
24048 if language_settings_changed {
24049 self.clear_disabled_lsp_folding_ranges(window, cx);
24050 self.refresh_document_symbols(None, cx);
24051 }
24052
24053 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24054 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24055 }) {
24056 if !inlay_splice.is_empty() {
24057 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24058 }
24059 self.refresh_document_colors(None, window, cx);
24060 }
24061
24062 self.refresh_inlay_hints(
24063 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24064 self.selections.newest_anchor().head(),
24065 &self.buffer.read(cx).snapshot(cx),
24066 cx,
24067 )),
24068 cx,
24069 );
24070
24071 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24072 .global_lsp_settings
24073 .semantic_token_rules
24074 .clone();
24075 let semantic_token_rules_changed = self
24076 .semantic_token_state
24077 .update_rules(new_semantic_token_rules);
24078 if language_settings_changed || semantic_token_rules_changed {
24079 self.invalidate_semantic_tokens(None);
24080 self.refresh_semantic_tokens(None, None, cx);
24081 }
24082 }
24083
24084 cx.notify();
24085 }
24086
24087 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24088 if !self.mode.is_full() {
24089 return;
24090 }
24091
24092 let new_accents = self.fetch_accent_data(cx);
24093 if new_accents != self.accent_data {
24094 self.accent_data = new_accents;
24095 self.colorize_brackets(true, cx);
24096 }
24097
24098 self.invalidate_semantic_tokens(None);
24099 self.refresh_semantic_tokens(None, None, cx);
24100 }
24101
24102 pub fn set_searchable(&mut self, searchable: bool) {
24103 self.searchable = searchable;
24104 }
24105
24106 pub fn searchable(&self) -> bool {
24107 self.searchable
24108 }
24109
24110 pub fn open_excerpts_in_split(
24111 &mut self,
24112 _: &OpenExcerptsSplit,
24113 window: &mut Window,
24114 cx: &mut Context<Self>,
24115 ) {
24116 self.open_excerpts_common(None, true, window, cx)
24117 }
24118
24119 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24120 self.open_excerpts_common(None, false, window, cx)
24121 }
24122
24123 pub(crate) fn open_excerpts_common(
24124 &mut self,
24125 jump_data: Option<JumpData>,
24126 split: bool,
24127 window: &mut Window,
24128 cx: &mut Context<Self>,
24129 ) {
24130 if self.buffer.read(cx).is_singleton() {
24131 cx.propagate();
24132 return;
24133 }
24134
24135 let mut new_selections_by_buffer = HashMap::default();
24136 match &jump_data {
24137 Some(JumpData::MultiBufferPoint {
24138 excerpt_id,
24139 position,
24140 anchor,
24141 line_offset_from_top,
24142 }) => {
24143 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24144 if let Some(buffer) = multi_buffer_snapshot
24145 .buffer_id_for_excerpt(*excerpt_id)
24146 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24147 {
24148 let buffer_snapshot = buffer.read(cx).snapshot();
24149 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24150 language::ToPoint::to_point(anchor, &buffer_snapshot)
24151 } else {
24152 buffer_snapshot.clip_point(*position, Bias::Left)
24153 };
24154 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24155 new_selections_by_buffer.insert(
24156 buffer,
24157 (
24158 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24159 Some(*line_offset_from_top),
24160 ),
24161 );
24162 }
24163 }
24164 Some(JumpData::MultiBufferRow {
24165 row,
24166 line_offset_from_top,
24167 }) => {
24168 let point = MultiBufferPoint::new(row.0, 0);
24169 if let Some((buffer, buffer_point, _)) =
24170 self.buffer.read(cx).point_to_buffer_point(point, cx)
24171 {
24172 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24173 new_selections_by_buffer
24174 .entry(buffer)
24175 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24176 .0
24177 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24178 }
24179 }
24180 None => {
24181 let selections = self
24182 .selections
24183 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24184 let multi_buffer = self.buffer.read(cx);
24185 for selection in selections {
24186 for (snapshot, range, _, anchor) in multi_buffer
24187 .snapshot(cx)
24188 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24189 {
24190 if let Some(anchor) = anchor {
24191 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24192 else {
24193 continue;
24194 };
24195 let offset = text::ToOffset::to_offset(
24196 &anchor.text_anchor,
24197 &buffer_handle.read(cx).snapshot(),
24198 );
24199 let range = BufferOffset(offset)..BufferOffset(offset);
24200 new_selections_by_buffer
24201 .entry(buffer_handle)
24202 .or_insert((Vec::new(), None))
24203 .0
24204 .push(range)
24205 } else {
24206 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24207 else {
24208 continue;
24209 };
24210 new_selections_by_buffer
24211 .entry(buffer_handle)
24212 .or_insert((Vec::new(), None))
24213 .0
24214 .push(range)
24215 }
24216 }
24217 }
24218 }
24219 }
24220
24221 if self.delegate_open_excerpts {
24222 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24223 .into_iter()
24224 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24225 .collect();
24226 if !selections_by_buffer.is_empty() {
24227 cx.emit(EditorEvent::OpenExcerptsRequested {
24228 selections_by_buffer,
24229 split,
24230 });
24231 }
24232 return;
24233 }
24234
24235 let Some(workspace) = self.workspace() else {
24236 cx.propagate();
24237 return;
24238 };
24239
24240 new_selections_by_buffer
24241 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24242
24243 if new_selections_by_buffer.is_empty() {
24244 return;
24245 }
24246
24247 Self::open_buffers_in_workspace(
24248 workspace.downgrade(),
24249 new_selections_by_buffer,
24250 split,
24251 window,
24252 cx,
24253 );
24254 }
24255
24256 pub(crate) fn open_buffers_in_workspace(
24257 workspace: WeakEntity<Workspace>,
24258 new_selections_by_buffer: HashMap<
24259 Entity<language::Buffer>,
24260 (Vec<Range<BufferOffset>>, Option<u32>),
24261 >,
24262 split: bool,
24263 window: &mut Window,
24264 cx: &mut App,
24265 ) {
24266 // We defer the pane interaction because we ourselves are a workspace item
24267 // and activating a new item causes the pane to call a method on us reentrantly,
24268 // which panics if we're on the stack.
24269 window.defer(cx, move |window, cx| {
24270 workspace
24271 .update(cx, |workspace, cx| {
24272 let pane = if split {
24273 workspace.adjacent_pane(window, cx)
24274 } else {
24275 workspace.active_pane().clone()
24276 };
24277
24278 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24279 let buffer_read = buffer.read(cx);
24280 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24281 (true, project::File::from_dyn(Some(file)).is_some())
24282 } else {
24283 (false, false)
24284 };
24285
24286 // If project file is none workspace.open_project_item will fail to open the excerpt
24287 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24288 // so we check if there's a tab match in that case first
24289 let editor = (!has_file || !is_project_file)
24290 .then(|| {
24291 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24292 // so `workspace.open_project_item` will never find them, always opening a new editor.
24293 // Instead, we try to activate the existing editor in the pane first.
24294 let (editor, pane_item_index, pane_item_id) =
24295 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24296 let editor = item.downcast::<Editor>()?;
24297 let singleton_buffer =
24298 editor.read(cx).buffer().read(cx).as_singleton()?;
24299 if singleton_buffer == buffer {
24300 Some((editor, i, item.item_id()))
24301 } else {
24302 None
24303 }
24304 })?;
24305 pane.update(cx, |pane, cx| {
24306 pane.activate_item(pane_item_index, true, true, window, cx);
24307 if !PreviewTabsSettings::get_global(cx)
24308 .enable_preview_from_multibuffer
24309 {
24310 pane.unpreview_item_if_preview(pane_item_id);
24311 }
24312 });
24313 Some(editor)
24314 })
24315 .flatten()
24316 .unwrap_or_else(|| {
24317 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24318 .enable_keep_preview_on_code_navigation;
24319 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24320 .enable_preview_from_multibuffer;
24321 workspace.open_project_item::<Self>(
24322 pane.clone(),
24323 buffer,
24324 true,
24325 true,
24326 keep_old_preview,
24327 allow_new_preview,
24328 window,
24329 cx,
24330 )
24331 });
24332
24333 editor.update(cx, |editor, cx| {
24334 if has_file && !is_project_file {
24335 editor.set_read_only(true);
24336 }
24337 let autoscroll = match scroll_offset {
24338 Some(scroll_offset) => {
24339 Autoscroll::top_relative(scroll_offset as usize)
24340 }
24341 None => Autoscroll::newest(),
24342 };
24343 let nav_history = editor.nav_history.take();
24344 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24345 let Some((excerpt_id, _, buffer_snapshot)) =
24346 multibuffer_snapshot.as_singleton()
24347 else {
24348 return;
24349 };
24350 editor.change_selections(
24351 SelectionEffects::scroll(autoscroll),
24352 window,
24353 cx,
24354 |s| {
24355 s.select_ranges(ranges.into_iter().map(|range| {
24356 let range = buffer_snapshot.anchor_before(range.start)
24357 ..buffer_snapshot.anchor_after(range.end);
24358 multibuffer_snapshot
24359 .anchor_range_in_excerpt(excerpt_id, range)
24360 .unwrap()
24361 }));
24362 },
24363 );
24364 editor.nav_history = nav_history;
24365 });
24366 }
24367 })
24368 .ok();
24369 });
24370 }
24371
24372 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24373 let snapshot = self.buffer.read(cx).read(cx);
24374 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24375 Some(
24376 ranges
24377 .iter()
24378 .map(move |range| {
24379 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24380 })
24381 .collect(),
24382 )
24383 }
24384
24385 fn selection_replacement_ranges(
24386 &self,
24387 range: Range<MultiBufferOffsetUtf16>,
24388 cx: &mut App,
24389 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24390 let selections = self
24391 .selections
24392 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24393 let newest_selection = selections
24394 .iter()
24395 .max_by_key(|selection| selection.id)
24396 .unwrap();
24397 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24398 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24399 let snapshot = self.buffer.read(cx).read(cx);
24400 selections
24401 .into_iter()
24402 .map(|mut selection| {
24403 selection.start.0.0 =
24404 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24405 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24406 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24407 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24408 })
24409 .collect()
24410 }
24411
24412 fn report_editor_event(
24413 &self,
24414 reported_event: ReportEditorEvent,
24415 file_extension: Option<String>,
24416 cx: &App,
24417 ) {
24418 if cfg!(any(test, feature = "test-support")) {
24419 return;
24420 }
24421
24422 let Some(project) = &self.project else { return };
24423
24424 // If None, we are in a file without an extension
24425 let file = self
24426 .buffer
24427 .read(cx)
24428 .as_singleton()
24429 .and_then(|b| b.read(cx).file());
24430 let file_extension = file_extension.or(file
24431 .as_ref()
24432 .and_then(|file| Path::new(file.file_name(cx)).extension())
24433 .and_then(|e| e.to_str())
24434 .map(|a| a.to_string()));
24435
24436 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24437 .map(|vim_mode| vim_mode.0)
24438 .unwrap_or(false);
24439
24440 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24441 let copilot_enabled = edit_predictions_provider
24442 == language::language_settings::EditPredictionProvider::Copilot;
24443 let copilot_enabled_for_language = self
24444 .buffer
24445 .read(cx)
24446 .language_settings(cx)
24447 .show_edit_predictions;
24448
24449 let project = project.read(cx);
24450 let event_type = reported_event.event_type();
24451
24452 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24453 telemetry::event!(
24454 event_type,
24455 type = if auto_saved {"autosave"} else {"manual"},
24456 file_extension,
24457 vim_mode,
24458 copilot_enabled,
24459 copilot_enabled_for_language,
24460 edit_predictions_provider,
24461 is_via_ssh = project.is_via_remote_server(),
24462 );
24463 } else {
24464 telemetry::event!(
24465 event_type,
24466 file_extension,
24467 vim_mode,
24468 copilot_enabled,
24469 copilot_enabled_for_language,
24470 edit_predictions_provider,
24471 is_via_ssh = project.is_via_remote_server(),
24472 );
24473 };
24474 }
24475
24476 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24477 /// with each line being an array of {text, highlight} objects.
24478 fn copy_highlight_json(
24479 &mut self,
24480 _: &CopyHighlightJson,
24481 window: &mut Window,
24482 cx: &mut Context<Self>,
24483 ) {
24484 #[derive(Serialize)]
24485 struct Chunk<'a> {
24486 text: String,
24487 highlight: Option<&'a str>,
24488 }
24489
24490 let snapshot = self.buffer.read(cx).snapshot(cx);
24491 let range = self
24492 .selected_text_range(false, window, cx)
24493 .and_then(|selection| {
24494 if selection.range.is_empty() {
24495 None
24496 } else {
24497 Some(
24498 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24499 selection.range.start,
24500 )))
24501 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24502 selection.range.end,
24503 ))),
24504 )
24505 }
24506 })
24507 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24508
24509 let chunks = snapshot.chunks(range, true);
24510 let mut lines = Vec::new();
24511 let mut line: VecDeque<Chunk> = VecDeque::new();
24512
24513 let Some(style) = self.style.as_ref() else {
24514 return;
24515 };
24516
24517 for chunk in chunks {
24518 let highlight = chunk
24519 .syntax_highlight_id
24520 .and_then(|id| id.name(&style.syntax));
24521 let mut chunk_lines = chunk.text.split('\n').peekable();
24522 while let Some(text) = chunk_lines.next() {
24523 let mut merged_with_last_token = false;
24524 if let Some(last_token) = line.back_mut()
24525 && last_token.highlight == highlight
24526 {
24527 last_token.text.push_str(text);
24528 merged_with_last_token = true;
24529 }
24530
24531 if !merged_with_last_token {
24532 line.push_back(Chunk {
24533 text: text.into(),
24534 highlight,
24535 });
24536 }
24537
24538 if chunk_lines.peek().is_some() {
24539 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24540 line.pop_front();
24541 }
24542 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24543 line.pop_back();
24544 }
24545
24546 lines.push(mem::take(&mut line));
24547 }
24548 }
24549 }
24550
24551 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24552 return;
24553 };
24554 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24555 }
24556
24557 pub fn open_context_menu(
24558 &mut self,
24559 _: &OpenContextMenu,
24560 window: &mut Window,
24561 cx: &mut Context<Self>,
24562 ) {
24563 self.request_autoscroll(Autoscroll::newest(), cx);
24564 let position = self
24565 .selections
24566 .newest_display(&self.display_snapshot(cx))
24567 .start;
24568 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24569 }
24570
24571 pub fn replay_insert_event(
24572 &mut self,
24573 text: &str,
24574 relative_utf16_range: Option<Range<isize>>,
24575 window: &mut Window,
24576 cx: &mut Context<Self>,
24577 ) {
24578 if !self.input_enabled {
24579 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24580 return;
24581 }
24582 if let Some(relative_utf16_range) = relative_utf16_range {
24583 let selections = self
24584 .selections
24585 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24586 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24587 let new_ranges = selections.into_iter().map(|range| {
24588 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24589 range
24590 .head()
24591 .0
24592 .0
24593 .saturating_add_signed(relative_utf16_range.start),
24594 ));
24595 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24596 range
24597 .head()
24598 .0
24599 .0
24600 .saturating_add_signed(relative_utf16_range.end),
24601 ));
24602 start..end
24603 });
24604 s.select_ranges(new_ranges);
24605 });
24606 }
24607
24608 self.handle_input(text, window, cx);
24609 }
24610
24611 pub fn is_focused(&self, window: &Window) -> bool {
24612 self.focus_handle.is_focused(window)
24613 }
24614
24615 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24616 cx.emit(EditorEvent::Focused);
24617
24618 if let Some(descendant) = self
24619 .last_focused_descendant
24620 .take()
24621 .and_then(|descendant| descendant.upgrade())
24622 {
24623 window.focus(&descendant, cx);
24624 } else {
24625 if let Some(blame) = self.blame.as_ref() {
24626 blame.update(cx, GitBlame::focus)
24627 }
24628
24629 self.blink_manager.update(cx, BlinkManager::enable);
24630 self.show_cursor_names(window, cx);
24631 self.buffer.update(cx, |buffer, cx| {
24632 buffer.finalize_last_transaction(cx);
24633 if self.leader_id.is_none() {
24634 buffer.set_active_selections(
24635 &self.selections.disjoint_anchors_arc(),
24636 self.selections.line_mode(),
24637 self.cursor_shape,
24638 cx,
24639 );
24640 }
24641 });
24642
24643 if let Some(position_map) = self.last_position_map.clone() {
24644 EditorElement::mouse_moved(
24645 self,
24646 &MouseMoveEvent {
24647 position: window.mouse_position(),
24648 pressed_button: None,
24649 modifiers: window.modifiers(),
24650 },
24651 &position_map,
24652 None,
24653 window,
24654 cx,
24655 );
24656 }
24657 }
24658 }
24659
24660 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24661 cx.emit(EditorEvent::FocusedIn)
24662 }
24663
24664 fn handle_focus_out(
24665 &mut self,
24666 event: FocusOutEvent,
24667 _window: &mut Window,
24668 cx: &mut Context<Self>,
24669 ) {
24670 if event.blurred != self.focus_handle {
24671 self.last_focused_descendant = Some(event.blurred);
24672 }
24673 self.selection_drag_state = SelectionDragState::None;
24674 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24675 }
24676
24677 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24678 self.blink_manager.update(cx, BlinkManager::disable);
24679 self.buffer
24680 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24681
24682 if let Some(blame) = self.blame.as_ref() {
24683 blame.update(cx, GitBlame::blur)
24684 }
24685 if !self.hover_state.focused(window, cx) {
24686 hide_hover(self, cx);
24687 }
24688 if !self
24689 .context_menu
24690 .borrow()
24691 .as_ref()
24692 .is_some_and(|context_menu| context_menu.focused(window, cx))
24693 {
24694 self.hide_context_menu(window, cx);
24695 }
24696 self.take_active_edit_prediction(cx);
24697 cx.emit(EditorEvent::Blurred);
24698 cx.notify();
24699 }
24700
24701 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24702 let mut pending: String = window
24703 .pending_input_keystrokes()
24704 .into_iter()
24705 .flatten()
24706 .filter_map(|keystroke| keystroke.key_char.clone())
24707 .collect();
24708
24709 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24710 pending = "".to_string();
24711 }
24712
24713 let existing_pending = self
24714 .text_highlights(HighlightKey::PendingInput, cx)
24715 .map(|(_, ranges)| ranges.to_vec());
24716 if existing_pending.is_none() && pending.is_empty() {
24717 return;
24718 }
24719 let transaction =
24720 self.transact(window, cx, |this, window, cx| {
24721 let selections = this
24722 .selections
24723 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24724 let edits = selections
24725 .iter()
24726 .map(|selection| (selection.end..selection.end, pending.clone()));
24727 this.edit(edits, cx);
24728 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24729 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24730 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24731 }));
24732 });
24733 if let Some(existing_ranges) = existing_pending {
24734 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24735 this.edit(edits, cx);
24736 }
24737 });
24738
24739 let snapshot = self.snapshot(window, cx);
24740 let ranges = self
24741 .selections
24742 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24743 .into_iter()
24744 .map(|selection| {
24745 snapshot.buffer_snapshot().anchor_after(selection.end)
24746 ..snapshot
24747 .buffer_snapshot()
24748 .anchor_before(selection.end + pending.len())
24749 })
24750 .collect();
24751
24752 if pending.is_empty() {
24753 self.clear_highlights(HighlightKey::PendingInput, cx);
24754 } else {
24755 self.highlight_text(
24756 HighlightKey::PendingInput,
24757 ranges,
24758 HighlightStyle {
24759 underline: Some(UnderlineStyle {
24760 thickness: px(1.),
24761 color: None,
24762 wavy: false,
24763 }),
24764 ..Default::default()
24765 },
24766 cx,
24767 );
24768 }
24769
24770 self.ime_transaction = self.ime_transaction.or(transaction);
24771 if let Some(transaction) = self.ime_transaction {
24772 self.buffer.update(cx, |buffer, cx| {
24773 buffer.group_until_transaction(transaction, cx);
24774 });
24775 }
24776
24777 if self
24778 .text_highlights(HighlightKey::PendingInput, cx)
24779 .is_none()
24780 {
24781 self.ime_transaction.take();
24782 }
24783 }
24784
24785 pub fn register_action_renderer(
24786 &mut self,
24787 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24788 ) -> Subscription {
24789 let id = self.next_editor_action_id.post_inc();
24790 self.editor_actions
24791 .borrow_mut()
24792 .insert(id, Box::new(listener));
24793
24794 let editor_actions = self.editor_actions.clone();
24795 Subscription::new(move || {
24796 editor_actions.borrow_mut().remove(&id);
24797 })
24798 }
24799
24800 pub fn register_action<A: Action>(
24801 &mut self,
24802 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24803 ) -> Subscription {
24804 let id = self.next_editor_action_id.post_inc();
24805 let listener = Arc::new(listener);
24806 self.editor_actions.borrow_mut().insert(
24807 id,
24808 Box::new(move |_, window, _| {
24809 let listener = listener.clone();
24810 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24811 let action = action.downcast_ref().unwrap();
24812 if phase == DispatchPhase::Bubble {
24813 listener(action, window, cx)
24814 }
24815 })
24816 }),
24817 );
24818
24819 let editor_actions = self.editor_actions.clone();
24820 Subscription::new(move || {
24821 editor_actions.borrow_mut().remove(&id);
24822 })
24823 }
24824
24825 pub fn file_header_size(&self) -> u32 {
24826 FILE_HEADER_HEIGHT
24827 }
24828
24829 pub fn restore(
24830 &mut self,
24831 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24832 window: &mut Window,
24833 cx: &mut Context<Self>,
24834 ) {
24835 self.buffer().update(cx, |multi_buffer, cx| {
24836 for (buffer_id, changes) in revert_changes {
24837 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24838 buffer.update(cx, |buffer, cx| {
24839 buffer.edit(
24840 changes
24841 .into_iter()
24842 .map(|(range, text)| (range, text.to_string())),
24843 None,
24844 cx,
24845 );
24846 });
24847 }
24848 }
24849 });
24850 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24851 selections.refresh()
24852 });
24853 }
24854
24855 pub fn to_pixel_point(
24856 &mut self,
24857 source: Anchor,
24858 editor_snapshot: &EditorSnapshot,
24859 window: &mut Window,
24860 cx: &mut App,
24861 ) -> Option<gpui::Point<Pixels>> {
24862 let source_point = source.to_display_point(editor_snapshot);
24863 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
24864 }
24865
24866 pub fn display_to_pixel_point(
24867 &mut self,
24868 source: DisplayPoint,
24869 editor_snapshot: &EditorSnapshot,
24870 window: &mut Window,
24871 cx: &mut App,
24872 ) -> Option<gpui::Point<Pixels>> {
24873 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
24874 let text_layout_details = self.text_layout_details(window, cx);
24875 let scroll_top = text_layout_details
24876 .scroll_anchor
24877 .scroll_position(editor_snapshot)
24878 .y;
24879
24880 if source.row().as_f64() < scroll_top.floor() {
24881 return None;
24882 }
24883 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
24884 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
24885 Some(gpui::Point::new(source_x, source_y))
24886 }
24887
24888 pub fn has_visible_completions_menu(&self) -> bool {
24889 !self.edit_prediction_preview_is_active()
24890 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
24891 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
24892 })
24893 }
24894
24895 pub fn register_addon<T: Addon>(&mut self, instance: T) {
24896 if self.mode.is_minimap() {
24897 return;
24898 }
24899 self.addons
24900 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
24901 }
24902
24903 pub fn unregister_addon<T: Addon>(&mut self) {
24904 self.addons.remove(&std::any::TypeId::of::<T>());
24905 }
24906
24907 pub fn addon<T: Addon>(&self) -> Option<&T> {
24908 let type_id = std::any::TypeId::of::<T>();
24909 self.addons
24910 .get(&type_id)
24911 .and_then(|item| item.to_any().downcast_ref::<T>())
24912 }
24913
24914 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
24915 let type_id = std::any::TypeId::of::<T>();
24916 self.addons
24917 .get_mut(&type_id)
24918 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
24919 }
24920
24921 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
24922 let text_layout_details = self.text_layout_details(window, cx);
24923 let style = &text_layout_details.editor_style;
24924 let font_id = window.text_system().resolve_font(&style.text.font());
24925 let font_size = style.text.font_size.to_pixels(window.rem_size());
24926 let line_height = style.text.line_height_in_pixels(window.rem_size());
24927 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
24928 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
24929
24930 CharacterDimensions {
24931 em_width,
24932 em_advance,
24933 line_height,
24934 }
24935 }
24936
24937 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
24938 self.load_diff_task.clone()
24939 }
24940
24941 fn read_metadata_from_db(
24942 &mut self,
24943 item_id: u64,
24944 workspace_id: WorkspaceId,
24945 window: &mut Window,
24946 cx: &mut Context<Editor>,
24947 ) {
24948 if self.buffer_kind(cx) == ItemBufferKind::Singleton
24949 && !self.mode.is_minimap()
24950 && WorkspaceSettings::get(None, cx).restore_on_startup
24951 != RestoreOnStartupBehavior::EmptyTab
24952 {
24953 let buffer_snapshot = OnceCell::new();
24954
24955 // Get file path for path-based fold lookup
24956 let file_path: Option<Arc<Path>> =
24957 self.buffer().read(cx).as_singleton().and_then(|buffer| {
24958 project::File::from_dyn(buffer.read(cx).file())
24959 .map(|file| Arc::from(file.abs_path(cx)))
24960 });
24961
24962 // Try file_folds (path-based) first, fallback to editor_folds (migration)
24963 let (folds, needs_migration) = if let Some(ref path) = file_path {
24964 if let Some(folds) = DB.get_file_folds(workspace_id, path).log_err()
24965 && !folds.is_empty()
24966 {
24967 (Some(folds), false)
24968 } else if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
24969 && !folds.is_empty()
24970 {
24971 // Found old editor_folds data, will migrate to file_folds
24972 (Some(folds), true)
24973 } else {
24974 (None, false)
24975 }
24976 } else {
24977 // No file path, try editor_folds as fallback
24978 let folds = DB.get_editor_folds(item_id, workspace_id).log_err();
24979 (folds.filter(|f| !f.is_empty()), false)
24980 };
24981
24982 if let Some(folds) = folds {
24983 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
24984 let snapshot_len = snapshot.len().0;
24985
24986 // Helper: search for fingerprint in buffer, return offset if found
24987 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
24988 // Ensure we start at a character boundary (defensive)
24989 let search_start = snapshot
24990 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
24991 .0;
24992 let search_end = snapshot_len.saturating_sub(fingerprint.len());
24993
24994 let mut byte_offset = search_start;
24995 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
24996 if byte_offset > search_end {
24997 break;
24998 }
24999 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25000 return Some(byte_offset);
25001 }
25002 byte_offset += ch.len_utf8();
25003 }
25004 None
25005 };
25006
25007 // Track search position to handle duplicate fingerprints correctly.
25008 // Folds are stored in document order, so we advance after each match.
25009 let mut search_start = 0usize;
25010
25011 // Collect db_folds for migration (only folds with valid fingerprints)
25012 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25013
25014 let valid_folds: Vec<_> = folds
25015 .into_iter()
25016 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25017 // Skip folds without fingerprints (old data before migration)
25018 let sfp = start_fp?;
25019 let efp = end_fp?;
25020 let efp_len = efp.len();
25021
25022 // Fast path: check if fingerprints match at stored offsets
25023 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25024 let start_matches = stored_start < snapshot_len
25025 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25026 let efp_check_pos = stored_end.saturating_sub(efp_len);
25027 let end_matches = efp_check_pos >= stored_start
25028 && stored_end <= snapshot_len
25029 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25030
25031 let (new_start, new_end) = if start_matches && end_matches {
25032 // Offsets unchanged, use stored values
25033 (stored_start, stored_end)
25034 } else if sfp == efp {
25035 // Short fold: identical fingerprints can only match once per search
25036 // Use stored fold length to compute new_end
25037 let new_start = find_fingerprint(&sfp, search_start)?;
25038 let fold_len = stored_end - stored_start;
25039 let new_end = new_start + fold_len;
25040 (new_start, new_end)
25041 } else {
25042 // Slow path: search for fingerprints in buffer
25043 let new_start = find_fingerprint(&sfp, search_start)?;
25044 // Search for end_fp after start, then add efp_len to get actual fold end
25045 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25046 let new_end = efp_pos + efp_len;
25047 (new_start, new_end)
25048 };
25049
25050 // Advance search position for next fold
25051 search_start = new_end;
25052
25053 // Validate fold makes sense (end must be after start)
25054 if new_end <= new_start {
25055 return None;
25056 }
25057
25058 // Collect for migration if needed
25059 if needs_migration {
25060 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25061 }
25062
25063 Some(
25064 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25065 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25066 )
25067 })
25068 .collect();
25069
25070 if !valid_folds.is_empty() {
25071 self.fold_ranges(valid_folds, false, window, cx);
25072
25073 // Migrate from editor_folds to file_folds if we loaded from old table
25074 if needs_migration {
25075 if let Some(ref path) = file_path {
25076 let path = path.clone();
25077 cx.spawn(async move |_, _| {
25078 DB.save_file_folds(workspace_id, path, db_folds_for_migration)
25079 .await
25080 .log_err();
25081 })
25082 .detach();
25083 }
25084 }
25085 }
25086 }
25087
25088 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25089 && !selections.is_empty()
25090 {
25091 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25092 // skip adding the initial selection to selection history
25093 self.selection_history.mode = SelectionHistoryMode::Skipping;
25094 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25095 s.select_ranges(selections.into_iter().map(|(start, end)| {
25096 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25097 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25098 }));
25099 });
25100 self.selection_history.mode = SelectionHistoryMode::Normal;
25101 };
25102 }
25103
25104 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25105 }
25106
25107 /// Load folds from the file_folds database table by file path.
25108 /// Used when manually opening a file that was previously closed.
25109 fn load_folds_from_db(
25110 &mut self,
25111 workspace_id: WorkspaceId,
25112 file_path: PathBuf,
25113 window: &mut Window,
25114 cx: &mut Context<Editor>,
25115 ) {
25116 if self.mode.is_minimap()
25117 || WorkspaceSettings::get(None, cx).restore_on_startup
25118 == RestoreOnStartupBehavior::EmptyTab
25119 {
25120 return;
25121 }
25122
25123 let Some(folds) = DB.get_file_folds(workspace_id, &file_path).log_err() else {
25124 return;
25125 };
25126 if folds.is_empty() {
25127 return;
25128 }
25129
25130 let snapshot = self.buffer.read(cx).snapshot(cx);
25131 let snapshot_len = snapshot.len().0;
25132
25133 // Helper: search for fingerprint in buffer, return offset if found
25134 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25135 let search_start = snapshot
25136 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25137 .0;
25138 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25139
25140 let mut byte_offset = search_start;
25141 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25142 if byte_offset > search_end {
25143 break;
25144 }
25145 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25146 return Some(byte_offset);
25147 }
25148 byte_offset += ch.len_utf8();
25149 }
25150 None
25151 };
25152
25153 let mut search_start = 0usize;
25154
25155 let valid_folds: Vec<_> = folds
25156 .into_iter()
25157 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25158 let sfp = start_fp?;
25159 let efp = end_fp?;
25160 let efp_len = efp.len();
25161
25162 let start_matches = stored_start < snapshot_len
25163 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25164 let efp_check_pos = stored_end.saturating_sub(efp_len);
25165 let end_matches = efp_check_pos >= stored_start
25166 && stored_end <= snapshot_len
25167 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25168
25169 let (new_start, new_end) = if start_matches && end_matches {
25170 (stored_start, stored_end)
25171 } else if sfp == efp {
25172 let new_start = find_fingerprint(&sfp, search_start)?;
25173 let fold_len = stored_end - stored_start;
25174 let new_end = new_start + fold_len;
25175 (new_start, new_end)
25176 } else {
25177 let new_start = find_fingerprint(&sfp, search_start)?;
25178 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25179 let new_end = efp_pos + efp_len;
25180 (new_start, new_end)
25181 };
25182
25183 search_start = new_end;
25184
25185 if new_end <= new_start {
25186 return None;
25187 }
25188
25189 Some(
25190 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25191 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25192 )
25193 })
25194 .collect();
25195
25196 if !valid_folds.is_empty() {
25197 self.fold_ranges(valid_folds, false, window, cx);
25198 }
25199 }
25200
25201 fn lsp_data_enabled(&self) -> bool {
25202 self.enable_lsp_data && self.mode().is_full()
25203 }
25204
25205 fn update_lsp_data(
25206 &mut self,
25207 for_buffer: Option<BufferId>,
25208 window: &mut Window,
25209 cx: &mut Context<'_, Self>,
25210 ) {
25211 if !self.lsp_data_enabled() {
25212 return;
25213 }
25214
25215 if let Some(buffer_id) = for_buffer {
25216 self.pull_diagnostics(buffer_id, window, cx);
25217 }
25218 self.refresh_semantic_tokens(for_buffer, None, cx);
25219 self.refresh_document_colors(for_buffer, window, cx);
25220 self.refresh_folding_ranges(for_buffer, window, cx);
25221 self.refresh_document_symbols(for_buffer, cx);
25222 }
25223
25224 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25225 if !self.lsp_data_enabled() {
25226 return;
25227 }
25228 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25229 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25230 }
25231 }
25232
25233 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25234 if !self.lsp_data_enabled() {
25235 return;
25236 }
25237
25238 if !self.registered_buffers.contains_key(&buffer_id)
25239 && let Some(project) = self.project.as_ref()
25240 {
25241 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25242 project.update(cx, |project, cx| {
25243 self.registered_buffers.insert(
25244 buffer_id,
25245 project.register_buffer_with_language_servers(&buffer, cx),
25246 );
25247 });
25248 } else {
25249 self.registered_buffers.remove(&buffer_id);
25250 }
25251 }
25252 }
25253
25254 fn create_style(&self, cx: &App) -> EditorStyle {
25255 let settings = ThemeSettings::get_global(cx);
25256
25257 let mut text_style = match self.mode {
25258 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25259 color: cx.theme().colors().editor_foreground,
25260 font_family: settings.ui_font.family.clone(),
25261 font_features: settings.ui_font.features.clone(),
25262 font_fallbacks: settings.ui_font.fallbacks.clone(),
25263 font_size: rems(0.875).into(),
25264 font_weight: settings.ui_font.weight,
25265 line_height: relative(settings.buffer_line_height.value()),
25266 ..Default::default()
25267 },
25268 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25269 color: cx.theme().colors().editor_foreground,
25270 font_family: settings.buffer_font.family.clone(),
25271 font_features: settings.buffer_font.features.clone(),
25272 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25273 font_size: settings.buffer_font_size(cx).into(),
25274 font_weight: settings.buffer_font.weight,
25275 line_height: relative(settings.buffer_line_height.value()),
25276 ..Default::default()
25277 },
25278 };
25279 if let Some(text_style_refinement) = &self.text_style_refinement {
25280 text_style.refine(text_style_refinement)
25281 }
25282
25283 let background = match self.mode {
25284 EditorMode::SingleLine => cx.theme().system().transparent,
25285 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25286 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25287 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25288 };
25289
25290 EditorStyle {
25291 background,
25292 border: cx.theme().colors().border,
25293 local_player: cx.theme().players().local(),
25294 text: text_style,
25295 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25296 syntax: cx.theme().syntax().clone(),
25297 status: cx.theme().status().clone(),
25298 inlay_hints_style: make_inlay_hints_style(cx),
25299 edit_prediction_styles: make_suggestion_styles(cx),
25300 unnecessary_code_fade: settings.unnecessary_code_fade,
25301 show_underlines: self.diagnostics_enabled(),
25302 }
25303 }
25304
25305 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
25306 let multibuffer = self.buffer().read(cx);
25307 let is_singleton = multibuffer.is_singleton();
25308 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25309 let buffer = multibuffer.buffer(*buffer_id)?;
25310
25311 let buffer = buffer.read(cx);
25312 let settings = ThemeSettings::get_global(cx);
25313 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25314 let mut breadcrumbs = if is_singleton {
25315 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25316 buffer
25317 .snapshot()
25318 .resolve_file_path(
25319 self.project
25320 .as_ref()
25321 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25322 .unwrap_or_default(),
25323 cx,
25324 )
25325 .unwrap_or_else(|| {
25326 if multibuffer.is_singleton() {
25327 multibuffer.title(cx).to_string()
25328 } else {
25329 "untitled".to_string()
25330 }
25331 })
25332 });
25333 vec![BreadcrumbText {
25334 text,
25335 highlights: None,
25336 font: Some(settings.buffer_font.clone()),
25337 }]
25338 } else {
25339 vec![]
25340 };
25341
25342 breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
25343 text: symbol.text.clone(),
25344 highlights: Some(symbol.highlight_ranges.clone()),
25345 font: Some(settings.buffer_font.clone()),
25346 }));
25347 Some(breadcrumbs)
25348 }
25349
25350 fn disable_lsp_data(&mut self) {
25351 self.enable_lsp_data = false;
25352 }
25353
25354 fn disable_runnables(&mut self) {
25355 self.enable_runnables = false;
25356 }
25357}
25358
25359fn edit_for_markdown_paste<'a>(
25360 buffer: &MultiBufferSnapshot,
25361 range: Range<MultiBufferOffset>,
25362 to_insert: &'a str,
25363 url: Option<url::Url>,
25364) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25365 if url.is_none() {
25366 return (range, Cow::Borrowed(to_insert));
25367 };
25368
25369 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25370
25371 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25372 Cow::Borrowed(to_insert)
25373 } else {
25374 Cow::Owned(format!("[{old_text}]({to_insert})"))
25375 };
25376 (range, new_text)
25377}
25378
25379fn process_completion_for_edit(
25380 completion: &Completion,
25381 intent: CompletionIntent,
25382 buffer: &Entity<Buffer>,
25383 cursor_position: &text::Anchor,
25384 cx: &mut Context<Editor>,
25385) -> CompletionEdit {
25386 let buffer = buffer.read(cx);
25387 let buffer_snapshot = buffer.snapshot();
25388 let (snippet, new_text) = if completion.is_snippet() {
25389 let mut snippet_source = completion.new_text.clone();
25390 // Workaround for typescript language server issues so that methods don't expand within
25391 // strings and functions with type expressions. The previous point is used because the query
25392 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25393 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25394 let previous_point = if previous_point.column > 0 {
25395 cursor_position.to_previous_offset(&buffer_snapshot)
25396 } else {
25397 cursor_position.to_offset(&buffer_snapshot)
25398 };
25399 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25400 && scope.prefers_label_for_snippet_in_completion()
25401 && let Some(label) = completion.label()
25402 && matches!(
25403 completion.kind(),
25404 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25405 )
25406 {
25407 snippet_source = label;
25408 }
25409 match Snippet::parse(&snippet_source).log_err() {
25410 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25411 None => (None, completion.new_text.clone()),
25412 }
25413 } else {
25414 (None, completion.new_text.clone())
25415 };
25416
25417 let mut range_to_replace = {
25418 let replace_range = &completion.replace_range;
25419 if let CompletionSource::Lsp {
25420 insert_range: Some(insert_range),
25421 ..
25422 } = &completion.source
25423 {
25424 debug_assert_eq!(
25425 insert_range.start, replace_range.start,
25426 "insert_range and replace_range should start at the same position"
25427 );
25428 debug_assert!(
25429 insert_range
25430 .start
25431 .cmp(cursor_position, &buffer_snapshot)
25432 .is_le(),
25433 "insert_range should start before or at cursor position"
25434 );
25435 debug_assert!(
25436 replace_range
25437 .start
25438 .cmp(cursor_position, &buffer_snapshot)
25439 .is_le(),
25440 "replace_range should start before or at cursor position"
25441 );
25442
25443 let should_replace = match intent {
25444 CompletionIntent::CompleteWithInsert => false,
25445 CompletionIntent::CompleteWithReplace => true,
25446 CompletionIntent::Complete | CompletionIntent::Compose => {
25447 let insert_mode =
25448 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25449 .completions
25450 .lsp_insert_mode;
25451 match insert_mode {
25452 LspInsertMode::Insert => false,
25453 LspInsertMode::Replace => true,
25454 LspInsertMode::ReplaceSubsequence => {
25455 let mut text_to_replace = buffer.chars_for_range(
25456 buffer.anchor_before(replace_range.start)
25457 ..buffer.anchor_after(replace_range.end),
25458 );
25459 let mut current_needle = text_to_replace.next();
25460 for haystack_ch in completion.label.text.chars() {
25461 if let Some(needle_ch) = current_needle
25462 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25463 {
25464 current_needle = text_to_replace.next();
25465 }
25466 }
25467 current_needle.is_none()
25468 }
25469 LspInsertMode::ReplaceSuffix => {
25470 if replace_range
25471 .end
25472 .cmp(cursor_position, &buffer_snapshot)
25473 .is_gt()
25474 {
25475 let range_after_cursor = *cursor_position..replace_range.end;
25476 let text_after_cursor = buffer
25477 .text_for_range(
25478 buffer.anchor_before(range_after_cursor.start)
25479 ..buffer.anchor_after(range_after_cursor.end),
25480 )
25481 .collect::<String>()
25482 .to_ascii_lowercase();
25483 completion
25484 .label
25485 .text
25486 .to_ascii_lowercase()
25487 .ends_with(&text_after_cursor)
25488 } else {
25489 true
25490 }
25491 }
25492 }
25493 }
25494 };
25495
25496 if should_replace {
25497 replace_range.clone()
25498 } else {
25499 insert_range.clone()
25500 }
25501 } else {
25502 replace_range.clone()
25503 }
25504 };
25505
25506 if range_to_replace
25507 .end
25508 .cmp(cursor_position, &buffer_snapshot)
25509 .is_lt()
25510 {
25511 range_to_replace.end = *cursor_position;
25512 }
25513
25514 let replace_range = range_to_replace.to_offset(buffer);
25515 CompletionEdit {
25516 new_text,
25517 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25518 snippet,
25519 }
25520}
25521
25522struct CompletionEdit {
25523 new_text: String,
25524 replace_range: Range<BufferOffset>,
25525 snippet: Option<Snippet>,
25526}
25527
25528fn comment_delimiter_for_newline(
25529 start_point: &Point,
25530 buffer: &MultiBufferSnapshot,
25531 language: &LanguageScope,
25532) -> Option<Arc<str>> {
25533 let delimiters = language.line_comment_prefixes();
25534 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25535 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25536
25537 let num_of_whitespaces = snapshot
25538 .chars_for_range(range.clone())
25539 .take_while(|c| c.is_whitespace())
25540 .count();
25541 let comment_candidate = snapshot
25542 .chars_for_range(range.clone())
25543 .skip(num_of_whitespaces)
25544 .take(max_len_of_delimiter + 2)
25545 .collect::<String>();
25546 let (delimiter, trimmed_len, is_repl) = delimiters
25547 .iter()
25548 .filter_map(|delimiter| {
25549 let prefix = delimiter.trim_end();
25550 if comment_candidate.starts_with(prefix) {
25551 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
25552 {
25553 stripped_comment.starts_with(" %%")
25554 } else {
25555 false
25556 };
25557 Some((delimiter, prefix.len(), is_repl))
25558 } else {
25559 None
25560 }
25561 })
25562 .max_by_key(|(_, len, _)| *len)?;
25563
25564 if let Some(BlockCommentConfig {
25565 start: block_start, ..
25566 }) = language.block_comment()
25567 {
25568 let block_start_trimmed = block_start.trim_end();
25569 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25570 let line_content = snapshot
25571 .chars_for_range(range.clone())
25572 .skip(num_of_whitespaces)
25573 .take(block_start_trimmed.len())
25574 .collect::<String>();
25575
25576 if line_content.starts_with(block_start_trimmed) {
25577 return None;
25578 }
25579 }
25580 }
25581
25582 let cursor_is_placed_after_comment_marker =
25583 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25584 if cursor_is_placed_after_comment_marker {
25585 if !is_repl {
25586 return Some(delimiter.clone());
25587 }
25588
25589 let line_content_after_cursor: String = snapshot
25590 .chars_for_range(range)
25591 .skip(start_point.column as usize)
25592 .collect();
25593
25594 if line_content_after_cursor.trim().is_empty() {
25595 return None;
25596 } else {
25597 return Some(delimiter.clone());
25598 }
25599 } else {
25600 None
25601 }
25602}
25603
25604fn documentation_delimiter_for_newline(
25605 start_point: &Point,
25606 buffer: &MultiBufferSnapshot,
25607 language: &LanguageScope,
25608 newline_config: &mut NewlineConfig,
25609) -> Option<Arc<str>> {
25610 let BlockCommentConfig {
25611 start: start_tag,
25612 end: end_tag,
25613 prefix: delimiter,
25614 tab_size: len,
25615 } = language.documentation_comment()?;
25616 let is_within_block_comment = buffer
25617 .language_scope_at(*start_point)
25618 .is_some_and(|scope| scope.override_name() == Some("comment"));
25619 if !is_within_block_comment {
25620 return None;
25621 }
25622
25623 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25624
25625 let num_of_whitespaces = snapshot
25626 .chars_for_range(range.clone())
25627 .take_while(|c| c.is_whitespace())
25628 .count();
25629
25630 // 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.
25631 let column = start_point.column;
25632 let cursor_is_after_start_tag = {
25633 let start_tag_len = start_tag.len();
25634 let start_tag_line = snapshot
25635 .chars_for_range(range.clone())
25636 .skip(num_of_whitespaces)
25637 .take(start_tag_len)
25638 .collect::<String>();
25639 if start_tag_line.starts_with(start_tag.as_ref()) {
25640 num_of_whitespaces + start_tag_len <= column as usize
25641 } else {
25642 false
25643 }
25644 };
25645
25646 let cursor_is_after_delimiter = {
25647 let delimiter_trim = delimiter.trim_end();
25648 let delimiter_line = snapshot
25649 .chars_for_range(range.clone())
25650 .skip(num_of_whitespaces)
25651 .take(delimiter_trim.len())
25652 .collect::<String>();
25653 if delimiter_line.starts_with(delimiter_trim) {
25654 num_of_whitespaces + delimiter_trim.len() <= column as usize
25655 } else {
25656 false
25657 }
25658 };
25659
25660 let mut needs_extra_line = false;
25661 let mut extra_line_additional_indent = IndentSize::spaces(0);
25662
25663 let cursor_is_before_end_tag_if_exists = {
25664 let mut char_position = 0u32;
25665 let mut end_tag_offset = None;
25666
25667 'outer: for chunk in snapshot.text_for_range(range) {
25668 if let Some(byte_pos) = chunk.find(&**end_tag) {
25669 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25670 end_tag_offset = Some(char_position + chars_before_match);
25671 break 'outer;
25672 }
25673 char_position += chunk.chars().count() as u32;
25674 }
25675
25676 if let Some(end_tag_offset) = end_tag_offset {
25677 let cursor_is_before_end_tag = column <= end_tag_offset;
25678 if cursor_is_after_start_tag {
25679 if cursor_is_before_end_tag {
25680 needs_extra_line = true;
25681 }
25682 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25683 if cursor_is_at_start_of_end_tag {
25684 extra_line_additional_indent.len = *len;
25685 }
25686 }
25687 cursor_is_before_end_tag
25688 } else {
25689 true
25690 }
25691 };
25692
25693 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25694 && cursor_is_before_end_tag_if_exists
25695 {
25696 let additional_indent = if cursor_is_after_start_tag {
25697 IndentSize::spaces(*len)
25698 } else {
25699 IndentSize::spaces(0)
25700 };
25701
25702 *newline_config = NewlineConfig::Newline {
25703 additional_indent,
25704 extra_line_additional_indent: if needs_extra_line {
25705 Some(extra_line_additional_indent)
25706 } else {
25707 None
25708 },
25709 prevent_auto_indent: true,
25710 };
25711 Some(delimiter.clone())
25712 } else {
25713 None
25714 }
25715}
25716
25717const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25718
25719fn list_delimiter_for_newline(
25720 start_point: &Point,
25721 buffer: &MultiBufferSnapshot,
25722 language: &LanguageScope,
25723 newline_config: &mut NewlineConfig,
25724) -> Option<Arc<str>> {
25725 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25726
25727 let num_of_whitespaces = snapshot
25728 .chars_for_range(range.clone())
25729 .take_while(|c| c.is_whitespace())
25730 .count();
25731
25732 let task_list_entries: Vec<_> = language
25733 .task_list()
25734 .into_iter()
25735 .flat_map(|config| {
25736 config
25737 .prefixes
25738 .iter()
25739 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25740 })
25741 .collect();
25742 let unordered_list_entries: Vec<_> = language
25743 .unordered_list()
25744 .iter()
25745 .map(|marker| (marker.as_ref(), marker.as_ref()))
25746 .collect();
25747
25748 let all_entries: Vec<_> = task_list_entries
25749 .into_iter()
25750 .chain(unordered_list_entries)
25751 .collect();
25752
25753 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25754 let candidate: String = snapshot
25755 .chars_for_range(range.clone())
25756 .skip(num_of_whitespaces)
25757 .take(max_prefix_len)
25758 .collect();
25759
25760 if let Some((prefix, continuation)) = all_entries
25761 .iter()
25762 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25763 .max_by_key(|(prefix, _)| prefix.len())
25764 {
25765 let end_of_prefix = num_of_whitespaces + prefix.len();
25766 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25767 let has_content_after_marker = snapshot
25768 .chars_for_range(range)
25769 .skip(end_of_prefix)
25770 .any(|c| !c.is_whitespace());
25771
25772 if has_content_after_marker && cursor_is_after_prefix {
25773 return Some((*continuation).into());
25774 }
25775
25776 if start_point.column as usize == end_of_prefix {
25777 if num_of_whitespaces == 0 {
25778 *newline_config = NewlineConfig::ClearCurrentLine;
25779 } else {
25780 *newline_config = NewlineConfig::UnindentCurrentLine {
25781 continuation: (*continuation).into(),
25782 };
25783 }
25784 }
25785
25786 return None;
25787 }
25788 }
25789
25790 let candidate: String = snapshot
25791 .chars_for_range(range.clone())
25792 .skip(num_of_whitespaces)
25793 .take(ORDERED_LIST_MAX_MARKER_LEN)
25794 .collect();
25795
25796 for ordered_config in language.ordered_list() {
25797 let regex = match Regex::new(&ordered_config.pattern) {
25798 Ok(r) => r,
25799 Err(_) => continue,
25800 };
25801
25802 if let Some(captures) = regex.captures(&candidate) {
25803 let full_match = captures.get(0)?;
25804 let marker_len = full_match.len();
25805 let end_of_prefix = num_of_whitespaces + marker_len;
25806 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25807
25808 let has_content_after_marker = snapshot
25809 .chars_for_range(range)
25810 .skip(end_of_prefix)
25811 .any(|c| !c.is_whitespace());
25812
25813 if has_content_after_marker && cursor_is_after_prefix {
25814 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25815 let continuation = ordered_config
25816 .format
25817 .replace("{1}", &(number + 1).to_string());
25818 return Some(continuation.into());
25819 }
25820
25821 if start_point.column as usize == end_of_prefix {
25822 let continuation = ordered_config.format.replace("{1}", "1");
25823 if num_of_whitespaces == 0 {
25824 *newline_config = NewlineConfig::ClearCurrentLine;
25825 } else {
25826 *newline_config = NewlineConfig::UnindentCurrentLine {
25827 continuation: continuation.into(),
25828 };
25829 }
25830 }
25831
25832 return None;
25833 }
25834 }
25835
25836 None
25837}
25838
25839fn is_list_prefix_row(
25840 row: MultiBufferRow,
25841 buffer: &MultiBufferSnapshot,
25842 language: &LanguageScope,
25843) -> bool {
25844 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25845 return false;
25846 };
25847
25848 let num_of_whitespaces = snapshot
25849 .chars_for_range(range.clone())
25850 .take_while(|c| c.is_whitespace())
25851 .count();
25852
25853 let task_list_prefixes: Vec<_> = language
25854 .task_list()
25855 .into_iter()
25856 .flat_map(|config| {
25857 config
25858 .prefixes
25859 .iter()
25860 .map(|p| p.as_ref())
25861 .collect::<Vec<_>>()
25862 })
25863 .collect();
25864 let unordered_list_markers: Vec<_> = language
25865 .unordered_list()
25866 .iter()
25867 .map(|marker| marker.as_ref())
25868 .collect();
25869 let all_prefixes: Vec<_> = task_list_prefixes
25870 .into_iter()
25871 .chain(unordered_list_markers)
25872 .collect();
25873 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25874 let candidate: String = snapshot
25875 .chars_for_range(range.clone())
25876 .skip(num_of_whitespaces)
25877 .take(max_prefix_len)
25878 .collect();
25879 if all_prefixes
25880 .iter()
25881 .any(|prefix| candidate.starts_with(*prefix))
25882 {
25883 return true;
25884 }
25885 }
25886
25887 let ordered_list_candidate: String = snapshot
25888 .chars_for_range(range)
25889 .skip(num_of_whitespaces)
25890 .take(ORDERED_LIST_MAX_MARKER_LEN)
25891 .collect();
25892 for ordered_config in language.ordered_list() {
25893 let regex = match Regex::new(&ordered_config.pattern) {
25894 Ok(r) => r,
25895 Err(_) => continue,
25896 };
25897 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25898 return captures.get(0).is_some();
25899 }
25900 }
25901
25902 false
25903}
25904
25905#[derive(Debug)]
25906enum NewlineConfig {
25907 /// Insert newline with optional additional indent and optional extra blank line
25908 Newline {
25909 additional_indent: IndentSize,
25910 extra_line_additional_indent: Option<IndentSize>,
25911 prevent_auto_indent: bool,
25912 },
25913 /// Clear the current line
25914 ClearCurrentLine,
25915 /// Unindent the current line and add continuation
25916 UnindentCurrentLine { continuation: Arc<str> },
25917}
25918
25919impl NewlineConfig {
25920 fn has_extra_line(&self) -> bool {
25921 matches!(
25922 self,
25923 Self::Newline {
25924 extra_line_additional_indent: Some(_),
25925 ..
25926 }
25927 )
25928 }
25929
25930 fn insert_extra_newline_brackets(
25931 buffer: &MultiBufferSnapshot,
25932 range: Range<MultiBufferOffset>,
25933 language: &language::LanguageScope,
25934 ) -> bool {
25935 let leading_whitespace_len = buffer
25936 .reversed_chars_at(range.start)
25937 .take_while(|c| c.is_whitespace() && *c != '\n')
25938 .map(|c| c.len_utf8())
25939 .sum::<usize>();
25940 let trailing_whitespace_len = buffer
25941 .chars_at(range.end)
25942 .take_while(|c| c.is_whitespace() && *c != '\n')
25943 .map(|c| c.len_utf8())
25944 .sum::<usize>();
25945 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25946
25947 language.brackets().any(|(pair, enabled)| {
25948 let pair_start = pair.start.trim_end();
25949 let pair_end = pair.end.trim_start();
25950
25951 enabled
25952 && pair.newline
25953 && buffer.contains_str_at(range.end, pair_end)
25954 && buffer.contains_str_at(
25955 range.start.saturating_sub_usize(pair_start.len()),
25956 pair_start,
25957 )
25958 })
25959 }
25960
25961 fn insert_extra_newline_tree_sitter(
25962 buffer: &MultiBufferSnapshot,
25963 range: Range<MultiBufferOffset>,
25964 ) -> bool {
25965 let (buffer, range) = match buffer
25966 .range_to_buffer_ranges(range.start..=range.end)
25967 .as_slice()
25968 {
25969 [(buffer, range, _)] => (*buffer, range.clone()),
25970 _ => return false,
25971 };
25972 let pair = {
25973 let mut result: Option<BracketMatch<usize>> = None;
25974
25975 for pair in buffer
25976 .all_bracket_ranges(range.start.0..range.end.0)
25977 .filter(move |pair| {
25978 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
25979 })
25980 {
25981 let len = pair.close_range.end - pair.open_range.start;
25982
25983 if let Some(existing) = &result {
25984 let existing_len = existing.close_range.end - existing.open_range.start;
25985 if len > existing_len {
25986 continue;
25987 }
25988 }
25989
25990 result = Some(pair);
25991 }
25992
25993 result
25994 };
25995 let Some(pair) = pair else {
25996 return false;
25997 };
25998 pair.newline_only
25999 && buffer
26000 .chars_for_range(pair.open_range.end..range.start.0)
26001 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26002 .all(|c| c.is_whitespace() && c != '\n')
26003 }
26004}
26005
26006fn update_uncommitted_diff_for_buffer(
26007 editor: Entity<Editor>,
26008 project: &Entity<Project>,
26009 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26010 buffer: Entity<MultiBuffer>,
26011 cx: &mut App,
26012) -> Task<()> {
26013 let mut tasks = Vec::new();
26014 project.update(cx, |project, cx| {
26015 for buffer in buffers {
26016 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26017 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26018 }
26019 }
26020 });
26021 cx.spawn(async move |cx| {
26022 let diffs = future::join_all(tasks).await;
26023 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26024 return;
26025 }
26026
26027 buffer.update(cx, |buffer, cx| {
26028 for diff in diffs.into_iter().flatten() {
26029 buffer.add_diff(diff, cx);
26030 }
26031 });
26032 })
26033}
26034
26035fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26036 let tab_size = tab_size.get() as usize;
26037 let mut width = offset;
26038
26039 for ch in text.chars() {
26040 width += if ch == '\t' {
26041 tab_size - (width % tab_size)
26042 } else {
26043 1
26044 };
26045 }
26046
26047 width - offset
26048}
26049
26050#[cfg(test)]
26051mod tests {
26052 use super::*;
26053
26054 #[test]
26055 fn test_string_size_with_expanded_tabs() {
26056 let nz = |val| NonZeroU32::new(val).unwrap();
26057 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26058 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26059 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26060 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26061 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26062 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26063 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26064 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26065 }
26066}
26067
26068/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26069struct WordBreakingTokenizer<'a> {
26070 input: &'a str,
26071}
26072
26073impl<'a> WordBreakingTokenizer<'a> {
26074 fn new(input: &'a str) -> Self {
26075 Self { input }
26076 }
26077}
26078
26079fn is_char_ideographic(ch: char) -> bool {
26080 use unicode_script::Script::*;
26081 use unicode_script::UnicodeScript;
26082 matches!(ch.script(), Han | Tangut | Yi)
26083}
26084
26085fn is_grapheme_ideographic(text: &str) -> bool {
26086 text.chars().any(is_char_ideographic)
26087}
26088
26089fn is_grapheme_whitespace(text: &str) -> bool {
26090 text.chars().any(|x| x.is_whitespace())
26091}
26092
26093fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26094 text.chars()
26095 .next()
26096 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26097}
26098
26099#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26100enum WordBreakToken<'a> {
26101 Word { token: &'a str, grapheme_len: usize },
26102 InlineWhitespace { token: &'a str, grapheme_len: usize },
26103 Newline,
26104}
26105
26106impl<'a> Iterator for WordBreakingTokenizer<'a> {
26107 /// Yields a span, the count of graphemes in the token, and whether it was
26108 /// whitespace. Note that it also breaks at word boundaries.
26109 type Item = WordBreakToken<'a>;
26110
26111 fn next(&mut self) -> Option<Self::Item> {
26112 use unicode_segmentation::UnicodeSegmentation;
26113 if self.input.is_empty() {
26114 return None;
26115 }
26116
26117 let mut iter = self.input.graphemes(true).peekable();
26118 let mut offset = 0;
26119 let mut grapheme_len = 0;
26120 if let Some(first_grapheme) = iter.next() {
26121 let is_newline = first_grapheme == "\n";
26122 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26123 offset += first_grapheme.len();
26124 grapheme_len += 1;
26125 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26126 if let Some(grapheme) = iter.peek().copied()
26127 && should_stay_with_preceding_ideograph(grapheme)
26128 {
26129 offset += grapheme.len();
26130 grapheme_len += 1;
26131 }
26132 } else {
26133 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26134 let mut next_word_bound = words.peek().copied();
26135 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26136 next_word_bound = words.next();
26137 }
26138 while let Some(grapheme) = iter.peek().copied() {
26139 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26140 break;
26141 };
26142 if is_grapheme_whitespace(grapheme) != is_whitespace
26143 || (grapheme == "\n") != is_newline
26144 {
26145 break;
26146 };
26147 offset += grapheme.len();
26148 grapheme_len += 1;
26149 iter.next();
26150 }
26151 }
26152 let token = &self.input[..offset];
26153 self.input = &self.input[offset..];
26154 if token == "\n" {
26155 Some(WordBreakToken::Newline)
26156 } else if is_whitespace {
26157 Some(WordBreakToken::InlineWhitespace {
26158 token,
26159 grapheme_len,
26160 })
26161 } else {
26162 Some(WordBreakToken::Word {
26163 token,
26164 grapheme_len,
26165 })
26166 }
26167 } else {
26168 None
26169 }
26170 }
26171}
26172
26173#[test]
26174fn test_word_breaking_tokenizer() {
26175 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26176 ("", &[]),
26177 (" ", &[whitespace(" ", 2)]),
26178 ("Ʒ", &[word("Ʒ", 1)]),
26179 ("Ǽ", &[word("Ǽ", 1)]),
26180 ("⋑", &[word("⋑", 1)]),
26181 ("⋑⋑", &[word("⋑⋑", 2)]),
26182 (
26183 "原理,进而",
26184 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26185 ),
26186 (
26187 "hello world",
26188 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26189 ),
26190 (
26191 "hello, world",
26192 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26193 ),
26194 (
26195 " hello world",
26196 &[
26197 whitespace(" ", 2),
26198 word("hello", 5),
26199 whitespace(" ", 1),
26200 word("world", 5),
26201 ],
26202 ),
26203 (
26204 "这是什么 \n 钢笔",
26205 &[
26206 word("这", 1),
26207 word("是", 1),
26208 word("什", 1),
26209 word("么", 1),
26210 whitespace(" ", 1),
26211 newline(),
26212 whitespace(" ", 1),
26213 word("钢", 1),
26214 word("笔", 1),
26215 ],
26216 ),
26217 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26218 ];
26219
26220 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26221 WordBreakToken::Word {
26222 token,
26223 grapheme_len,
26224 }
26225 }
26226
26227 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26228 WordBreakToken::InlineWhitespace {
26229 token,
26230 grapheme_len,
26231 }
26232 }
26233
26234 fn newline() -> WordBreakToken<'static> {
26235 WordBreakToken::Newline
26236 }
26237
26238 for (input, result) in tests {
26239 assert_eq!(
26240 WordBreakingTokenizer::new(input)
26241 .collect::<Vec<_>>()
26242 .as_slice(),
26243 *result,
26244 );
26245 }
26246}
26247
26248fn wrap_with_prefix(
26249 first_line_prefix: String,
26250 subsequent_lines_prefix: String,
26251 unwrapped_text: String,
26252 wrap_column: usize,
26253 tab_size: NonZeroU32,
26254 preserve_existing_whitespace: bool,
26255) -> String {
26256 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26257 let subsequent_lines_prefix_len =
26258 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26259 let mut wrapped_text = String::new();
26260 let mut current_line = first_line_prefix;
26261 let mut is_first_line = true;
26262
26263 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26264 let mut current_line_len = first_line_prefix_len;
26265 let mut in_whitespace = false;
26266 for token in tokenizer {
26267 let have_preceding_whitespace = in_whitespace;
26268 match token {
26269 WordBreakToken::Word {
26270 token,
26271 grapheme_len,
26272 } => {
26273 in_whitespace = false;
26274 let current_prefix_len = if is_first_line {
26275 first_line_prefix_len
26276 } else {
26277 subsequent_lines_prefix_len
26278 };
26279 if current_line_len + grapheme_len > wrap_column
26280 && current_line_len != current_prefix_len
26281 {
26282 wrapped_text.push_str(current_line.trim_end());
26283 wrapped_text.push('\n');
26284 is_first_line = false;
26285 current_line = subsequent_lines_prefix.clone();
26286 current_line_len = subsequent_lines_prefix_len;
26287 }
26288 current_line.push_str(token);
26289 current_line_len += grapheme_len;
26290 }
26291 WordBreakToken::InlineWhitespace {
26292 mut token,
26293 mut grapheme_len,
26294 } => {
26295 in_whitespace = true;
26296 if have_preceding_whitespace && !preserve_existing_whitespace {
26297 continue;
26298 }
26299 if !preserve_existing_whitespace {
26300 // Keep a single whitespace grapheme as-is
26301 if let Some(first) =
26302 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26303 {
26304 token = first;
26305 } else {
26306 token = " ";
26307 }
26308 grapheme_len = 1;
26309 }
26310 let current_prefix_len = if is_first_line {
26311 first_line_prefix_len
26312 } else {
26313 subsequent_lines_prefix_len
26314 };
26315 if current_line_len + grapheme_len > wrap_column {
26316 wrapped_text.push_str(current_line.trim_end());
26317 wrapped_text.push('\n');
26318 is_first_line = false;
26319 current_line = subsequent_lines_prefix.clone();
26320 current_line_len = subsequent_lines_prefix_len;
26321 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26322 current_line.push_str(token);
26323 current_line_len += grapheme_len;
26324 }
26325 }
26326 WordBreakToken::Newline => {
26327 in_whitespace = true;
26328 let current_prefix_len = if is_first_line {
26329 first_line_prefix_len
26330 } else {
26331 subsequent_lines_prefix_len
26332 };
26333 if preserve_existing_whitespace {
26334 wrapped_text.push_str(current_line.trim_end());
26335 wrapped_text.push('\n');
26336 is_first_line = false;
26337 current_line = subsequent_lines_prefix.clone();
26338 current_line_len = subsequent_lines_prefix_len;
26339 } else if have_preceding_whitespace {
26340 continue;
26341 } else if current_line_len + 1 > wrap_column
26342 && current_line_len != current_prefix_len
26343 {
26344 wrapped_text.push_str(current_line.trim_end());
26345 wrapped_text.push('\n');
26346 is_first_line = false;
26347 current_line = subsequent_lines_prefix.clone();
26348 current_line_len = subsequent_lines_prefix_len;
26349 } else if current_line_len != current_prefix_len {
26350 current_line.push(' ');
26351 current_line_len += 1;
26352 }
26353 }
26354 }
26355 }
26356
26357 if !current_line.is_empty() {
26358 wrapped_text.push_str(¤t_line);
26359 }
26360 wrapped_text
26361}
26362
26363#[test]
26364fn test_wrap_with_prefix() {
26365 assert_eq!(
26366 wrap_with_prefix(
26367 "# ".to_string(),
26368 "# ".to_string(),
26369 "abcdefg".to_string(),
26370 4,
26371 NonZeroU32::new(4).unwrap(),
26372 false,
26373 ),
26374 "# abcdefg"
26375 );
26376 assert_eq!(
26377 wrap_with_prefix(
26378 "".to_string(),
26379 "".to_string(),
26380 "\thello world".to_string(),
26381 8,
26382 NonZeroU32::new(4).unwrap(),
26383 false,
26384 ),
26385 "hello\nworld"
26386 );
26387 assert_eq!(
26388 wrap_with_prefix(
26389 "// ".to_string(),
26390 "// ".to_string(),
26391 "xx \nyy zz aa bb cc".to_string(),
26392 12,
26393 NonZeroU32::new(4).unwrap(),
26394 false,
26395 ),
26396 "// xx yy zz\n// aa bb cc"
26397 );
26398 assert_eq!(
26399 wrap_with_prefix(
26400 String::new(),
26401 String::new(),
26402 "这是什么 \n 钢笔".to_string(),
26403 3,
26404 NonZeroU32::new(4).unwrap(),
26405 false,
26406 ),
26407 "这是什\n么 钢\n笔"
26408 );
26409 assert_eq!(
26410 wrap_with_prefix(
26411 String::new(),
26412 String::new(),
26413 format!("foo{}bar", '\u{2009}'), // thin space
26414 80,
26415 NonZeroU32::new(4).unwrap(),
26416 false,
26417 ),
26418 format!("foo{}bar", '\u{2009}')
26419 );
26420}
26421
26422pub trait CollaborationHub {
26423 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26424 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26425 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26426}
26427
26428impl CollaborationHub for Entity<Project> {
26429 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26430 self.read(cx).collaborators()
26431 }
26432
26433 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26434 self.read(cx).user_store().read(cx).participant_indices()
26435 }
26436
26437 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26438 let this = self.read(cx);
26439 let user_ids = this.collaborators().values().map(|c| c.user_id);
26440 this.user_store().read(cx).participant_names(user_ids, cx)
26441 }
26442}
26443
26444pub trait SemanticsProvider {
26445 fn hover(
26446 &self,
26447 buffer: &Entity<Buffer>,
26448 position: text::Anchor,
26449 cx: &mut App,
26450 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26451
26452 fn inline_values(
26453 &self,
26454 buffer_handle: Entity<Buffer>,
26455 range: Range<text::Anchor>,
26456 cx: &mut App,
26457 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26458
26459 fn applicable_inlay_chunks(
26460 &self,
26461 buffer: &Entity<Buffer>,
26462 ranges: &[Range<text::Anchor>],
26463 cx: &mut App,
26464 ) -> Vec<Range<BufferRow>>;
26465
26466 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26467
26468 fn inlay_hints(
26469 &self,
26470 invalidate: InvalidationStrategy,
26471 buffer: Entity<Buffer>,
26472 ranges: Vec<Range<text::Anchor>>,
26473 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26474 cx: &mut App,
26475 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26476
26477 fn semantic_tokens(
26478 &self,
26479 buffer: Entity<Buffer>,
26480 refresh: Option<RefreshForServer>,
26481 cx: &mut App,
26482 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
26483
26484 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26485
26486 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26487
26488 fn document_highlights(
26489 &self,
26490 buffer: &Entity<Buffer>,
26491 position: text::Anchor,
26492 cx: &mut App,
26493 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26494
26495 fn definitions(
26496 &self,
26497 buffer: &Entity<Buffer>,
26498 position: text::Anchor,
26499 kind: GotoDefinitionKind,
26500 cx: &mut App,
26501 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26502
26503 fn range_for_rename(
26504 &self,
26505 buffer: &Entity<Buffer>,
26506 position: text::Anchor,
26507 cx: &mut App,
26508 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26509
26510 fn perform_rename(
26511 &self,
26512 buffer: &Entity<Buffer>,
26513 position: text::Anchor,
26514 new_name: String,
26515 cx: &mut App,
26516 ) -> Option<Task<Result<ProjectTransaction>>>;
26517}
26518
26519pub trait CompletionProvider {
26520 fn completions(
26521 &self,
26522 excerpt_id: ExcerptId,
26523 buffer: &Entity<Buffer>,
26524 buffer_position: text::Anchor,
26525 trigger: CompletionContext,
26526 window: &mut Window,
26527 cx: &mut Context<Editor>,
26528 ) -> Task<Result<Vec<CompletionResponse>>>;
26529
26530 fn resolve_completions(
26531 &self,
26532 _buffer: Entity<Buffer>,
26533 _completion_indices: Vec<usize>,
26534 _completions: Rc<RefCell<Box<[Completion]>>>,
26535 _cx: &mut Context<Editor>,
26536 ) -> Task<Result<bool>> {
26537 Task::ready(Ok(false))
26538 }
26539
26540 fn apply_additional_edits_for_completion(
26541 &self,
26542 _buffer: Entity<Buffer>,
26543 _completions: Rc<RefCell<Box<[Completion]>>>,
26544 _completion_index: usize,
26545 _push_to_history: bool,
26546 _cx: &mut Context<Editor>,
26547 ) -> Task<Result<Option<language::Transaction>>> {
26548 Task::ready(Ok(None))
26549 }
26550
26551 fn is_completion_trigger(
26552 &self,
26553 buffer: &Entity<Buffer>,
26554 position: language::Anchor,
26555 text: &str,
26556 trigger_in_words: bool,
26557 cx: &mut Context<Editor>,
26558 ) -> bool;
26559
26560 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26561
26562 fn sort_completions(&self) -> bool {
26563 true
26564 }
26565
26566 fn filter_completions(&self) -> bool {
26567 true
26568 }
26569
26570 fn show_snippets(&self) -> bool {
26571 false
26572 }
26573}
26574
26575pub trait CodeActionProvider {
26576 fn id(&self) -> Arc<str>;
26577
26578 fn code_actions(
26579 &self,
26580 buffer: &Entity<Buffer>,
26581 range: Range<text::Anchor>,
26582 window: &mut Window,
26583 cx: &mut App,
26584 ) -> Task<Result<Vec<CodeAction>>>;
26585
26586 fn apply_code_action(
26587 &self,
26588 buffer_handle: Entity<Buffer>,
26589 action: CodeAction,
26590 excerpt_id: ExcerptId,
26591 push_to_history: bool,
26592 window: &mut Window,
26593 cx: &mut App,
26594 ) -> Task<Result<ProjectTransaction>>;
26595}
26596
26597impl CodeActionProvider for Entity<Project> {
26598 fn id(&self) -> Arc<str> {
26599 "project".into()
26600 }
26601
26602 fn code_actions(
26603 &self,
26604 buffer: &Entity<Buffer>,
26605 range: Range<text::Anchor>,
26606 _window: &mut Window,
26607 cx: &mut App,
26608 ) -> Task<Result<Vec<CodeAction>>> {
26609 self.update(cx, |project, cx| {
26610 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26611 let code_actions = project.code_actions(buffer, range, None, cx);
26612 cx.background_spawn(async move {
26613 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26614 Ok(code_lens_actions
26615 .context("code lens fetch")?
26616 .into_iter()
26617 .flatten()
26618 .chain(
26619 code_actions
26620 .context("code action fetch")?
26621 .into_iter()
26622 .flatten(),
26623 )
26624 .collect())
26625 })
26626 })
26627 }
26628
26629 fn apply_code_action(
26630 &self,
26631 buffer_handle: Entity<Buffer>,
26632 action: CodeAction,
26633 _excerpt_id: ExcerptId,
26634 push_to_history: bool,
26635 _window: &mut Window,
26636 cx: &mut App,
26637 ) -> Task<Result<ProjectTransaction>> {
26638 self.update(cx, |project, cx| {
26639 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26640 })
26641 }
26642}
26643
26644fn snippet_completions(
26645 project: &Project,
26646 buffer: &Entity<Buffer>,
26647 buffer_anchor: text::Anchor,
26648 classifier: CharClassifier,
26649 cx: &mut App,
26650) -> Task<Result<CompletionResponse>> {
26651 let languages = buffer.read(cx).languages_at(buffer_anchor);
26652 let snippet_store = project.snippets().read(cx);
26653
26654 let scopes: Vec<_> = languages
26655 .iter()
26656 .filter_map(|language| {
26657 let language_name = language.lsp_id();
26658 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26659
26660 if snippets.is_empty() {
26661 None
26662 } else {
26663 Some((language.default_scope(), snippets))
26664 }
26665 })
26666 .collect();
26667
26668 if scopes.is_empty() {
26669 return Task::ready(Ok(CompletionResponse {
26670 completions: vec![],
26671 display_options: CompletionDisplayOptions::default(),
26672 is_incomplete: false,
26673 }));
26674 }
26675
26676 let snapshot = buffer.read(cx).text_snapshot();
26677 let executor = cx.background_executor().clone();
26678
26679 cx.background_spawn(async move {
26680 let is_word_char = |c| classifier.is_word(c);
26681
26682 let mut is_incomplete = false;
26683 let mut completions: Vec<Completion> = Vec::new();
26684
26685 const MAX_PREFIX_LEN: usize = 128;
26686 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26687 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26688 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26689
26690 let max_buffer_window: String = snapshot
26691 .text_for_range(window_start..buffer_offset)
26692 .collect();
26693
26694 if max_buffer_window.is_empty() {
26695 return Ok(CompletionResponse {
26696 completions: vec![],
26697 display_options: CompletionDisplayOptions::default(),
26698 is_incomplete: true,
26699 });
26700 }
26701
26702 for (_scope, snippets) in scopes.into_iter() {
26703 // Sort snippets by word count to match longer snippet prefixes first.
26704 let mut sorted_snippet_candidates = snippets
26705 .iter()
26706 .enumerate()
26707 .flat_map(|(snippet_ix, snippet)| {
26708 snippet
26709 .prefix
26710 .iter()
26711 .enumerate()
26712 .map(move |(prefix_ix, prefix)| {
26713 let word_count =
26714 snippet_candidate_suffixes(prefix, &is_word_char).count();
26715 ((snippet_ix, prefix_ix), prefix, word_count)
26716 })
26717 })
26718 .collect_vec();
26719 sorted_snippet_candidates
26720 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26721
26722 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26723
26724 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
26725 .take(
26726 sorted_snippet_candidates
26727 .first()
26728 .map(|(_, _, word_count)| *word_count)
26729 .unwrap_or_default(),
26730 )
26731 .collect_vec();
26732
26733 const MAX_RESULTS: usize = 100;
26734 // Each match also remembers how many characters from the buffer it consumed
26735 let mut matches: Vec<(StringMatch, usize)> = vec![];
26736
26737 let mut snippet_list_cutoff_index = 0;
26738 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26739 let word_count = buffer_index + 1;
26740 // Increase `snippet_list_cutoff_index` until we have all of the
26741 // snippets with sufficiently many words.
26742 while sorted_snippet_candidates
26743 .get(snippet_list_cutoff_index)
26744 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26745 *snippet_word_count >= word_count
26746 })
26747 {
26748 snippet_list_cutoff_index += 1;
26749 }
26750
26751 // Take only the candidates with at least `word_count` many words
26752 let snippet_candidates_at_word_len =
26753 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26754
26755 let candidates = snippet_candidates_at_word_len
26756 .iter()
26757 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26758 .enumerate() // index in `sorted_snippet_candidates`
26759 // First char must match
26760 .filter(|(_ix, prefix)| {
26761 itertools::equal(
26762 prefix
26763 .chars()
26764 .next()
26765 .into_iter()
26766 .flat_map(|c| c.to_lowercase()),
26767 buffer_window
26768 .chars()
26769 .next()
26770 .into_iter()
26771 .flat_map(|c| c.to_lowercase()),
26772 )
26773 })
26774 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26775 .collect::<Vec<StringMatchCandidate>>();
26776
26777 matches.extend(
26778 fuzzy::match_strings(
26779 &candidates,
26780 &buffer_window,
26781 buffer_window.chars().any(|c| c.is_uppercase()),
26782 true,
26783 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26784 &Default::default(),
26785 executor.clone(),
26786 )
26787 .await
26788 .into_iter()
26789 .map(|string_match| (string_match, buffer_window.len())),
26790 );
26791
26792 if matches.len() >= MAX_RESULTS {
26793 break;
26794 }
26795 }
26796
26797 let to_lsp = |point: &text::Anchor| {
26798 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26799 point_to_lsp(end)
26800 };
26801 let lsp_end = to_lsp(&buffer_anchor);
26802
26803 if matches.len() >= MAX_RESULTS {
26804 is_incomplete = true;
26805 }
26806
26807 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26808 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26809 sorted_snippet_candidates[string_match.candidate_id];
26810 let snippet = &snippets[snippet_index];
26811 let start = buffer_offset - buffer_window_len;
26812 let start = snapshot.anchor_before(start);
26813 let range = start..buffer_anchor;
26814 let lsp_start = to_lsp(&start);
26815 let lsp_range = lsp::Range {
26816 start: lsp_start,
26817 end: lsp_end,
26818 };
26819 Completion {
26820 replace_range: range,
26821 new_text: snippet.body.clone(),
26822 source: CompletionSource::Lsp {
26823 insert_range: None,
26824 server_id: LanguageServerId(usize::MAX),
26825 resolved: true,
26826 lsp_completion: Box::new(lsp::CompletionItem {
26827 label: snippet.prefix.first().unwrap().clone(),
26828 kind: Some(CompletionItemKind::SNIPPET),
26829 label_details: snippet.description.as_ref().map(|description| {
26830 lsp::CompletionItemLabelDetails {
26831 detail: Some(description.clone()),
26832 description: None,
26833 }
26834 }),
26835 insert_text_format: Some(InsertTextFormat::SNIPPET),
26836 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26837 lsp::InsertReplaceEdit {
26838 new_text: snippet.body.clone(),
26839 insert: lsp_range,
26840 replace: lsp_range,
26841 },
26842 )),
26843 filter_text: Some(snippet.body.clone()),
26844 sort_text: Some(char::MAX.to_string()),
26845 ..lsp::CompletionItem::default()
26846 }),
26847 lsp_defaults: None,
26848 },
26849 label: CodeLabel {
26850 text: matching_prefix.clone(),
26851 runs: Vec::new(),
26852 filter_range: 0..matching_prefix.len(),
26853 },
26854 icon_path: None,
26855 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26856 single_line: snippet.name.clone().into(),
26857 plain_text: snippet
26858 .description
26859 .clone()
26860 .map(|description| description.into()),
26861 }),
26862 insert_text_mode: None,
26863 confirm: None,
26864 match_start: Some(start),
26865 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26866 }
26867 }));
26868 }
26869
26870 Ok(CompletionResponse {
26871 completions,
26872 display_options: CompletionDisplayOptions::default(),
26873 is_incomplete,
26874 })
26875 })
26876}
26877
26878impl CompletionProvider for Entity<Project> {
26879 fn completions(
26880 &self,
26881 _excerpt_id: ExcerptId,
26882 buffer: &Entity<Buffer>,
26883 buffer_position: text::Anchor,
26884 options: CompletionContext,
26885 _window: &mut Window,
26886 cx: &mut Context<Editor>,
26887 ) -> Task<Result<Vec<CompletionResponse>>> {
26888 self.update(cx, |project, cx| {
26889 let task = project.completions(buffer, buffer_position, options, cx);
26890 cx.background_spawn(task)
26891 })
26892 }
26893
26894 fn resolve_completions(
26895 &self,
26896 buffer: Entity<Buffer>,
26897 completion_indices: Vec<usize>,
26898 completions: Rc<RefCell<Box<[Completion]>>>,
26899 cx: &mut Context<Editor>,
26900 ) -> Task<Result<bool>> {
26901 self.update(cx, |project, cx| {
26902 project.lsp_store().update(cx, |lsp_store, cx| {
26903 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26904 })
26905 })
26906 }
26907
26908 fn apply_additional_edits_for_completion(
26909 &self,
26910 buffer: Entity<Buffer>,
26911 completions: Rc<RefCell<Box<[Completion]>>>,
26912 completion_index: usize,
26913 push_to_history: bool,
26914 cx: &mut Context<Editor>,
26915 ) -> Task<Result<Option<language::Transaction>>> {
26916 self.update(cx, |project, cx| {
26917 project.lsp_store().update(cx, |lsp_store, cx| {
26918 lsp_store.apply_additional_edits_for_completion(
26919 buffer,
26920 completions,
26921 completion_index,
26922 push_to_history,
26923 cx,
26924 )
26925 })
26926 })
26927 }
26928
26929 fn is_completion_trigger(
26930 &self,
26931 buffer: &Entity<Buffer>,
26932 position: language::Anchor,
26933 text: &str,
26934 trigger_in_words: bool,
26935 cx: &mut Context<Editor>,
26936 ) -> bool {
26937 let mut chars = text.chars();
26938 let char = if let Some(char) = chars.next() {
26939 char
26940 } else {
26941 return false;
26942 };
26943 if chars.next().is_some() {
26944 return false;
26945 }
26946
26947 let buffer = buffer.read(cx);
26948 let snapshot = buffer.snapshot();
26949 let classifier = snapshot
26950 .char_classifier_at(position)
26951 .scope_context(Some(CharScopeContext::Completion));
26952 if trigger_in_words && classifier.is_word(char) {
26953 return true;
26954 }
26955
26956 buffer.completion_triggers().contains(text)
26957 }
26958
26959 fn show_snippets(&self) -> bool {
26960 true
26961 }
26962}
26963
26964impl SemanticsProvider for WeakEntity<Project> {
26965 fn hover(
26966 &self,
26967 buffer: &Entity<Buffer>,
26968 position: text::Anchor,
26969 cx: &mut App,
26970 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26971 self.update(cx, |project, cx| project.hover(buffer, position, cx))
26972 .ok()
26973 }
26974
26975 fn document_highlights(
26976 &self,
26977 buffer: &Entity<Buffer>,
26978 position: text::Anchor,
26979 cx: &mut App,
26980 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
26981 self.update(cx, |project, cx| {
26982 project.document_highlights(buffer, position, cx)
26983 })
26984 .ok()
26985 }
26986
26987 fn definitions(
26988 &self,
26989 buffer: &Entity<Buffer>,
26990 position: text::Anchor,
26991 kind: GotoDefinitionKind,
26992 cx: &mut App,
26993 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
26994 self.update(cx, |project, cx| match kind {
26995 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
26996 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
26997 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
26998 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
26999 })
27000 .ok()
27001 }
27002
27003 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27004 self.update(cx, |project, cx| {
27005 if project
27006 .active_debug_session(cx)
27007 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27008 {
27009 return true;
27010 }
27011
27012 buffer.update(cx, |buffer, cx| {
27013 project.any_language_server_supports_inlay_hints(buffer, cx)
27014 })
27015 })
27016 .unwrap_or(false)
27017 }
27018
27019 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27020 self.update(cx, |project, cx| {
27021 buffer.update(cx, |buffer, cx| {
27022 project.any_language_server_supports_semantic_tokens(buffer, cx)
27023 })
27024 })
27025 .unwrap_or(false)
27026 }
27027
27028 fn inline_values(
27029 &self,
27030 buffer_handle: Entity<Buffer>,
27031 range: Range<text::Anchor>,
27032 cx: &mut App,
27033 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27034 self.update(cx, |project, cx| {
27035 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27036
27037 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27038 })
27039 .ok()
27040 .flatten()
27041 }
27042
27043 fn applicable_inlay_chunks(
27044 &self,
27045 buffer: &Entity<Buffer>,
27046 ranges: &[Range<text::Anchor>],
27047 cx: &mut App,
27048 ) -> Vec<Range<BufferRow>> {
27049 self.update(cx, |project, cx| {
27050 project.lsp_store().update(cx, |lsp_store, cx| {
27051 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27052 })
27053 })
27054 .unwrap_or_default()
27055 }
27056
27057 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27058 self.update(cx, |project, cx| {
27059 project.lsp_store().update(cx, |lsp_store, _| {
27060 lsp_store.invalidate_inlay_hints(for_buffers)
27061 })
27062 })
27063 .ok();
27064 }
27065
27066 fn inlay_hints(
27067 &self,
27068 invalidate: InvalidationStrategy,
27069 buffer: Entity<Buffer>,
27070 ranges: Vec<Range<text::Anchor>>,
27071 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27072 cx: &mut App,
27073 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27074 self.update(cx, |project, cx| {
27075 project.lsp_store().update(cx, |lsp_store, cx| {
27076 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27077 })
27078 })
27079 .ok()
27080 }
27081
27082 fn semantic_tokens(
27083 &self,
27084 buffer: Entity<Buffer>,
27085 refresh: Option<RefreshForServer>,
27086 cx: &mut App,
27087 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27088 self.update(cx, |this, cx| {
27089 this.lsp_store().update(cx, |lsp_store, cx| {
27090 lsp_store.semantic_tokens(buffer, refresh, cx)
27091 })
27092 })
27093 .ok()
27094 }
27095
27096 fn range_for_rename(
27097 &self,
27098 buffer: &Entity<Buffer>,
27099 position: text::Anchor,
27100 cx: &mut App,
27101 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27102 self.update(cx, |project, cx| {
27103 let buffer = buffer.clone();
27104 let task = project.prepare_rename(buffer.clone(), position, cx);
27105 cx.spawn(async move |_, cx| {
27106 Ok(match task.await? {
27107 PrepareRenameResponse::Success(range) => Some(range),
27108 PrepareRenameResponse::InvalidPosition => None,
27109 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27110 // Fallback on using TreeSitter info to determine identifier range
27111 buffer.read_with(cx, |buffer, _| {
27112 let snapshot = buffer.snapshot();
27113 let (range, kind) = snapshot.surrounding_word(position, None);
27114 if kind != Some(CharKind::Word) {
27115 return None;
27116 }
27117 Some(
27118 snapshot.anchor_before(range.start)
27119 ..snapshot.anchor_after(range.end),
27120 )
27121 })
27122 }
27123 })
27124 })
27125 })
27126 .ok()
27127 }
27128
27129 fn perform_rename(
27130 &self,
27131 buffer: &Entity<Buffer>,
27132 position: text::Anchor,
27133 new_name: String,
27134 cx: &mut App,
27135 ) -> Option<Task<Result<ProjectTransaction>>> {
27136 self.update(cx, |project, cx| {
27137 project.perform_rename(buffer.clone(), position, new_name, cx)
27138 })
27139 .ok()
27140 }
27141}
27142
27143fn consume_contiguous_rows(
27144 contiguous_row_selections: &mut Vec<Selection<Point>>,
27145 selection: &Selection<Point>,
27146 display_map: &DisplaySnapshot,
27147 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27148) -> (MultiBufferRow, MultiBufferRow) {
27149 contiguous_row_selections.push(selection.clone());
27150 let start_row = starting_row(selection, display_map);
27151 let mut end_row = ending_row(selection, display_map);
27152
27153 while let Some(next_selection) = selections.peek() {
27154 if next_selection.start.row <= end_row.0 {
27155 end_row = ending_row(next_selection, display_map);
27156 contiguous_row_selections.push(selections.next().unwrap().clone());
27157 } else {
27158 break;
27159 }
27160 }
27161 (start_row, end_row)
27162}
27163
27164fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27165 if selection.start.column > 0 {
27166 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27167 } else {
27168 MultiBufferRow(selection.start.row)
27169 }
27170}
27171
27172fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27173 if next_selection.end.column > 0 || next_selection.is_empty() {
27174 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27175 } else {
27176 MultiBufferRow(next_selection.end.row)
27177 }
27178}
27179
27180impl EditorSnapshot {
27181 pub fn remote_selections_in_range<'a>(
27182 &'a self,
27183 range: &'a Range<Anchor>,
27184 collaboration_hub: &dyn CollaborationHub,
27185 cx: &'a App,
27186 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27187 let participant_names = collaboration_hub.user_names(cx);
27188 let participant_indices = collaboration_hub.user_participant_indices(cx);
27189 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27190 let collaborators_by_replica_id = collaborators_by_peer_id
27191 .values()
27192 .map(|collaborator| (collaborator.replica_id, collaborator))
27193 .collect::<HashMap<_, _>>();
27194 self.buffer_snapshot()
27195 .selections_in_range(range, false)
27196 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27197 if replica_id == ReplicaId::AGENT {
27198 Some(RemoteSelection {
27199 replica_id,
27200 selection,
27201 cursor_shape,
27202 line_mode,
27203 collaborator_id: CollaboratorId::Agent,
27204 user_name: Some("Agent".into()),
27205 color: cx.theme().players().agent(),
27206 })
27207 } else {
27208 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27209 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27210 let user_name = participant_names.get(&collaborator.user_id).cloned();
27211 Some(RemoteSelection {
27212 replica_id,
27213 selection,
27214 cursor_shape,
27215 line_mode,
27216 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27217 user_name,
27218 color: if let Some(index) = participant_index {
27219 cx.theme().players().color_for_participant(index.0)
27220 } else {
27221 cx.theme().players().absent()
27222 },
27223 })
27224 }
27225 })
27226 }
27227
27228 pub fn hunks_for_ranges(
27229 &self,
27230 ranges: impl IntoIterator<Item = Range<Point>>,
27231 ) -> Vec<MultiBufferDiffHunk> {
27232 let mut hunks = Vec::new();
27233 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27234 HashMap::default();
27235 for query_range in ranges {
27236 let query_rows =
27237 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27238 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27239 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27240 ) {
27241 // Include deleted hunks that are adjacent to the query range, because
27242 // otherwise they would be missed.
27243 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27244 if hunk.status().is_deleted() {
27245 intersects_range |= hunk.row_range.start == query_rows.end;
27246 intersects_range |= hunk.row_range.end == query_rows.start;
27247 }
27248 if intersects_range {
27249 if !processed_buffer_rows
27250 .entry(hunk.buffer_id)
27251 .or_default()
27252 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27253 {
27254 continue;
27255 }
27256 hunks.push(hunk);
27257 }
27258 }
27259 }
27260
27261 hunks
27262 }
27263
27264 fn display_diff_hunks_for_rows<'a>(
27265 &'a self,
27266 display_rows: Range<DisplayRow>,
27267 folded_buffers: &'a HashSet<BufferId>,
27268 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27269 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27270 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27271
27272 self.buffer_snapshot()
27273 .diff_hunks_in_range(buffer_start..buffer_end)
27274 .filter_map(|hunk| {
27275 if folded_buffers.contains(&hunk.buffer_id)
27276 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27277 {
27278 return None;
27279 }
27280
27281 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27282 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27283 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27284 let line_len = self.buffer_snapshot().line_len(last_row);
27285 Point::new(last_row.0, line_len)
27286 } else {
27287 Point::new(hunk.row_range.end.0, 0)
27288 };
27289
27290 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27291 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27292
27293 let display_hunk = if hunk_display_start.column() != 0 {
27294 DisplayDiffHunk::Folded {
27295 display_row: hunk_display_start.row(),
27296 }
27297 } else {
27298 let mut end_row = hunk_display_end.row();
27299 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27300 end_row.0 += 1;
27301 }
27302 let is_created_file = hunk.is_created_file();
27303
27304 DisplayDiffHunk::Unfolded {
27305 status: hunk.status(),
27306 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27307 ..hunk.diff_base_byte_range.end.0,
27308 word_diffs: hunk.word_diffs,
27309 display_row_range: hunk_display_start.row()..end_row,
27310 multi_buffer_range: Anchor::range_in_buffer(
27311 hunk.excerpt_id,
27312 hunk.buffer_range,
27313 ),
27314 is_created_file,
27315 }
27316 };
27317
27318 Some(display_hunk)
27319 })
27320 }
27321
27322 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27323 self.display_snapshot
27324 .buffer_snapshot()
27325 .language_at(position)
27326 }
27327
27328 pub fn is_focused(&self) -> bool {
27329 self.is_focused
27330 }
27331
27332 pub fn placeholder_text(&self) -> Option<String> {
27333 self.placeholder_display_snapshot
27334 .as_ref()
27335 .map(|display_map| display_map.text())
27336 }
27337
27338 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27339 self.scroll_anchor.scroll_position(&self.display_snapshot)
27340 }
27341
27342 pub fn gutter_dimensions(
27343 &self,
27344 font_id: FontId,
27345 font_size: Pixels,
27346 style: &EditorStyle,
27347 window: &mut Window,
27348 cx: &App,
27349 ) -> GutterDimensions {
27350 if self.show_gutter
27351 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27352 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27353 {
27354 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27355 matches!(
27356 ProjectSettings::get_global(cx).git.git_gutter,
27357 GitGutterSetting::TrackedFiles
27358 )
27359 });
27360 let gutter_settings = EditorSettings::get_global(cx).gutter;
27361 let show_line_numbers = self
27362 .show_line_numbers
27363 .unwrap_or(gutter_settings.line_numbers);
27364 let line_gutter_width = if show_line_numbers {
27365 // Avoid flicker-like gutter resizes when the line number gains another digit by
27366 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27367 let min_width_for_number_on_gutter =
27368 ch_advance * gutter_settings.min_line_number_digits as f32;
27369 self.max_line_number_width(style, window)
27370 .max(min_width_for_number_on_gutter)
27371 } else {
27372 0.0.into()
27373 };
27374
27375 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27376 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27377
27378 let git_blame_entries_width =
27379 self.git_blame_gutter_max_author_length
27380 .map(|max_author_length| {
27381 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27382 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27383
27384 /// The number of characters to dedicate to gaps and margins.
27385 const SPACING_WIDTH: usize = 4;
27386
27387 let max_char_count = max_author_length.min(renderer.max_author_length())
27388 + ::git::SHORT_SHA_LENGTH
27389 + MAX_RELATIVE_TIMESTAMP.len()
27390 + SPACING_WIDTH;
27391
27392 ch_advance * max_char_count
27393 });
27394
27395 let is_singleton = self.buffer_snapshot().is_singleton();
27396
27397 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27398 left_padding += if !is_singleton {
27399 ch_width * 4.0
27400 } else if show_runnables || show_breakpoints {
27401 ch_width * 3.0
27402 } else if show_git_gutter && show_line_numbers {
27403 ch_width * 2.0
27404 } else if show_git_gutter || show_line_numbers {
27405 ch_width
27406 } else {
27407 px(0.)
27408 };
27409
27410 let shows_folds = is_singleton && gutter_settings.folds;
27411
27412 let right_padding = if shows_folds && show_line_numbers {
27413 ch_width * 4.0
27414 } else if shows_folds || (!is_singleton && show_line_numbers) {
27415 ch_width * 3.0
27416 } else if show_line_numbers {
27417 ch_width
27418 } else {
27419 px(0.)
27420 };
27421
27422 GutterDimensions {
27423 left_padding,
27424 right_padding,
27425 width: line_gutter_width + left_padding + right_padding,
27426 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27427 git_blame_entries_width,
27428 }
27429 } else if self.offset_content {
27430 GutterDimensions::default_with_margin(font_id, font_size, cx)
27431 } else {
27432 GutterDimensions::default()
27433 }
27434 }
27435
27436 pub fn render_crease_toggle(
27437 &self,
27438 buffer_row: MultiBufferRow,
27439 row_contains_cursor: bool,
27440 editor: Entity<Editor>,
27441 window: &mut Window,
27442 cx: &mut App,
27443 ) -> Option<AnyElement> {
27444 let folded = self.is_line_folded(buffer_row);
27445 let mut is_foldable = false;
27446
27447 if let Some(crease) = self
27448 .crease_snapshot
27449 .query_row(buffer_row, self.buffer_snapshot())
27450 {
27451 is_foldable = true;
27452 match crease {
27453 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27454 if let Some(render_toggle) = render_toggle {
27455 let toggle_callback =
27456 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27457 if folded {
27458 editor.update(cx, |editor, cx| {
27459 editor.fold_at(buffer_row, window, cx)
27460 });
27461 } else {
27462 editor.update(cx, |editor, cx| {
27463 editor.unfold_at(buffer_row, window, cx)
27464 });
27465 }
27466 });
27467 return Some((render_toggle)(
27468 buffer_row,
27469 folded,
27470 toggle_callback,
27471 window,
27472 cx,
27473 ));
27474 }
27475 }
27476 }
27477 }
27478
27479 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27480
27481 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27482 Some(
27483 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27484 .toggle_state(folded)
27485 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27486 if folded {
27487 this.unfold_at(buffer_row, window, cx);
27488 } else {
27489 this.fold_at(buffer_row, window, cx);
27490 }
27491 }))
27492 .into_any_element(),
27493 )
27494 } else {
27495 None
27496 }
27497 }
27498
27499 pub fn render_crease_trailer(
27500 &self,
27501 buffer_row: MultiBufferRow,
27502 window: &mut Window,
27503 cx: &mut App,
27504 ) -> Option<AnyElement> {
27505 let folded = self.is_line_folded(buffer_row);
27506 if let Crease::Inline { render_trailer, .. } = self
27507 .crease_snapshot
27508 .query_row(buffer_row, self.buffer_snapshot())?
27509 {
27510 let render_trailer = render_trailer.as_ref()?;
27511 Some(render_trailer(buffer_row, folded, window, cx))
27512 } else {
27513 None
27514 }
27515 }
27516
27517 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27518 let digit_count = self.widest_line_number().ilog10() + 1;
27519 column_pixels(style, digit_count as usize, window)
27520 }
27521
27522 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27523 ///
27524 /// This is positive if `base` is before `line`.
27525 fn relative_line_delta(
27526 &self,
27527 current_selection_head: DisplayRow,
27528 first_visible_row: DisplayRow,
27529 consider_wrapped_lines: bool,
27530 ) -> i64 {
27531 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27532 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27533
27534 if consider_wrapped_lines {
27535 let wrap_snapshot = self.wrap_snapshot();
27536 let base_wrap_row = wrap_snapshot
27537 .make_wrap_point(current_selection_head, Bias::Left)
27538 .row();
27539 let wrap_row = wrap_snapshot
27540 .make_wrap_point(first_visible_row, Bias::Left)
27541 .row();
27542
27543 wrap_row.0 as i64 - base_wrap_row.0 as i64
27544 } else {
27545 let fold_snapshot = self.fold_snapshot();
27546 let base_fold_row = fold_snapshot
27547 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27548 .row();
27549 let fold_row = fold_snapshot
27550 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27551 .row();
27552
27553 fold_row as i64 - base_fold_row as i64
27554 }
27555 }
27556
27557 /// Returns the unsigned relative line number to display for each row in `rows`.
27558 ///
27559 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27560 pub fn calculate_relative_line_numbers(
27561 &self,
27562 rows: &Range<DisplayRow>,
27563 current_selection_head: DisplayRow,
27564 count_wrapped_lines: bool,
27565 ) -> HashMap<DisplayRow, u32> {
27566 let initial_offset =
27567 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27568
27569 self.row_infos(rows.start)
27570 .take(rows.len())
27571 .enumerate()
27572 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27573 .filter(|(_row, row_info)| {
27574 row_info.buffer_row.is_some()
27575 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27576 })
27577 .enumerate()
27578 .filter_map(|(i, (row, row_info))| {
27579 // We want to ensure here that the current line has absolute
27580 // numbering, even if we are in a soft-wrapped line. With the
27581 // exception that if we are in a deleted line, we should number this
27582 // relative with 0, as otherwise it would have no line number at all
27583 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27584
27585 (relative_line_number != 0
27586 || row_info
27587 .diff_status
27588 .is_some_and(|status| status.is_deleted()))
27589 .then_some((row, relative_line_number))
27590 })
27591 .collect()
27592 }
27593}
27594
27595pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27596 let font_size = style.text.font_size.to_pixels(window.rem_size());
27597 let layout = window.text_system().shape_line(
27598 SharedString::from(" ".repeat(column)),
27599 font_size,
27600 &[TextRun {
27601 len: column,
27602 font: style.text.font(),
27603 color: Hsla::default(),
27604 ..Default::default()
27605 }],
27606 None,
27607 );
27608
27609 layout.width
27610}
27611
27612impl Deref for EditorSnapshot {
27613 type Target = DisplaySnapshot;
27614
27615 fn deref(&self) -> &Self::Target {
27616 &self.display_snapshot
27617 }
27618}
27619
27620#[derive(Clone, Debug, PartialEq, Eq)]
27621pub enum EditorEvent {
27622 /// Emitted when the stored review comments change (added, removed, or updated).
27623 ReviewCommentsChanged {
27624 /// The new total count of review comments.
27625 total_count: usize,
27626 },
27627 InputIgnored {
27628 text: Arc<str>,
27629 },
27630 InputHandled {
27631 utf16_range_to_replace: Option<Range<isize>>,
27632 text: Arc<str>,
27633 },
27634 ExcerptsAdded {
27635 buffer: Entity<Buffer>,
27636 predecessor: ExcerptId,
27637 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27638 },
27639 ExcerptsRemoved {
27640 ids: Vec<ExcerptId>,
27641 removed_buffer_ids: Vec<BufferId>,
27642 },
27643 BufferFoldToggled {
27644 ids: Vec<ExcerptId>,
27645 folded: bool,
27646 },
27647 ExcerptsEdited {
27648 ids: Vec<ExcerptId>,
27649 },
27650 ExcerptsExpanded {
27651 ids: Vec<ExcerptId>,
27652 },
27653 ExpandExcerptsRequested {
27654 excerpt_ids: Vec<ExcerptId>,
27655 lines: u32,
27656 direction: ExpandExcerptDirection,
27657 },
27658 StageOrUnstageRequested {
27659 stage: bool,
27660 hunks: Vec<MultiBufferDiffHunk>,
27661 },
27662 OpenExcerptsRequested {
27663 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
27664 split: bool,
27665 },
27666 RestoreRequested {
27667 hunks: Vec<MultiBufferDiffHunk>,
27668 },
27669 BufferEdited,
27670 Edited {
27671 transaction_id: clock::Lamport,
27672 },
27673 Reparsed(BufferId),
27674 Focused,
27675 FocusedIn,
27676 Blurred,
27677 DirtyChanged,
27678 Saved,
27679 TitleChanged,
27680 SelectionsChanged {
27681 local: bool,
27682 },
27683 ScrollPositionChanged {
27684 local: bool,
27685 autoscroll: bool,
27686 },
27687 TransactionUndone {
27688 transaction_id: clock::Lamport,
27689 },
27690 TransactionBegun {
27691 transaction_id: clock::Lamport,
27692 },
27693 CursorShapeChanged,
27694 BreadcrumbsChanged,
27695 OutlineSymbolsChanged,
27696 PushedToNavHistory {
27697 anchor: Anchor,
27698 is_deactivate: bool,
27699 },
27700}
27701
27702impl EventEmitter<EditorEvent> for Editor {}
27703
27704impl Focusable for Editor {
27705 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27706 self.focus_handle.clone()
27707 }
27708}
27709
27710impl Render for Editor {
27711 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27712 EditorElement::new(&cx.entity(), self.create_style(cx))
27713 }
27714}
27715
27716impl EntityInputHandler for Editor {
27717 fn text_for_range(
27718 &mut self,
27719 range_utf16: Range<usize>,
27720 adjusted_range: &mut Option<Range<usize>>,
27721 _: &mut Window,
27722 cx: &mut Context<Self>,
27723 ) -> Option<String> {
27724 let snapshot = self.buffer.read(cx).read(cx);
27725 let start = snapshot.clip_offset_utf16(
27726 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27727 Bias::Left,
27728 );
27729 let end = snapshot.clip_offset_utf16(
27730 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27731 Bias::Right,
27732 );
27733 if (start.0.0..end.0.0) != range_utf16 {
27734 adjusted_range.replace(start.0.0..end.0.0);
27735 }
27736 Some(snapshot.text_for_range(start..end).collect())
27737 }
27738
27739 fn selected_text_range(
27740 &mut self,
27741 ignore_disabled_input: bool,
27742 _: &mut Window,
27743 cx: &mut Context<Self>,
27744 ) -> Option<UTF16Selection> {
27745 // Prevent the IME menu from appearing when holding down an alphabetic key
27746 // while input is disabled.
27747 if !ignore_disabled_input && !self.input_enabled {
27748 return None;
27749 }
27750
27751 let selection = self
27752 .selections
27753 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27754 let range = selection.range();
27755
27756 Some(UTF16Selection {
27757 range: range.start.0.0..range.end.0.0,
27758 reversed: selection.reversed,
27759 })
27760 }
27761
27762 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27763 let snapshot = self.buffer.read(cx).read(cx);
27764 let range = self
27765 .text_highlights(HighlightKey::InputComposition, cx)?
27766 .1
27767 .first()?;
27768 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27769 }
27770
27771 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27772 self.clear_highlights(HighlightKey::InputComposition, cx);
27773 self.ime_transaction.take();
27774 }
27775
27776 fn replace_text_in_range(
27777 &mut self,
27778 range_utf16: Option<Range<usize>>,
27779 text: &str,
27780 window: &mut Window,
27781 cx: &mut Context<Self>,
27782 ) {
27783 if !self.input_enabled {
27784 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27785 return;
27786 }
27787
27788 self.transact(window, cx, |this, window, cx| {
27789 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27790 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27791 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27792 Some(this.selection_replacement_ranges(range_utf16, cx))
27793 } else {
27794 this.marked_text_ranges(cx)
27795 };
27796
27797 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27798 let newest_selection_id = this.selections.newest_anchor().id;
27799 this.selections
27800 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27801 .iter()
27802 .zip(ranges_to_replace.iter())
27803 .find_map(|(selection, range)| {
27804 if selection.id == newest_selection_id {
27805 Some(
27806 (range.start.0.0 as isize - selection.head().0.0 as isize)
27807 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27808 )
27809 } else {
27810 None
27811 }
27812 })
27813 });
27814
27815 cx.emit(EditorEvent::InputHandled {
27816 utf16_range_to_replace: range_to_replace,
27817 text: text.into(),
27818 });
27819
27820 if let Some(new_selected_ranges) = new_selected_ranges {
27821 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27822 selections.select_ranges(new_selected_ranges)
27823 });
27824 this.backspace(&Default::default(), window, cx);
27825 }
27826
27827 this.handle_input(text, window, cx);
27828 });
27829
27830 if let Some(transaction) = self.ime_transaction {
27831 self.buffer.update(cx, |buffer, cx| {
27832 buffer.group_until_transaction(transaction, cx);
27833 });
27834 }
27835
27836 self.unmark_text(window, cx);
27837 }
27838
27839 fn replace_and_mark_text_in_range(
27840 &mut self,
27841 range_utf16: Option<Range<usize>>,
27842 text: &str,
27843 new_selected_range_utf16: Option<Range<usize>>,
27844 window: &mut Window,
27845 cx: &mut Context<Self>,
27846 ) {
27847 if !self.input_enabled {
27848 return;
27849 }
27850
27851 let transaction = self.transact(window, cx, |this, window, cx| {
27852 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27853 let snapshot = this.buffer.read(cx).read(cx);
27854 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27855 for marked_range in &mut marked_ranges {
27856 marked_range.end = marked_range.start + relative_range_utf16.end;
27857 marked_range.start += relative_range_utf16.start;
27858 marked_range.start =
27859 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27860 marked_range.end =
27861 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27862 }
27863 }
27864 Some(marked_ranges)
27865 } else if let Some(range_utf16) = range_utf16 {
27866 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27867 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27868 Some(this.selection_replacement_ranges(range_utf16, cx))
27869 } else {
27870 None
27871 };
27872
27873 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27874 let newest_selection_id = this.selections.newest_anchor().id;
27875 this.selections
27876 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27877 .iter()
27878 .zip(ranges_to_replace.iter())
27879 .find_map(|(selection, range)| {
27880 if selection.id == newest_selection_id {
27881 Some(
27882 (range.start.0.0 as isize - selection.head().0.0 as isize)
27883 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27884 )
27885 } else {
27886 None
27887 }
27888 })
27889 });
27890
27891 cx.emit(EditorEvent::InputHandled {
27892 utf16_range_to_replace: range_to_replace,
27893 text: text.into(),
27894 });
27895
27896 if let Some(ranges) = ranges_to_replace {
27897 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27898 s.select_ranges(ranges)
27899 });
27900 }
27901
27902 let marked_ranges = {
27903 let snapshot = this.buffer.read(cx).read(cx);
27904 this.selections
27905 .disjoint_anchors_arc()
27906 .iter()
27907 .map(|selection| {
27908 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27909 })
27910 .collect::<Vec<_>>()
27911 };
27912
27913 if text.is_empty() {
27914 this.unmark_text(window, cx);
27915 } else {
27916 this.highlight_text(
27917 HighlightKey::InputComposition,
27918 marked_ranges.clone(),
27919 HighlightStyle {
27920 underline: Some(UnderlineStyle {
27921 thickness: px(1.),
27922 color: None,
27923 wavy: false,
27924 }),
27925 ..Default::default()
27926 },
27927 cx,
27928 );
27929 }
27930
27931 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27932 let use_autoclose = this.use_autoclose;
27933 let use_auto_surround = this.use_auto_surround;
27934 this.set_use_autoclose(false);
27935 this.set_use_auto_surround(false);
27936 this.handle_input(text, window, cx);
27937 this.set_use_autoclose(use_autoclose);
27938 this.set_use_auto_surround(use_auto_surround);
27939
27940 if let Some(new_selected_range) = new_selected_range_utf16 {
27941 let snapshot = this.buffer.read(cx).read(cx);
27942 let new_selected_ranges = marked_ranges
27943 .into_iter()
27944 .map(|marked_range| {
27945 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27946 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27947 insertion_start.0 + new_selected_range.start,
27948 ));
27949 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27950 insertion_start.0 + new_selected_range.end,
27951 ));
27952 snapshot.clip_offset_utf16(new_start, Bias::Left)
27953 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27954 })
27955 .collect::<Vec<_>>();
27956
27957 drop(snapshot);
27958 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27959 selections.select_ranges(new_selected_ranges)
27960 });
27961 }
27962 });
27963
27964 self.ime_transaction = self.ime_transaction.or(transaction);
27965 if let Some(transaction) = self.ime_transaction {
27966 self.buffer.update(cx, |buffer, cx| {
27967 buffer.group_until_transaction(transaction, cx);
27968 });
27969 }
27970
27971 if self
27972 .text_highlights(HighlightKey::InputComposition, cx)
27973 .is_none()
27974 {
27975 self.ime_transaction.take();
27976 }
27977 }
27978
27979 fn bounds_for_range(
27980 &mut self,
27981 range_utf16: Range<usize>,
27982 element_bounds: gpui::Bounds<Pixels>,
27983 window: &mut Window,
27984 cx: &mut Context<Self>,
27985 ) -> Option<gpui::Bounds<Pixels>> {
27986 let text_layout_details = self.text_layout_details(window, cx);
27987 let CharacterDimensions {
27988 em_width,
27989 em_advance,
27990 line_height,
27991 } = self.character_dimensions(window, cx);
27992
27993 let snapshot = self.snapshot(window, cx);
27994 let scroll_position = snapshot.scroll_position();
27995 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
27996
27997 let start =
27998 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
27999 let x = Pixels::from(
28000 ScrollOffset::from(
28001 snapshot.x_for_display_point(start, &text_layout_details)
28002 + self.gutter_dimensions.full_width(),
28003 ) - scroll_left,
28004 );
28005 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28006
28007 Some(Bounds {
28008 origin: element_bounds.origin + point(x, y),
28009 size: size(em_width, line_height),
28010 })
28011 }
28012
28013 fn character_index_for_point(
28014 &mut self,
28015 point: gpui::Point<Pixels>,
28016 _window: &mut Window,
28017 _cx: &mut Context<Self>,
28018 ) -> Option<usize> {
28019 let position_map = self.last_position_map.as_ref()?;
28020 if !position_map.text_hitbox.contains(&point) {
28021 return None;
28022 }
28023 let display_point = position_map.point_for_position(point).previous_valid;
28024 let anchor = position_map
28025 .snapshot
28026 .display_point_to_anchor(display_point, Bias::Left);
28027 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28028 Some(utf16_offset.0.0)
28029 }
28030
28031 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28032 self.expects_character_input
28033 }
28034}
28035
28036trait SelectionExt {
28037 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28038 fn spanned_rows(
28039 &self,
28040 include_end_if_at_line_start: bool,
28041 map: &DisplaySnapshot,
28042 ) -> Range<MultiBufferRow>;
28043}
28044
28045impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28046 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28047 let start = self
28048 .start
28049 .to_point(map.buffer_snapshot())
28050 .to_display_point(map);
28051 let end = self
28052 .end
28053 .to_point(map.buffer_snapshot())
28054 .to_display_point(map);
28055 if self.reversed {
28056 end..start
28057 } else {
28058 start..end
28059 }
28060 }
28061
28062 fn spanned_rows(
28063 &self,
28064 include_end_if_at_line_start: bool,
28065 map: &DisplaySnapshot,
28066 ) -> Range<MultiBufferRow> {
28067 let start = self.start.to_point(map.buffer_snapshot());
28068 let mut end = self.end.to_point(map.buffer_snapshot());
28069 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28070 end.row -= 1;
28071 }
28072
28073 let buffer_start = map.prev_line_boundary(start).0;
28074 let buffer_end = map.next_line_boundary(end).0;
28075 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28076 }
28077}
28078
28079impl<T: InvalidationRegion> InvalidationStack<T> {
28080 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28081 where
28082 S: Clone + ToOffset,
28083 {
28084 while let Some(region) = self.last() {
28085 let all_selections_inside_invalidation_ranges =
28086 if selections.len() == region.ranges().len() {
28087 selections
28088 .iter()
28089 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28090 .all(|(selection, invalidation_range)| {
28091 let head = selection.head().to_offset(buffer);
28092 invalidation_range.start <= head && invalidation_range.end >= head
28093 })
28094 } else {
28095 false
28096 };
28097
28098 if all_selections_inside_invalidation_ranges {
28099 break;
28100 } else {
28101 self.pop();
28102 }
28103 }
28104 }
28105}
28106
28107#[derive(Clone)]
28108struct ErasedEditorImpl(Entity<Editor>);
28109
28110impl ui_input::ErasedEditor for ErasedEditorImpl {
28111 fn text(&self, cx: &App) -> String {
28112 self.0.read(cx).text(cx)
28113 }
28114
28115 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28116 self.0.update(cx, |this, cx| {
28117 this.set_text(text, window, cx);
28118 })
28119 }
28120
28121 fn clear(&self, window: &mut Window, cx: &mut App) {
28122 self.0.update(cx, |this, cx| this.clear(window, cx));
28123 }
28124
28125 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28126 self.0.update(cx, |this, cx| {
28127 this.set_placeholder_text(text, window, cx);
28128 });
28129 }
28130
28131 fn focus_handle(&self, cx: &App) -> FocusHandle {
28132 self.0.read(cx).focus_handle(cx)
28133 }
28134
28135 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28136 let settings = ThemeSettings::get_global(cx);
28137 let theme_color = cx.theme().colors();
28138
28139 let text_style = TextStyle {
28140 font_family: settings.ui_font.family.clone(),
28141 font_features: settings.ui_font.features.clone(),
28142 font_size: rems(0.875).into(),
28143 font_weight: settings.ui_font.weight,
28144 font_style: FontStyle::Normal,
28145 line_height: relative(1.2),
28146 color: theme_color.text,
28147 ..Default::default()
28148 };
28149 let editor_style = EditorStyle {
28150 background: theme_color.ghost_element_background,
28151 local_player: cx.theme().players().local(),
28152 syntax: cx.theme().syntax().clone(),
28153 text: text_style,
28154 ..Default::default()
28155 };
28156 EditorElement::new(&self.0, editor_style).into_any()
28157 }
28158
28159 fn as_any(&self) -> &dyn Any {
28160 &self.0
28161 }
28162
28163 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28164 self.0.update(cx, |editor, cx| {
28165 let editor_offset = editor.buffer().read(cx).len(cx);
28166 editor.change_selections(
28167 SelectionEffects::scroll(Autoscroll::Next),
28168 window,
28169 cx,
28170 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28171 );
28172 });
28173 }
28174
28175 fn subscribe(
28176 &self,
28177 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28178 window: &mut Window,
28179 cx: &mut App,
28180 ) -> Subscription {
28181 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28182 let event = match event {
28183 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28184 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28185 _ => return,
28186 };
28187 (callback)(event, window, cx);
28188 })
28189 }
28190
28191 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28192 self.0.update(cx, |editor, cx| {
28193 editor.set_masked(masked, cx);
28194 });
28195 }
28196}
28197impl<T> Default for InvalidationStack<T> {
28198 fn default() -> Self {
28199 Self(Default::default())
28200 }
28201}
28202
28203impl<T> Deref for InvalidationStack<T> {
28204 type Target = Vec<T>;
28205
28206 fn deref(&self) -> &Self::Target {
28207 &self.0
28208 }
28209}
28210
28211impl<T> DerefMut for InvalidationStack<T> {
28212 fn deref_mut(&mut self) -> &mut Self::Target {
28213 &mut self.0
28214 }
28215}
28216
28217impl InvalidationRegion for SnippetState {
28218 fn ranges(&self) -> &[Range<Anchor>] {
28219 &self.ranges[self.active_index]
28220 }
28221}
28222
28223fn edit_prediction_edit_text(
28224 current_snapshot: &BufferSnapshot,
28225 edits: &[(Range<Anchor>, impl AsRef<str>)],
28226 edit_preview: &EditPreview,
28227 include_deletions: bool,
28228 cx: &App,
28229) -> HighlightedText {
28230 let edits = edits
28231 .iter()
28232 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28233 .collect::<Vec<_>>();
28234
28235 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28236}
28237
28238fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28239 // Fallback for providers that don't provide edit_preview (like Copilot)
28240 // Just show the raw edit text with basic styling
28241 let mut text = String::new();
28242 let mut highlights = Vec::new();
28243
28244 let insertion_highlight_style = HighlightStyle {
28245 color: Some(cx.theme().colors().text),
28246 ..Default::default()
28247 };
28248
28249 for (_, edit_text) in edits {
28250 let start_offset = text.len();
28251 text.push_str(edit_text);
28252 let end_offset = text.len();
28253
28254 if start_offset < end_offset {
28255 highlights.push((start_offset..end_offset, insertion_highlight_style));
28256 }
28257 }
28258
28259 HighlightedText {
28260 text: text.into(),
28261 highlights,
28262 }
28263}
28264
28265pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28266 match severity {
28267 lsp::DiagnosticSeverity::ERROR => colors.error,
28268 lsp::DiagnosticSeverity::WARNING => colors.warning,
28269 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28270 lsp::DiagnosticSeverity::HINT => colors.info,
28271 _ => colors.ignored,
28272 }
28273}
28274
28275pub fn styled_runs_for_code_label<'a>(
28276 label: &'a CodeLabel,
28277 syntax_theme: &'a theme::SyntaxTheme,
28278 local_player: &'a theme::PlayerColor,
28279) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28280 let fade_out = HighlightStyle {
28281 fade_out: Some(0.35),
28282 ..Default::default()
28283 };
28284
28285 let mut prev_end = label.filter_range.end;
28286 label
28287 .runs
28288 .iter()
28289 .enumerate()
28290 .flat_map(move |(ix, (range, highlight_id))| {
28291 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28292 HighlightStyle {
28293 color: Some(local_player.cursor),
28294 ..Default::default()
28295 }
28296 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28297 HighlightStyle {
28298 background_color: Some(local_player.selection),
28299 ..Default::default()
28300 }
28301 } else if let Some(style) = highlight_id.style(syntax_theme) {
28302 style
28303 } else {
28304 return Default::default();
28305 };
28306 let muted_style = style.highlight(fade_out);
28307
28308 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28309 if range.start >= label.filter_range.end {
28310 if range.start > prev_end {
28311 runs.push((prev_end..range.start, fade_out));
28312 }
28313 runs.push((range.clone(), muted_style));
28314 } else if range.end <= label.filter_range.end {
28315 runs.push((range.clone(), style));
28316 } else {
28317 runs.push((range.start..label.filter_range.end, style));
28318 runs.push((label.filter_range.end..range.end, muted_style));
28319 }
28320 prev_end = cmp::max(prev_end, range.end);
28321
28322 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28323 runs.push((prev_end..label.text.len(), fade_out));
28324 }
28325
28326 runs
28327 })
28328}
28329
28330pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28331 let mut prev_index = 0;
28332 let mut prev_codepoint: Option<char> = None;
28333 text.char_indices()
28334 .chain([(text.len(), '\0')])
28335 .filter_map(move |(index, codepoint)| {
28336 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28337 let is_boundary = index == text.len()
28338 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28339 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28340 if is_boundary {
28341 let chunk = &text[prev_index..index];
28342 prev_index = index;
28343 Some(chunk)
28344 } else {
28345 None
28346 }
28347 })
28348}
28349
28350/// Given a string of text immediately before the cursor, iterates over possible
28351/// strings a snippet could match to. More precisely: returns an iterator over
28352/// suffixes of `text` created by splitting at word boundaries (before & after
28353/// every non-word character).
28354///
28355/// Shorter suffixes are returned first.
28356pub(crate) fn snippet_candidate_suffixes<'a>(
28357 text: &'a str,
28358 is_word_char: &'a dyn Fn(char) -> bool,
28359) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28360 let mut prev_index = text.len();
28361 let mut prev_codepoint = None;
28362 text.char_indices()
28363 .rev()
28364 .chain([(0, '\0')])
28365 .filter_map(move |(index, codepoint)| {
28366 let prev_index = std::mem::replace(&mut prev_index, index);
28367 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28368 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28369 None
28370 } else {
28371 let chunk = &text[prev_index..]; // go to end of string
28372 Some(chunk)
28373 }
28374 })
28375}
28376
28377pub trait RangeToAnchorExt: Sized {
28378 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28379
28380 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28381 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28382 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28383 }
28384}
28385
28386impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28387 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28388 let start_offset = self.start.to_offset(snapshot);
28389 let end_offset = self.end.to_offset(snapshot);
28390 if start_offset == end_offset {
28391 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28392 } else {
28393 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28394 }
28395 }
28396}
28397
28398pub trait RowExt {
28399 fn as_f64(&self) -> f64;
28400
28401 fn next_row(&self) -> Self;
28402
28403 fn previous_row(&self) -> Self;
28404
28405 fn minus(&self, other: Self) -> u32;
28406}
28407
28408impl RowExt for DisplayRow {
28409 fn as_f64(&self) -> f64 {
28410 self.0 as _
28411 }
28412
28413 fn next_row(&self) -> Self {
28414 Self(self.0 + 1)
28415 }
28416
28417 fn previous_row(&self) -> Self {
28418 Self(self.0.saturating_sub(1))
28419 }
28420
28421 fn minus(&self, other: Self) -> u32 {
28422 self.0 - other.0
28423 }
28424}
28425
28426impl RowExt for MultiBufferRow {
28427 fn as_f64(&self) -> f64 {
28428 self.0 as _
28429 }
28430
28431 fn next_row(&self) -> Self {
28432 Self(self.0 + 1)
28433 }
28434
28435 fn previous_row(&self) -> Self {
28436 Self(self.0.saturating_sub(1))
28437 }
28438
28439 fn minus(&self, other: Self) -> u32 {
28440 self.0 - other.0
28441 }
28442}
28443
28444trait RowRangeExt {
28445 type Row;
28446
28447 fn len(&self) -> usize;
28448
28449 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28450}
28451
28452impl RowRangeExt for Range<MultiBufferRow> {
28453 type Row = MultiBufferRow;
28454
28455 fn len(&self) -> usize {
28456 (self.end.0 - self.start.0) as usize
28457 }
28458
28459 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28460 (self.start.0..self.end.0).map(MultiBufferRow)
28461 }
28462}
28463
28464impl RowRangeExt for Range<DisplayRow> {
28465 type Row = DisplayRow;
28466
28467 fn len(&self) -> usize {
28468 (self.end.0 - self.start.0) as usize
28469 }
28470
28471 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28472 (self.start.0..self.end.0).map(DisplayRow)
28473 }
28474}
28475
28476/// If select range has more than one line, we
28477/// just point the cursor to range.start.
28478fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28479 if range.start.row == range.end.row {
28480 range
28481 } else {
28482 range.start..range.start
28483 }
28484}
28485pub struct KillRing(ClipboardItem);
28486impl Global for KillRing {}
28487
28488const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28489
28490enum BreakpointPromptEditAction {
28491 Log,
28492 Condition,
28493 HitCondition,
28494}
28495
28496struct BreakpointPromptEditor {
28497 pub(crate) prompt: Entity<Editor>,
28498 editor: WeakEntity<Editor>,
28499 breakpoint_anchor: Anchor,
28500 breakpoint: Breakpoint,
28501 edit_action: BreakpointPromptEditAction,
28502 block_ids: HashSet<CustomBlockId>,
28503 editor_margins: Arc<Mutex<EditorMargins>>,
28504 _subscriptions: Vec<Subscription>,
28505}
28506
28507impl BreakpointPromptEditor {
28508 const MAX_LINES: u8 = 4;
28509
28510 fn new(
28511 editor: WeakEntity<Editor>,
28512 breakpoint_anchor: Anchor,
28513 breakpoint: Breakpoint,
28514 edit_action: BreakpointPromptEditAction,
28515 window: &mut Window,
28516 cx: &mut Context<Self>,
28517 ) -> Self {
28518 let base_text = match edit_action {
28519 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28520 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28521 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28522 }
28523 .map(|msg| msg.to_string())
28524 .unwrap_or_default();
28525
28526 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28527 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28528
28529 let prompt = cx.new(|cx| {
28530 let mut prompt = Editor::new(
28531 EditorMode::AutoHeight {
28532 min_lines: 1,
28533 max_lines: Some(Self::MAX_LINES as usize),
28534 },
28535 buffer,
28536 None,
28537 window,
28538 cx,
28539 );
28540 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28541 prompt.set_show_cursor_when_unfocused(false, cx);
28542 prompt.set_placeholder_text(
28543 match edit_action {
28544 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28545 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28546 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28547 },
28548 window,
28549 cx,
28550 );
28551
28552 prompt
28553 });
28554
28555 Self {
28556 prompt,
28557 editor,
28558 breakpoint_anchor,
28559 breakpoint,
28560 edit_action,
28561 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28562 block_ids: Default::default(),
28563 _subscriptions: vec![],
28564 }
28565 }
28566
28567 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28568 self.block_ids.extend(block_ids)
28569 }
28570
28571 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28572 if let Some(editor) = self.editor.upgrade() {
28573 let message = self
28574 .prompt
28575 .read(cx)
28576 .buffer
28577 .read(cx)
28578 .as_singleton()
28579 .expect("A multi buffer in breakpoint prompt isn't possible")
28580 .read(cx)
28581 .as_rope()
28582 .to_string();
28583
28584 editor.update(cx, |editor, cx| {
28585 editor.edit_breakpoint_at_anchor(
28586 self.breakpoint_anchor,
28587 self.breakpoint.clone(),
28588 match self.edit_action {
28589 BreakpointPromptEditAction::Log => {
28590 BreakpointEditAction::EditLogMessage(message.into())
28591 }
28592 BreakpointPromptEditAction::Condition => {
28593 BreakpointEditAction::EditCondition(message.into())
28594 }
28595 BreakpointPromptEditAction::HitCondition => {
28596 BreakpointEditAction::EditHitCondition(message.into())
28597 }
28598 },
28599 cx,
28600 );
28601
28602 editor.remove_blocks(self.block_ids.clone(), None, cx);
28603 cx.focus_self(window);
28604 });
28605 }
28606 }
28607
28608 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28609 self.editor
28610 .update(cx, |editor, cx| {
28611 editor.remove_blocks(self.block_ids.clone(), None, cx);
28612 window.focus(&editor.focus_handle, cx);
28613 })
28614 .log_err();
28615 }
28616
28617 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28618 let settings = ThemeSettings::get_global(cx);
28619 let text_style = TextStyle {
28620 color: if self.prompt.read(cx).read_only(cx) {
28621 cx.theme().colors().text_disabled
28622 } else {
28623 cx.theme().colors().text
28624 },
28625 font_family: settings.buffer_font.family.clone(),
28626 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28627 font_size: settings.buffer_font_size(cx).into(),
28628 font_weight: settings.buffer_font.weight,
28629 line_height: relative(settings.buffer_line_height.value()),
28630 ..Default::default()
28631 };
28632 EditorElement::new(
28633 &self.prompt,
28634 EditorStyle {
28635 background: cx.theme().colors().editor_background,
28636 local_player: cx.theme().players().local(),
28637 text: text_style,
28638 ..Default::default()
28639 },
28640 )
28641 }
28642
28643 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
28644 let focus_handle = self.prompt.focus_handle(cx);
28645 IconButton::new("cancel", IconName::Close)
28646 .icon_color(Color::Muted)
28647 .shape(IconButtonShape::Square)
28648 .tooltip(move |_window, cx| {
28649 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
28650 })
28651 .on_click(cx.listener(|this, _, window, cx| {
28652 this.cancel(&menu::Cancel, window, cx);
28653 }))
28654 }
28655
28656 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
28657 let focus_handle = self.prompt.focus_handle(cx);
28658 IconButton::new("confirm", IconName::Return)
28659 .icon_color(Color::Muted)
28660 .shape(IconButtonShape::Square)
28661 .tooltip(move |_window, cx| {
28662 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
28663 })
28664 .on_click(cx.listener(|this, _, window, cx| {
28665 this.confirm(&menu::Confirm, window, cx);
28666 }))
28667 }
28668}
28669
28670impl Render for BreakpointPromptEditor {
28671 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28672 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
28673 let editor_margins = *self.editor_margins.lock();
28674 let gutter_dimensions = editor_margins.gutter;
28675 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
28676 let right_padding = editor_margins.right + px(9.);
28677 h_flex()
28678 .key_context("Editor")
28679 .bg(cx.theme().colors().editor_background)
28680 .border_y_1()
28681 .border_color(cx.theme().status().info_border)
28682 .size_full()
28683 .py(window.line_height() / 2.5)
28684 .pr(right_padding)
28685 .on_action(cx.listener(Self::confirm))
28686 .on_action(cx.listener(Self::cancel))
28687 .child(
28688 WithRemSize::new(ui_font_size)
28689 .h_full()
28690 .w(left_gutter_width)
28691 .flex()
28692 .flex_row()
28693 .flex_shrink_0()
28694 .items_center()
28695 .justify_center()
28696 .gap_1()
28697 .child(self.render_close_button(cx)),
28698 )
28699 .child(
28700 h_flex()
28701 .w_full()
28702 .justify_between()
28703 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28704 .child(
28705 WithRemSize::new(ui_font_size)
28706 .flex()
28707 .flex_row()
28708 .items_center()
28709 .child(self.render_confirm_button(cx)),
28710 ),
28711 )
28712 }
28713}
28714
28715impl Focusable for BreakpointPromptEditor {
28716 fn focus_handle(&self, cx: &App) -> FocusHandle {
28717 self.prompt.focus_handle(cx)
28718 }
28719}
28720
28721fn all_edits_insertions_or_deletions(
28722 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28723 snapshot: &MultiBufferSnapshot,
28724) -> bool {
28725 let mut all_insertions = true;
28726 let mut all_deletions = true;
28727
28728 for (range, new_text) in edits.iter() {
28729 let range_is_empty = range.to_offset(snapshot).is_empty();
28730 let text_is_empty = new_text.is_empty();
28731
28732 if range_is_empty != text_is_empty {
28733 if range_is_empty {
28734 all_deletions = false;
28735 } else {
28736 all_insertions = false;
28737 }
28738 } else {
28739 return false;
28740 }
28741
28742 if !all_insertions && !all_deletions {
28743 return false;
28744 }
28745 }
28746 all_insertions || all_deletions
28747}
28748
28749struct MissingEditPredictionKeybindingTooltip;
28750
28751impl Render for MissingEditPredictionKeybindingTooltip {
28752 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28753 ui::tooltip_container(cx, |container, cx| {
28754 container
28755 .flex_shrink_0()
28756 .max_w_80()
28757 .min_h(rems_from_px(124.))
28758 .justify_between()
28759 .child(
28760 v_flex()
28761 .flex_1()
28762 .text_ui_sm(cx)
28763 .child(Label::new("Conflict with Accept Keybinding"))
28764 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28765 )
28766 .child(
28767 h_flex()
28768 .pb_1()
28769 .gap_1()
28770 .items_end()
28771 .w_full()
28772 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28773 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28774 }))
28775 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28776 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28777 })),
28778 )
28779 })
28780 }
28781}
28782
28783#[derive(Debug, Clone, Copy, PartialEq)]
28784pub struct LineHighlight {
28785 pub background: Background,
28786 pub border: Option<gpui::Hsla>,
28787 pub include_gutter: bool,
28788 pub type_id: Option<TypeId>,
28789}
28790
28791struct LineManipulationResult {
28792 pub new_text: String,
28793 pub line_count_before: usize,
28794 pub line_count_after: usize,
28795}
28796
28797fn render_diff_hunk_controls(
28798 row: u32,
28799 status: &DiffHunkStatus,
28800 hunk_range: Range<Anchor>,
28801 is_created_file: bool,
28802 line_height: Pixels,
28803 editor: &Entity<Editor>,
28804 _window: &mut Window,
28805 cx: &mut App,
28806) -> AnyElement {
28807 h_flex()
28808 .h(line_height)
28809 .mr_1()
28810 .gap_1()
28811 .px_0p5()
28812 .pb_1()
28813 .border_x_1()
28814 .border_b_1()
28815 .border_color(cx.theme().colors().border_variant)
28816 .rounded_b_lg()
28817 .bg(cx.theme().colors().editor_background)
28818 .gap_1()
28819 .block_mouse_except_scroll()
28820 .shadow_md()
28821 .child(if status.has_secondary_hunk() {
28822 Button::new(("stage", row as u64), "Stage")
28823 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28824 .tooltip({
28825 let focus_handle = editor.focus_handle(cx);
28826 move |_window, cx| {
28827 Tooltip::for_action_in(
28828 "Stage Hunk",
28829 &::git::ToggleStaged,
28830 &focus_handle,
28831 cx,
28832 )
28833 }
28834 })
28835 .on_click({
28836 let editor = editor.clone();
28837 move |_event, _window, cx| {
28838 editor.update(cx, |editor, cx| {
28839 editor.stage_or_unstage_diff_hunks(
28840 true,
28841 vec![hunk_range.start..hunk_range.start],
28842 cx,
28843 );
28844 });
28845 }
28846 })
28847 } else {
28848 Button::new(("unstage", row as u64), "Unstage")
28849 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28850 .tooltip({
28851 let focus_handle = editor.focus_handle(cx);
28852 move |_window, cx| {
28853 Tooltip::for_action_in(
28854 "Unstage Hunk",
28855 &::git::ToggleStaged,
28856 &focus_handle,
28857 cx,
28858 )
28859 }
28860 })
28861 .on_click({
28862 let editor = editor.clone();
28863 move |_event, _window, cx| {
28864 editor.update(cx, |editor, cx| {
28865 editor.stage_or_unstage_diff_hunks(
28866 false,
28867 vec![hunk_range.start..hunk_range.start],
28868 cx,
28869 );
28870 });
28871 }
28872 })
28873 })
28874 .child(
28875 Button::new(("restore", row as u64), "Restore")
28876 .tooltip({
28877 let focus_handle = editor.focus_handle(cx);
28878 move |_window, cx| {
28879 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28880 }
28881 })
28882 .on_click({
28883 let editor = editor.clone();
28884 move |_event, window, cx| {
28885 editor.update(cx, |editor, cx| {
28886 let snapshot = editor.snapshot(window, cx);
28887 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28888 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28889 });
28890 }
28891 })
28892 .disabled(is_created_file),
28893 )
28894 .when(
28895 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28896 |el| {
28897 el.child(
28898 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28899 .shape(IconButtonShape::Square)
28900 .icon_size(IconSize::Small)
28901 // .disabled(!has_multiple_hunks)
28902 .tooltip({
28903 let focus_handle = editor.focus_handle(cx);
28904 move |_window, cx| {
28905 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28906 }
28907 })
28908 .on_click({
28909 let editor = editor.clone();
28910 move |_event, window, cx| {
28911 editor.update(cx, |editor, cx| {
28912 let snapshot = editor.snapshot(window, cx);
28913 let position =
28914 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28915 editor.go_to_hunk_before_or_after_position(
28916 &snapshot,
28917 position,
28918 Direction::Next,
28919 true,
28920 window,
28921 cx,
28922 );
28923 editor.expand_selected_diff_hunks(cx);
28924 });
28925 }
28926 }),
28927 )
28928 .child(
28929 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28930 .shape(IconButtonShape::Square)
28931 .icon_size(IconSize::Small)
28932 // .disabled(!has_multiple_hunks)
28933 .tooltip({
28934 let focus_handle = editor.focus_handle(cx);
28935 move |_window, cx| {
28936 Tooltip::for_action_in(
28937 "Previous Hunk",
28938 &GoToPreviousHunk,
28939 &focus_handle,
28940 cx,
28941 )
28942 }
28943 })
28944 .on_click({
28945 let editor = editor.clone();
28946 move |_event, window, cx| {
28947 editor.update(cx, |editor, cx| {
28948 let snapshot = editor.snapshot(window, cx);
28949 let point =
28950 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28951 editor.go_to_hunk_before_or_after_position(
28952 &snapshot,
28953 point,
28954 Direction::Prev,
28955 true,
28956 window,
28957 cx,
28958 );
28959 editor.expand_selected_diff_hunks(cx);
28960 });
28961 }
28962 }),
28963 )
28964 },
28965 )
28966 .into_any_element()
28967}
28968
28969pub fn multibuffer_context_lines(cx: &App) -> u32 {
28970 EditorSettings::try_get(cx)
28971 .map(|settings| settings.excerpt_context_lines)
28972 .unwrap_or(2)
28973 .min(32)
28974}