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::{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.update_data_on_scroll(window, cx)
2625 })
2626 .ok();
2627 });
2628 }
2629 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2630 }
2631 EditorEvent::Edited { .. } => {
2632 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2633 .map(|vim_mode| vim_mode.0)
2634 .unwrap_or(false);
2635 if !vim_mode {
2636 let display_map = editor.display_snapshot(cx);
2637 let selections = editor.selections.all_adjusted_display(&display_map);
2638 let pop_state = editor
2639 .change_list
2640 .last()
2641 .map(|previous| {
2642 previous.len() == selections.len()
2643 && previous.iter().enumerate().all(|(ix, p)| {
2644 p.to_display_point(&display_map).row()
2645 == selections[ix].head().row()
2646 })
2647 })
2648 .unwrap_or(false);
2649 let new_positions = selections
2650 .into_iter()
2651 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2652 .collect();
2653 editor
2654 .change_list
2655 .push_to_change_list(pop_state, new_positions);
2656 }
2657 }
2658 _ => (),
2659 },
2660 ));
2661
2662 if let Some(dap_store) = editor
2663 .project
2664 .as_ref()
2665 .map(|project| project.read(cx).dap_store())
2666 {
2667 let weak_editor = cx.weak_entity();
2668
2669 editor
2670 ._subscriptions
2671 .push(
2672 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2673 let session_entity = cx.entity();
2674 weak_editor
2675 .update(cx, |editor, cx| {
2676 editor._subscriptions.push(
2677 cx.subscribe(&session_entity, Self::on_debug_session_event),
2678 );
2679 })
2680 .ok();
2681 }),
2682 );
2683
2684 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2685 editor
2686 ._subscriptions
2687 .push(cx.subscribe(&session, Self::on_debug_session_event));
2688 }
2689 }
2690
2691 // skip adding the initial selection to selection history
2692 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2693 editor.end_selection(window, cx);
2694 editor.selection_history.mode = SelectionHistoryMode::Normal;
2695
2696 editor.scroll_manager.show_scrollbars(window, cx);
2697 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2698
2699 if full_mode {
2700 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2701 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2702
2703 if editor.git_blame_inline_enabled {
2704 editor.start_git_blame_inline(false, window, cx);
2705 }
2706
2707 editor.go_to_active_debug_line(window, cx);
2708
2709 editor.minimap =
2710 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2711 editor.colors = Some(LspColorData::new(cx));
2712 editor.use_document_folding_ranges = true;
2713 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2714
2715 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2716 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2717 }
2718 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2719 }
2720
2721 editor
2722 }
2723
2724 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2725 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2726 }
2727
2728 pub fn deploy_mouse_context_menu(
2729 &mut self,
2730 position: gpui::Point<Pixels>,
2731 context_menu: Entity<ContextMenu>,
2732 window: &mut Window,
2733 cx: &mut Context<Self>,
2734 ) {
2735 self.mouse_context_menu = Some(MouseContextMenu::new(
2736 self,
2737 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2738 context_menu,
2739 window,
2740 cx,
2741 ));
2742 }
2743
2744 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2745 self.mouse_context_menu
2746 .as_ref()
2747 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2748 }
2749
2750 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2751 if self
2752 .selections
2753 .pending_anchor()
2754 .is_some_and(|pending_selection| {
2755 let snapshot = self.buffer().read(cx).snapshot(cx);
2756 pending_selection.range().includes(range, &snapshot)
2757 })
2758 {
2759 return true;
2760 }
2761
2762 self.selections
2763 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2764 .into_iter()
2765 .any(|selection| {
2766 // This is needed to cover a corner case, if we just check for an existing
2767 // selection in the fold range, having a cursor at the start of the fold
2768 // marks it as selected. Non-empty selections don't cause this.
2769 let length = selection.end - selection.start;
2770 length > 0
2771 })
2772 }
2773
2774 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2775 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2776 }
2777
2778 fn key_context_internal(
2779 &self,
2780 has_active_edit_prediction: bool,
2781 window: &mut Window,
2782 cx: &mut App,
2783 ) -> KeyContext {
2784 let mut key_context = KeyContext::new_with_defaults();
2785 key_context.add("Editor");
2786 let mode = match self.mode {
2787 EditorMode::SingleLine => "single_line",
2788 EditorMode::AutoHeight { .. } => "auto_height",
2789 EditorMode::Minimap { .. } => "minimap",
2790 EditorMode::Full { .. } => "full",
2791 };
2792
2793 if EditorSettings::jupyter_enabled(cx) {
2794 key_context.add("jupyter");
2795 }
2796
2797 key_context.set("mode", mode);
2798 if self.pending_rename.is_some() {
2799 key_context.add("renaming");
2800 }
2801
2802 if let Some(snippet_stack) = self.snippet_stack.last() {
2803 key_context.add("in_snippet");
2804
2805 if snippet_stack.active_index > 0 {
2806 key_context.add("has_previous_tabstop");
2807 }
2808
2809 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2810 key_context.add("has_next_tabstop");
2811 }
2812 }
2813
2814 match self.context_menu.borrow().as_ref() {
2815 Some(CodeContextMenu::Completions(menu)) => {
2816 if menu.visible() {
2817 key_context.add("menu");
2818 key_context.add("showing_completions");
2819 }
2820 }
2821 Some(CodeContextMenu::CodeActions(menu)) => {
2822 if menu.visible() {
2823 key_context.add("menu");
2824 key_context.add("showing_code_actions")
2825 }
2826 }
2827 None => {}
2828 }
2829
2830 if self.signature_help_state.has_multiple_signatures() {
2831 key_context.add("showing_signature_help");
2832 }
2833
2834 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2835 if !self.focus_handle(cx).contains_focused(window, cx)
2836 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2837 {
2838 for addon in self.addons.values() {
2839 addon.extend_key_context(&mut key_context, cx)
2840 }
2841 }
2842
2843 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2844 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2845 Some(
2846 file.full_path(cx)
2847 .extension()?
2848 .to_string_lossy()
2849 .to_lowercase(),
2850 )
2851 }) {
2852 key_context.set("extension", extension);
2853 }
2854 } else {
2855 key_context.add("multibuffer");
2856 }
2857
2858 if has_active_edit_prediction {
2859 if self.edit_prediction_in_conflict() {
2860 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2861 } else {
2862 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2863 key_context.add("copilot_suggestion");
2864 }
2865 }
2866
2867 if self.selection_mark_mode {
2868 key_context.add("selection_mode");
2869 }
2870
2871 let disjoint = self.selections.disjoint_anchors();
2872 let snapshot = self.snapshot(window, cx);
2873 let snapshot = snapshot.buffer_snapshot();
2874 if self.mode == EditorMode::SingleLine
2875 && let [selection] = disjoint
2876 && selection.start == selection.end
2877 && selection.end.to_offset(snapshot) == snapshot.len()
2878 {
2879 key_context.add("end_of_input");
2880 }
2881
2882 if self.has_any_expanded_diff_hunks(cx) {
2883 key_context.add("diffs_expanded");
2884 }
2885
2886 key_context
2887 }
2888
2889 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2890 self.last_bounds.as_ref()
2891 }
2892
2893 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2894 if self.mouse_cursor_hidden {
2895 self.mouse_cursor_hidden = false;
2896 cx.notify();
2897 }
2898 }
2899
2900 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2901 let hide_mouse_cursor = match origin {
2902 HideMouseCursorOrigin::TypingAction => {
2903 matches!(
2904 self.hide_mouse_mode,
2905 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2906 )
2907 }
2908 HideMouseCursorOrigin::MovementAction => {
2909 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2910 }
2911 };
2912 if self.mouse_cursor_hidden != hide_mouse_cursor {
2913 self.mouse_cursor_hidden = hide_mouse_cursor;
2914 cx.notify();
2915 }
2916 }
2917
2918 pub fn edit_prediction_in_conflict(&self) -> bool {
2919 if !self.show_edit_predictions_in_menu() {
2920 return false;
2921 }
2922
2923 let showing_completions = self
2924 .context_menu
2925 .borrow()
2926 .as_ref()
2927 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2928
2929 showing_completions
2930 || self.edit_prediction_requires_modifier()
2931 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2932 // bindings to insert tab characters.
2933 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2934 }
2935
2936 pub fn accept_edit_prediction_keybind(
2937 &self,
2938 granularity: EditPredictionGranularity,
2939 window: &mut Window,
2940 cx: &mut App,
2941 ) -> AcceptEditPredictionBinding {
2942 let key_context = self.key_context_internal(true, window, cx);
2943 let in_conflict = self.edit_prediction_in_conflict();
2944
2945 let bindings =
2946 match granularity {
2947 EditPredictionGranularity::Word => window
2948 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2949 EditPredictionGranularity::Line => window
2950 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2951 EditPredictionGranularity::Full => {
2952 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2953 }
2954 };
2955
2956 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2957 !in_conflict
2958 || binding
2959 .keystrokes()
2960 .first()
2961 .is_some_and(|keystroke| keystroke.modifiers().modified())
2962 }))
2963 }
2964
2965 pub fn new_file(
2966 workspace: &mut Workspace,
2967 _: &workspace::NewFile,
2968 window: &mut Window,
2969 cx: &mut Context<Workspace>,
2970 ) {
2971 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2972 "Failed to create buffer",
2973 window,
2974 cx,
2975 |e, _, _| match e.error_code() {
2976 ErrorCode::RemoteUpgradeRequired => Some(format!(
2977 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2978 e.error_tag("required").unwrap_or("the latest version")
2979 )),
2980 _ => None,
2981 },
2982 );
2983 }
2984
2985 pub fn new_in_workspace(
2986 workspace: &mut Workspace,
2987 window: &mut Window,
2988 cx: &mut Context<Workspace>,
2989 ) -> Task<Result<Entity<Editor>>> {
2990 let project = workspace.project().clone();
2991 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
2992
2993 cx.spawn_in(window, async move |workspace, cx| {
2994 let buffer = create.await?;
2995 workspace.update_in(cx, |workspace, window, cx| {
2996 let editor =
2997 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2998 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2999 editor
3000 })
3001 })
3002 }
3003
3004 fn new_file_vertical(
3005 workspace: &mut Workspace,
3006 _: &workspace::NewFileSplitVertical,
3007 window: &mut Window,
3008 cx: &mut Context<Workspace>,
3009 ) {
3010 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3011 }
3012
3013 fn new_file_horizontal(
3014 workspace: &mut Workspace,
3015 _: &workspace::NewFileSplitHorizontal,
3016 window: &mut Window,
3017 cx: &mut Context<Workspace>,
3018 ) {
3019 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3020 }
3021
3022 fn new_file_split(
3023 workspace: &mut Workspace,
3024 action: &workspace::NewFileSplit,
3025 window: &mut Window,
3026 cx: &mut Context<Workspace>,
3027 ) {
3028 Self::new_file_in_direction(workspace, action.0, window, cx)
3029 }
3030
3031 fn new_file_in_direction(
3032 workspace: &mut Workspace,
3033 direction: SplitDirection,
3034 window: &mut Window,
3035 cx: &mut Context<Workspace>,
3036 ) {
3037 let project = workspace.project().clone();
3038 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3039
3040 cx.spawn_in(window, async move |workspace, cx| {
3041 let buffer = create.await?;
3042 workspace.update_in(cx, move |workspace, window, cx| {
3043 workspace.split_item(
3044 direction,
3045 Box::new(
3046 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3047 ),
3048 window,
3049 cx,
3050 )
3051 })?;
3052 anyhow::Ok(())
3053 })
3054 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3055 match e.error_code() {
3056 ErrorCode::RemoteUpgradeRequired => Some(format!(
3057 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3058 e.error_tag("required").unwrap_or("the latest version")
3059 )),
3060 _ => None,
3061 }
3062 });
3063 }
3064
3065 pub fn leader_id(&self) -> Option<CollaboratorId> {
3066 self.leader_id
3067 }
3068
3069 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3070 &self.buffer
3071 }
3072
3073 pub fn project(&self) -> Option<&Entity<Project>> {
3074 self.project.as_ref()
3075 }
3076
3077 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3078 self.workspace.as_ref()?.0.upgrade()
3079 }
3080
3081 /// Detaches a task and shows an error notification in the workspace if available,
3082 /// otherwise just logs the error.
3083 pub fn detach_and_notify_err<R, E>(
3084 &self,
3085 task: Task<Result<R, E>>,
3086 window: &mut Window,
3087 cx: &mut App,
3088 ) where
3089 E: std::fmt::Debug + std::fmt::Display + 'static,
3090 R: 'static,
3091 {
3092 if let Some(workspace) = self.workspace() {
3093 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3094 } else {
3095 task.detach_and_log_err(cx);
3096 }
3097 }
3098
3099 /// Returns the workspace serialization ID if this editor should be serialized.
3100 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3101 self.workspace
3102 .as_ref()
3103 .filter(|_| self.should_serialize_buffer())
3104 .and_then(|workspace| workspace.1)
3105 }
3106
3107 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3108 self.buffer().read(cx).title(cx)
3109 }
3110
3111 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3112 let git_blame_gutter_max_author_length = self
3113 .render_git_blame_gutter(cx)
3114 .then(|| {
3115 if let Some(blame) = self.blame.as_ref() {
3116 let max_author_length =
3117 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3118 Some(max_author_length)
3119 } else {
3120 None
3121 }
3122 })
3123 .flatten();
3124
3125 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3126
3127 EditorSnapshot {
3128 mode: self.mode.clone(),
3129 show_gutter: self.show_gutter,
3130 offset_content: self.offset_content,
3131 show_line_numbers: self.show_line_numbers,
3132 number_deleted_lines: self.number_deleted_lines,
3133 show_git_diff_gutter: self.show_git_diff_gutter,
3134 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3135 show_code_actions: self.show_code_actions,
3136 show_runnables: self.show_runnables,
3137 show_breakpoints: self.show_breakpoints,
3138 git_blame_gutter_max_author_length,
3139 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3140 display_snapshot,
3141 placeholder_display_snapshot: self
3142 .placeholder_display_map
3143 .as_ref()
3144 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3145 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3146 is_focused: self.focus_handle.is_focused(window),
3147 current_line_highlight: self
3148 .current_line_highlight
3149 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3150 gutter_hovered: self.gutter_hovered,
3151 }
3152 }
3153
3154 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3155 self.buffer.read(cx).language_at(point, cx)
3156 }
3157
3158 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3159 self.buffer.read(cx).read(cx).file_at(point).cloned()
3160 }
3161
3162 pub fn active_excerpt(
3163 &self,
3164 cx: &App,
3165 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3166 self.buffer
3167 .read(cx)
3168 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3169 }
3170
3171 pub fn mode(&self) -> &EditorMode {
3172 &self.mode
3173 }
3174
3175 pub fn set_mode(&mut self, mode: EditorMode) {
3176 self.mode = mode;
3177 }
3178
3179 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3180 self.collaboration_hub.as_deref()
3181 }
3182
3183 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3184 self.collaboration_hub = Some(hub);
3185 }
3186
3187 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3188 self.in_project_search = in_project_search;
3189 }
3190
3191 pub fn set_custom_context_menu(
3192 &mut self,
3193 f: impl 'static
3194 + Fn(
3195 &mut Self,
3196 DisplayPoint,
3197 &mut Window,
3198 &mut Context<Self>,
3199 ) -> Option<Entity<ui::ContextMenu>>,
3200 ) {
3201 self.custom_context_menu = Some(Box::new(f))
3202 }
3203
3204 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3205 self.completion_provider = provider;
3206 }
3207
3208 #[cfg(any(test, feature = "test-support"))]
3209 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3210 self.completion_provider.clone()
3211 }
3212
3213 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3214 self.semantics_provider.clone()
3215 }
3216
3217 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3218 self.semantics_provider = provider;
3219 }
3220
3221 pub fn set_edit_prediction_provider<T>(
3222 &mut self,
3223 provider: Option<Entity<T>>,
3224 window: &mut Window,
3225 cx: &mut Context<Self>,
3226 ) where
3227 T: EditPredictionDelegate,
3228 {
3229 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3230 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3231 if this.focus_handle.is_focused(window) {
3232 this.update_visible_edit_prediction(window, cx);
3233 }
3234 }),
3235 provider: Arc::new(provider),
3236 });
3237 self.update_edit_prediction_settings(cx);
3238 self.refresh_edit_prediction(false, false, window, cx);
3239 }
3240
3241 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3242 self.placeholder_display_map
3243 .as_ref()
3244 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3245 }
3246
3247 pub fn set_placeholder_text(
3248 &mut self,
3249 placeholder_text: &str,
3250 window: &mut Window,
3251 cx: &mut Context<Self>,
3252 ) {
3253 let multibuffer = cx
3254 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3255
3256 let style = window.text_style();
3257
3258 self.placeholder_display_map = Some(cx.new(|cx| {
3259 DisplayMap::new(
3260 multibuffer,
3261 style.font(),
3262 style.font_size.to_pixels(window.rem_size()),
3263 None,
3264 FILE_HEADER_HEIGHT,
3265 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3266 Default::default(),
3267 DiagnosticSeverity::Off,
3268 cx,
3269 )
3270 }));
3271 cx.notify();
3272 }
3273
3274 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3275 self.cursor_shape = cursor_shape;
3276
3277 // Disrupt blink for immediate user feedback that the cursor shape has changed
3278 self.blink_manager.update(cx, BlinkManager::show_cursor);
3279
3280 cx.notify();
3281 }
3282
3283 pub fn cursor_shape(&self) -> CursorShape {
3284 self.cursor_shape
3285 }
3286
3287 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3288 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3289 }
3290
3291 pub fn set_current_line_highlight(
3292 &mut self,
3293 current_line_highlight: Option<CurrentLineHighlight>,
3294 ) {
3295 self.current_line_highlight = current_line_highlight;
3296 }
3297
3298 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3299 self.collapse_matches = collapse_matches;
3300 }
3301
3302 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3303 if self.collapse_matches {
3304 return range.start..range.start;
3305 }
3306 range.clone()
3307 }
3308
3309 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3310 self.display_map.read(cx).clip_at_line_ends
3311 }
3312
3313 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3314 if self.display_map.read(cx).clip_at_line_ends != clip {
3315 self.display_map
3316 .update(cx, |map, _| map.clip_at_line_ends = clip);
3317 }
3318 }
3319
3320 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3321 self.input_enabled = input_enabled;
3322 }
3323
3324 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3325 self.expects_character_input = expects_character_input;
3326 }
3327
3328 pub fn set_edit_predictions_hidden_for_vim_mode(
3329 &mut self,
3330 hidden: bool,
3331 window: &mut Window,
3332 cx: &mut Context<Self>,
3333 ) {
3334 if hidden != self.edit_predictions_hidden_for_vim_mode {
3335 self.edit_predictions_hidden_for_vim_mode = hidden;
3336 if hidden {
3337 self.update_visible_edit_prediction(window, cx);
3338 } else {
3339 self.refresh_edit_prediction(true, false, window, cx);
3340 }
3341 }
3342 }
3343
3344 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3345 self.menu_edit_predictions_policy = value;
3346 }
3347
3348 pub fn set_autoindent(&mut self, autoindent: bool) {
3349 if autoindent {
3350 self.autoindent_mode = Some(AutoindentMode::EachLine);
3351 } else {
3352 self.autoindent_mode = None;
3353 }
3354 }
3355
3356 pub fn capability(&self, cx: &App) -> Capability {
3357 if self.read_only {
3358 Capability::ReadOnly
3359 } else {
3360 self.buffer.read(cx).capability()
3361 }
3362 }
3363
3364 pub fn read_only(&self, cx: &App) -> bool {
3365 self.read_only || self.buffer.read(cx).read_only()
3366 }
3367
3368 pub fn set_read_only(&mut self, read_only: bool) {
3369 self.read_only = read_only;
3370 }
3371
3372 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3373 self.use_autoclose = autoclose;
3374 }
3375
3376 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3377 self.use_auto_surround = auto_surround;
3378 }
3379
3380 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3381 self.auto_replace_emoji_shortcode = auto_replace;
3382 }
3383
3384 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3385 self.buffer_serialization = should_serialize.then(|| {
3386 BufferSerialization::new(
3387 ProjectSettings::get_global(cx)
3388 .session
3389 .restore_unsaved_buffers,
3390 )
3391 })
3392 }
3393
3394 fn should_serialize_buffer(&self) -> bool {
3395 self.buffer_serialization.is_some()
3396 }
3397
3398 pub fn toggle_edit_predictions(
3399 &mut self,
3400 _: &ToggleEditPrediction,
3401 window: &mut Window,
3402 cx: &mut Context<Self>,
3403 ) {
3404 if self.show_edit_predictions_override.is_some() {
3405 self.set_show_edit_predictions(None, window, cx);
3406 } else {
3407 let show_edit_predictions = !self.edit_predictions_enabled();
3408 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3409 }
3410 }
3411
3412 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3413 self.show_completions_on_input_override = show_completions_on_input;
3414 }
3415
3416 pub fn set_show_edit_predictions(
3417 &mut self,
3418 show_edit_predictions: Option<bool>,
3419 window: &mut Window,
3420 cx: &mut Context<Self>,
3421 ) {
3422 self.show_edit_predictions_override = show_edit_predictions;
3423 self.update_edit_prediction_settings(cx);
3424
3425 if let Some(false) = show_edit_predictions {
3426 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3427 } else {
3428 self.refresh_edit_prediction(false, true, window, cx);
3429 }
3430 }
3431
3432 fn edit_predictions_disabled_in_scope(
3433 &self,
3434 buffer: &Entity<Buffer>,
3435 buffer_position: language::Anchor,
3436 cx: &App,
3437 ) -> bool {
3438 let snapshot = buffer.read(cx).snapshot();
3439 let settings = snapshot.settings_at(buffer_position, cx);
3440
3441 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3442 return false;
3443 };
3444
3445 scope.override_name().is_some_and(|scope_name| {
3446 settings
3447 .edit_predictions_disabled_in
3448 .iter()
3449 .any(|s| s == scope_name)
3450 })
3451 }
3452
3453 pub fn set_use_modal_editing(&mut self, to: bool) {
3454 self.use_modal_editing = to;
3455 }
3456
3457 pub fn use_modal_editing(&self) -> bool {
3458 self.use_modal_editing
3459 }
3460
3461 fn selections_did_change(
3462 &mut self,
3463 local: bool,
3464 old_cursor_position: &Anchor,
3465 effects: SelectionEffects,
3466 window: &mut Window,
3467 cx: &mut Context<Self>,
3468 ) {
3469 window.invalidate_character_coordinates();
3470
3471 // Copy selections to primary selection buffer
3472 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3473 if local {
3474 let selections = self
3475 .selections
3476 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3477 let buffer_handle = self.buffer.read(cx).read(cx);
3478
3479 let mut text = String::new();
3480 for (index, selection) in selections.iter().enumerate() {
3481 let text_for_selection = buffer_handle
3482 .text_for_range(selection.start..selection.end)
3483 .collect::<String>();
3484
3485 text.push_str(&text_for_selection);
3486 if index != selections.len() - 1 {
3487 text.push('\n');
3488 }
3489 }
3490
3491 if !text.is_empty() {
3492 cx.write_to_primary(ClipboardItem::new_string(text));
3493 }
3494 }
3495
3496 let selection_anchors = self.selections.disjoint_anchors_arc();
3497
3498 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3499 self.buffer.update(cx, |buffer, cx| {
3500 buffer.set_active_selections(
3501 &selection_anchors,
3502 self.selections.line_mode(),
3503 self.cursor_shape,
3504 cx,
3505 )
3506 });
3507 }
3508 let display_map = self
3509 .display_map
3510 .update(cx, |display_map, cx| display_map.snapshot(cx));
3511 let buffer = display_map.buffer_snapshot();
3512 if self.selections.count() == 1 {
3513 self.add_selections_state = None;
3514 }
3515 self.select_next_state = None;
3516 self.select_prev_state = None;
3517 self.select_syntax_node_history.try_clear();
3518 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3519 self.snippet_stack.invalidate(&selection_anchors, buffer);
3520 self.take_rename(false, window, cx);
3521
3522 let newest_selection = self.selections.newest_anchor();
3523 let new_cursor_position = newest_selection.head();
3524 let selection_start = newest_selection.start;
3525
3526 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3527 self.push_to_nav_history(
3528 *old_cursor_position,
3529 Some(new_cursor_position.to_point(buffer)),
3530 false,
3531 effects.nav_history == Some(true),
3532 cx,
3533 );
3534 }
3535
3536 if local {
3537 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3538 self.register_buffer(buffer_id, cx);
3539 }
3540
3541 let mut context_menu = self.context_menu.borrow_mut();
3542 let completion_menu = match context_menu.as_ref() {
3543 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3544 Some(CodeContextMenu::CodeActions(_)) => {
3545 *context_menu = None;
3546 None
3547 }
3548 None => None,
3549 };
3550 let completion_position = completion_menu.map(|menu| menu.initial_position);
3551 drop(context_menu);
3552
3553 if effects.completions
3554 && let Some(completion_position) = completion_position
3555 {
3556 let start_offset = selection_start.to_offset(buffer);
3557 let position_matches = start_offset == completion_position.to_offset(buffer);
3558 let continue_showing = if let Some((snap, ..)) =
3559 buffer.point_to_buffer_offset(completion_position)
3560 && !snap.capability.editable()
3561 {
3562 false
3563 } else if position_matches {
3564 if self.snippet_stack.is_empty() {
3565 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3566 == Some(CharKind::Word)
3567 } else {
3568 // Snippet choices can be shown even when the cursor is in whitespace.
3569 // Dismissing the menu with actions like backspace is handled by
3570 // invalidation regions.
3571 true
3572 }
3573 } else {
3574 false
3575 };
3576
3577 if continue_showing {
3578 self.open_or_update_completions_menu(None, None, false, window, cx);
3579 } else {
3580 self.hide_context_menu(window, cx);
3581 }
3582 }
3583
3584 hide_hover(self, cx);
3585
3586 if old_cursor_position.to_display_point(&display_map).row()
3587 != new_cursor_position.to_display_point(&display_map).row()
3588 {
3589 self.available_code_actions.take();
3590 }
3591 self.refresh_code_actions(window, cx);
3592 self.refresh_document_highlights(cx);
3593 refresh_linked_ranges(self, window, cx);
3594
3595 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3596 self.refresh_matching_bracket_highlights(&display_map, cx);
3597 self.refresh_outline_symbols_at_cursor(cx);
3598 self.update_visible_edit_prediction(window, cx);
3599 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3600 self.inline_blame_popover.take();
3601 if self.git_blame_inline_enabled {
3602 self.start_inline_blame_timer(window, cx);
3603 }
3604 }
3605
3606 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3607
3608 if local && !self.suppress_selection_callback {
3609 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3610 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3611 callback(cursor_position, window, cx);
3612 }
3613 }
3614
3615 cx.emit(EditorEvent::SelectionsChanged { local });
3616
3617 let selections = &self.selections.disjoint_anchors_arc();
3618 if selections.len() == 1 {
3619 cx.emit(SearchEvent::ActiveMatchChanged)
3620 }
3621 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3622 let inmemory_selections = selections
3623 .iter()
3624 .map(|s| {
3625 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3626 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3627 })
3628 .collect();
3629 self.update_restoration_data(cx, |data| {
3630 data.selections = inmemory_selections;
3631 });
3632
3633 if WorkspaceSettings::get(None, cx).restore_on_startup
3634 != RestoreOnStartupBehavior::EmptyTab
3635 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3636 {
3637 let snapshot = self.buffer().read(cx).snapshot(cx);
3638 let selections = selections.clone();
3639 let background_executor = cx.background_executor().clone();
3640 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3641 self.serialize_selections = cx.background_spawn(async move {
3642 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3643 let db_selections = selections
3644 .iter()
3645 .map(|selection| {
3646 (
3647 selection.start.to_offset(&snapshot).0,
3648 selection.end.to_offset(&snapshot).0,
3649 )
3650 })
3651 .collect();
3652
3653 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3654 .await
3655 .with_context(|| {
3656 format!(
3657 "persisting editor selections for editor {editor_id}, \
3658 workspace {workspace_id:?}"
3659 )
3660 })
3661 .log_err();
3662 });
3663 }
3664 }
3665
3666 cx.notify();
3667 }
3668
3669 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3670 use text::ToOffset as _;
3671 use text::ToPoint as _;
3672
3673 if self.mode.is_minimap()
3674 || WorkspaceSettings::get(None, cx).restore_on_startup
3675 == RestoreOnStartupBehavior::EmptyTab
3676 {
3677 return;
3678 }
3679
3680 if !self.buffer().read(cx).is_singleton() {
3681 return;
3682 }
3683
3684 let display_snapshot = self
3685 .display_map
3686 .update(cx, |display_map, cx| display_map.snapshot(cx));
3687 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3688 return;
3689 };
3690 let inmemory_folds = display_snapshot
3691 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3692 .map(|fold| {
3693 fold.range.start.text_anchor.to_point(&snapshot)
3694 ..fold.range.end.text_anchor.to_point(&snapshot)
3695 })
3696 .collect();
3697 self.update_restoration_data(cx, |data| {
3698 data.folds = inmemory_folds;
3699 });
3700
3701 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3702 return;
3703 };
3704
3705 // Get file path for path-based fold storage (survives tab close)
3706 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3707 project::File::from_dyn(buffer.read(cx).file())
3708 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3709 }) else {
3710 return;
3711 };
3712
3713 let background_executor = cx.background_executor().clone();
3714 const FINGERPRINT_LEN: usize = 32;
3715 let db_folds = display_snapshot
3716 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3717 .map(|fold| {
3718 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3719 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3720
3721 // Extract fingerprints - content at fold boundaries for validation on restore
3722 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3723 // content that might change independently.
3724 // start_fp: first min(32, fold_len) bytes of fold content
3725 // end_fp: last min(32, fold_len) bytes of fold content
3726 // Clip to character boundaries to handle multibyte UTF-8 characters.
3727 let fold_len = end - start;
3728 let start_fp_end = snapshot
3729 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3730 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3731 let end_fp_start = snapshot
3732 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3733 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3734
3735 (start, end, start_fp, end_fp)
3736 })
3737 .collect::<Vec<_>>();
3738 self.serialize_folds = cx.background_spawn(async move {
3739 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3740 if db_folds.is_empty() {
3741 // No folds - delete any persisted folds for this file
3742 DB.delete_file_folds(workspace_id, file_path)
3743 .await
3744 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3745 .log_err();
3746 } else {
3747 DB.save_file_folds(workspace_id, file_path, db_folds)
3748 .await
3749 .with_context(|| {
3750 format!("persisting file folds for workspace {workspace_id:?}")
3751 })
3752 .log_err();
3753 }
3754 });
3755 }
3756
3757 pub fn sync_selections(
3758 &mut self,
3759 other: Entity<Editor>,
3760 cx: &mut Context<Self>,
3761 ) -> gpui::Subscription {
3762 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3763 if !other_selections.is_empty() {
3764 self.selections
3765 .change_with(&self.display_snapshot(cx), |selections| {
3766 selections.select_anchors(other_selections);
3767 });
3768 }
3769
3770 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3771 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3772 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3773 if other_selections.is_empty() {
3774 return;
3775 }
3776 let snapshot = this.display_snapshot(cx);
3777 this.selections.change_with(&snapshot, |selections| {
3778 selections.select_anchors(other_selections);
3779 });
3780 }
3781 });
3782
3783 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3784 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3785 let these_selections = this.selections.disjoint_anchors().to_vec();
3786 if these_selections.is_empty() {
3787 return;
3788 }
3789 other.update(cx, |other_editor, cx| {
3790 let snapshot = other_editor.display_snapshot(cx);
3791 other_editor
3792 .selections
3793 .change_with(&snapshot, |selections| {
3794 selections.select_anchors(these_selections);
3795 })
3796 });
3797 }
3798 });
3799
3800 Subscription::join(other_subscription, this_subscription)
3801 }
3802
3803 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3804 if self.buffer().read(cx).is_singleton() {
3805 return;
3806 }
3807 let snapshot = self.buffer.read(cx).snapshot(cx);
3808 let buffer_ids: HashSet<BufferId> = self
3809 .selections
3810 .disjoint_anchor_ranges()
3811 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3812 .collect();
3813 for buffer_id in buffer_ids {
3814 self.unfold_buffer(buffer_id, cx);
3815 }
3816 }
3817
3818 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3819 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3820 /// effects of selection change occur at the end of the transaction.
3821 pub fn change_selections<R>(
3822 &mut self,
3823 effects: SelectionEffects,
3824 window: &mut Window,
3825 cx: &mut Context<Self>,
3826 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3827 ) -> R {
3828 let snapshot = self.display_snapshot(cx);
3829 if let Some(state) = &mut self.deferred_selection_effects_state {
3830 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3831 state.effects.completions = effects.completions;
3832 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3833 let (changed, result) = self.selections.change_with(&snapshot, change);
3834 state.changed |= changed;
3835 return result;
3836 }
3837 let mut state = DeferredSelectionEffectsState {
3838 changed: false,
3839 effects,
3840 old_cursor_position: self.selections.newest_anchor().head(),
3841 history_entry: SelectionHistoryEntry {
3842 selections: self.selections.disjoint_anchors_arc(),
3843 select_next_state: self.select_next_state.clone(),
3844 select_prev_state: self.select_prev_state.clone(),
3845 add_selections_state: self.add_selections_state.clone(),
3846 },
3847 };
3848 let (changed, result) = self.selections.change_with(&snapshot, change);
3849 state.changed = state.changed || changed;
3850 if self.defer_selection_effects {
3851 self.deferred_selection_effects_state = Some(state);
3852 } else {
3853 self.apply_selection_effects(state, window, cx);
3854 }
3855 result
3856 }
3857
3858 /// Defers the effects of selection change, so that the effects of multiple calls to
3859 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3860 /// to selection history and the state of popovers based on selection position aren't
3861 /// erroneously updated.
3862 pub fn with_selection_effects_deferred<R>(
3863 &mut self,
3864 window: &mut Window,
3865 cx: &mut Context<Self>,
3866 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3867 ) -> R {
3868 let already_deferred = self.defer_selection_effects;
3869 self.defer_selection_effects = true;
3870 let result = update(self, window, cx);
3871 if !already_deferred {
3872 self.defer_selection_effects = false;
3873 if let Some(state) = self.deferred_selection_effects_state.take() {
3874 self.apply_selection_effects(state, window, cx);
3875 }
3876 }
3877 result
3878 }
3879
3880 fn apply_selection_effects(
3881 &mut self,
3882 state: DeferredSelectionEffectsState,
3883 window: &mut Window,
3884 cx: &mut Context<Self>,
3885 ) {
3886 if state.changed {
3887 self.selection_history.push(state.history_entry);
3888
3889 if let Some(autoscroll) = state.effects.scroll {
3890 self.request_autoscroll(autoscroll, cx);
3891 }
3892
3893 let old_cursor_position = &state.old_cursor_position;
3894
3895 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3896
3897 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3898 self.show_signature_help_auto(window, cx);
3899 }
3900 }
3901 }
3902
3903 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3904 where
3905 I: IntoIterator<Item = (Range<S>, T)>,
3906 S: ToOffset,
3907 T: Into<Arc<str>>,
3908 {
3909 if self.read_only(cx) {
3910 return;
3911 }
3912
3913 self.buffer
3914 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3915 }
3916
3917 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3918 where
3919 I: IntoIterator<Item = (Range<S>, T)>,
3920 S: ToOffset,
3921 T: Into<Arc<str>>,
3922 {
3923 if self.read_only(cx) {
3924 return;
3925 }
3926
3927 self.buffer.update(cx, |buffer, cx| {
3928 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3929 });
3930 }
3931
3932 pub fn edit_with_block_indent<I, S, T>(
3933 &mut self,
3934 edits: I,
3935 original_indent_columns: Vec<Option<u32>>,
3936 cx: &mut Context<Self>,
3937 ) where
3938 I: IntoIterator<Item = (Range<S>, T)>,
3939 S: ToOffset,
3940 T: Into<Arc<str>>,
3941 {
3942 if self.read_only(cx) {
3943 return;
3944 }
3945
3946 self.buffer.update(cx, |buffer, cx| {
3947 buffer.edit(
3948 edits,
3949 Some(AutoindentMode::Block {
3950 original_indent_columns,
3951 }),
3952 cx,
3953 )
3954 });
3955 }
3956
3957 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3958 self.hide_context_menu(window, cx);
3959
3960 match phase {
3961 SelectPhase::Begin {
3962 position,
3963 add,
3964 click_count,
3965 } => self.begin_selection(position, add, click_count, window, cx),
3966 SelectPhase::BeginColumnar {
3967 position,
3968 goal_column,
3969 reset,
3970 mode,
3971 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3972 SelectPhase::Extend {
3973 position,
3974 click_count,
3975 } => self.extend_selection(position, click_count, window, cx),
3976 SelectPhase::Update {
3977 position,
3978 goal_column,
3979 scroll_delta,
3980 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3981 SelectPhase::End => self.end_selection(window, cx),
3982 }
3983 }
3984
3985 fn extend_selection(
3986 &mut self,
3987 position: DisplayPoint,
3988 click_count: usize,
3989 window: &mut Window,
3990 cx: &mut Context<Self>,
3991 ) {
3992 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3993 let tail = self
3994 .selections
3995 .newest::<MultiBufferOffset>(&display_map)
3996 .tail();
3997 let click_count = click_count.max(match self.selections.select_mode() {
3998 SelectMode::Character => 1,
3999 SelectMode::Word(_) => 2,
4000 SelectMode::Line(_) => 3,
4001 SelectMode::All => 4,
4002 });
4003 self.begin_selection(position, false, click_count, window, cx);
4004
4005 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4006
4007 let current_selection = match self.selections.select_mode() {
4008 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4009 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4010 };
4011
4012 let mut pending_selection = self
4013 .selections
4014 .pending_anchor()
4015 .cloned()
4016 .expect("extend_selection not called with pending selection");
4017
4018 if pending_selection
4019 .start
4020 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4021 == Ordering::Greater
4022 {
4023 pending_selection.start = current_selection.start;
4024 }
4025 if pending_selection
4026 .end
4027 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4028 == Ordering::Less
4029 {
4030 pending_selection.end = current_selection.end;
4031 pending_selection.reversed = true;
4032 }
4033
4034 let mut pending_mode = self.selections.pending_mode().unwrap();
4035 match &mut pending_mode {
4036 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4037 _ => {}
4038 }
4039
4040 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4041 SelectionEffects::scroll(Autoscroll::fit())
4042 } else {
4043 SelectionEffects::no_scroll()
4044 };
4045
4046 self.change_selections(effects, window, cx, |s| {
4047 s.set_pending(pending_selection.clone(), pending_mode);
4048 s.set_is_extending(true);
4049 });
4050 }
4051
4052 fn begin_selection(
4053 &mut self,
4054 position: DisplayPoint,
4055 add: bool,
4056 click_count: usize,
4057 window: &mut Window,
4058 cx: &mut Context<Self>,
4059 ) {
4060 if !self.focus_handle.is_focused(window) {
4061 self.last_focused_descendant = None;
4062 window.focus(&self.focus_handle, cx);
4063 }
4064
4065 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4066 let buffer = display_map.buffer_snapshot();
4067 let position = display_map.clip_point(position, Bias::Left);
4068
4069 let start;
4070 let end;
4071 let mode;
4072 let mut auto_scroll;
4073 match click_count {
4074 1 => {
4075 start = buffer.anchor_before(position.to_point(&display_map));
4076 end = start;
4077 mode = SelectMode::Character;
4078 auto_scroll = true;
4079 }
4080 2 => {
4081 let position = display_map
4082 .clip_point(position, Bias::Left)
4083 .to_offset(&display_map, Bias::Left);
4084 let (range, _) = buffer.surrounding_word(position, None);
4085 start = buffer.anchor_before(range.start);
4086 end = buffer.anchor_before(range.end);
4087 mode = SelectMode::Word(start..end);
4088 auto_scroll = true;
4089 }
4090 3 => {
4091 let position = display_map
4092 .clip_point(position, Bias::Left)
4093 .to_point(&display_map);
4094 let line_start = display_map.prev_line_boundary(position).0;
4095 let next_line_start = buffer.clip_point(
4096 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4097 Bias::Left,
4098 );
4099 start = buffer.anchor_before(line_start);
4100 end = buffer.anchor_before(next_line_start);
4101 mode = SelectMode::Line(start..end);
4102 auto_scroll = true;
4103 }
4104 _ => {
4105 start = buffer.anchor_before(MultiBufferOffset(0));
4106 end = buffer.anchor_before(buffer.len());
4107 mode = SelectMode::All;
4108 auto_scroll = false;
4109 }
4110 }
4111 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4112
4113 let point_to_delete: Option<usize> = {
4114 let selected_points: Vec<Selection<Point>> =
4115 self.selections.disjoint_in_range(start..end, &display_map);
4116
4117 if !add || click_count > 1 {
4118 None
4119 } else if !selected_points.is_empty() {
4120 Some(selected_points[0].id)
4121 } else {
4122 let clicked_point_already_selected =
4123 self.selections.disjoint_anchors().iter().find(|selection| {
4124 selection.start.to_point(buffer) == start.to_point(buffer)
4125 || selection.end.to_point(buffer) == end.to_point(buffer)
4126 });
4127
4128 clicked_point_already_selected.map(|selection| selection.id)
4129 }
4130 };
4131
4132 let selections_count = self.selections.count();
4133 let effects = if auto_scroll {
4134 SelectionEffects::default()
4135 } else {
4136 SelectionEffects::no_scroll()
4137 };
4138
4139 self.change_selections(effects, window, cx, |s| {
4140 if let Some(point_to_delete) = point_to_delete {
4141 s.delete(point_to_delete);
4142
4143 if selections_count == 1 {
4144 s.set_pending_anchor_range(start..end, mode);
4145 }
4146 } else {
4147 if !add {
4148 s.clear_disjoint();
4149 }
4150
4151 s.set_pending_anchor_range(start..end, mode);
4152 }
4153 });
4154 }
4155
4156 fn begin_columnar_selection(
4157 &mut self,
4158 position: DisplayPoint,
4159 goal_column: u32,
4160 reset: bool,
4161 mode: ColumnarMode,
4162 window: &mut Window,
4163 cx: &mut Context<Self>,
4164 ) {
4165 if !self.focus_handle.is_focused(window) {
4166 self.last_focused_descendant = None;
4167 window.focus(&self.focus_handle, cx);
4168 }
4169
4170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4171
4172 if reset {
4173 let pointer_position = display_map
4174 .buffer_snapshot()
4175 .anchor_before(position.to_point(&display_map));
4176
4177 self.change_selections(
4178 SelectionEffects::scroll(Autoscroll::newest()),
4179 window,
4180 cx,
4181 |s| {
4182 s.clear_disjoint();
4183 s.set_pending_anchor_range(
4184 pointer_position..pointer_position,
4185 SelectMode::Character,
4186 );
4187 },
4188 );
4189 };
4190
4191 let tail = self.selections.newest::<Point>(&display_map).tail();
4192 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4193 self.columnar_selection_state = match mode {
4194 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4195 selection_tail: selection_anchor,
4196 display_point: if reset {
4197 if position.column() != goal_column {
4198 Some(DisplayPoint::new(position.row(), goal_column))
4199 } else {
4200 None
4201 }
4202 } else {
4203 None
4204 },
4205 }),
4206 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4207 selection_tail: selection_anchor,
4208 }),
4209 };
4210
4211 if !reset {
4212 self.select_columns(position, goal_column, &display_map, window, cx);
4213 }
4214 }
4215
4216 fn update_selection(
4217 &mut self,
4218 position: DisplayPoint,
4219 goal_column: u32,
4220 scroll_delta: gpui::Point<f32>,
4221 window: &mut Window,
4222 cx: &mut Context<Self>,
4223 ) {
4224 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4225
4226 if self.columnar_selection_state.is_some() {
4227 self.select_columns(position, goal_column, &display_map, window, cx);
4228 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4229 let buffer = display_map.buffer_snapshot();
4230 let head;
4231 let tail;
4232 let mode = self.selections.pending_mode().unwrap();
4233 match &mode {
4234 SelectMode::Character => {
4235 head = position.to_point(&display_map);
4236 tail = pending.tail().to_point(buffer);
4237 }
4238 SelectMode::Word(original_range) => {
4239 let offset = display_map
4240 .clip_point(position, Bias::Left)
4241 .to_offset(&display_map, Bias::Left);
4242 let original_range = original_range.to_offset(buffer);
4243
4244 let head_offset = if buffer.is_inside_word(offset, None)
4245 || original_range.contains(&offset)
4246 {
4247 let (word_range, _) = buffer.surrounding_word(offset, None);
4248 if word_range.start < original_range.start {
4249 word_range.start
4250 } else {
4251 word_range.end
4252 }
4253 } else {
4254 offset
4255 };
4256
4257 head = head_offset.to_point(buffer);
4258 if head_offset <= original_range.start {
4259 tail = original_range.end.to_point(buffer);
4260 } else {
4261 tail = original_range.start.to_point(buffer);
4262 }
4263 }
4264 SelectMode::Line(original_range) => {
4265 let original_range = original_range.to_point(display_map.buffer_snapshot());
4266
4267 let position = display_map
4268 .clip_point(position, Bias::Left)
4269 .to_point(&display_map);
4270 let line_start = display_map.prev_line_boundary(position).0;
4271 let next_line_start = buffer.clip_point(
4272 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4273 Bias::Left,
4274 );
4275
4276 if line_start < original_range.start {
4277 head = line_start
4278 } else {
4279 head = next_line_start
4280 }
4281
4282 if head <= original_range.start {
4283 tail = original_range.end;
4284 } else {
4285 tail = original_range.start;
4286 }
4287 }
4288 SelectMode::All => {
4289 return;
4290 }
4291 };
4292
4293 if head < tail {
4294 pending.start = buffer.anchor_before(head);
4295 pending.end = buffer.anchor_before(tail);
4296 pending.reversed = true;
4297 } else {
4298 pending.start = buffer.anchor_before(tail);
4299 pending.end = buffer.anchor_before(head);
4300 pending.reversed = false;
4301 }
4302
4303 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4304 s.set_pending(pending.clone(), mode);
4305 });
4306 } else {
4307 log::error!("update_selection dispatched with no pending selection");
4308 return;
4309 }
4310
4311 self.apply_scroll_delta(scroll_delta, window, cx);
4312 cx.notify();
4313 }
4314
4315 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4316 self.columnar_selection_state.take();
4317 if let Some(pending_mode) = self.selections.pending_mode() {
4318 let selections = self
4319 .selections
4320 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4321 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4322 s.select(selections);
4323 s.clear_pending();
4324 if s.is_extending() {
4325 s.set_is_extending(false);
4326 } else {
4327 s.set_select_mode(pending_mode);
4328 }
4329 });
4330 }
4331 }
4332
4333 fn select_columns(
4334 &mut self,
4335 head: DisplayPoint,
4336 goal_column: u32,
4337 display_map: &DisplaySnapshot,
4338 window: &mut Window,
4339 cx: &mut Context<Self>,
4340 ) {
4341 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4342 return;
4343 };
4344
4345 let tail = match columnar_state {
4346 ColumnarSelectionState::FromMouse {
4347 selection_tail,
4348 display_point,
4349 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4350 ColumnarSelectionState::FromSelection { selection_tail } => {
4351 selection_tail.to_display_point(display_map)
4352 }
4353 };
4354
4355 let start_row = cmp::min(tail.row(), head.row());
4356 let end_row = cmp::max(tail.row(), head.row());
4357 let start_column = cmp::min(tail.column(), goal_column);
4358 let end_column = cmp::max(tail.column(), goal_column);
4359 let reversed = start_column < tail.column();
4360
4361 let selection_ranges = (start_row.0..=end_row.0)
4362 .map(DisplayRow)
4363 .filter_map(|row| {
4364 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4365 || start_column <= display_map.line_len(row))
4366 && !display_map.is_block_line(row)
4367 {
4368 let start = display_map
4369 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4370 .to_point(display_map);
4371 let end = display_map
4372 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4373 .to_point(display_map);
4374 if reversed {
4375 Some(end..start)
4376 } else {
4377 Some(start..end)
4378 }
4379 } else {
4380 None
4381 }
4382 })
4383 .collect::<Vec<_>>();
4384 if selection_ranges.is_empty() {
4385 return;
4386 }
4387
4388 let ranges = match columnar_state {
4389 ColumnarSelectionState::FromMouse { .. } => {
4390 let mut non_empty_ranges = selection_ranges
4391 .iter()
4392 .filter(|selection_range| selection_range.start != selection_range.end)
4393 .peekable();
4394 if non_empty_ranges.peek().is_some() {
4395 non_empty_ranges.cloned().collect()
4396 } else {
4397 selection_ranges
4398 }
4399 }
4400 _ => selection_ranges,
4401 };
4402
4403 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4404 s.select_ranges(ranges);
4405 });
4406 cx.notify();
4407 }
4408
4409 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4410 self.selections
4411 .all_adjusted(snapshot)
4412 .iter()
4413 .any(|selection| !selection.is_empty())
4414 }
4415
4416 pub fn has_pending_nonempty_selection(&self) -> bool {
4417 let pending_nonempty_selection = match self.selections.pending_anchor() {
4418 Some(Selection { start, end, .. }) => start != end,
4419 None => false,
4420 };
4421
4422 pending_nonempty_selection
4423 || (self.columnar_selection_state.is_some()
4424 && self.selections.disjoint_anchors().len() > 1)
4425 }
4426
4427 pub fn has_pending_selection(&self) -> bool {
4428 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4429 }
4430
4431 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4432 self.selection_mark_mode = false;
4433 self.selection_drag_state = SelectionDragState::None;
4434
4435 if self.dismiss_menus_and_popups(true, window, cx) {
4436 cx.notify();
4437 return;
4438 }
4439 if self.clear_expanded_diff_hunks(cx) {
4440 cx.notify();
4441 return;
4442 }
4443 if self.show_git_blame_gutter {
4444 self.show_git_blame_gutter = false;
4445 cx.notify();
4446 return;
4447 }
4448
4449 if self.mode.is_full()
4450 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4451 {
4452 cx.notify();
4453 return;
4454 }
4455
4456 cx.propagate();
4457 }
4458
4459 pub fn dismiss_menus_and_popups(
4460 &mut self,
4461 is_user_requested: bool,
4462 window: &mut Window,
4463 cx: &mut Context<Self>,
4464 ) -> bool {
4465 let mut dismissed = false;
4466
4467 dismissed |= self.take_rename(false, window, cx).is_some();
4468 dismissed |= self.hide_blame_popover(true, cx);
4469 dismissed |= hide_hover(self, cx);
4470 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4471 dismissed |= self.hide_context_menu(window, cx).is_some();
4472 dismissed |= self.mouse_context_menu.take().is_some();
4473 dismissed |= is_user_requested
4474 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4475 dismissed |= self.snippet_stack.pop().is_some();
4476 if self.diff_review_drag_state.is_some() {
4477 self.cancel_diff_review_drag(cx);
4478 dismissed = true;
4479 }
4480 if !self.diff_review_overlays.is_empty() {
4481 self.dismiss_all_diff_review_overlays(cx);
4482 dismissed = true;
4483 }
4484
4485 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4486 self.dismiss_diagnostics(cx);
4487 dismissed = true;
4488 }
4489
4490 dismissed
4491 }
4492
4493 fn linked_editing_ranges_for(
4494 &self,
4495 selection: Range<text::Anchor>,
4496 cx: &App,
4497 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4498 if self.linked_edit_ranges.is_empty() {
4499 return None;
4500 }
4501 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4502 selection.end.buffer_id.and_then(|end_buffer_id| {
4503 if selection.start.buffer_id != Some(end_buffer_id) {
4504 return None;
4505 }
4506 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4507 let snapshot = buffer.read(cx).snapshot();
4508 self.linked_edit_ranges
4509 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4510 .map(|ranges| (ranges, snapshot, buffer))
4511 })?;
4512 use text::ToOffset as TO;
4513 // find offset from the start of current range to current cursor position
4514 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4515
4516 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4517 let start_difference = start_offset - start_byte_offset;
4518 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4519 let end_difference = end_offset - start_byte_offset;
4520
4521 // Current range has associated linked ranges.
4522 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4523 for range in linked_ranges.iter() {
4524 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4525 let end_offset = start_offset + end_difference;
4526 let start_offset = start_offset + start_difference;
4527 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4528 continue;
4529 }
4530 if self.selections.disjoint_anchor_ranges().any(|s| {
4531 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4532 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4533 {
4534 return false;
4535 }
4536 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4537 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4538 }) {
4539 continue;
4540 }
4541 let start = buffer_snapshot.anchor_after(start_offset);
4542 let end = buffer_snapshot.anchor_after(end_offset);
4543 linked_edits
4544 .entry(buffer.clone())
4545 .or_default()
4546 .push(start..end);
4547 }
4548 Some(linked_edits)
4549 }
4550
4551 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4552 let text: Arc<str> = text.into();
4553
4554 if self.read_only(cx) {
4555 return;
4556 }
4557
4558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4559
4560 self.unfold_buffers_with_selections(cx);
4561
4562 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4563 let mut bracket_inserted = false;
4564 let mut edits = Vec::new();
4565 let mut linked_edits = LinkedEdits::new();
4566 let mut new_selections = Vec::with_capacity(selections.len());
4567 let mut new_autoclose_regions = Vec::new();
4568 let snapshot = self.buffer.read(cx).read(cx);
4569 let mut clear_linked_edit_ranges = false;
4570 let mut all_selections_read_only = true;
4571 let mut has_adjacent_edits = false;
4572 let mut in_adjacent_group = false;
4573
4574 let mut regions = self
4575 .selections_with_autoclose_regions(selections, &snapshot)
4576 .peekable();
4577
4578 while let Some((selection, autoclose_region)) = regions.next() {
4579 if snapshot
4580 .point_to_buffer_point(selection.head())
4581 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4582 {
4583 continue;
4584 }
4585 if snapshot
4586 .point_to_buffer_point(selection.tail())
4587 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4588 {
4589 // note, ideally we'd clip the tail to the closest writeable region towards the head
4590 continue;
4591 }
4592 all_selections_read_only = false;
4593
4594 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4595 // Determine if the inserted text matches the opening or closing
4596 // bracket of any of this language's bracket pairs.
4597 let mut bracket_pair = None;
4598 let mut is_bracket_pair_start = false;
4599 let mut is_bracket_pair_end = false;
4600 if !text.is_empty() {
4601 let mut bracket_pair_matching_end = None;
4602 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4603 // and they are removing the character that triggered IME popup.
4604 for (pair, enabled) in scope.brackets() {
4605 if !pair.close && !pair.surround {
4606 continue;
4607 }
4608
4609 if enabled && pair.start.ends_with(text.as_ref()) {
4610 let prefix_len = pair.start.len() - text.len();
4611 let preceding_text_matches_prefix = prefix_len == 0
4612 || (selection.start.column >= (prefix_len as u32)
4613 && snapshot.contains_str_at(
4614 Point::new(
4615 selection.start.row,
4616 selection.start.column - (prefix_len as u32),
4617 ),
4618 &pair.start[..prefix_len],
4619 ));
4620 if preceding_text_matches_prefix {
4621 bracket_pair = Some(pair.clone());
4622 is_bracket_pair_start = true;
4623 break;
4624 }
4625 }
4626 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4627 {
4628 // take first bracket pair matching end, but don't break in case a later bracket
4629 // pair matches start
4630 bracket_pair_matching_end = Some(pair.clone());
4631 }
4632 }
4633 if let Some(end) = bracket_pair_matching_end
4634 && bracket_pair.is_none()
4635 {
4636 bracket_pair = Some(end);
4637 is_bracket_pair_end = true;
4638 }
4639 }
4640
4641 if let Some(bracket_pair) = bracket_pair {
4642 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4643 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4644 let auto_surround =
4645 self.use_auto_surround && snapshot_settings.use_auto_surround;
4646 if selection.is_empty() {
4647 if is_bracket_pair_start {
4648 // If the inserted text is a suffix of an opening bracket and the
4649 // selection is preceded by the rest of the opening bracket, then
4650 // insert the closing bracket.
4651 let following_text_allows_autoclose = snapshot
4652 .chars_at(selection.start)
4653 .next()
4654 .is_none_or(|c| scope.should_autoclose_before(c));
4655
4656 let preceding_text_allows_autoclose = selection.start.column == 0
4657 || snapshot
4658 .reversed_chars_at(selection.start)
4659 .next()
4660 .is_none_or(|c| {
4661 bracket_pair.start != bracket_pair.end
4662 || !snapshot
4663 .char_classifier_at(selection.start)
4664 .is_word(c)
4665 });
4666
4667 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4668 && bracket_pair.start.len() == 1
4669 {
4670 let target = bracket_pair.start.chars().next().unwrap();
4671 let mut byte_offset = 0u32;
4672 let current_line_count = snapshot
4673 .reversed_chars_at(selection.start)
4674 .take_while(|&c| c != '\n')
4675 .filter(|c| {
4676 byte_offset += c.len_utf8() as u32;
4677 if *c != target {
4678 return false;
4679 }
4680
4681 let point = Point::new(
4682 selection.start.row,
4683 selection.start.column.saturating_sub(byte_offset),
4684 );
4685
4686 let is_enabled = snapshot
4687 .language_scope_at(point)
4688 .and_then(|scope| {
4689 scope
4690 .brackets()
4691 .find(|(pair, _)| {
4692 pair.start == bracket_pair.start
4693 })
4694 .map(|(_, enabled)| enabled)
4695 })
4696 .unwrap_or(true);
4697
4698 let is_delimiter = snapshot
4699 .language_scope_at(Point::new(
4700 point.row,
4701 point.column + 1,
4702 ))
4703 .and_then(|scope| {
4704 scope
4705 .brackets()
4706 .find(|(pair, _)| {
4707 pair.start == bracket_pair.start
4708 })
4709 .map(|(_, enabled)| !enabled)
4710 })
4711 .unwrap_or(false);
4712
4713 is_enabled && !is_delimiter
4714 })
4715 .count();
4716 current_line_count % 2 == 1
4717 } else {
4718 false
4719 };
4720
4721 if autoclose
4722 && bracket_pair.close
4723 && following_text_allows_autoclose
4724 && preceding_text_allows_autoclose
4725 && !is_closing_quote
4726 {
4727 let anchor = snapshot.anchor_before(selection.end);
4728 new_selections.push((selection.map(|_| anchor), text.len()));
4729 new_autoclose_regions.push((
4730 anchor,
4731 text.len(),
4732 selection.id,
4733 bracket_pair.clone(),
4734 ));
4735 edits.push((
4736 selection.range(),
4737 format!("{}{}", text, bracket_pair.end).into(),
4738 ));
4739 bracket_inserted = true;
4740 continue;
4741 }
4742 }
4743
4744 if let Some(region) = autoclose_region {
4745 // If the selection is followed by an auto-inserted closing bracket,
4746 // then don't insert that closing bracket again; just move the selection
4747 // past the closing bracket.
4748 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4749 && text.as_ref() == region.pair.end.as_str()
4750 && snapshot.contains_str_at(region.range.end, text.as_ref());
4751 if should_skip {
4752 let anchor = snapshot.anchor_after(selection.end);
4753 new_selections
4754 .push((selection.map(|_| anchor), region.pair.end.len()));
4755 continue;
4756 }
4757 }
4758
4759 let always_treat_brackets_as_autoclosed = snapshot
4760 .language_settings_at(selection.start, cx)
4761 .always_treat_brackets_as_autoclosed;
4762 if always_treat_brackets_as_autoclosed
4763 && is_bracket_pair_end
4764 && snapshot.contains_str_at(selection.end, text.as_ref())
4765 {
4766 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4767 // and the inserted text is a closing bracket and the selection is followed
4768 // by the closing bracket then move the selection past the closing bracket.
4769 let anchor = snapshot.anchor_after(selection.end);
4770 new_selections.push((selection.map(|_| anchor), text.len()));
4771 continue;
4772 }
4773 }
4774 // If an opening bracket is 1 character long and is typed while
4775 // text is selected, then surround that text with the bracket pair.
4776 else if auto_surround
4777 && bracket_pair.surround
4778 && is_bracket_pair_start
4779 && bracket_pair.start.chars().count() == 1
4780 {
4781 edits.push((selection.start..selection.start, text.clone()));
4782 edits.push((
4783 selection.end..selection.end,
4784 bracket_pair.end.as_str().into(),
4785 ));
4786 bracket_inserted = true;
4787 new_selections.push((
4788 Selection {
4789 id: selection.id,
4790 start: snapshot.anchor_after(selection.start),
4791 end: snapshot.anchor_before(selection.end),
4792 reversed: selection.reversed,
4793 goal: selection.goal,
4794 },
4795 0,
4796 ));
4797 continue;
4798 }
4799 }
4800 }
4801
4802 if self.auto_replace_emoji_shortcode
4803 && selection.is_empty()
4804 && text.as_ref().ends_with(':')
4805 && let Some(possible_emoji_short_code) =
4806 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4807 && !possible_emoji_short_code.is_empty()
4808 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4809 {
4810 let emoji_shortcode_start = Point::new(
4811 selection.start.row,
4812 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4813 );
4814
4815 // Remove shortcode from buffer
4816 edits.push((
4817 emoji_shortcode_start..selection.start,
4818 "".to_string().into(),
4819 ));
4820 new_selections.push((
4821 Selection {
4822 id: selection.id,
4823 start: snapshot.anchor_after(emoji_shortcode_start),
4824 end: snapshot.anchor_before(selection.start),
4825 reversed: selection.reversed,
4826 goal: selection.goal,
4827 },
4828 0,
4829 ));
4830
4831 // Insert emoji
4832 let selection_start_anchor = snapshot.anchor_after(selection.start);
4833 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4834 edits.push((selection.start..selection.end, emoji.to_string().into()));
4835
4836 continue;
4837 }
4838
4839 let next_is_adjacent = regions
4840 .peek()
4841 .is_some_and(|(next, _)| selection.end == next.start);
4842
4843 // If not handling any auto-close operation, then just replace the selected
4844 // text with the given input and move the selection to the end of the
4845 // newly inserted text.
4846 let anchor = if in_adjacent_group || next_is_adjacent {
4847 // After edits the right bias would shift those anchor to the next visible fragment
4848 // but we want to resolve to the previous one
4849 snapshot.anchor_before(selection.end)
4850 } else {
4851 snapshot.anchor_after(selection.end)
4852 };
4853
4854 if !self.linked_edit_ranges.is_empty() {
4855 let start_anchor = snapshot.anchor_before(selection.start);
4856
4857 let is_word_char = text.chars().next().is_none_or(|char| {
4858 let classifier = snapshot
4859 .char_classifier_at(start_anchor.to_offset(&snapshot))
4860 .scope_context(Some(CharScopeContext::LinkedEdit));
4861 classifier.is_word(char)
4862 });
4863 let is_dot = text.as_ref() == ".";
4864 let should_apply_linked_edit = is_word_char || is_dot;
4865
4866 if should_apply_linked_edit {
4867 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
4868 linked_edits.push(&self, anchor_range, text.clone(), cx);
4869 } else {
4870 clear_linked_edit_ranges = true;
4871 }
4872 }
4873
4874 new_selections.push((selection.map(|_| anchor), 0));
4875 edits.push((selection.start..selection.end, text.clone()));
4876
4877 has_adjacent_edits |= next_is_adjacent;
4878 in_adjacent_group = next_is_adjacent;
4879 }
4880
4881 if all_selections_read_only {
4882 return;
4883 }
4884
4885 drop(regions);
4886 drop(snapshot);
4887
4888 self.transact(window, cx, |this, window, cx| {
4889 if clear_linked_edit_ranges {
4890 this.linked_edit_ranges.clear();
4891 }
4892 let initial_buffer_versions =
4893 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4894
4895 this.buffer.update(cx, |buffer, cx| {
4896 if has_adjacent_edits {
4897 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4898 } else {
4899 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4900 }
4901 });
4902 linked_edits.apply(cx);
4903 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4904 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4905 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4906 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4907 new_anchor_selections,
4908 &map,
4909 )
4910 .zip(new_selection_deltas)
4911 .map(|(selection, delta)| Selection {
4912 id: selection.id,
4913 start: selection.start + delta,
4914 end: selection.end + delta,
4915 reversed: selection.reversed,
4916 goal: SelectionGoal::None,
4917 })
4918 .collect::<Vec<_>>();
4919
4920 let mut i = 0;
4921 for (position, delta, selection_id, pair) in new_autoclose_regions {
4922 let position = position.to_offset(map.buffer_snapshot()) + delta;
4923 let start = map.buffer_snapshot().anchor_before(position);
4924 let end = map.buffer_snapshot().anchor_after(position);
4925 while let Some(existing_state) = this.autoclose_regions.get(i) {
4926 match existing_state
4927 .range
4928 .start
4929 .cmp(&start, map.buffer_snapshot())
4930 {
4931 Ordering::Less => i += 1,
4932 Ordering::Greater => break,
4933 Ordering::Equal => {
4934 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4935 Ordering::Less => i += 1,
4936 Ordering::Equal => break,
4937 Ordering::Greater => break,
4938 }
4939 }
4940 }
4941 }
4942 this.autoclose_regions.insert(
4943 i,
4944 AutocloseRegion {
4945 selection_id,
4946 range: start..end,
4947 pair,
4948 },
4949 );
4950 }
4951
4952 let had_active_edit_prediction = this.has_active_edit_prediction();
4953 this.change_selections(
4954 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4955 window,
4956 cx,
4957 |s| s.select(new_selections),
4958 );
4959
4960 if !bracket_inserted
4961 && let Some(on_type_format_task) =
4962 this.trigger_on_type_formatting(text.to_string(), window, cx)
4963 {
4964 on_type_format_task.detach_and_log_err(cx);
4965 }
4966
4967 let editor_settings = EditorSettings::get_global(cx);
4968 if bracket_inserted
4969 && (editor_settings.auto_signature_help
4970 || editor_settings.show_signature_help_after_edits)
4971 {
4972 this.show_signature_help(&ShowSignatureHelp, window, cx);
4973 }
4974
4975 let trigger_in_words =
4976 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4977 if this.hard_wrap.is_some() {
4978 let latest: Range<Point> = this.selections.newest(&map).range();
4979 if latest.is_empty()
4980 && this
4981 .buffer()
4982 .read(cx)
4983 .snapshot(cx)
4984 .line_len(MultiBufferRow(latest.start.row))
4985 == latest.start.column
4986 {
4987 this.rewrap_impl(
4988 RewrapOptions {
4989 override_language_settings: true,
4990 preserve_existing_whitespace: true,
4991 },
4992 cx,
4993 )
4994 }
4995 }
4996 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4997 refresh_linked_ranges(this, window, cx);
4998 this.refresh_edit_prediction(true, false, window, cx);
4999 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5000 });
5001 }
5002
5003 fn find_possible_emoji_shortcode_at_position(
5004 snapshot: &MultiBufferSnapshot,
5005 position: Point,
5006 ) -> Option<String> {
5007 let mut chars = Vec::new();
5008 let mut found_colon = false;
5009 for char in snapshot.reversed_chars_at(position).take(100) {
5010 // Found a possible emoji shortcode in the middle of the buffer
5011 if found_colon {
5012 if char.is_whitespace() {
5013 chars.reverse();
5014 return Some(chars.iter().collect());
5015 }
5016 // If the previous character is not a whitespace, we are in the middle of a word
5017 // and we only want to complete the shortcode if the word is made up of other emojis
5018 let mut containing_word = String::new();
5019 for ch in snapshot
5020 .reversed_chars_at(position)
5021 .skip(chars.len() + 1)
5022 .take(100)
5023 {
5024 if ch.is_whitespace() {
5025 break;
5026 }
5027 containing_word.push(ch);
5028 }
5029 let containing_word = containing_word.chars().rev().collect::<String>();
5030 if util::word_consists_of_emojis(containing_word.as_str()) {
5031 chars.reverse();
5032 return Some(chars.iter().collect());
5033 }
5034 }
5035
5036 if char.is_whitespace() || !char.is_ascii() {
5037 return None;
5038 }
5039 if char == ':' {
5040 found_colon = true;
5041 } else {
5042 chars.push(char);
5043 }
5044 }
5045 // Found a possible emoji shortcode at the beginning of the buffer
5046 chars.reverse();
5047 Some(chars.iter().collect())
5048 }
5049
5050 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5051 if self.read_only(cx) {
5052 return;
5053 }
5054
5055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5056 self.transact(window, cx, |this, window, cx| {
5057 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5058 let selections = this
5059 .selections
5060 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5061 let multi_buffer = this.buffer.read(cx);
5062 let buffer = multi_buffer.snapshot(cx);
5063 selections
5064 .iter()
5065 .map(|selection| {
5066 let start_point = selection.start.to_point(&buffer);
5067 let mut existing_indent =
5068 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5069 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5070 let start = selection.start;
5071 let end = selection.end;
5072 let selection_is_empty = start == end;
5073 let language_scope = buffer.language_scope_at(start);
5074 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5075 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5076 &buffer,
5077 start..end,
5078 language,
5079 )
5080 || NewlineConfig::insert_extra_newline_tree_sitter(
5081 &buffer,
5082 start..end,
5083 );
5084
5085 let mut newline_config = NewlineConfig::Newline {
5086 additional_indent: IndentSize::spaces(0),
5087 extra_line_additional_indent: if needs_extra_newline {
5088 Some(IndentSize::spaces(0))
5089 } else {
5090 None
5091 },
5092 prevent_auto_indent: false,
5093 };
5094
5095 let comment_delimiter = maybe!({
5096 if !selection_is_empty {
5097 return None;
5098 }
5099
5100 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5101 return None;
5102 }
5103
5104 return comment_delimiter_for_newline(
5105 &start_point,
5106 &buffer,
5107 language,
5108 );
5109 });
5110
5111 let doc_delimiter = maybe!({
5112 if !selection_is_empty {
5113 return None;
5114 }
5115
5116 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5117 return None;
5118 }
5119
5120 return documentation_delimiter_for_newline(
5121 &start_point,
5122 &buffer,
5123 language,
5124 &mut newline_config,
5125 );
5126 });
5127
5128 let list_delimiter = maybe!({
5129 if !selection_is_empty {
5130 return None;
5131 }
5132
5133 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5134 return None;
5135 }
5136
5137 return list_delimiter_for_newline(
5138 &start_point,
5139 &buffer,
5140 language,
5141 &mut newline_config,
5142 );
5143 });
5144
5145 (
5146 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5147 newline_config,
5148 )
5149 } else {
5150 (
5151 None,
5152 NewlineConfig::Newline {
5153 additional_indent: IndentSize::spaces(0),
5154 extra_line_additional_indent: None,
5155 prevent_auto_indent: false,
5156 },
5157 )
5158 };
5159
5160 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5161 NewlineConfig::ClearCurrentLine => {
5162 let row_start =
5163 buffer.point_to_offset(Point::new(start_point.row, 0));
5164 (row_start, String::new(), false)
5165 }
5166 NewlineConfig::UnindentCurrentLine { continuation } => {
5167 let row_start =
5168 buffer.point_to_offset(Point::new(start_point.row, 0));
5169 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5170 let tab_size_indent = IndentSize::spaces(tab_size.get());
5171 let reduced_indent =
5172 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5173 let mut new_text = String::new();
5174 new_text.extend(reduced_indent.chars());
5175 new_text.push_str(continuation);
5176 (row_start, new_text, true)
5177 }
5178 NewlineConfig::Newline {
5179 additional_indent,
5180 extra_line_additional_indent,
5181 prevent_auto_indent,
5182 } => {
5183 let auto_indent_mode =
5184 buffer.language_settings_at(start, cx).auto_indent;
5185 let preserve_indent =
5186 auto_indent_mode != language::AutoIndentMode::None;
5187 let apply_syntax_indent =
5188 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5189 let capacity_for_delimiter =
5190 delimiter.as_deref().map(str::len).unwrap_or_default();
5191 let existing_indent_len = if preserve_indent {
5192 existing_indent.len as usize
5193 } else {
5194 0
5195 };
5196 let extra_line_len = extra_line_additional_indent
5197 .map(|i| 1 + existing_indent_len + i.len as usize)
5198 .unwrap_or(0);
5199 let mut new_text = String::with_capacity(
5200 1 + capacity_for_delimiter
5201 + existing_indent_len
5202 + additional_indent.len as usize
5203 + extra_line_len,
5204 );
5205 new_text.push('\n');
5206 if preserve_indent {
5207 new_text.extend(existing_indent.chars());
5208 }
5209 new_text.extend(additional_indent.chars());
5210 if let Some(delimiter) = &delimiter {
5211 new_text.push_str(delimiter);
5212 }
5213 if let Some(extra_indent) = extra_line_additional_indent {
5214 new_text.push('\n');
5215 if preserve_indent {
5216 new_text.extend(existing_indent.chars());
5217 }
5218 new_text.extend(extra_indent.chars());
5219 }
5220 (
5221 start,
5222 new_text,
5223 *prevent_auto_indent || !apply_syntax_indent,
5224 )
5225 }
5226 };
5227
5228 let anchor = buffer.anchor_after(end);
5229 let new_selection = selection.map(|_| anchor);
5230 (
5231 ((edit_start..end, new_text), prevent_auto_indent),
5232 (newline_config.has_extra_line(), new_selection),
5233 )
5234 })
5235 .unzip()
5236 };
5237
5238 let mut auto_indent_edits = Vec::new();
5239 let mut edits = Vec::new();
5240 for (edit, prevent_auto_indent) in edits_with_flags {
5241 if prevent_auto_indent {
5242 edits.push(edit);
5243 } else {
5244 auto_indent_edits.push(edit);
5245 }
5246 }
5247 if !edits.is_empty() {
5248 this.edit(edits, cx);
5249 }
5250 if !auto_indent_edits.is_empty() {
5251 this.edit_with_autoindent(auto_indent_edits, cx);
5252 }
5253
5254 let buffer = this.buffer.read(cx).snapshot(cx);
5255 let new_selections = selection_info
5256 .into_iter()
5257 .map(|(extra_newline_inserted, new_selection)| {
5258 let mut cursor = new_selection.end.to_point(&buffer);
5259 if extra_newline_inserted {
5260 cursor.row -= 1;
5261 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5262 }
5263 new_selection.map(|_| cursor)
5264 })
5265 .collect();
5266
5267 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5268 this.refresh_edit_prediction(true, false, window, cx);
5269 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5270 task.detach_and_log_err(cx);
5271 }
5272 });
5273 }
5274
5275 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5276 if self.read_only(cx) {
5277 return;
5278 }
5279
5280 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5281
5282 let buffer = self.buffer.read(cx);
5283 let snapshot = buffer.snapshot(cx);
5284
5285 let mut edits = Vec::new();
5286 let mut rows = Vec::new();
5287
5288 for (rows_inserted, selection) in self
5289 .selections
5290 .all_adjusted(&self.display_snapshot(cx))
5291 .into_iter()
5292 .enumerate()
5293 {
5294 let cursor = selection.head();
5295 let row = cursor.row;
5296
5297 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5298
5299 let newline = "\n".to_string();
5300 edits.push((start_of_line..start_of_line, newline));
5301
5302 rows.push(row + rows_inserted as u32);
5303 }
5304
5305 self.transact(window, cx, |editor, window, cx| {
5306 editor.edit(edits, cx);
5307
5308 editor.change_selections(Default::default(), window, cx, |s| {
5309 let mut index = 0;
5310 s.move_cursors_with(&mut |map, _, _| {
5311 let row = rows[index];
5312 index += 1;
5313
5314 let point = Point::new(row, 0);
5315 let boundary = map.next_line_boundary(point).1;
5316 let clipped = map.clip_point(boundary, Bias::Left);
5317
5318 (clipped, SelectionGoal::None)
5319 });
5320 });
5321
5322 let mut indent_edits = Vec::new();
5323 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5324 for row in rows {
5325 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5326 for (row, indent) in indents {
5327 if indent.len == 0 {
5328 continue;
5329 }
5330
5331 let text = match indent.kind {
5332 IndentKind::Space => " ".repeat(indent.len as usize),
5333 IndentKind::Tab => "\t".repeat(indent.len as usize),
5334 };
5335 let point = Point::new(row.0, 0);
5336 indent_edits.push((point..point, text));
5337 }
5338 }
5339 editor.edit(indent_edits, cx);
5340 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5341 format.detach_and_log_err(cx);
5342 }
5343 });
5344 }
5345
5346 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5347 if self.read_only(cx) {
5348 return;
5349 }
5350
5351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5352
5353 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5354 let mut rows = Vec::new();
5355 let mut rows_inserted = 0;
5356
5357 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5358 let cursor = selection.head();
5359 let row = cursor.row;
5360
5361 let point = Point::new(row, 0);
5362 let Some((buffer_handle, buffer_point, _)) =
5363 self.buffer.read(cx).point_to_buffer_point(point, cx)
5364 else {
5365 continue;
5366 };
5367
5368 buffer_edits
5369 .entry(buffer_handle.entity_id())
5370 .or_insert_with(|| (buffer_handle, Vec::new()))
5371 .1
5372 .push(buffer_point);
5373
5374 rows_inserted += 1;
5375 rows.push(row + rows_inserted);
5376 }
5377
5378 self.transact(window, cx, |editor, window, cx| {
5379 for (_, (buffer_handle, points)) in &buffer_edits {
5380 buffer_handle.update(cx, |buffer, cx| {
5381 let edits: Vec<_> = points
5382 .iter()
5383 .map(|point| {
5384 let target = Point::new(point.row + 1, 0);
5385 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5386 (start_of_line..start_of_line, "\n")
5387 })
5388 .collect();
5389 buffer.edit(edits, None, cx);
5390 });
5391 }
5392
5393 editor.change_selections(Default::default(), window, cx, |s| {
5394 let mut index = 0;
5395 s.move_cursors_with(&mut |map, _, _| {
5396 let row = rows[index];
5397 index += 1;
5398
5399 let point = Point::new(row, 0);
5400 let boundary = map.next_line_boundary(point).1;
5401 let clipped = map.clip_point(boundary, Bias::Left);
5402
5403 (clipped, SelectionGoal::None)
5404 });
5405 });
5406
5407 let mut indent_edits = Vec::new();
5408 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5409 for row in rows {
5410 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5411 for (row, indent) in indents {
5412 if indent.len == 0 {
5413 continue;
5414 }
5415
5416 let text = match indent.kind {
5417 IndentKind::Space => " ".repeat(indent.len as usize),
5418 IndentKind::Tab => "\t".repeat(indent.len as usize),
5419 };
5420 let point = Point::new(row.0, 0);
5421 indent_edits.push((point..point, text));
5422 }
5423 }
5424 editor.edit(indent_edits, cx);
5425 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5426 format.detach_and_log_err(cx);
5427 }
5428 });
5429 }
5430
5431 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5432 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5433 original_indent_columns: Vec::new(),
5434 });
5435 self.replace_selections(text, autoindent, window, cx, false);
5436 }
5437
5438 /// Replaces the editor's selections with the provided `text`, applying the
5439 /// given `autoindent_mode` (`None` will skip autoindentation).
5440 ///
5441 /// Early returns if the editor is in read-only mode, without applying any
5442 /// edits.
5443 fn replace_selections(
5444 &mut self,
5445 text: &str,
5446 autoindent_mode: Option<AutoindentMode>,
5447 window: &mut Window,
5448 cx: &mut Context<Self>,
5449 apply_linked_edits: bool,
5450 ) {
5451 if self.read_only(cx) {
5452 return;
5453 }
5454
5455 let text: Arc<str> = text.into();
5456 self.transact(window, cx, |this, window, cx| {
5457 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5458 let linked_edits = if apply_linked_edits {
5459 this.linked_edits_for_selections(text.clone(), cx)
5460 } else {
5461 LinkedEdits::new()
5462 };
5463
5464 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5465 let anchors = {
5466 let snapshot = buffer.read(cx);
5467 old_selections
5468 .iter()
5469 .map(|s| {
5470 let anchor = snapshot.anchor_after(s.head());
5471 s.map(|_| anchor)
5472 })
5473 .collect::<Vec<_>>()
5474 };
5475 buffer.edit(
5476 old_selections
5477 .iter()
5478 .map(|s| (s.start..s.end, text.clone())),
5479 autoindent_mode,
5480 cx,
5481 );
5482 anchors
5483 });
5484
5485 linked_edits.apply(cx);
5486
5487 this.change_selections(Default::default(), window, cx, |s| {
5488 s.select_anchors(selection_anchors);
5489 });
5490
5491 if apply_linked_edits {
5492 refresh_linked_ranges(this, window, cx);
5493 }
5494
5495 cx.notify();
5496 });
5497 }
5498
5499 /// Collects linked edits for the current selections, pairing each linked
5500 /// range with `text`.
5501 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5502 let mut linked_edits = LinkedEdits::new();
5503 if !self.linked_edit_ranges.is_empty() {
5504 for selection in self.selections.disjoint_anchors() {
5505 let start = selection.start.text_anchor;
5506 let end = selection.end.text_anchor;
5507 linked_edits.push(self, start..end, text.clone(), cx);
5508 }
5509 }
5510 linked_edits
5511 }
5512
5513 /// Deletes the content covered by the current selections and applies
5514 /// linked edits.
5515 pub fn delete_selections_with_linked_edits(
5516 &mut self,
5517 window: &mut Window,
5518 cx: &mut Context<Self>,
5519 ) {
5520 self.replace_selections("", None, window, cx, true);
5521 }
5522
5523 #[cfg(any(test, feature = "test-support"))]
5524 pub fn set_linked_edit_ranges_for_testing(
5525 &mut self,
5526 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5527 cx: &mut Context<Self>,
5528 ) -> Option<()> {
5529 let Some((buffer, _)) = self
5530 .buffer
5531 .read(cx)
5532 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5533 else {
5534 return None;
5535 };
5536 let buffer = buffer.read(cx);
5537 let buffer_id = buffer.remote_id();
5538 let mut linked_ranges = Vec::with_capacity(ranges.len());
5539 for (base_range, linked_ranges_points) in ranges {
5540 let base_anchor =
5541 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5542 let linked_anchors = linked_ranges_points
5543 .into_iter()
5544 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5545 .collect();
5546 linked_ranges.push((base_anchor, linked_anchors));
5547 }
5548 let mut map = HashMap::default();
5549 map.insert(buffer_id, linked_ranges);
5550 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5551 Some(())
5552 }
5553
5554 fn trigger_completion_on_input(
5555 &mut self,
5556 text: &str,
5557 trigger_in_words: bool,
5558 window: &mut Window,
5559 cx: &mut Context<Self>,
5560 ) {
5561 let completions_source = self
5562 .context_menu
5563 .borrow()
5564 .as_ref()
5565 .and_then(|menu| match menu {
5566 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5567 CodeContextMenu::CodeActions(_) => None,
5568 });
5569
5570 match completions_source {
5571 Some(CompletionsMenuSource::Words { .. }) => {
5572 self.open_or_update_completions_menu(
5573 Some(CompletionsMenuSource::Words {
5574 ignore_threshold: false,
5575 }),
5576 None,
5577 trigger_in_words,
5578 window,
5579 cx,
5580 );
5581 }
5582 _ => self.open_or_update_completions_menu(
5583 None,
5584 Some(text.to_owned()).filter(|x| !x.is_empty()),
5585 true,
5586 window,
5587 cx,
5588 ),
5589 }
5590 }
5591
5592 /// If any empty selections is touching the start of its innermost containing autoclose
5593 /// region, expand it to select the brackets.
5594 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5595 let selections = self
5596 .selections
5597 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5598 let buffer = self.buffer.read(cx).read(cx);
5599 let new_selections = self
5600 .selections_with_autoclose_regions(selections, &buffer)
5601 .map(|(mut selection, region)| {
5602 if !selection.is_empty() {
5603 return selection;
5604 }
5605
5606 if let Some(region) = region {
5607 let mut range = region.range.to_offset(&buffer);
5608 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5609 range.start -= region.pair.start.len();
5610 if buffer.contains_str_at(range.start, ®ion.pair.start)
5611 && buffer.contains_str_at(range.end, ®ion.pair.end)
5612 {
5613 range.end += region.pair.end.len();
5614 selection.start = range.start;
5615 selection.end = range.end;
5616
5617 return selection;
5618 }
5619 }
5620 }
5621
5622 let always_treat_brackets_as_autoclosed = buffer
5623 .language_settings_at(selection.start, cx)
5624 .always_treat_brackets_as_autoclosed;
5625
5626 if !always_treat_brackets_as_autoclosed {
5627 return selection;
5628 }
5629
5630 if let Some(scope) = buffer.language_scope_at(selection.start) {
5631 for (pair, enabled) in scope.brackets() {
5632 if !enabled || !pair.close {
5633 continue;
5634 }
5635
5636 if buffer.contains_str_at(selection.start, &pair.end) {
5637 let pair_start_len = pair.start.len();
5638 if buffer.contains_str_at(
5639 selection.start.saturating_sub_usize(pair_start_len),
5640 &pair.start,
5641 ) {
5642 selection.start -= pair_start_len;
5643 selection.end += pair.end.len();
5644
5645 return selection;
5646 }
5647 }
5648 }
5649 }
5650
5651 selection
5652 })
5653 .collect();
5654
5655 drop(buffer);
5656 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5657 selections.select(new_selections)
5658 });
5659 }
5660
5661 /// Iterate the given selections, and for each one, find the smallest surrounding
5662 /// autoclose region. This uses the ordering of the selections and the autoclose
5663 /// regions to avoid repeated comparisons.
5664 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5665 &'a self,
5666 selections: impl IntoIterator<Item = Selection<D>>,
5667 buffer: &'a MultiBufferSnapshot,
5668 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5669 let mut i = 0;
5670 let mut regions = self.autoclose_regions.as_slice();
5671 selections.into_iter().map(move |selection| {
5672 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5673
5674 let mut enclosing = None;
5675 while let Some(pair_state) = regions.get(i) {
5676 if pair_state.range.end.to_offset(buffer) < range.start {
5677 regions = ®ions[i + 1..];
5678 i = 0;
5679 } else if pair_state.range.start.to_offset(buffer) > range.end {
5680 break;
5681 } else {
5682 if pair_state.selection_id == selection.id {
5683 enclosing = Some(pair_state);
5684 }
5685 i += 1;
5686 }
5687 }
5688
5689 (selection, enclosing)
5690 })
5691 }
5692
5693 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5694 fn invalidate_autoclose_regions(
5695 &mut self,
5696 mut selections: &[Selection<Anchor>],
5697 buffer: &MultiBufferSnapshot,
5698 ) {
5699 self.autoclose_regions.retain(|state| {
5700 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5701 return false;
5702 }
5703
5704 let mut i = 0;
5705 while let Some(selection) = selections.get(i) {
5706 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5707 selections = &selections[1..];
5708 continue;
5709 }
5710 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5711 break;
5712 }
5713 if selection.id == state.selection_id {
5714 return true;
5715 } else {
5716 i += 1;
5717 }
5718 }
5719 false
5720 });
5721 }
5722
5723 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5724 let offset = position.to_offset(buffer);
5725 let (word_range, kind) =
5726 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5727 if offset > word_range.start && kind == Some(CharKind::Word) {
5728 Some(
5729 buffer
5730 .text_for_range(word_range.start..offset)
5731 .collect::<String>(),
5732 )
5733 } else {
5734 None
5735 }
5736 }
5737
5738 pub fn visible_excerpts(
5739 &self,
5740 lsp_related_only: bool,
5741 cx: &mut Context<Editor>,
5742 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5743 let project = self.project().cloned();
5744 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5745 let multi_buffer = self.buffer().read(cx);
5746 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5747 multi_buffer_snapshot
5748 .range_to_buffer_ranges(
5749 self.multi_buffer_visible_range(&display_snapshot, cx)
5750 .to_inclusive(),
5751 )
5752 .into_iter()
5753 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5754 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5755 if !lsp_related_only {
5756 return Some((
5757 excerpt_id,
5758 (
5759 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5760 buffer.version().clone(),
5761 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5762 ),
5763 ));
5764 }
5765
5766 let project = project.as_ref()?.read(cx);
5767 let buffer_file = project::File::from_dyn(buffer.file())?;
5768 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5769 let worktree_entry = buffer_worktree
5770 .read(cx)
5771 .entry_for_id(buffer_file.project_entry_id()?)?;
5772 if worktree_entry.is_ignored {
5773 None
5774 } else {
5775 Some((
5776 excerpt_id,
5777 (
5778 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5779 buffer.version().clone(),
5780 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5781 ),
5782 ))
5783 }
5784 })
5785 .collect()
5786 }
5787
5788 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5789 TextLayoutDetails {
5790 text_system: window.text_system().clone(),
5791 editor_style: self.style.clone().unwrap(),
5792 rem_size: window.rem_size(),
5793 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5794 visible_rows: self.visible_line_count(),
5795 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5796 }
5797 }
5798
5799 fn trigger_on_type_formatting(
5800 &self,
5801 input: String,
5802 window: &mut Window,
5803 cx: &mut Context<Self>,
5804 ) -> Option<Task<Result<()>>> {
5805 if input.chars().count() != 1 {
5806 return None;
5807 }
5808
5809 let project = self.project()?;
5810 let position = self.selections.newest_anchor().head();
5811 let (buffer, buffer_position) = self
5812 .buffer
5813 .read(cx)
5814 .text_anchor_for_position(position, cx)?;
5815
5816 let settings = language_settings::language_settings(
5817 buffer
5818 .read(cx)
5819 .language_at(buffer_position)
5820 .map(|l| l.name()),
5821 buffer.read(cx).file(),
5822 cx,
5823 );
5824 if !settings.use_on_type_format {
5825 return None;
5826 }
5827
5828 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5829 // hence we do LSP request & edit on host side only — add formats to host's history.
5830 let push_to_lsp_host_history = true;
5831 // If this is not the host, append its history with new edits.
5832 let push_to_client_history = project.read(cx).is_via_collab();
5833
5834 let on_type_formatting = project.update(cx, |project, cx| {
5835 project.on_type_format(
5836 buffer.clone(),
5837 buffer_position,
5838 input,
5839 push_to_lsp_host_history,
5840 cx,
5841 )
5842 });
5843 Some(cx.spawn_in(window, async move |editor, cx| {
5844 if let Some(transaction) = on_type_formatting.await? {
5845 if push_to_client_history {
5846 buffer.update(cx, |buffer, _| {
5847 buffer.push_transaction(transaction, Instant::now());
5848 buffer.finalize_last_transaction();
5849 });
5850 }
5851 editor.update(cx, |editor, cx| {
5852 editor.refresh_document_highlights(cx);
5853 })?;
5854 }
5855 Ok(())
5856 }))
5857 }
5858
5859 pub fn show_word_completions(
5860 &mut self,
5861 _: &ShowWordCompletions,
5862 window: &mut Window,
5863 cx: &mut Context<Self>,
5864 ) {
5865 self.open_or_update_completions_menu(
5866 Some(CompletionsMenuSource::Words {
5867 ignore_threshold: true,
5868 }),
5869 None,
5870 false,
5871 window,
5872 cx,
5873 );
5874 }
5875
5876 pub fn show_completions(
5877 &mut self,
5878 _: &ShowCompletions,
5879 window: &mut Window,
5880 cx: &mut Context<Self>,
5881 ) {
5882 self.open_or_update_completions_menu(None, None, false, window, cx);
5883 }
5884
5885 fn open_or_update_completions_menu(
5886 &mut self,
5887 requested_source: Option<CompletionsMenuSource>,
5888 trigger: Option<String>,
5889 trigger_in_words: bool,
5890 window: &mut Window,
5891 cx: &mut Context<Self>,
5892 ) {
5893 if self.pending_rename.is_some() {
5894 return;
5895 }
5896
5897 let completions_source = self
5898 .context_menu
5899 .borrow()
5900 .as_ref()
5901 .and_then(|menu| match menu {
5902 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5903 CodeContextMenu::CodeActions(_) => None,
5904 });
5905
5906 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5907
5908 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5909 // inserted and selected. To handle that case, the start of the selection is used so that
5910 // the menu starts with all choices.
5911 let position = self
5912 .selections
5913 .newest_anchor()
5914 .start
5915 .bias_right(&multibuffer_snapshot);
5916 if position.diff_base_anchor.is_some() {
5917 return;
5918 }
5919 let buffer_position = multibuffer_snapshot.anchor_before(position);
5920 let Some(buffer) = buffer_position
5921 .text_anchor
5922 .buffer_id
5923 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5924 else {
5925 return;
5926 };
5927 let buffer_snapshot = buffer.read(cx).snapshot();
5928
5929 let menu_is_open = matches!(
5930 self.context_menu.borrow().as_ref(),
5931 Some(CodeContextMenu::Completions(_))
5932 );
5933
5934 let language = buffer_snapshot
5935 .language_at(buffer_position.text_anchor)
5936 .map(|language| language.name());
5937
5938 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5939 let completion_settings = language_settings.completions.clone();
5940
5941 let show_completions_on_input = self
5942 .show_completions_on_input_override
5943 .unwrap_or(language_settings.show_completions_on_input);
5944 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5945 return;
5946 }
5947
5948 let query: Option<Arc<String>> =
5949 Self::completion_query(&multibuffer_snapshot, buffer_position)
5950 .map(|query| query.into());
5951
5952 drop(multibuffer_snapshot);
5953
5954 // Hide the current completions menu when query is empty. Without this, cached
5955 // completions from before the trigger char may be reused (#32774).
5956 if query.is_none() && menu_is_open {
5957 self.hide_context_menu(window, cx);
5958 }
5959
5960 let mut ignore_word_threshold = false;
5961 let provider = match requested_source {
5962 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5963 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5964 ignore_word_threshold = ignore_threshold;
5965 None
5966 }
5967 Some(CompletionsMenuSource::SnippetChoices)
5968 | Some(CompletionsMenuSource::SnippetsOnly) => {
5969 log::error!("bug: SnippetChoices requested_source is not handled");
5970 None
5971 }
5972 };
5973
5974 let sort_completions = provider
5975 .as_ref()
5976 .is_some_and(|provider| provider.sort_completions());
5977
5978 let filter_completions = provider
5979 .as_ref()
5980 .is_none_or(|provider| provider.filter_completions());
5981
5982 let was_snippets_only = matches!(
5983 completions_source,
5984 Some(CompletionsMenuSource::SnippetsOnly)
5985 );
5986
5987 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5988 if filter_completions {
5989 menu.filter(
5990 query.clone().unwrap_or_default(),
5991 buffer_position.text_anchor,
5992 &buffer,
5993 provider.clone(),
5994 window,
5995 cx,
5996 );
5997 }
5998 // When `is_incomplete` is false, no need to re-query completions when the current query
5999 // is a suffix of the initial query.
6000 let was_complete = !menu.is_incomplete;
6001 if was_complete && !was_snippets_only {
6002 // If the new query is a suffix of the old query (typing more characters) and
6003 // the previous result was complete, the existing completions can be filtered.
6004 //
6005 // Note that snippet completions are always complete.
6006 let query_matches = match (&menu.initial_query, &query) {
6007 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6008 (None, _) => true,
6009 _ => false,
6010 };
6011 if query_matches {
6012 let position_matches = if menu.initial_position == position {
6013 true
6014 } else {
6015 let snapshot = self.buffer.read(cx).read(cx);
6016 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6017 };
6018 if position_matches {
6019 return;
6020 }
6021 }
6022 }
6023 };
6024
6025 let Anchor {
6026 excerpt_id: buffer_excerpt_id,
6027 text_anchor: buffer_position,
6028 ..
6029 } = buffer_position;
6030
6031 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6032 buffer_snapshot.surrounding_word(buffer_position, None)
6033 {
6034 let word_to_exclude = buffer_snapshot
6035 .text_for_range(word_range.clone())
6036 .collect::<String>();
6037 (
6038 buffer_snapshot.anchor_before(word_range.start)
6039 ..buffer_snapshot.anchor_after(buffer_position),
6040 Some(word_to_exclude),
6041 )
6042 } else {
6043 (buffer_position..buffer_position, None)
6044 };
6045
6046 let show_completion_documentation = buffer_snapshot
6047 .settings_at(buffer_position, cx)
6048 .show_completion_documentation;
6049
6050 // The document can be large, so stay in reasonable bounds when searching for words,
6051 // otherwise completion pop-up might be slow to appear.
6052 const WORD_LOOKUP_ROWS: u32 = 5_000;
6053 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6054 let min_word_search = buffer_snapshot.clip_point(
6055 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6056 Bias::Left,
6057 );
6058 let max_word_search = buffer_snapshot.clip_point(
6059 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6060 Bias::Right,
6061 );
6062 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6063 ..buffer_snapshot.point_to_offset(max_word_search);
6064
6065 let skip_digits = query
6066 .as_ref()
6067 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6068
6069 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6070 trigger.as_ref().is_none_or(|trigger| {
6071 provider.is_completion_trigger(
6072 &buffer,
6073 position.text_anchor,
6074 trigger,
6075 trigger_in_words,
6076 cx,
6077 )
6078 })
6079 });
6080
6081 let provider_responses = if let Some(provider) = &provider
6082 && load_provider_completions
6083 {
6084 let trigger_character =
6085 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6086 let completion_context = CompletionContext {
6087 trigger_kind: match &trigger_character {
6088 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6089 None => CompletionTriggerKind::INVOKED,
6090 },
6091 trigger_character,
6092 };
6093
6094 provider.completions(
6095 buffer_excerpt_id,
6096 &buffer,
6097 buffer_position,
6098 completion_context,
6099 window,
6100 cx,
6101 )
6102 } else {
6103 Task::ready(Ok(Vec::new()))
6104 };
6105
6106 let load_word_completions = if !self.word_completions_enabled {
6107 false
6108 } else if requested_source
6109 == Some(CompletionsMenuSource::Words {
6110 ignore_threshold: true,
6111 })
6112 {
6113 true
6114 } else {
6115 load_provider_completions
6116 && completion_settings.words != WordsCompletionMode::Disabled
6117 && (ignore_word_threshold || {
6118 let words_min_length = completion_settings.words_min_length;
6119 // check whether word has at least `words_min_length` characters
6120 let query_chars = query.iter().flat_map(|q| q.chars());
6121 query_chars.take(words_min_length).count() == words_min_length
6122 })
6123 };
6124
6125 let mut words = if load_word_completions {
6126 cx.background_spawn({
6127 let buffer_snapshot = buffer_snapshot.clone();
6128 async move {
6129 buffer_snapshot.words_in_range(WordsQuery {
6130 fuzzy_contents: None,
6131 range: word_search_range,
6132 skip_digits,
6133 })
6134 }
6135 })
6136 } else {
6137 Task::ready(BTreeMap::default())
6138 };
6139
6140 let snippets = if let Some(provider) = &provider
6141 && provider.show_snippets()
6142 && let Some(project) = self.project()
6143 {
6144 let char_classifier = buffer_snapshot
6145 .char_classifier_at(buffer_position)
6146 .scope_context(Some(CharScopeContext::Completion));
6147 project.update(cx, |project, cx| {
6148 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6149 })
6150 } else {
6151 Task::ready(Ok(CompletionResponse {
6152 completions: Vec::new(),
6153 display_options: Default::default(),
6154 is_incomplete: false,
6155 }))
6156 };
6157
6158 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6159
6160 let id = post_inc(&mut self.next_completion_id);
6161 let task = cx.spawn_in(window, async move |editor, cx| {
6162 let Ok(()) = editor.update(cx, |this, _| {
6163 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6164 }) else {
6165 return;
6166 };
6167
6168 // TODO: Ideally completions from different sources would be selectively re-queried, so
6169 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6170 let mut completions = Vec::new();
6171 let mut is_incomplete = false;
6172 let mut display_options: Option<CompletionDisplayOptions> = None;
6173 if let Some(provider_responses) = provider_responses.await.log_err()
6174 && !provider_responses.is_empty()
6175 {
6176 for response in provider_responses {
6177 completions.extend(response.completions);
6178 is_incomplete = is_incomplete || response.is_incomplete;
6179 match display_options.as_mut() {
6180 None => {
6181 display_options = Some(response.display_options);
6182 }
6183 Some(options) => options.merge(&response.display_options),
6184 }
6185 }
6186 if completion_settings.words == WordsCompletionMode::Fallback {
6187 words = Task::ready(BTreeMap::default());
6188 }
6189 }
6190 let display_options = display_options.unwrap_or_default();
6191
6192 let mut words = words.await;
6193 if let Some(word_to_exclude) = &word_to_exclude {
6194 words.remove(word_to_exclude);
6195 }
6196 for lsp_completion in &completions {
6197 words.remove(&lsp_completion.new_text);
6198 }
6199 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6200 replace_range: word_replace_range.clone(),
6201 new_text: word.clone(),
6202 label: CodeLabel::plain(word, None),
6203 match_start: None,
6204 snippet_deduplication_key: None,
6205 icon_path: None,
6206 documentation: None,
6207 source: CompletionSource::BufferWord {
6208 word_range,
6209 resolved: false,
6210 },
6211 insert_text_mode: Some(InsertTextMode::AS_IS),
6212 confirm: None,
6213 }));
6214
6215 completions.extend(
6216 snippets
6217 .await
6218 .into_iter()
6219 .flat_map(|response| response.completions),
6220 );
6221
6222 let menu = if completions.is_empty() {
6223 None
6224 } else {
6225 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6226 let languages = editor
6227 .workspace
6228 .as_ref()
6229 .and_then(|(workspace, _)| workspace.upgrade())
6230 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6231 let menu = CompletionsMenu::new(
6232 id,
6233 requested_source.unwrap_or(if load_provider_completions {
6234 CompletionsMenuSource::Normal
6235 } else {
6236 CompletionsMenuSource::SnippetsOnly
6237 }),
6238 sort_completions,
6239 show_completion_documentation,
6240 position,
6241 query.clone(),
6242 is_incomplete,
6243 buffer.clone(),
6244 completions.into(),
6245 editor
6246 .context_menu()
6247 .borrow_mut()
6248 .as_ref()
6249 .map(|menu| menu.primary_scroll_handle()),
6250 display_options,
6251 snippet_sort_order,
6252 languages,
6253 language,
6254 cx,
6255 );
6256
6257 let query = if filter_completions { query } else { None };
6258 let matches_task = menu.do_async_filtering(
6259 query.unwrap_or_default(),
6260 buffer_position,
6261 &buffer,
6262 cx,
6263 );
6264 (menu, matches_task)
6265 }) else {
6266 return;
6267 };
6268
6269 let matches = matches_task.await;
6270
6271 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6272 // Newer menu already set, so exit.
6273 if let Some(CodeContextMenu::Completions(prev_menu)) =
6274 editor.context_menu.borrow().as_ref()
6275 && prev_menu.id > id
6276 {
6277 return;
6278 };
6279
6280 // Only valid to take prev_menu because either the new menu is immediately set
6281 // below, or the menu is hidden.
6282 if let Some(CodeContextMenu::Completions(prev_menu)) =
6283 editor.context_menu.borrow_mut().take()
6284 {
6285 let position_matches =
6286 if prev_menu.initial_position == menu.initial_position {
6287 true
6288 } else {
6289 let snapshot = editor.buffer.read(cx).read(cx);
6290 prev_menu.initial_position.to_offset(&snapshot)
6291 == menu.initial_position.to_offset(&snapshot)
6292 };
6293 if position_matches {
6294 // Preserve markdown cache before `set_filter_results` because it will
6295 // try to populate the documentation cache.
6296 menu.preserve_markdown_cache(prev_menu);
6297 }
6298 };
6299
6300 menu.set_filter_results(matches, provider, window, cx);
6301 }) else {
6302 return;
6303 };
6304
6305 menu.visible().then_some(menu)
6306 };
6307
6308 editor
6309 .update_in(cx, |editor, window, cx| {
6310 if editor.focus_handle.is_focused(window)
6311 && let Some(menu) = menu
6312 {
6313 *editor.context_menu.borrow_mut() =
6314 Some(CodeContextMenu::Completions(menu));
6315
6316 crate::hover_popover::hide_hover(editor, cx);
6317 if editor.show_edit_predictions_in_menu() {
6318 editor.update_visible_edit_prediction(window, cx);
6319 } else {
6320 editor
6321 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6322 }
6323
6324 cx.notify();
6325 return;
6326 }
6327
6328 if editor.completion_tasks.len() <= 1 {
6329 // If there are no more completion tasks and the last menu was empty, we should hide it.
6330 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6331 // If it was already hidden and we don't show edit predictions in the menu,
6332 // we should also show the edit prediction when available.
6333 if was_hidden && editor.show_edit_predictions_in_menu() {
6334 editor.update_visible_edit_prediction(window, cx);
6335 }
6336 }
6337 })
6338 .ok();
6339 });
6340
6341 self.completion_tasks.push((id, task));
6342 }
6343
6344 #[cfg(any(test, feature = "test-support"))]
6345 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6346 let menu = self.context_menu.borrow();
6347 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6348 let completions = menu.completions.borrow();
6349 Some(completions.to_vec())
6350 } else {
6351 None
6352 }
6353 }
6354
6355 pub fn with_completions_menu_matching_id<R>(
6356 &self,
6357 id: CompletionId,
6358 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6359 ) -> R {
6360 let mut context_menu = self.context_menu.borrow_mut();
6361 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6362 return f(None);
6363 };
6364 if completions_menu.id != id {
6365 return f(None);
6366 }
6367 f(Some(completions_menu))
6368 }
6369
6370 pub fn confirm_completion(
6371 &mut self,
6372 action: &ConfirmCompletion,
6373 window: &mut Window,
6374 cx: &mut Context<Self>,
6375 ) -> Option<Task<Result<()>>> {
6376 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6377 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6378 }
6379
6380 pub fn confirm_completion_insert(
6381 &mut self,
6382 _: &ConfirmCompletionInsert,
6383 window: &mut Window,
6384 cx: &mut Context<Self>,
6385 ) -> Option<Task<Result<()>>> {
6386 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6387 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6388 }
6389
6390 pub fn confirm_completion_replace(
6391 &mut self,
6392 _: &ConfirmCompletionReplace,
6393 window: &mut Window,
6394 cx: &mut Context<Self>,
6395 ) -> Option<Task<Result<()>>> {
6396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6397 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6398 }
6399
6400 pub fn compose_completion(
6401 &mut self,
6402 action: &ComposeCompletion,
6403 window: &mut Window,
6404 cx: &mut Context<Self>,
6405 ) -> Option<Task<Result<()>>> {
6406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6407 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6408 }
6409
6410 fn do_completion(
6411 &mut self,
6412 item_ix: Option<usize>,
6413 intent: CompletionIntent,
6414 window: &mut Window,
6415 cx: &mut Context<Editor>,
6416 ) -> Option<Task<Result<()>>> {
6417 use language::ToOffset as _;
6418
6419 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6420 else {
6421 return None;
6422 };
6423
6424 let candidate_id = {
6425 let entries = completions_menu.entries.borrow();
6426 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6427 if self.show_edit_predictions_in_menu() {
6428 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6429 }
6430 mat.candidate_id
6431 };
6432
6433 let completion = completions_menu
6434 .completions
6435 .borrow()
6436 .get(candidate_id)?
6437 .clone();
6438 cx.stop_propagation();
6439
6440 let buffer_handle = completions_menu.buffer.clone();
6441
6442 let CompletionEdit {
6443 new_text,
6444 snippet,
6445 replace_range,
6446 } = process_completion_for_edit(
6447 &completion,
6448 intent,
6449 &buffer_handle,
6450 &completions_menu.initial_position.text_anchor,
6451 cx,
6452 );
6453
6454 let buffer = buffer_handle.read(cx);
6455 let snapshot = self.buffer.read(cx).snapshot(cx);
6456 let newest_anchor = self.selections.newest_anchor();
6457 let replace_range_multibuffer = {
6458 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6459 excerpt.map_range_from_buffer(replace_range.clone())
6460 };
6461 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6462 return None;
6463 }
6464
6465 let old_text = buffer
6466 .text_for_range(replace_range.clone())
6467 .collect::<String>();
6468 let lookbehind = newest_anchor
6469 .start
6470 .text_anchor
6471 .to_offset(buffer)
6472 .saturating_sub(replace_range.start.0);
6473 let lookahead = replace_range
6474 .end
6475 .0
6476 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6477 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6478 let suffix = &old_text[lookbehind.min(old_text.len())..];
6479
6480 let selections = self
6481 .selections
6482 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6483 let mut ranges = Vec::new();
6484 let mut linked_edits = LinkedEdits::new();
6485
6486 let text: Arc<str> = new_text.clone().into();
6487 for selection in &selections {
6488 let range = if selection.id == newest_anchor.id {
6489 replace_range_multibuffer.clone()
6490 } else {
6491 let mut range = selection.range();
6492
6493 // if prefix is present, don't duplicate it
6494 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6495 range.start = range.start.saturating_sub_usize(lookbehind);
6496
6497 // if suffix is also present, mimic the newest cursor and replace it
6498 if selection.id != newest_anchor.id
6499 && snapshot.contains_str_at(range.end, suffix)
6500 {
6501 range.end += lookahead;
6502 }
6503 }
6504 range
6505 };
6506
6507 ranges.push(range.clone());
6508
6509 if !self.linked_edit_ranges.is_empty() {
6510 let start_anchor = snapshot.anchor_before(range.start);
6511 let end_anchor = snapshot.anchor_after(range.end);
6512 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6513 linked_edits.push(&self, anchor_range, text.clone(), cx);
6514 }
6515 }
6516
6517 let common_prefix_len = old_text
6518 .chars()
6519 .zip(new_text.chars())
6520 .take_while(|(a, b)| a == b)
6521 .map(|(a, _)| a.len_utf8())
6522 .sum::<usize>();
6523
6524 cx.emit(EditorEvent::InputHandled {
6525 utf16_range_to_replace: None,
6526 text: new_text[common_prefix_len..].into(),
6527 });
6528
6529 self.transact(window, cx, |editor, window, cx| {
6530 if let Some(mut snippet) = snippet {
6531 snippet.text = new_text.to_string();
6532 editor
6533 .insert_snippet(&ranges, snippet, window, cx)
6534 .log_err();
6535 } else {
6536 editor.buffer.update(cx, |multi_buffer, cx| {
6537 let auto_indent = match completion.insert_text_mode {
6538 Some(InsertTextMode::AS_IS) => None,
6539 _ => editor.autoindent_mode.clone(),
6540 };
6541 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6542 multi_buffer.edit(edits, auto_indent, cx);
6543 });
6544 }
6545 linked_edits.apply(cx);
6546 editor.refresh_edit_prediction(true, false, window, cx);
6547 });
6548 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6549
6550 let show_new_completions_on_confirm = completion
6551 .confirm
6552 .as_ref()
6553 .is_some_and(|confirm| confirm(intent, window, cx));
6554 if show_new_completions_on_confirm {
6555 self.open_or_update_completions_menu(None, None, false, window, cx);
6556 }
6557
6558 let provider = self.completion_provider.as_ref()?;
6559
6560 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6561 let command = lsp_store.as_ref().and_then(|lsp_store| {
6562 let CompletionSource::Lsp {
6563 lsp_completion,
6564 server_id,
6565 ..
6566 } = &completion.source
6567 else {
6568 return None;
6569 };
6570 let lsp_command = lsp_completion.command.as_ref()?;
6571 let available_commands = lsp_store
6572 .read(cx)
6573 .lsp_server_capabilities
6574 .get(server_id)
6575 .and_then(|server_capabilities| {
6576 server_capabilities
6577 .execute_command_provider
6578 .as_ref()
6579 .map(|options| options.commands.as_slice())
6580 })?;
6581 if available_commands.contains(&lsp_command.command) {
6582 Some(CodeAction {
6583 server_id: *server_id,
6584 range: language::Anchor::MIN..language::Anchor::MIN,
6585 lsp_action: LspAction::Command(lsp_command.clone()),
6586 resolved: false,
6587 })
6588 } else {
6589 None
6590 }
6591 });
6592
6593 drop(completion);
6594 let apply_edits = provider.apply_additional_edits_for_completion(
6595 buffer_handle.clone(),
6596 completions_menu.completions.clone(),
6597 candidate_id,
6598 true,
6599 cx,
6600 );
6601
6602 let editor_settings = EditorSettings::get_global(cx);
6603 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6604 // After the code completion is finished, users often want to know what signatures are needed.
6605 // so we should automatically call signature_help
6606 self.show_signature_help(&ShowSignatureHelp, window, cx);
6607 }
6608
6609 Some(cx.spawn_in(window, async move |editor, cx| {
6610 apply_edits.await?;
6611
6612 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6613 let title = command.lsp_action.title().to_owned();
6614 let project_transaction = lsp_store
6615 .update(cx, |lsp_store, cx| {
6616 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6617 })
6618 .await
6619 .context("applying post-completion command")?;
6620 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6621 Self::open_project_transaction(
6622 &editor,
6623 workspace.downgrade(),
6624 project_transaction,
6625 title,
6626 cx,
6627 )
6628 .await?;
6629 }
6630 }
6631
6632 Ok(())
6633 }))
6634 }
6635
6636 pub fn toggle_code_actions(
6637 &mut self,
6638 action: &ToggleCodeActions,
6639 window: &mut Window,
6640 cx: &mut Context<Self>,
6641 ) {
6642 let quick_launch = action.quick_launch;
6643 let mut context_menu = self.context_menu.borrow_mut();
6644 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6645 if code_actions.deployed_from == action.deployed_from {
6646 // Toggle if we're selecting the same one
6647 *context_menu = None;
6648 cx.notify();
6649 return;
6650 } else {
6651 // Otherwise, clear it and start a new one
6652 *context_menu = None;
6653 cx.notify();
6654 }
6655 }
6656 drop(context_menu);
6657 let snapshot = self.snapshot(window, cx);
6658 let deployed_from = action.deployed_from.clone();
6659 let action = action.clone();
6660 self.completion_tasks.clear();
6661 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6662
6663 let multibuffer_point = match &action.deployed_from {
6664 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6665 DisplayPoint::new(*row, 0).to_point(&snapshot)
6666 }
6667 _ => self
6668 .selections
6669 .newest::<Point>(&snapshot.display_snapshot)
6670 .head(),
6671 };
6672 let Some((buffer, buffer_row)) = snapshot
6673 .buffer_snapshot()
6674 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6675 .and_then(|(buffer_snapshot, range)| {
6676 self.buffer()
6677 .read(cx)
6678 .buffer(buffer_snapshot.remote_id())
6679 .map(|buffer| (buffer, range.start.row))
6680 })
6681 else {
6682 return;
6683 };
6684 let buffer_id = buffer.read(cx).remote_id();
6685 let tasks = self
6686 .runnables
6687 .runnables((buffer_id, buffer_row))
6688 .map(|t| Arc::new(t.to_owned()));
6689
6690 if !self.focus_handle.is_focused(window) {
6691 return;
6692 }
6693 let project = self.project.clone();
6694
6695 let code_actions_task = match deployed_from {
6696 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6697 _ => self.code_actions(buffer_row, window, cx),
6698 };
6699
6700 let runnable_task = match deployed_from {
6701 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6702 _ => {
6703 let mut task_context_task = Task::ready(None);
6704 if let Some(tasks) = &tasks
6705 && let Some(project) = project
6706 {
6707 task_context_task =
6708 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6709 }
6710
6711 cx.spawn_in(window, {
6712 let buffer = buffer.clone();
6713 async move |editor, cx| {
6714 let task_context = task_context_task.await;
6715
6716 let resolved_tasks =
6717 tasks
6718 .zip(task_context.clone())
6719 .map(|(tasks, task_context)| ResolvedTasks {
6720 templates: tasks.resolve(&task_context).collect(),
6721 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6722 multibuffer_point.row,
6723 tasks.column,
6724 )),
6725 });
6726 let debug_scenarios = editor
6727 .update(cx, |editor, cx| {
6728 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6729 })?
6730 .await;
6731 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6732 }
6733 })
6734 }
6735 };
6736
6737 cx.spawn_in(window, async move |editor, cx| {
6738 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6739 let code_actions = code_actions_task.await;
6740 let spawn_straight_away = quick_launch
6741 && resolved_tasks
6742 .as_ref()
6743 .is_some_and(|tasks| tasks.templates.len() == 1)
6744 && code_actions
6745 .as_ref()
6746 .is_none_or(|actions| actions.is_empty())
6747 && debug_scenarios.is_empty();
6748
6749 editor.update_in(cx, |editor, window, cx| {
6750 crate::hover_popover::hide_hover(editor, cx);
6751 let actions = CodeActionContents::new(
6752 resolved_tasks,
6753 code_actions,
6754 debug_scenarios,
6755 task_context.unwrap_or_default(),
6756 );
6757
6758 // Don't show the menu if there are no actions available
6759 if actions.is_empty() {
6760 cx.notify();
6761 return Task::ready(Ok(()));
6762 }
6763
6764 *editor.context_menu.borrow_mut() =
6765 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6766 buffer,
6767 actions,
6768 selected_item: Default::default(),
6769 scroll_handle: UniformListScrollHandle::default(),
6770 deployed_from,
6771 }));
6772 cx.notify();
6773 if spawn_straight_away
6774 && let Some(task) = editor.confirm_code_action(
6775 &ConfirmCodeAction { item_ix: Some(0) },
6776 window,
6777 cx,
6778 )
6779 {
6780 return task;
6781 }
6782
6783 Task::ready(Ok(()))
6784 })
6785 })
6786 .detach_and_log_err(cx);
6787 }
6788
6789 fn debug_scenarios(
6790 &mut self,
6791 resolved_tasks: &Option<ResolvedTasks>,
6792 buffer: &Entity<Buffer>,
6793 cx: &mut App,
6794 ) -> Task<Vec<task::DebugScenario>> {
6795 maybe!({
6796 let project = self.project()?;
6797 let dap_store = project.read(cx).dap_store();
6798 let mut scenarios = vec![];
6799 let resolved_tasks = resolved_tasks.as_ref()?;
6800 let buffer = buffer.read(cx);
6801 let language = buffer.language()?;
6802 let file = buffer.file();
6803 let debug_adapter = language_settings(language.name().into(), file, cx)
6804 .debuggers
6805 .first()
6806 .map(SharedString::from)
6807 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6808
6809 dap_store.update(cx, |dap_store, cx| {
6810 for (_, task) in &resolved_tasks.templates {
6811 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6812 task.original_task().clone(),
6813 debug_adapter.clone().into(),
6814 task.display_label().to_owned().into(),
6815 cx,
6816 );
6817 scenarios.push(maybe_scenario);
6818 }
6819 });
6820 Some(cx.background_spawn(async move {
6821 futures::future::join_all(scenarios)
6822 .await
6823 .into_iter()
6824 .flatten()
6825 .collect::<Vec<_>>()
6826 }))
6827 })
6828 .unwrap_or_else(|| Task::ready(vec![]))
6829 }
6830
6831 fn code_actions(
6832 &mut self,
6833 buffer_row: u32,
6834 window: &mut Window,
6835 cx: &mut Context<Self>,
6836 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6837 let mut task = self.code_actions_task.take();
6838 cx.spawn_in(window, async move |editor, cx| {
6839 while let Some(prev_task) = task {
6840 prev_task.await.log_err();
6841 task = editor
6842 .update(cx, |this, _| this.code_actions_task.take())
6843 .ok()?;
6844 }
6845
6846 editor
6847 .update(cx, |editor, cx| {
6848 editor
6849 .available_code_actions
6850 .clone()
6851 .and_then(|(location, code_actions)| {
6852 let snapshot = location.buffer.read(cx).snapshot();
6853 let point_range = location.range.to_point(&snapshot);
6854 let point_range = point_range.start.row..=point_range.end.row;
6855 if point_range.contains(&buffer_row) {
6856 Some(code_actions)
6857 } else {
6858 None
6859 }
6860 })
6861 })
6862 .ok()
6863 .flatten()
6864 })
6865 }
6866
6867 pub fn confirm_code_action(
6868 &mut self,
6869 action: &ConfirmCodeAction,
6870 window: &mut Window,
6871 cx: &mut Context<Self>,
6872 ) -> Option<Task<Result<()>>> {
6873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6874
6875 let actions_menu =
6876 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6877 menu
6878 } else {
6879 return None;
6880 };
6881
6882 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6883 let action = actions_menu.actions.get(action_ix)?;
6884 let title = action.label();
6885 let buffer = actions_menu.buffer;
6886 let workspace = self.workspace()?;
6887
6888 match action {
6889 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6890 workspace.update(cx, |workspace, cx| {
6891 workspace.schedule_resolved_task(
6892 task_source_kind,
6893 resolved_task,
6894 false,
6895 window,
6896 cx,
6897 );
6898
6899 Some(Task::ready(Ok(())))
6900 })
6901 }
6902 CodeActionsItem::CodeAction {
6903 excerpt_id,
6904 action,
6905 provider,
6906 } => {
6907 let apply_code_action =
6908 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6909 let workspace = workspace.downgrade();
6910 Some(cx.spawn_in(window, async move |editor, cx| {
6911 let project_transaction = apply_code_action.await?;
6912 Self::open_project_transaction(
6913 &editor,
6914 workspace,
6915 project_transaction,
6916 title,
6917 cx,
6918 )
6919 .await
6920 }))
6921 }
6922 CodeActionsItem::DebugScenario(scenario) => {
6923 let context = actions_menu.actions.context.into();
6924
6925 workspace.update(cx, |workspace, cx| {
6926 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6927 workspace.start_debug_session(
6928 scenario,
6929 context,
6930 Some(buffer),
6931 None,
6932 window,
6933 cx,
6934 );
6935 });
6936 Some(Task::ready(Ok(())))
6937 }
6938 }
6939 }
6940
6941 fn open_transaction_for_hidden_buffers(
6942 workspace: Entity<Workspace>,
6943 transaction: ProjectTransaction,
6944 title: String,
6945 window: &mut Window,
6946 cx: &mut Context<Self>,
6947 ) {
6948 if transaction.0.is_empty() {
6949 return;
6950 }
6951
6952 let edited_buffers_already_open = {
6953 let other_editors: Vec<Entity<Editor>> = workspace
6954 .read(cx)
6955 .panes()
6956 .iter()
6957 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6958 .filter(|editor| editor.entity_id() != cx.entity_id())
6959 .collect();
6960
6961 transaction.0.keys().all(|buffer| {
6962 other_editors.iter().any(|editor| {
6963 let multi_buffer = editor.read(cx).buffer();
6964 multi_buffer.read(cx).is_singleton()
6965 && multi_buffer
6966 .read(cx)
6967 .as_singleton()
6968 .map_or(false, |singleton| {
6969 singleton.entity_id() == buffer.entity_id()
6970 })
6971 })
6972 })
6973 };
6974 if !edited_buffers_already_open {
6975 let workspace = workspace.downgrade();
6976 cx.defer_in(window, move |_, window, cx| {
6977 cx.spawn_in(window, async move |editor, cx| {
6978 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6979 .await
6980 .ok()
6981 })
6982 .detach();
6983 });
6984 }
6985 }
6986
6987 pub async fn open_project_transaction(
6988 editor: &WeakEntity<Editor>,
6989 workspace: WeakEntity<Workspace>,
6990 transaction: ProjectTransaction,
6991 title: String,
6992 cx: &mut AsyncWindowContext,
6993 ) -> Result<()> {
6994 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6995 cx.update(|_, cx| {
6996 entries.sort_unstable_by_key(|(buffer, _)| {
6997 buffer.read(cx).file().map(|f| f.path().clone())
6998 });
6999 })?;
7000 if entries.is_empty() {
7001 return Ok(());
7002 }
7003
7004 // If the project transaction's edits are all contained within this editor, then
7005 // avoid opening a new editor to display them.
7006
7007 if let [(buffer, transaction)] = &*entries {
7008 let excerpt = editor.update(cx, |editor, cx| {
7009 editor
7010 .buffer()
7011 .read(cx)
7012 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7013 })?;
7014 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7015 && excerpted_buffer == *buffer
7016 {
7017 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7018 let excerpt_range = excerpt_range.to_offset(buffer);
7019 buffer
7020 .edited_ranges_for_transaction::<usize>(transaction)
7021 .all(|range| {
7022 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7023 })
7024 });
7025
7026 if all_edits_within_excerpt {
7027 return Ok(());
7028 }
7029 }
7030 }
7031
7032 let mut ranges_to_highlight = Vec::new();
7033 let excerpt_buffer = cx.new(|cx| {
7034 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7035 for (buffer_handle, transaction) in &entries {
7036 let edited_ranges = buffer_handle
7037 .read(cx)
7038 .edited_ranges_for_transaction::<Point>(transaction)
7039 .collect::<Vec<_>>();
7040 let (ranges, _) = multibuffer.set_excerpts_for_path(
7041 PathKey::for_buffer(buffer_handle, cx),
7042 buffer_handle.clone(),
7043 edited_ranges,
7044 multibuffer_context_lines(cx),
7045 cx,
7046 );
7047
7048 ranges_to_highlight.extend(ranges);
7049 }
7050 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7051 multibuffer
7052 });
7053
7054 workspace.update_in(cx, |workspace, window, cx| {
7055 let project = workspace.project().clone();
7056 let editor =
7057 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7058 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7059 editor.update(cx, |editor, cx| {
7060 editor.highlight_background(
7061 HighlightKey::Editor,
7062 &ranges_to_highlight,
7063 |_, theme| theme.colors().editor_highlighted_line_background,
7064 cx,
7065 );
7066 });
7067 })?;
7068
7069 Ok(())
7070 }
7071
7072 pub fn clear_code_action_providers(&mut self) {
7073 self.code_action_providers.clear();
7074 self.available_code_actions.take();
7075 }
7076
7077 pub fn add_code_action_provider(
7078 &mut self,
7079 provider: Rc<dyn CodeActionProvider>,
7080 window: &mut Window,
7081 cx: &mut Context<Self>,
7082 ) {
7083 if self
7084 .code_action_providers
7085 .iter()
7086 .any(|existing_provider| existing_provider.id() == provider.id())
7087 {
7088 return;
7089 }
7090
7091 self.code_action_providers.push(provider);
7092 self.refresh_code_actions(window, cx);
7093 }
7094
7095 pub fn remove_code_action_provider(
7096 &mut self,
7097 id: Arc<str>,
7098 window: &mut Window,
7099 cx: &mut Context<Self>,
7100 ) {
7101 self.code_action_providers
7102 .retain(|provider| provider.id() != id);
7103 self.refresh_code_actions(window, cx);
7104 }
7105
7106 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7107 !self.code_action_providers.is_empty()
7108 && EditorSettings::get_global(cx).toolbar.code_actions
7109 }
7110
7111 pub fn has_available_code_actions(&self) -> bool {
7112 self.available_code_actions
7113 .as_ref()
7114 .is_some_and(|(_, actions)| !actions.is_empty())
7115 }
7116
7117 fn render_inline_code_actions(
7118 &self,
7119 icon_size: ui::IconSize,
7120 display_row: DisplayRow,
7121 is_active: bool,
7122 cx: &mut Context<Self>,
7123 ) -> AnyElement {
7124 let show_tooltip = !self.context_menu_visible();
7125 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7126 .icon_size(icon_size)
7127 .shape(ui::IconButtonShape::Square)
7128 .icon_color(ui::Color::Hidden)
7129 .toggle_state(is_active)
7130 .when(show_tooltip, |this| {
7131 this.tooltip({
7132 let focus_handle = self.focus_handle.clone();
7133 move |_window, cx| {
7134 Tooltip::for_action_in(
7135 "Toggle Code Actions",
7136 &ToggleCodeActions {
7137 deployed_from: None,
7138 quick_launch: false,
7139 },
7140 &focus_handle,
7141 cx,
7142 )
7143 }
7144 })
7145 })
7146 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7147 window.focus(&editor.focus_handle(cx), cx);
7148 editor.toggle_code_actions(
7149 &crate::actions::ToggleCodeActions {
7150 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7151 display_row,
7152 )),
7153 quick_launch: false,
7154 },
7155 window,
7156 cx,
7157 );
7158 }))
7159 .into_any_element()
7160 }
7161
7162 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7163 &self.context_menu
7164 }
7165
7166 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7167 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7168 cx.background_executor()
7169 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7170 .await;
7171
7172 let (start_buffer, start, _, end, newest_selection) = this
7173 .update(cx, |this, cx| {
7174 let newest_selection = this.selections.newest_anchor().clone();
7175 if newest_selection.head().diff_base_anchor.is_some() {
7176 return None;
7177 }
7178 let display_snapshot = this.display_snapshot(cx);
7179 let newest_selection_adjusted =
7180 this.selections.newest_adjusted(&display_snapshot);
7181 let buffer = this.buffer.read(cx);
7182
7183 let (start_buffer, start) =
7184 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7185 let (end_buffer, end) =
7186 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7187
7188 Some((start_buffer, start, end_buffer, end, newest_selection))
7189 })?
7190 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7191 .context(
7192 "Expected selection to lie in a single buffer when refreshing code actions",
7193 )?;
7194 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7195 let providers = this.code_action_providers.clone();
7196 let tasks = this
7197 .code_action_providers
7198 .iter()
7199 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7200 .collect::<Vec<_>>();
7201 (providers, tasks)
7202 })?;
7203
7204 let mut actions = Vec::new();
7205 for (provider, provider_actions) in
7206 providers.into_iter().zip(future::join_all(tasks).await)
7207 {
7208 if let Some(provider_actions) = provider_actions.log_err() {
7209 actions.extend(provider_actions.into_iter().map(|action| {
7210 AvailableCodeAction {
7211 excerpt_id: newest_selection.start.excerpt_id,
7212 action,
7213 provider: provider.clone(),
7214 }
7215 }));
7216 }
7217 }
7218
7219 this.update(cx, |this, cx| {
7220 this.available_code_actions = if actions.is_empty() {
7221 None
7222 } else {
7223 Some((
7224 Location {
7225 buffer: start_buffer,
7226 range: start..end,
7227 },
7228 actions.into(),
7229 ))
7230 };
7231 cx.notify();
7232 })
7233 }));
7234 }
7235
7236 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7237 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7238 self.show_git_blame_inline = false;
7239
7240 self.show_git_blame_inline_delay_task =
7241 Some(cx.spawn_in(window, async move |this, cx| {
7242 cx.background_executor().timer(delay).await;
7243
7244 this.update(cx, |this, cx| {
7245 this.show_git_blame_inline = true;
7246 cx.notify();
7247 })
7248 .log_err();
7249 }));
7250 }
7251 }
7252
7253 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7254 let snapshot = self.snapshot(window, cx);
7255 let cursor = self
7256 .selections
7257 .newest::<Point>(&snapshot.display_snapshot)
7258 .head();
7259 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7260 else {
7261 return;
7262 };
7263
7264 if self.blame.is_none() {
7265 self.start_git_blame(true, window, cx);
7266 }
7267 let Some(blame) = self.blame.as_ref() else {
7268 return;
7269 };
7270
7271 let row_info = RowInfo {
7272 buffer_id: Some(buffer.remote_id()),
7273 buffer_row: Some(point.row),
7274 ..Default::default()
7275 };
7276 let Some((buffer, blame_entry)) = blame
7277 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7278 .flatten()
7279 else {
7280 return;
7281 };
7282
7283 let anchor = self.selections.newest_anchor().head();
7284 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7285 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7286 self.show_blame_popover(
7287 buffer,
7288 &blame_entry,
7289 position + last_bounds.origin,
7290 true,
7291 cx,
7292 );
7293 };
7294 }
7295
7296 fn show_blame_popover(
7297 &mut self,
7298 buffer: BufferId,
7299 blame_entry: &BlameEntry,
7300 position: gpui::Point<Pixels>,
7301 ignore_timeout: bool,
7302 cx: &mut Context<Self>,
7303 ) {
7304 if let Some(state) = &mut self.inline_blame_popover {
7305 state.hide_task.take();
7306 } else {
7307 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7308 let blame_entry = blame_entry.clone();
7309 let show_task = cx.spawn(async move |editor, cx| {
7310 if !ignore_timeout {
7311 cx.background_executor()
7312 .timer(std::time::Duration::from_millis(blame_popover_delay))
7313 .await;
7314 }
7315 editor
7316 .update(cx, |editor, cx| {
7317 editor.inline_blame_popover_show_task.take();
7318 let Some(blame) = editor.blame.as_ref() else {
7319 return;
7320 };
7321 let blame = blame.read(cx);
7322 let details = blame.details_for_entry(buffer, &blame_entry);
7323 let markdown = cx.new(|cx| {
7324 Markdown::new(
7325 details
7326 .as_ref()
7327 .map(|message| message.message.clone())
7328 .unwrap_or_default(),
7329 None,
7330 None,
7331 cx,
7332 )
7333 });
7334 editor.inline_blame_popover = Some(InlineBlamePopover {
7335 position,
7336 hide_task: None,
7337 popover_bounds: None,
7338 popover_state: InlineBlamePopoverState {
7339 scroll_handle: ScrollHandle::new(),
7340 commit_message: details,
7341 markdown,
7342 },
7343 keyboard_grace: ignore_timeout,
7344 });
7345 cx.notify();
7346 })
7347 .ok();
7348 });
7349 self.inline_blame_popover_show_task = Some(show_task);
7350 }
7351 }
7352
7353 pub fn has_mouse_context_menu(&self) -> bool {
7354 self.mouse_context_menu.is_some()
7355 }
7356
7357 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7358 self.inline_blame_popover_show_task.take();
7359 if let Some(state) = &mut self.inline_blame_popover {
7360 let hide_task = cx.spawn(async move |editor, cx| {
7361 if !ignore_timeout {
7362 cx.background_executor()
7363 .timer(std::time::Duration::from_millis(100))
7364 .await;
7365 }
7366 editor
7367 .update(cx, |editor, cx| {
7368 editor.inline_blame_popover.take();
7369 cx.notify();
7370 })
7371 .ok();
7372 });
7373 state.hide_task = Some(hide_task);
7374 true
7375 } else {
7376 false
7377 }
7378 }
7379
7380 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7381 if self.pending_rename.is_some() {
7382 return None;
7383 }
7384
7385 let provider = self.semantics_provider.clone()?;
7386 let buffer = self.buffer.read(cx);
7387 let newest_selection = self.selections.newest_anchor().clone();
7388 let cursor_position = newest_selection.head();
7389 let (cursor_buffer, cursor_buffer_position) =
7390 buffer.text_anchor_for_position(cursor_position, cx)?;
7391 let (tail_buffer, tail_buffer_position) =
7392 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7393 if cursor_buffer != tail_buffer {
7394 return None;
7395 }
7396
7397 let snapshot = cursor_buffer.read(cx).snapshot();
7398 let word_ranges = cx.background_spawn(async move {
7399 // this might look odd to put on the background thread, but
7400 // `surrounding_word` can be quite expensive as it calls into
7401 // tree-sitter language scopes
7402 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7403 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7404 (start_word_range, end_word_range)
7405 });
7406
7407 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7408 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7409 let (start_word_range, end_word_range) = word_ranges.await;
7410 if start_word_range != end_word_range {
7411 this.update(cx, |this, cx| {
7412 this.document_highlights_task.take();
7413 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7414 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7415 })
7416 .ok();
7417 return;
7418 }
7419 cx.background_executor()
7420 .timer(Duration::from_millis(debounce))
7421 .await;
7422
7423 let highlights = if let Some(highlights) = cx.update(|cx| {
7424 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7425 }) {
7426 highlights.await.log_err()
7427 } else {
7428 None
7429 };
7430
7431 if let Some(highlights) = highlights {
7432 this.update(cx, |this, cx| {
7433 if this.pending_rename.is_some() {
7434 return;
7435 }
7436
7437 let buffer = this.buffer.read(cx);
7438 if buffer
7439 .text_anchor_for_position(cursor_position, cx)
7440 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7441 {
7442 return;
7443 }
7444
7445 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7446 let mut write_ranges = Vec::new();
7447 let mut read_ranges = Vec::new();
7448 for highlight in highlights {
7449 let buffer_id = cursor_buffer.read(cx).remote_id();
7450 for (excerpt_id, _, excerpt_range) in
7451 buffer.excerpts_for_buffer(buffer_id, cx)
7452 {
7453 let start = highlight
7454 .range
7455 .start
7456 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7457 let end = highlight
7458 .range
7459 .end
7460 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7461 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7462 continue;
7463 }
7464
7465 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7466 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7467 write_ranges.push(range);
7468 } else {
7469 read_ranges.push(range);
7470 }
7471 }
7472 }
7473
7474 this.highlight_background(
7475 HighlightKey::DocumentHighlightRead,
7476 &read_ranges,
7477 |_, theme| theme.colors().editor_document_highlight_read_background,
7478 cx,
7479 );
7480 this.highlight_background(
7481 HighlightKey::DocumentHighlightWrite,
7482 &write_ranges,
7483 |_, theme| theme.colors().editor_document_highlight_write_background,
7484 cx,
7485 );
7486 cx.notify();
7487 })
7488 .log_err();
7489 }
7490 }));
7491 None
7492 }
7493
7494 fn prepare_highlight_query_from_selection(
7495 &mut self,
7496 snapshot: &DisplaySnapshot,
7497 cx: &mut Context<Editor>,
7498 ) -> Option<(String, Range<Anchor>)> {
7499 if matches!(self.mode, EditorMode::SingleLine) {
7500 return None;
7501 }
7502 if !EditorSettings::get_global(cx).selection_highlight {
7503 return None;
7504 }
7505 if self.selections.count() != 1 || self.selections.line_mode() {
7506 return None;
7507 }
7508 let selection = self.selections.newest::<Point>(&snapshot);
7509 // If the selection spans multiple rows OR it is empty
7510 if selection.start.row != selection.end.row
7511 || selection.start.column == selection.end.column
7512 {
7513 return None;
7514 }
7515 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7516 let query = snapshot
7517 .buffer_snapshot()
7518 .text_for_range(selection_anchor_range.clone())
7519 .collect::<String>();
7520 if query.trim().is_empty() {
7521 return None;
7522 }
7523 Some((query, selection_anchor_range))
7524 }
7525
7526 #[ztracing::instrument(skip_all)]
7527 fn update_selection_occurrence_highlights(
7528 &mut self,
7529 multi_buffer_snapshot: MultiBufferSnapshot,
7530 query_text: String,
7531 query_range: Range<Anchor>,
7532 multi_buffer_range_to_query: Range<Point>,
7533 use_debounce: bool,
7534 window: &mut Window,
7535 cx: &mut Context<Editor>,
7536 ) -> Task<()> {
7537 cx.spawn_in(window, async move |editor, cx| {
7538 if use_debounce {
7539 cx.background_executor()
7540 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7541 .await;
7542 }
7543 let match_task = cx.background_spawn(async move {
7544 let buffer_ranges = multi_buffer_snapshot
7545 .range_to_buffer_ranges(
7546 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7547 )
7548 .into_iter()
7549 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7550 let mut match_ranges = Vec::new();
7551 let Ok(regex) = project::search::SearchQuery::text(
7552 query_text,
7553 false,
7554 false,
7555 false,
7556 Default::default(),
7557 Default::default(),
7558 false,
7559 None,
7560 ) else {
7561 return Vec::default();
7562 };
7563 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7564 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7565 match_ranges.extend(
7566 regex
7567 .search(
7568 buffer_snapshot,
7569 Some(search_range.start.0..search_range.end.0),
7570 )
7571 .await
7572 .into_iter()
7573 .filter_map(|match_range| {
7574 let match_start = buffer_snapshot
7575 .anchor_after(search_range.start + match_range.start);
7576 let match_end = buffer_snapshot
7577 .anchor_before(search_range.start + match_range.end);
7578 let match_anchor_range =
7579 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7580 (match_anchor_range != query_range).then_some(match_anchor_range)
7581 }),
7582 );
7583 }
7584 match_ranges
7585 });
7586 let match_ranges = match_task.await;
7587 editor
7588 .update_in(cx, |editor, _, cx| {
7589 if use_debounce {
7590 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7591 editor.debounced_selection_highlight_complete = true;
7592 } else if editor.debounced_selection_highlight_complete {
7593 return;
7594 }
7595 if !match_ranges.is_empty() {
7596 editor.highlight_background(
7597 HighlightKey::SelectedTextHighlight,
7598 &match_ranges,
7599 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7600 cx,
7601 )
7602 }
7603 })
7604 .log_err();
7605 })
7606 }
7607
7608 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7609 struct NewlineFold;
7610 let type_id = std::any::TypeId::of::<NewlineFold>();
7611 if !self.mode.is_single_line() {
7612 return;
7613 }
7614 let snapshot = self.snapshot(window, cx);
7615 if snapshot.buffer_snapshot().max_point().row == 0 {
7616 return;
7617 }
7618 let task = cx.background_spawn(async move {
7619 let new_newlines = snapshot
7620 .buffer_chars_at(MultiBufferOffset(0))
7621 .filter_map(|(c, i)| {
7622 if c == '\n' {
7623 Some(
7624 snapshot.buffer_snapshot().anchor_after(i)
7625 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7626 )
7627 } else {
7628 None
7629 }
7630 })
7631 .collect::<Vec<_>>();
7632 let existing_newlines = snapshot
7633 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7634 .filter_map(|fold| {
7635 if fold.placeholder.type_tag == Some(type_id) {
7636 Some(fold.range.start..fold.range.end)
7637 } else {
7638 None
7639 }
7640 })
7641 .collect::<Vec<_>>();
7642
7643 (new_newlines, existing_newlines)
7644 });
7645 self.folding_newlines = cx.spawn(async move |this, cx| {
7646 let (new_newlines, existing_newlines) = task.await;
7647 if new_newlines == existing_newlines {
7648 return;
7649 }
7650 let placeholder = FoldPlaceholder {
7651 render: Arc::new(move |_, _, cx| {
7652 div()
7653 .bg(cx.theme().status().hint_background)
7654 .border_b_1()
7655 .size_full()
7656 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7657 .border_color(cx.theme().status().hint)
7658 .child("\\n")
7659 .into_any()
7660 }),
7661 constrain_width: false,
7662 merge_adjacent: false,
7663 type_tag: Some(type_id),
7664 collapsed_text: None,
7665 };
7666 let creases = new_newlines
7667 .into_iter()
7668 .map(|range| Crease::simple(range, placeholder.clone()))
7669 .collect();
7670 this.update(cx, |this, cx| {
7671 this.display_map.update(cx, |display_map, cx| {
7672 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7673 display_map.fold(creases, cx);
7674 });
7675 })
7676 .ok();
7677 });
7678 }
7679
7680 #[ztracing::instrument(skip_all)]
7681 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7682 if !self.lsp_data_enabled() {
7683 return;
7684 }
7685 let cursor = self.selections.newest_anchor().head();
7686 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7687
7688 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7689 self.outline_symbols_at_cursor =
7690 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7691 cx.emit(EditorEvent::OutlineSymbolsChanged);
7692 cx.notify();
7693 } else {
7694 let syntax = cx.theme().syntax().clone();
7695 let background_task = cx.background_spawn(async move {
7696 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7697 });
7698 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7699 cx.spawn(async move |this, cx| {
7700 let symbols = background_task.await;
7701 this.update(cx, |this, cx| {
7702 this.outline_symbols_at_cursor = symbols;
7703 cx.emit(EditorEvent::OutlineSymbolsChanged);
7704 cx.notify();
7705 })
7706 .ok();
7707 });
7708 }
7709 }
7710
7711 #[ztracing::instrument(skip_all)]
7712 fn refresh_selected_text_highlights(
7713 &mut self,
7714 snapshot: &DisplaySnapshot,
7715 on_buffer_edit: bool,
7716 window: &mut Window,
7717 cx: &mut Context<Editor>,
7718 ) {
7719 let Some((query_text, query_range)) =
7720 self.prepare_highlight_query_from_selection(snapshot, cx)
7721 else {
7722 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7723 self.quick_selection_highlight_task.take();
7724 self.debounced_selection_highlight_task.take();
7725 self.debounced_selection_highlight_complete = false;
7726 return;
7727 };
7728 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7729 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7730 let query_changed = self
7731 .quick_selection_highlight_task
7732 .as_ref()
7733 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7734 if query_changed {
7735 self.debounced_selection_highlight_complete = false;
7736 }
7737 if on_buffer_edit || query_changed {
7738 self.quick_selection_highlight_task = Some((
7739 query_range.clone(),
7740 self.update_selection_occurrence_highlights(
7741 snapshot.buffer.clone(),
7742 query_text.clone(),
7743 query_range.clone(),
7744 self.multi_buffer_visible_range(&display_snapshot, cx),
7745 false,
7746 window,
7747 cx,
7748 ),
7749 ));
7750 }
7751 if on_buffer_edit
7752 || self
7753 .debounced_selection_highlight_task
7754 .as_ref()
7755 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7756 {
7757 let multi_buffer_start = multi_buffer_snapshot
7758 .anchor_before(MultiBufferOffset(0))
7759 .to_point(&multi_buffer_snapshot);
7760 let multi_buffer_end = multi_buffer_snapshot
7761 .anchor_after(multi_buffer_snapshot.len())
7762 .to_point(&multi_buffer_snapshot);
7763 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7764 self.debounced_selection_highlight_task = Some((
7765 query_range.clone(),
7766 self.update_selection_occurrence_highlights(
7767 snapshot.buffer.clone(),
7768 query_text,
7769 query_range,
7770 multi_buffer_full_range,
7771 true,
7772 window,
7773 cx,
7774 ),
7775 ));
7776 }
7777 }
7778
7779 pub fn multi_buffer_visible_range(
7780 &self,
7781 display_snapshot: &DisplaySnapshot,
7782 cx: &App,
7783 ) -> Range<Point> {
7784 let visible_start = self
7785 .scroll_manager
7786 .native_anchor(display_snapshot, cx)
7787 .anchor
7788 .to_point(display_snapshot.buffer_snapshot())
7789 .to_display_point(display_snapshot);
7790
7791 let mut target_end = visible_start;
7792 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7793
7794 visible_start.to_point(display_snapshot)
7795 ..display_snapshot
7796 .clip_point(target_end, Bias::Right)
7797 .to_point(display_snapshot)
7798 }
7799
7800 pub fn refresh_edit_prediction(
7801 &mut self,
7802 debounce: bool,
7803 user_requested: bool,
7804 window: &mut Window,
7805 cx: &mut Context<Self>,
7806 ) -> Option<()> {
7807 let provider = self.edit_prediction_provider()?;
7808 let cursor = self.selections.newest_anchor().head();
7809 let (buffer, cursor_buffer_position) =
7810 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7811
7812 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7813 return None;
7814 }
7815
7816 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7817 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7818 return None;
7819 }
7820
7821 self.update_visible_edit_prediction(window, cx);
7822
7823 if !user_requested
7824 && (!self.should_show_edit_predictions()
7825 || !self.is_focused(window)
7826 || buffer.read(cx).is_empty())
7827 {
7828 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7829 return None;
7830 }
7831
7832 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7833 Some(())
7834 }
7835
7836 fn show_edit_predictions_in_menu(&self) -> bool {
7837 match self.edit_prediction_settings {
7838 EditPredictionSettings::Disabled => false,
7839 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7840 }
7841 }
7842
7843 pub fn edit_predictions_enabled(&self) -> bool {
7844 match self.edit_prediction_settings {
7845 EditPredictionSettings::Disabled => false,
7846 EditPredictionSettings::Enabled { .. } => true,
7847 }
7848 }
7849
7850 fn edit_prediction_requires_modifier(&self) -> bool {
7851 match self.edit_prediction_settings {
7852 EditPredictionSettings::Disabled => false,
7853 EditPredictionSettings::Enabled {
7854 preview_requires_modifier,
7855 ..
7856 } => preview_requires_modifier,
7857 }
7858 }
7859
7860 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7861 if self.edit_prediction_provider.is_none() {
7862 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7863 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7864 return;
7865 }
7866
7867 let selection = self.selections.newest_anchor();
7868 let cursor = selection.head();
7869
7870 if let Some((buffer, cursor_buffer_position)) =
7871 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7872 {
7873 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7874 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7875 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7876 return;
7877 }
7878 self.edit_prediction_settings =
7879 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7880 }
7881 }
7882
7883 fn edit_prediction_settings_at_position(
7884 &self,
7885 buffer: &Entity<Buffer>,
7886 buffer_position: language::Anchor,
7887 cx: &App,
7888 ) -> EditPredictionSettings {
7889 if !self.mode.is_full()
7890 || !self.show_edit_predictions_override.unwrap_or(true)
7891 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7892 {
7893 return EditPredictionSettings::Disabled;
7894 }
7895
7896 let buffer = buffer.read(cx);
7897
7898 let file = buffer.file();
7899
7900 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7901 return EditPredictionSettings::Disabled;
7902 };
7903
7904 let by_provider = matches!(
7905 self.menu_edit_predictions_policy,
7906 MenuEditPredictionsPolicy::ByProvider
7907 );
7908
7909 let show_in_menu = by_provider
7910 && self
7911 .edit_prediction_provider
7912 .as_ref()
7913 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7914
7915 let preview_requires_modifier =
7916 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7917
7918 EditPredictionSettings::Enabled {
7919 show_in_menu,
7920 preview_requires_modifier,
7921 }
7922 }
7923
7924 fn should_show_edit_predictions(&self) -> bool {
7925 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7926 }
7927
7928 pub fn edit_prediction_preview_is_active(&self) -> bool {
7929 matches!(
7930 self.edit_prediction_preview,
7931 EditPredictionPreview::Active { .. }
7932 )
7933 }
7934
7935 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7936 let cursor = self.selections.newest_anchor().head();
7937 if let Some((buffer, cursor_position)) =
7938 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7939 {
7940 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7941 } else {
7942 false
7943 }
7944 }
7945
7946 pub fn supports_minimap(&self, cx: &App) -> bool {
7947 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7948 }
7949
7950 fn edit_predictions_enabled_in_buffer(
7951 &self,
7952 buffer: &Entity<Buffer>,
7953 buffer_position: language::Anchor,
7954 cx: &App,
7955 ) -> bool {
7956 maybe!({
7957 if self.read_only(cx) {
7958 return Some(false);
7959 }
7960 let provider = self.edit_prediction_provider()?;
7961 if !provider.is_enabled(buffer, buffer_position, cx) {
7962 return Some(false);
7963 }
7964 let buffer = buffer.read(cx);
7965 let Some(file) = buffer.file() else {
7966 return Some(true);
7967 };
7968 let settings = all_language_settings(Some(file), cx);
7969 Some(settings.edit_predictions_enabled_for_file(file, cx))
7970 })
7971 .unwrap_or(false)
7972 }
7973
7974 pub fn show_edit_prediction(
7975 &mut self,
7976 _: &ShowEditPrediction,
7977 window: &mut Window,
7978 cx: &mut Context<Self>,
7979 ) {
7980 if !self.has_active_edit_prediction() {
7981 self.refresh_edit_prediction(false, true, window, cx);
7982 return;
7983 }
7984
7985 self.update_visible_edit_prediction(window, cx);
7986 }
7987
7988 pub fn display_cursor_names(
7989 &mut self,
7990 _: &DisplayCursorNames,
7991 window: &mut Window,
7992 cx: &mut Context<Self>,
7993 ) {
7994 self.show_cursor_names(window, cx);
7995 }
7996
7997 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7998 self.show_cursor_names = true;
7999 cx.notify();
8000 cx.spawn_in(window, async move |this, cx| {
8001 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8002 this.update(cx, |this, cx| {
8003 this.show_cursor_names = false;
8004 cx.notify()
8005 })
8006 .ok()
8007 })
8008 .detach();
8009 }
8010
8011 pub fn accept_partial_edit_prediction(
8012 &mut self,
8013 granularity: EditPredictionGranularity,
8014 window: &mut Window,
8015 cx: &mut Context<Self>,
8016 ) {
8017 if self.show_edit_predictions_in_menu() {
8018 self.hide_context_menu(window, cx);
8019 }
8020
8021 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8022 return;
8023 };
8024
8025 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8026 return;
8027 }
8028
8029 match &active_edit_prediction.completion {
8030 EditPrediction::MoveWithin { target, .. } => {
8031 let target = *target;
8032
8033 if matches!(granularity, EditPredictionGranularity::Full) {
8034 if let Some(position_map) = &self.last_position_map {
8035 let target_row = target.to_display_point(&position_map.snapshot).row();
8036 let is_visible = position_map.visible_row_range.contains(&target_row);
8037
8038 if is_visible || !self.edit_prediction_requires_modifier() {
8039 self.unfold_ranges(&[target..target], true, false, cx);
8040 self.change_selections(
8041 SelectionEffects::scroll(Autoscroll::newest()),
8042 window,
8043 cx,
8044 |selections| {
8045 selections.select_anchor_ranges([target..target]);
8046 },
8047 );
8048 self.clear_row_highlights::<EditPredictionPreview>();
8049 self.edit_prediction_preview
8050 .set_previous_scroll_position(None);
8051 } else {
8052 // Highlight and request scroll
8053 self.edit_prediction_preview
8054 .set_previous_scroll_position(Some(
8055 position_map.snapshot.scroll_anchor,
8056 ));
8057 self.highlight_rows::<EditPredictionPreview>(
8058 target..target,
8059 cx.theme().colors().editor_highlighted_line_background,
8060 RowHighlightOptions {
8061 autoscroll: true,
8062 ..Default::default()
8063 },
8064 cx,
8065 );
8066 self.request_autoscroll(Autoscroll::fit(), cx);
8067 }
8068 }
8069 } else {
8070 self.change_selections(
8071 SelectionEffects::scroll(Autoscroll::newest()),
8072 window,
8073 cx,
8074 |selections| {
8075 selections.select_anchor_ranges([target..target]);
8076 },
8077 );
8078 }
8079 }
8080 EditPrediction::MoveOutside { snapshot, target } => {
8081 if let Some(workspace) = self.workspace() {
8082 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8083 .detach_and_log_err(cx);
8084 }
8085 }
8086 EditPrediction::Edit {
8087 edits,
8088 cursor_position,
8089 ..
8090 } => {
8091 self.report_edit_prediction_event(
8092 active_edit_prediction.completion_id.clone(),
8093 true,
8094 cx,
8095 );
8096
8097 match granularity {
8098 EditPredictionGranularity::Full => {
8099 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8100
8101 // Compute fallback cursor position BEFORE applying the edit,
8102 // so the anchor tracks through the edit correctly
8103 let fallback_cursor_target = {
8104 let snapshot = self.buffer.read(cx).snapshot(cx);
8105 edits.last().unwrap().0.end.bias_right(&snapshot)
8106 };
8107
8108 self.buffer.update(cx, |buffer, cx| {
8109 buffer.edit(edits.iter().cloned(), None, cx)
8110 });
8111
8112 if let Some(provider) = self.edit_prediction_provider() {
8113 provider.accept(cx);
8114 }
8115
8116 // Resolve cursor position after the edit is applied
8117 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8118 // The anchor tracks through the edit, then we add the offset
8119 let snapshot = self.buffer.read(cx).snapshot(cx);
8120 let base_offset = anchor.to_offset(&snapshot).0;
8121 let target_offset =
8122 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8123 snapshot.anchor_after(target_offset)
8124 } else {
8125 fallback_cursor_target
8126 };
8127
8128 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8129 s.select_anchor_ranges([cursor_target..cursor_target]);
8130 });
8131
8132 let selections = self.selections.disjoint_anchors_arc();
8133 if let Some(transaction_id_now) =
8134 self.buffer.read(cx).last_transaction_id(cx)
8135 {
8136 if transaction_id_prev != Some(transaction_id_now) {
8137 self.selection_history
8138 .insert_transaction(transaction_id_now, selections);
8139 }
8140 }
8141
8142 self.update_visible_edit_prediction(window, cx);
8143 if self.active_edit_prediction.is_none() {
8144 self.refresh_edit_prediction(true, true, window, cx);
8145 }
8146 cx.notify();
8147 }
8148 _ => {
8149 let snapshot = self.buffer.read(cx).snapshot(cx);
8150 let cursor_offset = self
8151 .selections
8152 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8153 .head();
8154
8155 let insertion = edits.iter().find_map(|(range, text)| {
8156 let range = range.to_offset(&snapshot);
8157 if range.is_empty() && range.start == cursor_offset {
8158 Some(text)
8159 } else {
8160 None
8161 }
8162 });
8163
8164 if let Some(text) = insertion {
8165 let text_to_insert = match granularity {
8166 EditPredictionGranularity::Word => {
8167 let mut partial = text
8168 .chars()
8169 .by_ref()
8170 .take_while(|c| c.is_alphabetic())
8171 .collect::<String>();
8172 if partial.is_empty() {
8173 partial = text
8174 .chars()
8175 .by_ref()
8176 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8177 .collect::<String>();
8178 }
8179 partial
8180 }
8181 EditPredictionGranularity::Line => {
8182 if let Some(line) = text.split_inclusive('\n').next() {
8183 line.to_string()
8184 } else {
8185 text.to_string()
8186 }
8187 }
8188 EditPredictionGranularity::Full => unreachable!(),
8189 };
8190
8191 cx.emit(EditorEvent::InputHandled {
8192 utf16_range_to_replace: None,
8193 text: text_to_insert.clone().into(),
8194 });
8195
8196 self.replace_selections(&text_to_insert, None, window, cx, false);
8197 self.refresh_edit_prediction(true, true, window, cx);
8198 cx.notify();
8199 } else {
8200 self.accept_partial_edit_prediction(
8201 EditPredictionGranularity::Full,
8202 window,
8203 cx,
8204 );
8205 }
8206 }
8207 }
8208 }
8209 }
8210
8211 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8212 }
8213
8214 pub fn accept_next_word_edit_prediction(
8215 &mut self,
8216 _: &AcceptNextWordEditPrediction,
8217 window: &mut Window,
8218 cx: &mut Context<Self>,
8219 ) {
8220 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8221 }
8222
8223 pub fn accept_next_line_edit_prediction(
8224 &mut self,
8225 _: &AcceptNextLineEditPrediction,
8226 window: &mut Window,
8227 cx: &mut Context<Self>,
8228 ) {
8229 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8230 }
8231
8232 pub fn accept_edit_prediction(
8233 &mut self,
8234 _: &AcceptEditPrediction,
8235 window: &mut Window,
8236 cx: &mut Context<Self>,
8237 ) {
8238 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8239 }
8240
8241 fn discard_edit_prediction(
8242 &mut self,
8243 reason: EditPredictionDiscardReason,
8244 cx: &mut Context<Self>,
8245 ) -> bool {
8246 if reason == EditPredictionDiscardReason::Rejected {
8247 let completion_id = self
8248 .active_edit_prediction
8249 .as_ref()
8250 .and_then(|active_completion| active_completion.completion_id.clone());
8251
8252 self.report_edit_prediction_event(completion_id, false, cx);
8253 }
8254
8255 if let Some(provider) = self.edit_prediction_provider() {
8256 provider.discard(reason, cx);
8257 }
8258
8259 self.take_active_edit_prediction(cx)
8260 }
8261
8262 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8263 let Some(provider) = self.edit_prediction_provider() else {
8264 return;
8265 };
8266
8267 let Some((_, buffer, _)) = self
8268 .buffer
8269 .read(cx)
8270 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8271 else {
8272 return;
8273 };
8274
8275 let extension = buffer
8276 .read(cx)
8277 .file()
8278 .and_then(|file| Some(file.path().extension()?.to_string()));
8279
8280 let event_type = match accepted {
8281 true => "Edit Prediction Accepted",
8282 false => "Edit Prediction Discarded",
8283 };
8284 telemetry::event!(
8285 event_type,
8286 provider = provider.name(),
8287 prediction_id = id,
8288 suggestion_accepted = accepted,
8289 file_extension = extension,
8290 );
8291 }
8292
8293 fn open_editor_at_anchor(
8294 snapshot: &language::BufferSnapshot,
8295 target: language::Anchor,
8296 workspace: &Entity<Workspace>,
8297 window: &mut Window,
8298 cx: &mut App,
8299 ) -> Task<Result<()>> {
8300 workspace.update(cx, |workspace, cx| {
8301 let path = snapshot.file().map(|file| file.full_path(cx));
8302 let Some(path) =
8303 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8304 else {
8305 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8306 };
8307 let target = text::ToPoint::to_point(&target, snapshot);
8308 let item = workspace.open_path(path, None, true, window, cx);
8309 window.spawn(cx, async move |cx| {
8310 let Some(editor) = item.await?.downcast::<Editor>() else {
8311 return Ok(());
8312 };
8313 editor
8314 .update_in(cx, |editor, window, cx| {
8315 editor.go_to_singleton_buffer_point(target, window, cx);
8316 })
8317 .ok();
8318 anyhow::Ok(())
8319 })
8320 })
8321 }
8322
8323 pub fn has_active_edit_prediction(&self) -> bool {
8324 self.active_edit_prediction.is_some()
8325 }
8326
8327 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8328 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8329 return false;
8330 };
8331
8332 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8333 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8334 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8335 true
8336 }
8337
8338 /// Returns true when we're displaying the edit prediction popover below the cursor
8339 /// like we are not previewing and the LSP autocomplete menu is visible
8340 /// or we are in `when_holding_modifier` mode.
8341 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8342 if self.edit_prediction_preview_is_active()
8343 || !self.show_edit_predictions_in_menu()
8344 || !self.edit_predictions_enabled()
8345 {
8346 return false;
8347 }
8348
8349 if self.has_visible_completions_menu() {
8350 return true;
8351 }
8352
8353 has_completion && self.edit_prediction_requires_modifier()
8354 }
8355
8356 fn handle_modifiers_changed(
8357 &mut self,
8358 modifiers: Modifiers,
8359 position_map: &PositionMap,
8360 window: &mut Window,
8361 cx: &mut Context<Self>,
8362 ) {
8363 // Ensure that the edit prediction preview is updated, even when not
8364 // enabled, if there's an active edit prediction preview.
8365 if self.show_edit_predictions_in_menu()
8366 || matches!(
8367 self.edit_prediction_preview,
8368 EditPredictionPreview::Active { .. }
8369 )
8370 {
8371 self.update_edit_prediction_preview(&modifiers, window, cx);
8372 }
8373
8374 self.update_selection_mode(&modifiers, position_map, window, cx);
8375
8376 let mouse_position = window.mouse_position();
8377 if !position_map.text_hitbox.is_hovered(window) {
8378 return;
8379 }
8380
8381 self.update_hovered_link(
8382 position_map.point_for_position(mouse_position),
8383 Some(mouse_position),
8384 &position_map.snapshot,
8385 modifiers,
8386 window,
8387 cx,
8388 )
8389 }
8390
8391 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8392 match EditorSettings::get_global(cx).multi_cursor_modifier {
8393 MultiCursorModifier::Alt => modifiers.secondary(),
8394 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8395 }
8396 }
8397
8398 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8399 match EditorSettings::get_global(cx).multi_cursor_modifier {
8400 MultiCursorModifier::Alt => modifiers.alt,
8401 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8402 }
8403 }
8404
8405 fn columnar_selection_mode(
8406 modifiers: &Modifiers,
8407 cx: &mut Context<Self>,
8408 ) -> Option<ColumnarMode> {
8409 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8410 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8411 Some(ColumnarMode::FromMouse)
8412 } else if Self::is_alt_pressed(modifiers, cx) {
8413 Some(ColumnarMode::FromSelection)
8414 } else {
8415 None
8416 }
8417 } else {
8418 None
8419 }
8420 }
8421
8422 fn update_selection_mode(
8423 &mut self,
8424 modifiers: &Modifiers,
8425 position_map: &PositionMap,
8426 window: &mut Window,
8427 cx: &mut Context<Self>,
8428 ) {
8429 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8430 return;
8431 };
8432 if self.selections.pending_anchor().is_none() {
8433 return;
8434 }
8435
8436 let mouse_position = window.mouse_position();
8437 let point_for_position = position_map.point_for_position(mouse_position);
8438 let position = point_for_position.previous_valid;
8439
8440 self.select(
8441 SelectPhase::BeginColumnar {
8442 position,
8443 reset: false,
8444 mode,
8445 goal_column: point_for_position.exact_unclipped.column(),
8446 },
8447 window,
8448 cx,
8449 );
8450 }
8451
8452 fn update_edit_prediction_preview(
8453 &mut self,
8454 modifiers: &Modifiers,
8455 window: &mut Window,
8456 cx: &mut Context<Self>,
8457 ) {
8458 let mut modifiers_held = false;
8459
8460 // Check bindings for all granularities.
8461 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8462 let granularities = [
8463 EditPredictionGranularity::Full,
8464 EditPredictionGranularity::Line,
8465 EditPredictionGranularity::Word,
8466 ];
8467
8468 for granularity in granularities {
8469 if let Some(keystroke) = self
8470 .accept_edit_prediction_keybind(granularity, window, cx)
8471 .keystroke()
8472 {
8473 modifiers_held = modifiers_held
8474 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8475 }
8476 }
8477
8478 if modifiers_held {
8479 if matches!(
8480 self.edit_prediction_preview,
8481 EditPredictionPreview::Inactive { .. }
8482 ) {
8483 self.edit_prediction_preview = EditPredictionPreview::Active {
8484 previous_scroll_position: None,
8485 since: Instant::now(),
8486 };
8487
8488 self.update_visible_edit_prediction(window, cx);
8489 cx.notify();
8490 }
8491 } else if let EditPredictionPreview::Active {
8492 previous_scroll_position,
8493 since,
8494 } = self.edit_prediction_preview
8495 {
8496 if let (Some(previous_scroll_position), Some(position_map)) =
8497 (previous_scroll_position, self.last_position_map.as_ref())
8498 {
8499 self.set_scroll_position(
8500 previous_scroll_position
8501 .scroll_position(&position_map.snapshot.display_snapshot),
8502 window,
8503 cx,
8504 );
8505 }
8506
8507 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8508 released_too_fast: since.elapsed() < Duration::from_millis(200),
8509 };
8510 self.clear_row_highlights::<EditPredictionPreview>();
8511 self.update_visible_edit_prediction(window, cx);
8512 cx.notify();
8513 }
8514 }
8515
8516 fn update_visible_edit_prediction(
8517 &mut self,
8518 _window: &mut Window,
8519 cx: &mut Context<Self>,
8520 ) -> Option<()> {
8521 if self.ime_transaction.is_some() {
8522 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8523 return None;
8524 }
8525
8526 let selection = self.selections.newest_anchor();
8527 let cursor = selection.head();
8528 let multibuffer = self.buffer.read(cx).snapshot(cx);
8529
8530 // Check project-level disable_ai setting for the current buffer
8531 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8532 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8533 return None;
8534 }
8535 }
8536 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8537 let excerpt_id = cursor.excerpt_id;
8538
8539 let show_in_menu = self.show_edit_predictions_in_menu();
8540 let completions_menu_has_precedence = !show_in_menu
8541 && (self.context_menu.borrow().is_some()
8542 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8543
8544 if completions_menu_has_precedence
8545 || !offset_selection.is_empty()
8546 || self
8547 .active_edit_prediction
8548 .as_ref()
8549 .is_some_and(|completion| {
8550 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8551 return false;
8552 };
8553 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8554 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8555 !invalidation_range.contains(&offset_selection.head())
8556 })
8557 {
8558 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8559 return None;
8560 }
8561
8562 self.take_active_edit_prediction(cx);
8563 let Some(provider) = self.edit_prediction_provider() else {
8564 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8565 return None;
8566 };
8567
8568 let (buffer, cursor_buffer_position) =
8569 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8570
8571 self.edit_prediction_settings =
8572 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8573
8574 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8575
8576 if self.edit_prediction_indent_conflict {
8577 let cursor_point = cursor.to_point(&multibuffer);
8578 let mut suggested_indent = None;
8579 multibuffer.suggested_indents_callback(
8580 cursor_point.row..cursor_point.row + 1,
8581 &mut |_, indent| {
8582 suggested_indent = Some(indent);
8583 ControlFlow::Break(())
8584 },
8585 cx,
8586 );
8587
8588 if let Some(indent) = suggested_indent
8589 && indent.len == cursor_point.column
8590 {
8591 self.edit_prediction_indent_conflict = false;
8592 }
8593 }
8594
8595 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8596
8597 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8598 {
8599 edit_prediction_types::EditPrediction::Local {
8600 id,
8601 edits,
8602 cursor_position,
8603 edit_preview,
8604 } => (id, edits, cursor_position, edit_preview),
8605 edit_prediction_types::EditPrediction::Jump {
8606 id,
8607 snapshot,
8608 target,
8609 } => {
8610 if let Some(provider) = &self.edit_prediction_provider {
8611 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8612 }
8613 self.stale_edit_prediction_in_menu = None;
8614 self.active_edit_prediction = Some(EditPredictionState {
8615 inlay_ids: vec![],
8616 completion: EditPrediction::MoveOutside { snapshot, target },
8617 completion_id: id,
8618 invalidation_range: None,
8619 });
8620 cx.notify();
8621 return Some(());
8622 }
8623 };
8624
8625 let edits = edits
8626 .into_iter()
8627 .flat_map(|(range, new_text)| {
8628 Some((
8629 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8630 new_text,
8631 ))
8632 })
8633 .collect::<Vec<_>>();
8634 if edits.is_empty() {
8635 return None;
8636 }
8637
8638 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8639 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8640 Some((anchor, predicted.offset))
8641 });
8642
8643 let first_edit_start = edits.first().unwrap().0.start;
8644 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8645 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8646
8647 let last_edit_end = edits.last().unwrap().0.end;
8648 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8649 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8650
8651 let cursor_row = cursor.to_point(&multibuffer).row;
8652
8653 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8654
8655 let mut inlay_ids = Vec::new();
8656 let invalidation_row_range;
8657 let move_invalidation_row_range = if cursor_row < edit_start_row {
8658 Some(cursor_row..edit_end_row)
8659 } else if cursor_row > edit_end_row {
8660 Some(edit_start_row..cursor_row)
8661 } else {
8662 None
8663 };
8664 let supports_jump = self
8665 .edit_prediction_provider
8666 .as_ref()
8667 .map(|provider| provider.provider.supports_jump_to_edit())
8668 .unwrap_or(true);
8669
8670 let is_move = supports_jump
8671 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8672 let completion = if is_move {
8673 if let Some(provider) = &self.edit_prediction_provider {
8674 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8675 }
8676 invalidation_row_range =
8677 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8678 let target = first_edit_start;
8679 EditPrediction::MoveWithin { target, snapshot }
8680 } else {
8681 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8682 && !self.edit_predictions_hidden_for_vim_mode;
8683
8684 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8685 if provider.show_tab_accept_marker() {
8686 EditDisplayMode::TabAccept
8687 } else {
8688 EditDisplayMode::Inline
8689 }
8690 } else {
8691 EditDisplayMode::DiffPopover
8692 };
8693
8694 if show_completions_in_buffer {
8695 if let Some(provider) = &self.edit_prediction_provider {
8696 let suggestion_display_type = match display_mode {
8697 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8698 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8699 SuggestionDisplayType::GhostText
8700 }
8701 };
8702 provider.provider.did_show(suggestion_display_type, cx);
8703 }
8704 if edits
8705 .iter()
8706 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8707 {
8708 let mut inlays = Vec::new();
8709 for (range, new_text) in &edits {
8710 let inlay = Inlay::edit_prediction(
8711 post_inc(&mut self.next_inlay_id),
8712 range.start,
8713 new_text.as_ref(),
8714 );
8715 inlay_ids.push(inlay.id);
8716 inlays.push(inlay);
8717 }
8718
8719 self.splice_inlays(&[], inlays, cx);
8720 } else {
8721 let background_color = cx.theme().status().deleted_background;
8722 self.highlight_text(
8723 HighlightKey::EditPredictionHighlight,
8724 edits.iter().map(|(range, _)| range.clone()).collect(),
8725 HighlightStyle {
8726 background_color: Some(background_color),
8727 ..Default::default()
8728 },
8729 cx,
8730 );
8731 }
8732 }
8733
8734 invalidation_row_range = edit_start_row..edit_end_row;
8735
8736 EditPrediction::Edit {
8737 edits,
8738 cursor_position,
8739 edit_preview,
8740 display_mode,
8741 snapshot,
8742 }
8743 };
8744
8745 let invalidation_range = multibuffer
8746 .anchor_before(Point::new(invalidation_row_range.start, 0))
8747 ..multibuffer.anchor_after(Point::new(
8748 invalidation_row_range.end,
8749 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8750 ));
8751
8752 self.stale_edit_prediction_in_menu = None;
8753 self.active_edit_prediction = Some(EditPredictionState {
8754 inlay_ids,
8755 completion,
8756 completion_id,
8757 invalidation_range: Some(invalidation_range),
8758 });
8759
8760 cx.notify();
8761
8762 Some(())
8763 }
8764
8765 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8766 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8767 }
8768
8769 /// Get all display points of breakpoints that will be rendered within editor
8770 ///
8771 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8772 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8773 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8774 fn active_breakpoints(
8775 &self,
8776 range: Range<DisplayRow>,
8777 window: &mut Window,
8778 cx: &mut Context<Self>,
8779 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8780 let mut breakpoint_display_points = HashMap::default();
8781
8782 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8783 return breakpoint_display_points;
8784 };
8785
8786 let snapshot = self.snapshot(window, cx);
8787
8788 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8789 let Some(project) = self.project() else {
8790 return breakpoint_display_points;
8791 };
8792
8793 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8794 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8795
8796 for (buffer_snapshot, range, excerpt_id) in
8797 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8798 {
8799 let Some(buffer) = project
8800 .read(cx)
8801 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8802 else {
8803 continue;
8804 };
8805 let breakpoints = breakpoint_store.read(cx).breakpoints(
8806 &buffer,
8807 Some(
8808 buffer_snapshot.anchor_before(range.start)
8809 ..buffer_snapshot.anchor_after(range.end),
8810 ),
8811 buffer_snapshot,
8812 cx,
8813 );
8814 for (breakpoint, state) in breakpoints {
8815 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8816 let position = multi_buffer_anchor
8817 .to_point(&multi_buffer_snapshot)
8818 .to_display_point(&snapshot);
8819
8820 breakpoint_display_points.insert(
8821 position.row(),
8822 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8823 );
8824 }
8825 }
8826
8827 breakpoint_display_points
8828 }
8829
8830 fn breakpoint_context_menu(
8831 &self,
8832 anchor: Anchor,
8833 window: &mut Window,
8834 cx: &mut Context<Self>,
8835 ) -> Entity<ui::ContextMenu> {
8836 let weak_editor = cx.weak_entity();
8837 let focus_handle = self.focus_handle(cx);
8838
8839 let row = self
8840 .buffer
8841 .read(cx)
8842 .snapshot(cx)
8843 .summary_for_anchor::<Point>(&anchor)
8844 .row;
8845
8846 let breakpoint = self
8847 .breakpoint_at_row(row, window, cx)
8848 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8849
8850 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8851 "Edit Log Breakpoint"
8852 } else {
8853 "Set Log Breakpoint"
8854 };
8855
8856 let condition_breakpoint_msg = if breakpoint
8857 .as_ref()
8858 .is_some_and(|bp| bp.1.condition.is_some())
8859 {
8860 "Edit Condition Breakpoint"
8861 } else {
8862 "Set Condition Breakpoint"
8863 };
8864
8865 let hit_condition_breakpoint_msg = if breakpoint
8866 .as_ref()
8867 .is_some_and(|bp| bp.1.hit_condition.is_some())
8868 {
8869 "Edit Hit Condition Breakpoint"
8870 } else {
8871 "Set Hit Condition Breakpoint"
8872 };
8873
8874 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8875 "Unset Breakpoint"
8876 } else {
8877 "Set Breakpoint"
8878 };
8879
8880 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8881
8882 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8883 BreakpointState::Enabled => Some("Disable"),
8884 BreakpointState::Disabled => Some("Enable"),
8885 });
8886
8887 let (anchor, breakpoint) =
8888 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8889
8890 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8891 menu.on_blur_subscription(Subscription::new(|| {}))
8892 .context(focus_handle)
8893 .when(run_to_cursor, |this| {
8894 let weak_editor = weak_editor.clone();
8895 this.entry("Run to Cursor", None, move |window, cx| {
8896 weak_editor
8897 .update(cx, |editor, cx| {
8898 editor.change_selections(
8899 SelectionEffects::no_scroll(),
8900 window,
8901 cx,
8902 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8903 );
8904 })
8905 .ok();
8906
8907 window.dispatch_action(Box::new(RunToCursor), cx);
8908 })
8909 .separator()
8910 })
8911 .when_some(toggle_state_msg, |this, msg| {
8912 this.entry(msg, None, {
8913 let weak_editor = weak_editor.clone();
8914 let breakpoint = breakpoint.clone();
8915 move |_window, cx| {
8916 weak_editor
8917 .update(cx, |this, cx| {
8918 this.edit_breakpoint_at_anchor(
8919 anchor,
8920 breakpoint.as_ref().clone(),
8921 BreakpointEditAction::InvertState,
8922 cx,
8923 );
8924 })
8925 .log_err();
8926 }
8927 })
8928 })
8929 .entry(set_breakpoint_msg, None, {
8930 let weak_editor = weak_editor.clone();
8931 let breakpoint = breakpoint.clone();
8932 move |_window, cx| {
8933 weak_editor
8934 .update(cx, |this, cx| {
8935 this.edit_breakpoint_at_anchor(
8936 anchor,
8937 breakpoint.as_ref().clone(),
8938 BreakpointEditAction::Toggle,
8939 cx,
8940 );
8941 })
8942 .log_err();
8943 }
8944 })
8945 .entry(log_breakpoint_msg, None, {
8946 let breakpoint = breakpoint.clone();
8947 let weak_editor = weak_editor.clone();
8948 move |window, cx| {
8949 weak_editor
8950 .update(cx, |this, cx| {
8951 this.add_edit_breakpoint_block(
8952 anchor,
8953 breakpoint.as_ref(),
8954 BreakpointPromptEditAction::Log,
8955 window,
8956 cx,
8957 );
8958 })
8959 .log_err();
8960 }
8961 })
8962 .entry(condition_breakpoint_msg, None, {
8963 let breakpoint = breakpoint.clone();
8964 let weak_editor = weak_editor.clone();
8965 move |window, cx| {
8966 weak_editor
8967 .update(cx, |this, cx| {
8968 this.add_edit_breakpoint_block(
8969 anchor,
8970 breakpoint.as_ref(),
8971 BreakpointPromptEditAction::Condition,
8972 window,
8973 cx,
8974 );
8975 })
8976 .log_err();
8977 }
8978 })
8979 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8980 weak_editor
8981 .update(cx, |this, cx| {
8982 this.add_edit_breakpoint_block(
8983 anchor,
8984 breakpoint.as_ref(),
8985 BreakpointPromptEditAction::HitCondition,
8986 window,
8987 cx,
8988 );
8989 })
8990 .log_err();
8991 })
8992 })
8993 }
8994
8995 fn render_breakpoint(
8996 &self,
8997 position: Anchor,
8998 row: DisplayRow,
8999 breakpoint: &Breakpoint,
9000 state: Option<BreakpointSessionState>,
9001 cx: &mut Context<Self>,
9002 ) -> IconButton {
9003 let is_rejected = state.is_some_and(|s| !s.verified);
9004 // Is it a breakpoint that shows up when hovering over gutter?
9005 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9006 (false, false),
9007 |PhantomBreakpointIndicator {
9008 is_active,
9009 display_row,
9010 collides_with_existing_breakpoint,
9011 }| {
9012 (
9013 is_active && display_row == row,
9014 collides_with_existing_breakpoint,
9015 )
9016 },
9017 );
9018
9019 let (color, icon) = {
9020 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9021 (false, false) => ui::IconName::DebugBreakpoint,
9022 (true, false) => ui::IconName::DebugLogBreakpoint,
9023 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9024 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9025 };
9026
9027 let theme_colors = cx.theme().colors();
9028
9029 let color = if is_phantom {
9030 if collides_with_existing {
9031 Color::Custom(
9032 theme_colors
9033 .debugger_accent
9034 .blend(theme_colors.text.opacity(0.6)),
9035 )
9036 } else {
9037 Color::Hint
9038 }
9039 } else if is_rejected {
9040 Color::Disabled
9041 } else {
9042 Color::Debugger
9043 };
9044
9045 (color, icon)
9046 };
9047
9048 let breakpoint = Arc::from(breakpoint.clone());
9049
9050 let alt_as_text = gpui::Keystroke {
9051 modifiers: Modifiers::secondary_key(),
9052 ..Default::default()
9053 };
9054 let primary_action_text = if breakpoint.is_disabled() {
9055 "Enable breakpoint"
9056 } else if is_phantom && !collides_with_existing {
9057 "Set breakpoint"
9058 } else {
9059 "Unset breakpoint"
9060 };
9061 let focus_handle = self.focus_handle.clone();
9062
9063 let meta = if is_rejected {
9064 SharedString::from("No executable code is associated with this line.")
9065 } else if collides_with_existing && !breakpoint.is_disabled() {
9066 SharedString::from(format!(
9067 "{alt_as_text}-click to disable,\nright-click for more options."
9068 ))
9069 } else {
9070 SharedString::from("Right-click for more options.")
9071 };
9072 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9073 .icon_size(IconSize::XSmall)
9074 .size(ui::ButtonSize::None)
9075 .when(is_rejected, |this| {
9076 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9077 })
9078 .icon_color(color)
9079 .style(ButtonStyle::Transparent)
9080 .on_click(cx.listener({
9081 move |editor, event: &ClickEvent, window, cx| {
9082 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9083 BreakpointEditAction::InvertState
9084 } else {
9085 BreakpointEditAction::Toggle
9086 };
9087
9088 window.focus(&editor.focus_handle(cx), cx);
9089 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9090 editor.edit_breakpoint_at_anchor(
9091 position,
9092 breakpoint.as_ref().clone(),
9093 edit_action,
9094 cx,
9095 );
9096 }
9097 }))
9098 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9099 editor.set_breakpoint_context_menu(
9100 row,
9101 Some(position),
9102 event.position(),
9103 window,
9104 cx,
9105 );
9106 }))
9107 .tooltip(move |_window, cx| {
9108 Tooltip::with_meta_in(
9109 primary_action_text,
9110 Some(&ToggleBreakpoint),
9111 meta.clone(),
9112 &focus_handle,
9113 cx,
9114 )
9115 })
9116 }
9117
9118 fn build_tasks_context(
9119 project: &Entity<Project>,
9120 buffer: &Entity<Buffer>,
9121 buffer_row: u32,
9122 tasks: &Arc<RunnableTasks>,
9123 cx: &mut Context<Self>,
9124 ) -> Task<Option<task::TaskContext>> {
9125 let position = Point::new(buffer_row, tasks.column);
9126 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9127 let location = Location {
9128 buffer: buffer.clone(),
9129 range: range_start..range_start,
9130 };
9131 // Fill in the environmental variables from the tree-sitter captures
9132 let mut captured_task_variables = TaskVariables::default();
9133 for (capture_name, value) in tasks.extra_variables.clone() {
9134 captured_task_variables.insert(
9135 task::VariableName::Custom(capture_name.into()),
9136 value.clone(),
9137 );
9138 }
9139 project.update(cx, |project, cx| {
9140 project.task_store().update(cx, |task_store, cx| {
9141 task_store.task_context_for_location(captured_task_variables, location, cx)
9142 })
9143 })
9144 }
9145
9146 pub fn context_menu_visible(&self) -> bool {
9147 !self.edit_prediction_preview_is_active()
9148 && self
9149 .context_menu
9150 .borrow()
9151 .as_ref()
9152 .is_some_and(|menu| menu.visible())
9153 }
9154
9155 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9156 self.context_menu
9157 .borrow()
9158 .as_ref()
9159 .map(|menu| menu.origin())
9160 }
9161
9162 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9163 self.context_menu_options = Some(options);
9164 }
9165
9166 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9167 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9168
9169 fn render_edit_prediction_popover(
9170 &mut self,
9171 text_bounds: &Bounds<Pixels>,
9172 content_origin: gpui::Point<Pixels>,
9173 right_margin: Pixels,
9174 editor_snapshot: &EditorSnapshot,
9175 visible_row_range: Range<DisplayRow>,
9176 scroll_top: ScrollOffset,
9177 scroll_bottom: ScrollOffset,
9178 line_layouts: &[LineWithInvisibles],
9179 line_height: Pixels,
9180 scroll_position: gpui::Point<ScrollOffset>,
9181 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9182 newest_selection_head: Option<DisplayPoint>,
9183 editor_width: Pixels,
9184 style: &EditorStyle,
9185 window: &mut Window,
9186 cx: &mut App,
9187 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9188 if self.mode().is_minimap() {
9189 return None;
9190 }
9191 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9192
9193 if self.edit_prediction_visible_in_cursor_popover(true) {
9194 return None;
9195 }
9196
9197 match &active_edit_prediction.completion {
9198 EditPrediction::MoveWithin { target, .. } => {
9199 let target_display_point = target.to_display_point(editor_snapshot);
9200
9201 if self.edit_prediction_requires_modifier() {
9202 if !self.edit_prediction_preview_is_active() {
9203 return None;
9204 }
9205
9206 self.render_edit_prediction_modifier_jump_popover(
9207 text_bounds,
9208 content_origin,
9209 visible_row_range,
9210 line_layouts,
9211 line_height,
9212 scroll_pixel_position,
9213 newest_selection_head,
9214 target_display_point,
9215 window,
9216 cx,
9217 )
9218 } else {
9219 self.render_edit_prediction_eager_jump_popover(
9220 text_bounds,
9221 content_origin,
9222 editor_snapshot,
9223 visible_row_range,
9224 scroll_top,
9225 scroll_bottom,
9226 line_height,
9227 scroll_pixel_position,
9228 target_display_point,
9229 editor_width,
9230 window,
9231 cx,
9232 )
9233 }
9234 }
9235 EditPrediction::Edit {
9236 display_mode: EditDisplayMode::Inline,
9237 ..
9238 } => None,
9239 EditPrediction::Edit {
9240 display_mode: EditDisplayMode::TabAccept,
9241 edits,
9242 ..
9243 } => {
9244 let range = &edits.first()?.0;
9245 let target_display_point = range.end.to_display_point(editor_snapshot);
9246
9247 self.render_edit_prediction_end_of_line_popover(
9248 "Accept",
9249 editor_snapshot,
9250 visible_row_range,
9251 target_display_point,
9252 line_height,
9253 scroll_pixel_position,
9254 content_origin,
9255 editor_width,
9256 window,
9257 cx,
9258 )
9259 }
9260 EditPrediction::Edit {
9261 edits,
9262 edit_preview,
9263 display_mode: EditDisplayMode::DiffPopover,
9264 snapshot,
9265 ..
9266 } => self.render_edit_prediction_diff_popover(
9267 text_bounds,
9268 content_origin,
9269 right_margin,
9270 editor_snapshot,
9271 visible_row_range,
9272 line_layouts,
9273 line_height,
9274 scroll_position,
9275 scroll_pixel_position,
9276 newest_selection_head,
9277 editor_width,
9278 style,
9279 edits,
9280 edit_preview,
9281 snapshot,
9282 window,
9283 cx,
9284 ),
9285 EditPrediction::MoveOutside { snapshot, .. } => {
9286 let mut element = self
9287 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9288 .into_any();
9289
9290 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9291 let origin_x = text_bounds.size.width - size.width - px(30.);
9292 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9293 element.prepaint_at(origin, window, cx);
9294
9295 Some((element, origin))
9296 }
9297 }
9298 }
9299
9300 fn render_edit_prediction_modifier_jump_popover(
9301 &mut self,
9302 text_bounds: &Bounds<Pixels>,
9303 content_origin: gpui::Point<Pixels>,
9304 visible_row_range: Range<DisplayRow>,
9305 line_layouts: &[LineWithInvisibles],
9306 line_height: Pixels,
9307 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9308 newest_selection_head: Option<DisplayPoint>,
9309 target_display_point: DisplayPoint,
9310 window: &mut Window,
9311 cx: &mut App,
9312 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9313 let scrolled_content_origin =
9314 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9315
9316 const SCROLL_PADDING_Y: Pixels = px(12.);
9317
9318 if target_display_point.row() < visible_row_range.start {
9319 return self.render_edit_prediction_scroll_popover(
9320 &|_| SCROLL_PADDING_Y,
9321 IconName::ArrowUp,
9322 visible_row_range,
9323 line_layouts,
9324 newest_selection_head,
9325 scrolled_content_origin,
9326 window,
9327 cx,
9328 );
9329 } else if target_display_point.row() >= visible_row_range.end {
9330 return self.render_edit_prediction_scroll_popover(
9331 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9332 IconName::ArrowDown,
9333 visible_row_range,
9334 line_layouts,
9335 newest_selection_head,
9336 scrolled_content_origin,
9337 window,
9338 cx,
9339 );
9340 }
9341
9342 const POLE_WIDTH: Pixels = px(2.);
9343
9344 let line_layout =
9345 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9346 let target_column = target_display_point.column() as usize;
9347
9348 let target_x = line_layout.x_for_index(target_column);
9349 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9350 - scroll_pixel_position.y;
9351
9352 let flag_on_right = target_x < text_bounds.size.width / 2.;
9353
9354 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9355 border_color.l += 0.001;
9356
9357 let mut element = v_flex()
9358 .items_end()
9359 .when(flag_on_right, |el| el.items_start())
9360 .child(if flag_on_right {
9361 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9362 .rounded_bl(px(0.))
9363 .rounded_tl(px(0.))
9364 .border_l_2()
9365 .border_color(border_color)
9366 } else {
9367 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9368 .rounded_br(px(0.))
9369 .rounded_tr(px(0.))
9370 .border_r_2()
9371 .border_color(border_color)
9372 })
9373 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9374 .into_any();
9375
9376 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9377
9378 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9379 - point(
9380 if flag_on_right {
9381 POLE_WIDTH
9382 } else {
9383 size.width - POLE_WIDTH
9384 },
9385 size.height - line_height,
9386 );
9387
9388 origin.x = origin.x.max(content_origin.x);
9389
9390 element.prepaint_at(origin, window, cx);
9391
9392 Some((element, origin))
9393 }
9394
9395 fn render_edit_prediction_scroll_popover(
9396 &mut self,
9397 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9398 scroll_icon: IconName,
9399 visible_row_range: Range<DisplayRow>,
9400 line_layouts: &[LineWithInvisibles],
9401 newest_selection_head: Option<DisplayPoint>,
9402 scrolled_content_origin: gpui::Point<Pixels>,
9403 window: &mut Window,
9404 cx: &mut App,
9405 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9406 let mut element = self
9407 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9408 .into_any();
9409
9410 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9411
9412 let cursor = newest_selection_head?;
9413 let cursor_row_layout =
9414 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9415 let cursor_column = cursor.column() as usize;
9416
9417 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9418
9419 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9420
9421 element.prepaint_at(origin, window, cx);
9422 Some((element, origin))
9423 }
9424
9425 fn render_edit_prediction_eager_jump_popover(
9426 &mut self,
9427 text_bounds: &Bounds<Pixels>,
9428 content_origin: gpui::Point<Pixels>,
9429 editor_snapshot: &EditorSnapshot,
9430 visible_row_range: Range<DisplayRow>,
9431 scroll_top: ScrollOffset,
9432 scroll_bottom: ScrollOffset,
9433 line_height: Pixels,
9434 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9435 target_display_point: DisplayPoint,
9436 editor_width: Pixels,
9437 window: &mut Window,
9438 cx: &mut App,
9439 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9440 if target_display_point.row().as_f64() < scroll_top {
9441 let mut element = self
9442 .render_edit_prediction_line_popover(
9443 "Jump to Edit",
9444 Some(IconName::ArrowUp),
9445 window,
9446 cx,
9447 )
9448 .into_any();
9449
9450 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9451 let offset = point(
9452 (text_bounds.size.width - size.width) / 2.,
9453 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9454 );
9455
9456 let origin = text_bounds.origin + offset;
9457 element.prepaint_at(origin, window, cx);
9458 Some((element, origin))
9459 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9460 let mut element = self
9461 .render_edit_prediction_line_popover(
9462 "Jump to Edit",
9463 Some(IconName::ArrowDown),
9464 window,
9465 cx,
9466 )
9467 .into_any();
9468
9469 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9470 let offset = point(
9471 (text_bounds.size.width - size.width) / 2.,
9472 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9473 );
9474
9475 let origin = text_bounds.origin + offset;
9476 element.prepaint_at(origin, window, cx);
9477 Some((element, origin))
9478 } else {
9479 self.render_edit_prediction_end_of_line_popover(
9480 "Jump to Edit",
9481 editor_snapshot,
9482 visible_row_range,
9483 target_display_point,
9484 line_height,
9485 scroll_pixel_position,
9486 content_origin,
9487 editor_width,
9488 window,
9489 cx,
9490 )
9491 }
9492 }
9493
9494 fn render_edit_prediction_end_of_line_popover(
9495 self: &mut Editor,
9496 label: &'static str,
9497 editor_snapshot: &EditorSnapshot,
9498 visible_row_range: Range<DisplayRow>,
9499 target_display_point: DisplayPoint,
9500 line_height: Pixels,
9501 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9502 content_origin: gpui::Point<Pixels>,
9503 editor_width: Pixels,
9504 window: &mut Window,
9505 cx: &mut App,
9506 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9507 let target_line_end = DisplayPoint::new(
9508 target_display_point.row(),
9509 editor_snapshot.line_len(target_display_point.row()),
9510 );
9511
9512 let mut element = self
9513 .render_edit_prediction_line_popover(label, None, window, cx)
9514 .into_any();
9515
9516 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9517
9518 let line_origin =
9519 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9520
9521 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9522 let mut origin = start_point
9523 + line_origin
9524 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9525 origin.x = origin.x.max(content_origin.x);
9526
9527 let max_x = content_origin.x + editor_width - size.width;
9528
9529 if origin.x > max_x {
9530 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9531
9532 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9533 origin.y += offset;
9534 IconName::ArrowUp
9535 } else {
9536 origin.y -= offset;
9537 IconName::ArrowDown
9538 };
9539
9540 element = self
9541 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9542 .into_any();
9543
9544 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9545
9546 origin.x = content_origin.x + editor_width - size.width - px(2.);
9547 }
9548
9549 element.prepaint_at(origin, window, cx);
9550 Some((element, origin))
9551 }
9552
9553 fn render_edit_prediction_diff_popover(
9554 self: &Editor,
9555 text_bounds: &Bounds<Pixels>,
9556 content_origin: gpui::Point<Pixels>,
9557 right_margin: Pixels,
9558 editor_snapshot: &EditorSnapshot,
9559 visible_row_range: Range<DisplayRow>,
9560 line_layouts: &[LineWithInvisibles],
9561 line_height: Pixels,
9562 scroll_position: gpui::Point<ScrollOffset>,
9563 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9564 newest_selection_head: Option<DisplayPoint>,
9565 editor_width: Pixels,
9566 style: &EditorStyle,
9567 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9568 edit_preview: &Option<language::EditPreview>,
9569 snapshot: &language::BufferSnapshot,
9570 window: &mut Window,
9571 cx: &mut App,
9572 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9573 let edit_start = edits
9574 .first()
9575 .unwrap()
9576 .0
9577 .start
9578 .to_display_point(editor_snapshot);
9579 let edit_end = edits
9580 .last()
9581 .unwrap()
9582 .0
9583 .end
9584 .to_display_point(editor_snapshot);
9585
9586 let is_visible = visible_row_range.contains(&edit_start.row())
9587 || visible_row_range.contains(&edit_end.row());
9588 if !is_visible {
9589 return None;
9590 }
9591
9592 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9593 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9594 } else {
9595 // Fallback for providers without edit_preview
9596 crate::edit_prediction_fallback_text(edits, cx)
9597 };
9598
9599 let styled_text = highlighted_edits.to_styled_text(&style.text);
9600 let line_count = highlighted_edits.text.lines().count();
9601
9602 const BORDER_WIDTH: Pixels = px(1.);
9603
9604 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9605 let has_keybind = keybind.is_some();
9606
9607 let mut element = h_flex()
9608 .items_start()
9609 .child(
9610 h_flex()
9611 .bg(cx.theme().colors().editor_background)
9612 .border(BORDER_WIDTH)
9613 .shadow_xs()
9614 .border_color(cx.theme().colors().border)
9615 .rounded_l_lg()
9616 .when(line_count > 1, |el| el.rounded_br_lg())
9617 .pr_1()
9618 .child(styled_text),
9619 )
9620 .child(
9621 h_flex()
9622 .h(line_height + BORDER_WIDTH * 2.)
9623 .px_1p5()
9624 .gap_1()
9625 // Workaround: For some reason, there's a gap if we don't do this
9626 .ml(-BORDER_WIDTH)
9627 .shadow(vec![gpui::BoxShadow {
9628 color: gpui::black().opacity(0.05),
9629 offset: point(px(1.), px(1.)),
9630 blur_radius: px(2.),
9631 spread_radius: px(0.),
9632 }])
9633 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9634 .border(BORDER_WIDTH)
9635 .border_color(cx.theme().colors().border)
9636 .rounded_r_lg()
9637 .id("edit_prediction_diff_popover_keybind")
9638 .when(!has_keybind, |el| {
9639 let status_colors = cx.theme().status();
9640
9641 el.bg(status_colors.error_background)
9642 .border_color(status_colors.error.opacity(0.6))
9643 .child(Icon::new(IconName::Info).color(Color::Error))
9644 .cursor_default()
9645 .hoverable_tooltip(move |_window, cx| {
9646 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9647 })
9648 })
9649 .children(keybind),
9650 )
9651 .into_any();
9652
9653 let longest_row =
9654 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9655 let longest_line_width = if visible_row_range.contains(&longest_row) {
9656 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9657 } else {
9658 layout_line(
9659 longest_row,
9660 editor_snapshot,
9661 style,
9662 editor_width,
9663 |_| false,
9664 window,
9665 cx,
9666 )
9667 .width
9668 };
9669
9670 let viewport_bounds =
9671 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9672 right: -right_margin,
9673 ..Default::default()
9674 });
9675
9676 let x_after_longest = Pixels::from(
9677 ScrollPixelOffset::from(
9678 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9679 ) - scroll_pixel_position.x,
9680 );
9681
9682 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9683
9684 // Fully visible if it can be displayed within the window (allow overlapping other
9685 // panes). However, this is only allowed if the popover starts within text_bounds.
9686 let can_position_to_the_right = x_after_longest < text_bounds.right()
9687 && x_after_longest + element_bounds.width < viewport_bounds.right();
9688
9689 let mut origin = if can_position_to_the_right {
9690 point(
9691 x_after_longest,
9692 text_bounds.origin.y
9693 + Pixels::from(
9694 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9695 - scroll_pixel_position.y,
9696 ),
9697 )
9698 } else {
9699 let cursor_row = newest_selection_head.map(|head| head.row());
9700 let above_edit = edit_start
9701 .row()
9702 .0
9703 .checked_sub(line_count as u32)
9704 .map(DisplayRow);
9705 let below_edit = Some(edit_end.row() + 1);
9706 let above_cursor =
9707 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9708 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9709
9710 // Place the edit popover adjacent to the edit if there is a location
9711 // available that is onscreen and does not obscure the cursor. Otherwise,
9712 // place it adjacent to the cursor.
9713 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9714 .into_iter()
9715 .flatten()
9716 .find(|&start_row| {
9717 let end_row = start_row + line_count as u32;
9718 visible_row_range.contains(&start_row)
9719 && visible_row_range.contains(&end_row)
9720 && cursor_row
9721 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9722 })?;
9723
9724 content_origin
9725 + point(
9726 Pixels::from(-scroll_pixel_position.x),
9727 Pixels::from(
9728 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9729 ),
9730 )
9731 };
9732
9733 origin.x -= BORDER_WIDTH;
9734
9735 window.with_content_mask(
9736 Some(gpui::ContentMask {
9737 bounds: *text_bounds,
9738 }),
9739 |window| {
9740 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9741 },
9742 );
9743
9744 // Do not return an element, since it will already be drawn due to defer_draw.
9745 None
9746 }
9747
9748 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9749 px(30.)
9750 }
9751
9752 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9753 if self.read_only(cx) {
9754 cx.theme().players().read_only()
9755 } else {
9756 self.style.as_ref().unwrap().local_player
9757 }
9758 }
9759
9760 fn render_edit_prediction_accept_keybind(
9761 &self,
9762 window: &mut Window,
9763 cx: &mut App,
9764 ) -> Option<AnyElement> {
9765 let accept_binding =
9766 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9767 let accept_keystroke = accept_binding.keystroke()?;
9768
9769 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9770
9771 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9772 Color::Accent
9773 } else {
9774 Color::Muted
9775 };
9776
9777 h_flex()
9778 .px_0p5()
9779 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9780 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9781 .text_size(TextSize::XSmall.rems(cx))
9782 .child(h_flex().children(ui::render_modifiers(
9783 accept_keystroke.modifiers(),
9784 PlatformStyle::platform(),
9785 Some(modifiers_color),
9786 Some(IconSize::XSmall.rems().into()),
9787 true,
9788 )))
9789 .when(is_platform_style_mac, |parent| {
9790 parent.child(accept_keystroke.key().to_string())
9791 })
9792 .when(!is_platform_style_mac, |parent| {
9793 parent.child(
9794 Key::new(
9795 util::capitalize(accept_keystroke.key()),
9796 Some(Color::Default),
9797 )
9798 .size(Some(IconSize::XSmall.rems().into())),
9799 )
9800 })
9801 .into_any()
9802 .into()
9803 }
9804
9805 fn render_edit_prediction_line_popover(
9806 &self,
9807 label: impl Into<SharedString>,
9808 icon: Option<IconName>,
9809 window: &mut Window,
9810 cx: &mut App,
9811 ) -> Stateful<Div> {
9812 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9813
9814 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9815 let has_keybind = keybind.is_some();
9816 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9817
9818 h_flex()
9819 .id("ep-line-popover")
9820 .py_0p5()
9821 .pl_1()
9822 .pr(padding_right)
9823 .gap_1()
9824 .rounded_md()
9825 .border_1()
9826 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9827 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9828 .shadow_xs()
9829 .when(!has_keybind, |el| {
9830 let status_colors = cx.theme().status();
9831
9832 el.bg(status_colors.error_background)
9833 .border_color(status_colors.error.opacity(0.6))
9834 .pl_2()
9835 .child(Icon::new(icons.error).color(Color::Error))
9836 .cursor_default()
9837 .hoverable_tooltip(move |_window, cx| {
9838 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9839 })
9840 })
9841 .children(keybind)
9842 .child(
9843 Label::new(label)
9844 .size(LabelSize::Small)
9845 .when(!has_keybind, |el| {
9846 el.color(cx.theme().status().error.into()).strikethrough()
9847 }),
9848 )
9849 .when(!has_keybind, |el| {
9850 el.child(
9851 h_flex().ml_1().child(
9852 Icon::new(IconName::Info)
9853 .size(IconSize::Small)
9854 .color(cx.theme().status().error.into()),
9855 ),
9856 )
9857 })
9858 .when_some(icon, |element, icon| {
9859 element.child(
9860 div()
9861 .mt(px(1.5))
9862 .child(Icon::new(icon).size(IconSize::Small)),
9863 )
9864 })
9865 }
9866
9867 fn render_edit_prediction_jump_outside_popover(
9868 &self,
9869 snapshot: &BufferSnapshot,
9870 window: &mut Window,
9871 cx: &mut App,
9872 ) -> Stateful<Div> {
9873 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9874 let has_keybind = keybind.is_some();
9875 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9876
9877 let file_name = snapshot
9878 .file()
9879 .map(|file| SharedString::new(file.file_name(cx)))
9880 .unwrap_or(SharedString::new_static("untitled"));
9881
9882 h_flex()
9883 .id("ep-jump-outside-popover")
9884 .py_1()
9885 .px_2()
9886 .gap_1()
9887 .rounded_md()
9888 .border_1()
9889 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9890 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9891 .shadow_xs()
9892 .when(!has_keybind, |el| {
9893 let status_colors = cx.theme().status();
9894
9895 el.bg(status_colors.error_background)
9896 .border_color(status_colors.error.opacity(0.6))
9897 .pl_2()
9898 .child(Icon::new(icons.error).color(Color::Error))
9899 .cursor_default()
9900 .hoverable_tooltip(move |_window, cx| {
9901 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9902 })
9903 })
9904 .children(keybind)
9905 .child(
9906 Label::new(file_name)
9907 .size(LabelSize::Small)
9908 .buffer_font(cx)
9909 .when(!has_keybind, |el| {
9910 el.color(cx.theme().status().error.into()).strikethrough()
9911 }),
9912 )
9913 .when(!has_keybind, |el| {
9914 el.child(
9915 h_flex().ml_1().child(
9916 Icon::new(IconName::Info)
9917 .size(IconSize::Small)
9918 .color(cx.theme().status().error.into()),
9919 ),
9920 )
9921 })
9922 .child(
9923 div()
9924 .mt(px(1.5))
9925 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9926 )
9927 }
9928
9929 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9930 let accent_color = cx.theme().colors().text_accent;
9931 let editor_bg_color = cx.theme().colors().editor_background;
9932 editor_bg_color.blend(accent_color.opacity(0.1))
9933 }
9934
9935 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9936 let accent_color = cx.theme().colors().text_accent;
9937 let editor_bg_color = cx.theme().colors().editor_background;
9938 editor_bg_color.blend(accent_color.opacity(0.6))
9939 }
9940 fn get_prediction_provider_icons(
9941 provider: &Option<RegisteredEditPredictionDelegate>,
9942 cx: &App,
9943 ) -> edit_prediction_types::EditPredictionIconSet {
9944 match provider {
9945 Some(provider) => provider.provider.icons(cx),
9946 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
9947 }
9948 }
9949
9950 fn render_edit_prediction_cursor_popover(
9951 &self,
9952 min_width: Pixels,
9953 max_width: Pixels,
9954 cursor_point: Point,
9955 style: &EditorStyle,
9956 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9957 _window: &Window,
9958 cx: &mut Context<Editor>,
9959 ) -> Option<AnyElement> {
9960 let provider = self.edit_prediction_provider.as_ref()?;
9961 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9962
9963 let is_refreshing = provider.provider.is_refreshing(cx);
9964
9965 fn pending_completion_container(icon: IconName) -> Div {
9966 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9967 }
9968
9969 let completion = match &self.active_edit_prediction {
9970 Some(prediction) => {
9971 if !self.has_visible_completions_menu() {
9972 const RADIUS: Pixels = px(6.);
9973 const BORDER_WIDTH: Pixels = px(1.);
9974
9975 return Some(
9976 h_flex()
9977 .elevation_2(cx)
9978 .border(BORDER_WIDTH)
9979 .border_color(cx.theme().colors().border)
9980 .when(accept_keystroke.is_none(), |el| {
9981 el.border_color(cx.theme().status().error)
9982 })
9983 .rounded(RADIUS)
9984 .rounded_tl(px(0.))
9985 .overflow_hidden()
9986 .child(div().px_1p5().child(match &prediction.completion {
9987 EditPrediction::MoveWithin { target, snapshot } => {
9988 use text::ToPoint as _;
9989 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9990 {
9991 Icon::new(icons.down)
9992 } else {
9993 Icon::new(icons.up)
9994 }
9995 }
9996 EditPrediction::MoveOutside { .. } => {
9997 // TODO [zeta2] custom icon for external jump?
9998 Icon::new(icons.base)
9999 }
10000 EditPrediction::Edit { .. } => Icon::new(icons.base),
10001 }))
10002 .child(
10003 h_flex()
10004 .gap_1()
10005 .py_1()
10006 .px_2()
10007 .rounded_r(RADIUS - BORDER_WIDTH)
10008 .border_l_1()
10009 .border_color(cx.theme().colors().border)
10010 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10011 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10012 el.child(
10013 Label::new("Hold")
10014 .size(LabelSize::Small)
10015 .when(accept_keystroke.is_none(), |el| {
10016 el.strikethrough()
10017 })
10018 .line_height_style(LineHeightStyle::UiLabel),
10019 )
10020 })
10021 .id("edit_prediction_cursor_popover_keybind")
10022 .when(accept_keystroke.is_none(), |el| {
10023 let status_colors = cx.theme().status();
10024
10025 el.bg(status_colors.error_background)
10026 .border_color(status_colors.error.opacity(0.6))
10027 .child(Icon::new(IconName::Info).color(Color::Error))
10028 .cursor_default()
10029 .hoverable_tooltip(move |_window, cx| {
10030 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10031 .into()
10032 })
10033 })
10034 .when_some(
10035 accept_keystroke.as_ref(),
10036 |el, accept_keystroke| {
10037 el.child(h_flex().children(ui::render_modifiers(
10038 accept_keystroke.modifiers(),
10039 PlatformStyle::platform(),
10040 Some(Color::Default),
10041 Some(IconSize::XSmall.rems().into()),
10042 false,
10043 )))
10044 },
10045 ),
10046 )
10047 .into_any(),
10048 );
10049 }
10050
10051 self.render_edit_prediction_cursor_popover_preview(
10052 prediction,
10053 cursor_point,
10054 style,
10055 cx,
10056 )?
10057 }
10058
10059 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10060 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10061 stale_completion,
10062 cursor_point,
10063 style,
10064 cx,
10065 )?,
10066
10067 None => pending_completion_container(icons.base)
10068 .child(Label::new("...").size(LabelSize::Small)),
10069 },
10070
10071 None => pending_completion_container(icons.base)
10072 .child(Label::new("...").size(LabelSize::Small)),
10073 };
10074
10075 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10076 completion
10077 .with_animation(
10078 "loading-completion",
10079 Animation::new(Duration::from_secs(2))
10080 .repeat()
10081 .with_easing(pulsating_between(0.4, 0.8)),
10082 |label, delta| label.opacity(delta),
10083 )
10084 .into_any_element()
10085 } else {
10086 completion.into_any_element()
10087 };
10088
10089 let has_completion = self.active_edit_prediction.is_some();
10090
10091 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10092 Some(
10093 h_flex()
10094 .min_w(min_width)
10095 .max_w(max_width)
10096 .flex_1()
10097 .elevation_2(cx)
10098 .border_color(cx.theme().colors().border)
10099 .child(
10100 div()
10101 .flex_1()
10102 .py_1()
10103 .px_2()
10104 .overflow_hidden()
10105 .child(completion),
10106 )
10107 .when_some(accept_keystroke, |el, accept_keystroke| {
10108 if !accept_keystroke.modifiers().modified() {
10109 return el;
10110 }
10111
10112 el.child(
10113 h_flex()
10114 .h_full()
10115 .border_l_1()
10116 .rounded_r_lg()
10117 .border_color(cx.theme().colors().border)
10118 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10119 .gap_1()
10120 .py_1()
10121 .px_2()
10122 .child(
10123 h_flex()
10124 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10125 .when(is_platform_style_mac, |parent| parent.gap_1())
10126 .child(h_flex().children(ui::render_modifiers(
10127 accept_keystroke.modifiers(),
10128 PlatformStyle::platform(),
10129 Some(if !has_completion {
10130 Color::Muted
10131 } else {
10132 Color::Default
10133 }),
10134 None,
10135 false,
10136 ))),
10137 )
10138 .child(Label::new("Preview").into_any_element())
10139 .opacity(if has_completion { 1.0 } else { 0.4 }),
10140 )
10141 })
10142 .into_any(),
10143 )
10144 }
10145
10146 fn render_edit_prediction_cursor_popover_preview(
10147 &self,
10148 completion: &EditPredictionState,
10149 cursor_point: Point,
10150 style: &EditorStyle,
10151 cx: &mut Context<Editor>,
10152 ) -> Option<Div> {
10153 use text::ToPoint as _;
10154
10155 fn render_relative_row_jump(
10156 prefix: impl Into<String>,
10157 current_row: u32,
10158 target_row: u32,
10159 ) -> Div {
10160 let (row_diff, arrow) = if target_row < current_row {
10161 (current_row - target_row, IconName::ArrowUp)
10162 } else {
10163 (target_row - current_row, IconName::ArrowDown)
10164 };
10165
10166 h_flex()
10167 .child(
10168 Label::new(format!("{}{}", prefix.into(), row_diff))
10169 .color(Color::Muted)
10170 .size(LabelSize::Small),
10171 )
10172 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10173 }
10174
10175 let supports_jump = self
10176 .edit_prediction_provider
10177 .as_ref()
10178 .map(|provider| provider.provider.supports_jump_to_edit())
10179 .unwrap_or(true);
10180
10181 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10182
10183 match &completion.completion {
10184 EditPrediction::MoveWithin {
10185 target, snapshot, ..
10186 } => {
10187 if !supports_jump {
10188 return None;
10189 }
10190
10191 Some(
10192 h_flex()
10193 .px_2()
10194 .gap_2()
10195 .flex_1()
10196 .child(
10197 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10198 Icon::new(icons.down)
10199 } else {
10200 Icon::new(icons.up)
10201 },
10202 )
10203 .child(Label::new("Jump to Edit")),
10204 )
10205 }
10206 EditPrediction::MoveOutside { snapshot, .. } => {
10207 let file_name = snapshot
10208 .file()
10209 .map(|file| file.file_name(cx))
10210 .unwrap_or("untitled");
10211 Some(
10212 h_flex()
10213 .px_2()
10214 .gap_2()
10215 .flex_1()
10216 .child(Icon::new(icons.base))
10217 .child(Label::new(format!("Jump to {file_name}"))),
10218 )
10219 }
10220 EditPrediction::Edit {
10221 edits,
10222 edit_preview,
10223 snapshot,
10224 ..
10225 } => {
10226 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10227
10228 let (highlighted_edits, has_more_lines) =
10229 if let Some(edit_preview) = edit_preview.as_ref() {
10230 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10231 .first_line_preview()
10232 } else {
10233 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10234 };
10235
10236 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10237 .with_default_highlights(&style.text, highlighted_edits.highlights);
10238
10239 let preview = h_flex()
10240 .gap_1()
10241 .min_w_16()
10242 .child(styled_text)
10243 .when(has_more_lines, |parent| parent.child("…"));
10244
10245 let left = if supports_jump && first_edit_row != cursor_point.row {
10246 render_relative_row_jump("", cursor_point.row, first_edit_row)
10247 .into_any_element()
10248 } else {
10249 Icon::new(icons.base).into_any_element()
10250 };
10251
10252 Some(
10253 h_flex()
10254 .h_full()
10255 .flex_1()
10256 .gap_2()
10257 .pr_1()
10258 .overflow_x_hidden()
10259 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10260 .child(left)
10261 .child(preview),
10262 )
10263 }
10264 }
10265 }
10266
10267 pub fn render_context_menu(
10268 &mut self,
10269 max_height_in_lines: u32,
10270 window: &mut Window,
10271 cx: &mut Context<Editor>,
10272 ) -> Option<AnyElement> {
10273 let menu = self.context_menu.borrow();
10274 let menu = menu.as_ref()?;
10275 if !menu.visible() {
10276 return None;
10277 };
10278 self.style
10279 .as_ref()
10280 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10281 }
10282
10283 fn render_context_menu_aside(
10284 &mut self,
10285 max_size: Size<Pixels>,
10286 window: &mut Window,
10287 cx: &mut Context<Editor>,
10288 ) -> Option<AnyElement> {
10289 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10290 if menu.visible() {
10291 menu.render_aside(max_size, window, cx)
10292 } else {
10293 None
10294 }
10295 })
10296 }
10297
10298 fn hide_context_menu(
10299 &mut self,
10300 window: &mut Window,
10301 cx: &mut Context<Self>,
10302 ) -> Option<CodeContextMenu> {
10303 cx.notify();
10304 self.completion_tasks.clear();
10305 let context_menu = self.context_menu.borrow_mut().take();
10306 self.stale_edit_prediction_in_menu.take();
10307 self.update_visible_edit_prediction(window, cx);
10308 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10309 && let Some(completion_provider) = &self.completion_provider
10310 {
10311 completion_provider.selection_changed(None, window, cx);
10312 }
10313 context_menu
10314 }
10315
10316 fn show_snippet_choices(
10317 &mut self,
10318 choices: &Vec<String>,
10319 selection: Range<Anchor>,
10320 cx: &mut Context<Self>,
10321 ) {
10322 let Some((_, buffer, _)) = self
10323 .buffer()
10324 .read(cx)
10325 .excerpt_containing(selection.start, cx)
10326 else {
10327 return;
10328 };
10329 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10330 else {
10331 return;
10332 };
10333 if buffer != end_buffer {
10334 log::error!("expected anchor range to have matching buffer IDs");
10335 return;
10336 }
10337
10338 let id = post_inc(&mut self.next_completion_id);
10339 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10340 let mut context_menu = self.context_menu.borrow_mut();
10341 let old_menu = context_menu.take();
10342 *context_menu = Some(CodeContextMenu::Completions(
10343 CompletionsMenu::new_snippet_choices(
10344 id,
10345 true,
10346 choices,
10347 selection,
10348 buffer,
10349 old_menu.map(|menu| menu.primary_scroll_handle()),
10350 snippet_sort_order,
10351 ),
10352 ));
10353 }
10354
10355 pub fn insert_snippet(
10356 &mut self,
10357 insertion_ranges: &[Range<MultiBufferOffset>],
10358 snippet: Snippet,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) -> Result<()> {
10362 struct Tabstop<T> {
10363 is_end_tabstop: bool,
10364 ranges: Vec<Range<T>>,
10365 choices: Option<Vec<String>>,
10366 }
10367
10368 let tabstops = self.buffer.update(cx, |buffer, cx| {
10369 let snippet_text: Arc<str> = snippet.text.clone().into();
10370 let edits = insertion_ranges
10371 .iter()
10372 .cloned()
10373 .map(|range| (range, snippet_text.clone()));
10374 let autoindent_mode = AutoindentMode::Block {
10375 original_indent_columns: Vec::new(),
10376 };
10377 buffer.edit(edits, Some(autoindent_mode), cx);
10378
10379 let snapshot = &*buffer.read(cx);
10380 let snippet = &snippet;
10381 snippet
10382 .tabstops
10383 .iter()
10384 .map(|tabstop| {
10385 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10386 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10387 });
10388 let mut tabstop_ranges = tabstop
10389 .ranges
10390 .iter()
10391 .flat_map(|tabstop_range| {
10392 let mut delta = 0_isize;
10393 insertion_ranges.iter().map(move |insertion_range| {
10394 let insertion_start = insertion_range.start + delta;
10395 delta += snippet.text.len() as isize
10396 - (insertion_range.end - insertion_range.start) as isize;
10397
10398 let start =
10399 (insertion_start + tabstop_range.start).min(snapshot.len());
10400 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10401 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10402 })
10403 })
10404 .collect::<Vec<_>>();
10405 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10406
10407 Tabstop {
10408 is_end_tabstop,
10409 ranges: tabstop_ranges,
10410 choices: tabstop.choices.clone(),
10411 }
10412 })
10413 .collect::<Vec<_>>()
10414 });
10415 if let Some(tabstop) = tabstops.first() {
10416 self.change_selections(Default::default(), window, cx, |s| {
10417 // Reverse order so that the first range is the newest created selection.
10418 // Completions will use it and autoscroll will prioritize it.
10419 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10420 });
10421
10422 if let Some(choices) = &tabstop.choices
10423 && let Some(selection) = tabstop.ranges.first()
10424 {
10425 self.show_snippet_choices(choices, selection.clone(), cx)
10426 }
10427
10428 // If we're already at the last tabstop and it's at the end of the snippet,
10429 // we're done, we don't need to keep the state around.
10430 if !tabstop.is_end_tabstop {
10431 let choices = tabstops
10432 .iter()
10433 .map(|tabstop| tabstop.choices.clone())
10434 .collect();
10435
10436 let ranges = tabstops
10437 .into_iter()
10438 .map(|tabstop| tabstop.ranges)
10439 .collect::<Vec<_>>();
10440
10441 self.snippet_stack.push(SnippetState {
10442 active_index: 0,
10443 ranges,
10444 choices,
10445 });
10446 }
10447
10448 // Check whether the just-entered snippet ends with an auto-closable bracket.
10449 if self.autoclose_regions.is_empty() {
10450 let snapshot = self.buffer.read(cx).snapshot(cx);
10451 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10452 let selection_head = selection.head();
10453 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10454 continue;
10455 };
10456
10457 let mut bracket_pair = None;
10458 let max_lookup_length = scope
10459 .brackets()
10460 .map(|(pair, _)| {
10461 pair.start
10462 .as_str()
10463 .chars()
10464 .count()
10465 .max(pair.end.as_str().chars().count())
10466 })
10467 .max();
10468 if let Some(max_lookup_length) = max_lookup_length {
10469 let next_text = snapshot
10470 .chars_at(selection_head)
10471 .take(max_lookup_length)
10472 .collect::<String>();
10473 let prev_text = snapshot
10474 .reversed_chars_at(selection_head)
10475 .take(max_lookup_length)
10476 .collect::<String>();
10477
10478 for (pair, enabled) in scope.brackets() {
10479 if enabled
10480 && pair.close
10481 && prev_text.starts_with(pair.start.as_str())
10482 && next_text.starts_with(pair.end.as_str())
10483 {
10484 bracket_pair = Some(pair.clone());
10485 break;
10486 }
10487 }
10488 }
10489
10490 if let Some(pair) = bracket_pair {
10491 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10492 let autoclose_enabled =
10493 self.use_autoclose && snapshot_settings.use_autoclose;
10494 if autoclose_enabled {
10495 let start = snapshot.anchor_after(selection_head);
10496 let end = snapshot.anchor_after(selection_head);
10497 self.autoclose_regions.push(AutocloseRegion {
10498 selection_id: selection.id,
10499 range: start..end,
10500 pair,
10501 });
10502 }
10503 }
10504 }
10505 }
10506 }
10507 Ok(())
10508 }
10509
10510 pub fn move_to_next_snippet_tabstop(
10511 &mut self,
10512 window: &mut Window,
10513 cx: &mut Context<Self>,
10514 ) -> bool {
10515 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10516 }
10517
10518 pub fn move_to_prev_snippet_tabstop(
10519 &mut self,
10520 window: &mut Window,
10521 cx: &mut Context<Self>,
10522 ) -> bool {
10523 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10524 }
10525
10526 pub fn move_to_snippet_tabstop(
10527 &mut self,
10528 bias: Bias,
10529 window: &mut Window,
10530 cx: &mut Context<Self>,
10531 ) -> bool {
10532 if let Some(mut snippet) = self.snippet_stack.pop() {
10533 match bias {
10534 Bias::Left => {
10535 if snippet.active_index > 0 {
10536 snippet.active_index -= 1;
10537 } else {
10538 self.snippet_stack.push(snippet);
10539 return false;
10540 }
10541 }
10542 Bias::Right => {
10543 if snippet.active_index + 1 < snippet.ranges.len() {
10544 snippet.active_index += 1;
10545 } else {
10546 self.snippet_stack.push(snippet);
10547 return false;
10548 }
10549 }
10550 }
10551 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10552 self.change_selections(Default::default(), window, cx, |s| {
10553 // Reverse order so that the first range is the newest created selection.
10554 // Completions will use it and autoscroll will prioritize it.
10555 s.select_ranges(current_ranges.iter().rev().cloned())
10556 });
10557
10558 if let Some(choices) = &snippet.choices[snippet.active_index]
10559 && let Some(selection) = current_ranges.first()
10560 {
10561 self.show_snippet_choices(choices, selection.clone(), cx);
10562 }
10563
10564 // If snippet state is not at the last tabstop, push it back on the stack
10565 if snippet.active_index + 1 < snippet.ranges.len() {
10566 self.snippet_stack.push(snippet);
10567 }
10568 return true;
10569 }
10570 }
10571
10572 false
10573 }
10574
10575 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10576 self.transact(window, cx, |this, window, cx| {
10577 this.select_all(&SelectAll, window, cx);
10578 this.insert("", window, cx);
10579 });
10580 }
10581
10582 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10583 if self.read_only(cx) {
10584 return;
10585 }
10586 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10587 self.transact(window, cx, |this, window, cx| {
10588 this.select_autoclose_pair(window, cx);
10589
10590 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10591
10592 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10593 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10594 for selection in &mut selections {
10595 if selection.is_empty() {
10596 let old_head = selection.head();
10597 let mut new_head =
10598 movement::left(&display_map, old_head.to_display_point(&display_map))
10599 .to_point(&display_map);
10600 if let Some((buffer, line_buffer_range)) = display_map
10601 .buffer_snapshot()
10602 .buffer_line_for_row(MultiBufferRow(old_head.row))
10603 {
10604 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10605 let indent_len = match indent_size.kind {
10606 IndentKind::Space => {
10607 buffer.settings_at(line_buffer_range.start, cx).tab_size
10608 }
10609 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10610 };
10611 if old_head.column <= indent_size.len && old_head.column > 0 {
10612 let indent_len = indent_len.get();
10613 new_head = cmp::min(
10614 new_head,
10615 MultiBufferPoint::new(
10616 old_head.row,
10617 ((old_head.column - 1) / indent_len) * indent_len,
10618 ),
10619 );
10620 }
10621 }
10622
10623 selection.set_head(new_head, SelectionGoal::None);
10624 }
10625 }
10626
10627 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10628 this.insert("", window, cx);
10629 linked_edits.apply_with_left_expansion(cx);
10630 this.refresh_edit_prediction(true, false, window, cx);
10631 refresh_linked_ranges(this, window, cx);
10632 });
10633 }
10634
10635 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10636 if self.read_only(cx) {
10637 return;
10638 }
10639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10640 self.transact(window, cx, |this, window, cx| {
10641 this.change_selections(Default::default(), window, cx, |s| {
10642 s.move_with(&mut |map, selection| {
10643 if selection.is_empty() {
10644 let cursor = movement::right(map, selection.head());
10645 selection.end = cursor;
10646 selection.reversed = true;
10647 selection.goal = SelectionGoal::None;
10648 }
10649 })
10650 });
10651 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10652 this.insert("", window, cx);
10653 linked_edits.apply(cx);
10654 this.refresh_edit_prediction(true, false, window, cx);
10655 refresh_linked_ranges(this, window, cx);
10656 });
10657 }
10658
10659 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10660 if self.mode.is_single_line() {
10661 cx.propagate();
10662 return;
10663 }
10664
10665 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10666 if self.move_to_prev_snippet_tabstop(window, cx) {
10667 return;
10668 }
10669 self.outdent(&Outdent, window, cx);
10670 }
10671
10672 pub fn next_snippet_tabstop(
10673 &mut self,
10674 _: &NextSnippetTabstop,
10675 window: &mut Window,
10676 cx: &mut Context<Self>,
10677 ) {
10678 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10679 cx.propagate();
10680 return;
10681 }
10682
10683 if self.move_to_next_snippet_tabstop(window, cx) {
10684 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10685 return;
10686 }
10687 cx.propagate();
10688 }
10689
10690 pub fn previous_snippet_tabstop(
10691 &mut self,
10692 _: &PreviousSnippetTabstop,
10693 window: &mut Window,
10694 cx: &mut Context<Self>,
10695 ) {
10696 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10697 cx.propagate();
10698 return;
10699 }
10700
10701 if self.move_to_prev_snippet_tabstop(window, cx) {
10702 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10703 return;
10704 }
10705 cx.propagate();
10706 }
10707
10708 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10709 if self.mode.is_single_line() {
10710 cx.propagate();
10711 return;
10712 }
10713
10714 if self.move_to_next_snippet_tabstop(window, cx) {
10715 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10716 return;
10717 }
10718 if self.read_only(cx) {
10719 return;
10720 }
10721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10722 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10723 let buffer = self.buffer.read(cx);
10724 let snapshot = buffer.snapshot(cx);
10725 let rows_iter = selections.iter().map(|s| s.head().row);
10726 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10727
10728 let has_some_cursor_in_whitespace = selections
10729 .iter()
10730 .filter(|selection| selection.is_empty())
10731 .any(|selection| {
10732 let cursor = selection.head();
10733 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10734 cursor.column < current_indent.len
10735 });
10736
10737 let mut edits = Vec::new();
10738 let mut prev_edited_row = 0;
10739 let mut row_delta = 0;
10740 for selection in &mut selections {
10741 if selection.start.row != prev_edited_row {
10742 row_delta = 0;
10743 }
10744 prev_edited_row = selection.end.row;
10745
10746 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10747 if selection.is_empty() {
10748 let cursor = selection.head();
10749 let settings = buffer.language_settings_at(cursor, cx);
10750 if settings.indent_list_on_tab {
10751 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10752 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10753 row_delta = Self::indent_selection(
10754 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10755 );
10756 continue;
10757 }
10758 }
10759 }
10760 }
10761
10762 // If the selection is non-empty, then increase the indentation of the selected lines.
10763 if !selection.is_empty() {
10764 row_delta =
10765 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10766 continue;
10767 }
10768
10769 let cursor = selection.head();
10770 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10771 if let Some(suggested_indent) =
10772 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10773 {
10774 // Don't do anything if already at suggested indent
10775 // and there is any other cursor which is not
10776 if has_some_cursor_in_whitespace
10777 && cursor.column == current_indent.len
10778 && current_indent.len == suggested_indent.len
10779 {
10780 continue;
10781 }
10782
10783 // Adjust line and move cursor to suggested indent
10784 // if cursor is not at suggested indent
10785 if cursor.column < suggested_indent.len
10786 && cursor.column <= current_indent.len
10787 && current_indent.len <= suggested_indent.len
10788 {
10789 selection.start = Point::new(cursor.row, suggested_indent.len);
10790 selection.end = selection.start;
10791 if row_delta == 0 {
10792 edits.extend(Buffer::edit_for_indent_size_adjustment(
10793 cursor.row,
10794 current_indent,
10795 suggested_indent,
10796 ));
10797 row_delta = suggested_indent.len - current_indent.len;
10798 }
10799 continue;
10800 }
10801
10802 // If current indent is more than suggested indent
10803 // only move cursor to current indent and skip indent
10804 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10805 selection.start = Point::new(cursor.row, current_indent.len);
10806 selection.end = selection.start;
10807 continue;
10808 }
10809 }
10810
10811 // Otherwise, insert a hard or soft tab.
10812 let settings = buffer.language_settings_at(cursor, cx);
10813 let tab_size = if settings.hard_tabs {
10814 IndentSize::tab()
10815 } else {
10816 let tab_size = settings.tab_size.get();
10817 let indent_remainder = snapshot
10818 .text_for_range(Point::new(cursor.row, 0)..cursor)
10819 .flat_map(str::chars)
10820 .fold(row_delta % tab_size, |counter: u32, c| {
10821 if c == '\t' {
10822 0
10823 } else {
10824 (counter + 1) % tab_size
10825 }
10826 });
10827
10828 let chars_to_next_tab_stop = tab_size - indent_remainder;
10829 IndentSize::spaces(chars_to_next_tab_stop)
10830 };
10831 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10832 selection.end = selection.start;
10833 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10834 row_delta += tab_size.len;
10835 }
10836
10837 self.transact(window, cx, |this, window, cx| {
10838 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10839 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10840 this.refresh_edit_prediction(true, false, window, cx);
10841 });
10842 }
10843
10844 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10845 if self.read_only(cx) {
10846 return;
10847 }
10848 if self.mode.is_single_line() {
10849 cx.propagate();
10850 return;
10851 }
10852
10853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10854 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10855 let mut prev_edited_row = 0;
10856 let mut row_delta = 0;
10857 let mut edits = Vec::new();
10858 let buffer = self.buffer.read(cx);
10859 let snapshot = buffer.snapshot(cx);
10860 for selection in &mut selections {
10861 if selection.start.row != prev_edited_row {
10862 row_delta = 0;
10863 }
10864 prev_edited_row = selection.end.row;
10865
10866 row_delta =
10867 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10868 }
10869
10870 self.transact(window, cx, |this, window, cx| {
10871 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10872 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10873 });
10874 }
10875
10876 fn indent_selection(
10877 buffer: &MultiBuffer,
10878 snapshot: &MultiBufferSnapshot,
10879 selection: &mut Selection<Point>,
10880 edits: &mut Vec<(Range<Point>, String)>,
10881 delta_for_start_row: u32,
10882 cx: &App,
10883 ) -> u32 {
10884 let settings = buffer.language_settings_at(selection.start, cx);
10885 let tab_size = settings.tab_size.get();
10886 let indent_kind = if settings.hard_tabs {
10887 IndentKind::Tab
10888 } else {
10889 IndentKind::Space
10890 };
10891 let mut start_row = selection.start.row;
10892 let mut end_row = selection.end.row + 1;
10893
10894 // If a selection ends at the beginning of a line, don't indent
10895 // that last line.
10896 if selection.end.column == 0 && selection.end.row > selection.start.row {
10897 end_row -= 1;
10898 }
10899
10900 // Avoid re-indenting a row that has already been indented by a
10901 // previous selection, but still update this selection's column
10902 // to reflect that indentation.
10903 if delta_for_start_row > 0 {
10904 start_row += 1;
10905 selection.start.column += delta_for_start_row;
10906 if selection.end.row == selection.start.row {
10907 selection.end.column += delta_for_start_row;
10908 }
10909 }
10910
10911 let mut delta_for_end_row = 0;
10912 let has_multiple_rows = start_row + 1 != end_row;
10913 for row in start_row..end_row {
10914 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10915 let indent_delta = match (current_indent.kind, indent_kind) {
10916 (IndentKind::Space, IndentKind::Space) => {
10917 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10918 IndentSize::spaces(columns_to_next_tab_stop)
10919 }
10920 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10921 (_, IndentKind::Tab) => IndentSize::tab(),
10922 };
10923
10924 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10925 0
10926 } else {
10927 selection.start.column
10928 };
10929 let row_start = Point::new(row, start);
10930 edits.push((
10931 row_start..row_start,
10932 indent_delta.chars().collect::<String>(),
10933 ));
10934
10935 // Update this selection's endpoints to reflect the indentation.
10936 if row == selection.start.row {
10937 selection.start.column += indent_delta.len;
10938 }
10939 if row == selection.end.row {
10940 selection.end.column += indent_delta.len;
10941 delta_for_end_row = indent_delta.len;
10942 }
10943 }
10944
10945 if selection.start.row == selection.end.row {
10946 delta_for_start_row + delta_for_end_row
10947 } else {
10948 delta_for_end_row
10949 }
10950 }
10951
10952 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10953 if self.read_only(cx) {
10954 return;
10955 }
10956 if self.mode.is_single_line() {
10957 cx.propagate();
10958 return;
10959 }
10960
10961 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10962 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10963 let selections = self.selections.all::<Point>(&display_map);
10964 let mut deletion_ranges = Vec::new();
10965 let mut last_outdent = None;
10966 {
10967 let buffer = self.buffer.read(cx);
10968 let snapshot = buffer.snapshot(cx);
10969 for selection in &selections {
10970 let settings = buffer.language_settings_at(selection.start, cx);
10971 let tab_size = settings.tab_size.get();
10972 let mut rows = selection.spanned_rows(false, &display_map);
10973
10974 // Avoid re-outdenting a row that has already been outdented by a
10975 // previous selection.
10976 if let Some(last_row) = last_outdent
10977 && last_row == rows.start
10978 {
10979 rows.start = rows.start.next_row();
10980 }
10981 let has_multiple_rows = rows.len() > 1;
10982 for row in rows.iter_rows() {
10983 let indent_size = snapshot.indent_size_for_line(row);
10984 if indent_size.len > 0 {
10985 let deletion_len = match indent_size.kind {
10986 IndentKind::Space => {
10987 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10988 if columns_to_prev_tab_stop == 0 {
10989 tab_size
10990 } else {
10991 columns_to_prev_tab_stop
10992 }
10993 }
10994 IndentKind::Tab => 1,
10995 };
10996 let start = if has_multiple_rows
10997 || deletion_len > selection.start.column
10998 || indent_size.len < selection.start.column
10999 {
11000 0
11001 } else {
11002 selection.start.column - deletion_len
11003 };
11004 deletion_ranges.push(
11005 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11006 );
11007 last_outdent = Some(row);
11008 }
11009 }
11010 }
11011 }
11012
11013 self.transact(window, cx, |this, window, cx| {
11014 this.buffer.update(cx, |buffer, cx| {
11015 let empty_str: Arc<str> = Arc::default();
11016 buffer.edit(
11017 deletion_ranges
11018 .into_iter()
11019 .map(|range| (range, empty_str.clone())),
11020 None,
11021 cx,
11022 );
11023 });
11024 let selections = this
11025 .selections
11026 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11027 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11028 });
11029 }
11030
11031 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11032 if self.read_only(cx) {
11033 return;
11034 }
11035 if self.mode.is_single_line() {
11036 cx.propagate();
11037 return;
11038 }
11039
11040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11041 let selections = self
11042 .selections
11043 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11044 .into_iter()
11045 .map(|s| s.range());
11046
11047 self.transact(window, cx, |this, window, cx| {
11048 this.buffer.update(cx, |buffer, cx| {
11049 buffer.autoindent_ranges(selections, cx);
11050 });
11051 let selections = this
11052 .selections
11053 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11054 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11055 });
11056 }
11057
11058 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11059 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11060 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11061 let selections = self.selections.all::<Point>(&display_map);
11062
11063 let mut new_cursors = Vec::new();
11064 let mut edit_ranges = Vec::new();
11065 let mut selections = selections.iter().peekable();
11066 while let Some(selection) = selections.next() {
11067 let mut rows = selection.spanned_rows(false, &display_map);
11068
11069 // Accumulate contiguous regions of rows that we want to delete.
11070 while let Some(next_selection) = selections.peek() {
11071 let next_rows = next_selection.spanned_rows(false, &display_map);
11072 if next_rows.start <= rows.end {
11073 rows.end = next_rows.end;
11074 selections.next().unwrap();
11075 } else {
11076 break;
11077 }
11078 }
11079
11080 let buffer = display_map.buffer_snapshot();
11081 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11082 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11083 // If there's a line after the range, delete the \n from the end of the row range
11084 (
11085 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11086 rows.end,
11087 )
11088 } else {
11089 // If there isn't a line after the range, delete the \n from the line before the
11090 // start of the row range
11091 edit_start = edit_start.saturating_sub_usize(1);
11092 (buffer.len(), rows.start.previous_row())
11093 };
11094
11095 let text_layout_details = self.text_layout_details(window, cx);
11096 let x = display_map.x_for_display_point(
11097 selection.head().to_display_point(&display_map),
11098 &text_layout_details,
11099 );
11100 let row = Point::new(target_row.0, 0)
11101 .to_display_point(&display_map)
11102 .row();
11103 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11104
11105 new_cursors.push((
11106 selection.id,
11107 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11108 SelectionGoal::None,
11109 ));
11110 edit_ranges.push(edit_start..edit_end);
11111 }
11112
11113 self.transact(window, cx, |this, window, cx| {
11114 let buffer = this.buffer.update(cx, |buffer, cx| {
11115 let empty_str: Arc<str> = Arc::default();
11116 buffer.edit(
11117 edit_ranges
11118 .into_iter()
11119 .map(|range| (range, empty_str.clone())),
11120 None,
11121 cx,
11122 );
11123 buffer.snapshot(cx)
11124 });
11125 let new_selections = new_cursors
11126 .into_iter()
11127 .map(|(id, cursor, goal)| {
11128 let cursor = cursor.to_point(&buffer);
11129 Selection {
11130 id,
11131 start: cursor,
11132 end: cursor,
11133 reversed: false,
11134 goal,
11135 }
11136 })
11137 .collect();
11138
11139 this.change_selections(Default::default(), window, cx, |s| {
11140 s.select(new_selections);
11141 });
11142 });
11143 }
11144
11145 pub fn join_lines_impl(
11146 &mut self,
11147 insert_whitespace: bool,
11148 window: &mut Window,
11149 cx: &mut Context<Self>,
11150 ) {
11151 if self.read_only(cx) {
11152 return;
11153 }
11154 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11155 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11156 let start = MultiBufferRow(selection.start.row);
11157 // Treat single line selections as if they include the next line. Otherwise this action
11158 // would do nothing for single line selections individual cursors.
11159 let end = if selection.start.row == selection.end.row {
11160 MultiBufferRow(selection.start.row + 1)
11161 } else if selection.end.column == 0 {
11162 // If the selection ends at the start of a line, it's logically at the end of the
11163 // previous line (plus its newline).
11164 // Don't include the end line unless there's only one line selected.
11165 if selection.start.row + 1 == selection.end.row {
11166 MultiBufferRow(selection.end.row)
11167 } else {
11168 MultiBufferRow(selection.end.row - 1)
11169 }
11170 } else {
11171 MultiBufferRow(selection.end.row)
11172 };
11173
11174 if let Some(last_row_range) = row_ranges.last_mut()
11175 && start <= last_row_range.end
11176 {
11177 last_row_range.end = end;
11178 continue;
11179 }
11180 row_ranges.push(start..end);
11181 }
11182
11183 let snapshot = self.buffer.read(cx).snapshot(cx);
11184 let mut cursor_positions = Vec::new();
11185 for row_range in &row_ranges {
11186 let anchor = snapshot.anchor_before(Point::new(
11187 row_range.end.previous_row().0,
11188 snapshot.line_len(row_range.end.previous_row()),
11189 ));
11190 cursor_positions.push(anchor..anchor);
11191 }
11192
11193 self.transact(window, cx, |this, window, cx| {
11194 for row_range in row_ranges.into_iter().rev() {
11195 for row in row_range.iter_rows().rev() {
11196 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11197 let next_line_row = row.next_row();
11198 let indent = snapshot.indent_size_for_line(next_line_row);
11199 let mut join_start_column = indent.len;
11200
11201 if let Some(language_scope) =
11202 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11203 {
11204 let line_end =
11205 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11206 let line_text_after_indent = snapshot
11207 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11208 .collect::<String>();
11209
11210 if !line_text_after_indent.is_empty() {
11211 let block_prefix = language_scope
11212 .block_comment()
11213 .map(|c| c.prefix.as_ref())
11214 .filter(|p| !p.is_empty());
11215 let doc_prefix = language_scope
11216 .documentation_comment()
11217 .map(|c| c.prefix.as_ref())
11218 .filter(|p| !p.is_empty());
11219 let all_prefixes = language_scope
11220 .line_comment_prefixes()
11221 .iter()
11222 .map(|p| p.as_ref())
11223 .chain(block_prefix)
11224 .chain(doc_prefix)
11225 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11226
11227 let mut longest_prefix_len = None;
11228 for prefix in all_prefixes {
11229 let trimmed = prefix.trim_end();
11230 if line_text_after_indent.starts_with(trimmed) {
11231 let candidate_len =
11232 if line_text_after_indent.starts_with(prefix) {
11233 prefix.len()
11234 } else {
11235 trimmed.len()
11236 };
11237 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11238 longest_prefix_len = Some(candidate_len);
11239 }
11240 }
11241 }
11242
11243 if let Some(prefix_len) = longest_prefix_len {
11244 join_start_column =
11245 join_start_column.saturating_add(prefix_len as u32);
11246 }
11247 }
11248 }
11249
11250 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11251
11252 let replace = if snapshot.line_len(next_line_row) > join_start_column
11253 && insert_whitespace
11254 {
11255 " "
11256 } else {
11257 ""
11258 };
11259
11260 this.buffer.update(cx, |buffer, cx| {
11261 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11262 });
11263 }
11264 }
11265
11266 this.change_selections(Default::default(), window, cx, |s| {
11267 s.select_anchor_ranges(cursor_positions)
11268 });
11269 });
11270 }
11271
11272 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11273 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11274 self.join_lines_impl(true, window, cx);
11275 }
11276
11277 pub fn sort_lines_case_sensitive(
11278 &mut self,
11279 _: &SortLinesCaseSensitive,
11280 window: &mut Window,
11281 cx: &mut Context<Self>,
11282 ) {
11283 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11284 }
11285
11286 pub fn sort_lines_by_length(
11287 &mut self,
11288 _: &SortLinesByLength,
11289 window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 self.manipulate_immutable_lines(window, cx, |lines| {
11293 lines.sort_by_key(|&line| line.chars().count())
11294 })
11295 }
11296
11297 pub fn sort_lines_case_insensitive(
11298 &mut self,
11299 _: &SortLinesCaseInsensitive,
11300 window: &mut Window,
11301 cx: &mut Context<Self>,
11302 ) {
11303 self.manipulate_immutable_lines(window, cx, |lines| {
11304 lines.sort_by_key(|line| line.to_lowercase())
11305 })
11306 }
11307
11308 pub fn unique_lines_case_insensitive(
11309 &mut self,
11310 _: &UniqueLinesCaseInsensitive,
11311 window: &mut Window,
11312 cx: &mut Context<Self>,
11313 ) {
11314 self.manipulate_immutable_lines(window, cx, |lines| {
11315 let mut seen = HashSet::default();
11316 lines.retain(|line| seen.insert(line.to_lowercase()));
11317 })
11318 }
11319
11320 pub fn unique_lines_case_sensitive(
11321 &mut self,
11322 _: &UniqueLinesCaseSensitive,
11323 window: &mut Window,
11324 cx: &mut Context<Self>,
11325 ) {
11326 self.manipulate_immutable_lines(window, cx, |lines| {
11327 let mut seen = HashSet::default();
11328 lines.retain(|line| seen.insert(*line));
11329 })
11330 }
11331
11332 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11333 let snapshot = self.buffer.read(cx).snapshot(cx);
11334 for selection in self.selections.disjoint_anchors_arc().iter() {
11335 if snapshot
11336 .language_at(selection.start)
11337 .and_then(|lang| lang.config().wrap_characters.as_ref())
11338 .is_some()
11339 {
11340 return true;
11341 }
11342 }
11343 false
11344 }
11345
11346 fn wrap_selections_in_tag(
11347 &mut self,
11348 _: &WrapSelectionsInTag,
11349 window: &mut Window,
11350 cx: &mut Context<Self>,
11351 ) {
11352 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11353
11354 let snapshot = self.buffer.read(cx).snapshot(cx);
11355
11356 let mut edits = Vec::new();
11357 let mut boundaries = Vec::new();
11358
11359 for selection in self
11360 .selections
11361 .all_adjusted(&self.display_snapshot(cx))
11362 .iter()
11363 {
11364 let Some(wrap_config) = snapshot
11365 .language_at(selection.start)
11366 .and_then(|lang| lang.config().wrap_characters.clone())
11367 else {
11368 continue;
11369 };
11370
11371 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11372 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11373
11374 let start_before = snapshot.anchor_before(selection.start);
11375 let end_after = snapshot.anchor_after(selection.end);
11376
11377 edits.push((start_before..start_before, open_tag));
11378 edits.push((end_after..end_after, close_tag));
11379
11380 boundaries.push((
11381 start_before,
11382 end_after,
11383 wrap_config.start_prefix.len(),
11384 wrap_config.end_suffix.len(),
11385 ));
11386 }
11387
11388 if edits.is_empty() {
11389 return;
11390 }
11391
11392 self.transact(window, cx, |this, window, cx| {
11393 let buffer = this.buffer.update(cx, |buffer, cx| {
11394 buffer.edit(edits, None, cx);
11395 buffer.snapshot(cx)
11396 });
11397
11398 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11399 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11400 boundaries.into_iter()
11401 {
11402 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11403 let close_offset = end_after
11404 .to_offset(&buffer)
11405 .saturating_sub_usize(end_suffix_len);
11406 new_selections.push(open_offset..open_offset);
11407 new_selections.push(close_offset..close_offset);
11408 }
11409
11410 this.change_selections(Default::default(), window, cx, |s| {
11411 s.select_ranges(new_selections);
11412 });
11413
11414 this.request_autoscroll(Autoscroll::fit(), cx);
11415 });
11416 }
11417
11418 pub fn toggle_read_only(
11419 &mut self,
11420 _: &workspace::ToggleReadOnlyFile,
11421 _: &mut Window,
11422 cx: &mut Context<Self>,
11423 ) {
11424 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11425 buffer.update(cx, |buffer, cx| {
11426 buffer.set_capability(
11427 match buffer.capability() {
11428 Capability::ReadWrite => Capability::Read,
11429 Capability::Read => Capability::ReadWrite,
11430 Capability::ReadOnly => Capability::ReadOnly,
11431 },
11432 cx,
11433 );
11434 })
11435 }
11436 }
11437
11438 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11439 let Some(project) = self.project.clone() else {
11440 return;
11441 };
11442 let task = self.reload(project, window, cx);
11443 self.detach_and_notify_err(task, window, cx);
11444 }
11445
11446 pub fn restore_file(
11447 &mut self,
11448 _: &::git::RestoreFile,
11449 window: &mut Window,
11450 cx: &mut Context<Self>,
11451 ) {
11452 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11453 let mut buffer_ids = HashSet::default();
11454 let snapshot = self.buffer().read(cx).snapshot(cx);
11455 for selection in self
11456 .selections
11457 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11458 {
11459 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11460 }
11461
11462 let buffer = self.buffer().read(cx);
11463 let ranges = buffer_ids
11464 .into_iter()
11465 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11466 .collect::<Vec<_>>();
11467
11468 self.restore_hunks_in_ranges(ranges, window, cx);
11469 }
11470
11471 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11472 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11473 let selections = self
11474 .selections
11475 .all(&self.display_snapshot(cx))
11476 .into_iter()
11477 .map(|s| s.range())
11478 .collect();
11479 self.restore_hunks_in_ranges(selections, window, cx);
11480 }
11481
11482 /// Restores the diff hunks in the editor's selections and moves the cursor
11483 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11484 /// not all diff hunks are expanded.
11485 pub fn restore_and_next(
11486 &mut self,
11487 _: &::git::RestoreAndNext,
11488 window: &mut Window,
11489 cx: &mut Context<Self>,
11490 ) {
11491 let selections = self
11492 .selections
11493 .all(&self.display_snapshot(cx))
11494 .into_iter()
11495 .map(|selection| selection.range())
11496 .collect();
11497
11498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11499 self.restore_hunks_in_ranges(selections, window, cx);
11500
11501 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11502 let wrap_around = !all_diff_hunks_expanded;
11503 let snapshot = self.snapshot(window, cx);
11504 let position = self
11505 .selections
11506 .newest::<Point>(&snapshot.display_snapshot)
11507 .head();
11508
11509 self.go_to_hunk_before_or_after_position(
11510 &snapshot,
11511 position,
11512 Direction::Next,
11513 wrap_around,
11514 window,
11515 cx,
11516 );
11517 }
11518
11519 pub fn restore_hunks_in_ranges(
11520 &mut self,
11521 ranges: Vec<Range<Point>>,
11522 window: &mut Window,
11523 cx: &mut Context<Editor>,
11524 ) {
11525 if self.delegate_stage_and_restore {
11526 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11527 if !hunks.is_empty() {
11528 cx.emit(EditorEvent::RestoreRequested { hunks });
11529 }
11530 return;
11531 }
11532 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11533 self.transact(window, cx, |editor, window, cx| {
11534 editor.restore_diff_hunks(hunks, cx);
11535 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11536 selections.refresh()
11537 });
11538 });
11539 }
11540
11541 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11542 let mut revert_changes = HashMap::default();
11543 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11544 for (buffer_id, hunks) in &chunk_by {
11545 let hunks = hunks.collect::<Vec<_>>();
11546 for hunk in &hunks {
11547 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11548 }
11549 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11550 }
11551 if !revert_changes.is_empty() {
11552 self.buffer().update(cx, |multi_buffer, cx| {
11553 for (buffer_id, changes) in revert_changes {
11554 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11555 buffer.update(cx, |buffer, cx| {
11556 buffer.edit(
11557 changes
11558 .into_iter()
11559 .map(|(range, text)| (range, text.to_string())),
11560 None,
11561 cx,
11562 );
11563 });
11564 }
11565 }
11566 });
11567 }
11568 }
11569
11570 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11571 if let Some(status) = self
11572 .addons
11573 .iter()
11574 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11575 {
11576 return Some(status);
11577 }
11578 self.project
11579 .as_ref()?
11580 .read(cx)
11581 .status_for_buffer_id(buffer_id, cx)
11582 }
11583
11584 pub fn open_active_item_in_terminal(
11585 &mut self,
11586 _: &OpenInTerminal,
11587 window: &mut Window,
11588 cx: &mut Context<Self>,
11589 ) {
11590 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11591 let project_path = buffer.read(cx).project_path(cx)?;
11592 let project = self.project()?.read(cx);
11593 let entry = project.entry_for_path(&project_path, cx)?;
11594 let parent = match &entry.canonical_path {
11595 Some(canonical_path) => canonical_path.to_path_buf(),
11596 None => project.absolute_path(&project_path, cx)?,
11597 }
11598 .parent()?
11599 .to_path_buf();
11600 Some(parent)
11601 }) {
11602 window.dispatch_action(
11603 OpenTerminal {
11604 working_directory,
11605 local: false,
11606 }
11607 .boxed_clone(),
11608 cx,
11609 );
11610 }
11611 }
11612
11613 fn set_breakpoint_context_menu(
11614 &mut self,
11615 display_row: DisplayRow,
11616 position: Option<Anchor>,
11617 clicked_point: gpui::Point<Pixels>,
11618 window: &mut Window,
11619 cx: &mut Context<Self>,
11620 ) {
11621 let source = self
11622 .buffer
11623 .read(cx)
11624 .snapshot(cx)
11625 .anchor_before(Point::new(display_row.0, 0u32));
11626
11627 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11628
11629 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11630 self,
11631 source,
11632 clicked_point,
11633 context_menu,
11634 window,
11635 cx,
11636 );
11637 }
11638
11639 fn add_edit_breakpoint_block(
11640 &mut self,
11641 anchor: Anchor,
11642 breakpoint: &Breakpoint,
11643 edit_action: BreakpointPromptEditAction,
11644 window: &mut Window,
11645 cx: &mut Context<Self>,
11646 ) {
11647 let weak_editor = cx.weak_entity();
11648 let bp_prompt = cx.new(|cx| {
11649 BreakpointPromptEditor::new(
11650 weak_editor,
11651 anchor,
11652 breakpoint.clone(),
11653 edit_action,
11654 window,
11655 cx,
11656 )
11657 });
11658
11659 let height = bp_prompt.update(cx, |this, cx| {
11660 this.prompt
11661 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11662 });
11663 let cloned_prompt = bp_prompt.clone();
11664 let blocks = vec![BlockProperties {
11665 style: BlockStyle::Sticky,
11666 placement: BlockPlacement::Above(anchor),
11667 height: Some(height),
11668 render: Arc::new(move |cx| {
11669 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11670 cloned_prompt.clone().into_any_element()
11671 }),
11672 priority: 0,
11673 }];
11674
11675 let focus_handle = bp_prompt.focus_handle(cx);
11676 window.focus(&focus_handle, cx);
11677
11678 let block_ids = self.insert_blocks(blocks, None, cx);
11679 bp_prompt.update(cx, |prompt, _| {
11680 prompt.add_block_ids(block_ids);
11681 });
11682 }
11683
11684 pub(crate) fn breakpoint_at_row(
11685 &self,
11686 row: u32,
11687 window: &mut Window,
11688 cx: &mut Context<Self>,
11689 ) -> Option<(Anchor, Breakpoint)> {
11690 let snapshot = self.snapshot(window, cx);
11691 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11692
11693 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11694 }
11695
11696 pub(crate) fn breakpoint_at_anchor(
11697 &self,
11698 breakpoint_position: Anchor,
11699 snapshot: &EditorSnapshot,
11700 cx: &mut Context<Self>,
11701 ) -> Option<(Anchor, Breakpoint)> {
11702 let buffer = self
11703 .buffer
11704 .read(cx)
11705 .buffer_for_anchor(breakpoint_position, cx)?;
11706
11707 let enclosing_excerpt = breakpoint_position.excerpt_id;
11708 let buffer_snapshot = buffer.read(cx).snapshot();
11709
11710 let row = buffer_snapshot
11711 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11712 .row;
11713
11714 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11715 let anchor_end = snapshot
11716 .buffer_snapshot()
11717 .anchor_after(Point::new(row, line_len));
11718
11719 self.breakpoint_store
11720 .as_ref()?
11721 .read_with(cx, |breakpoint_store, cx| {
11722 breakpoint_store
11723 .breakpoints(
11724 &buffer,
11725 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11726 &buffer_snapshot,
11727 cx,
11728 )
11729 .next()
11730 .and_then(|(bp, _)| {
11731 let breakpoint_row = buffer_snapshot
11732 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11733 .row;
11734
11735 if breakpoint_row == row {
11736 snapshot
11737 .buffer_snapshot()
11738 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11739 .map(|position| (position, bp.bp.clone()))
11740 } else {
11741 None
11742 }
11743 })
11744 })
11745 }
11746
11747 pub fn edit_log_breakpoint(
11748 &mut self,
11749 _: &EditLogBreakpoint,
11750 window: &mut Window,
11751 cx: &mut Context<Self>,
11752 ) {
11753 if self.breakpoint_store.is_none() {
11754 return;
11755 }
11756
11757 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11758 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11759 message: None,
11760 state: BreakpointState::Enabled,
11761 condition: None,
11762 hit_condition: None,
11763 });
11764
11765 self.add_edit_breakpoint_block(
11766 anchor,
11767 &breakpoint,
11768 BreakpointPromptEditAction::Log,
11769 window,
11770 cx,
11771 );
11772 }
11773 }
11774
11775 fn breakpoints_at_cursors(
11776 &self,
11777 window: &mut Window,
11778 cx: &mut Context<Self>,
11779 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11780 let snapshot = self.snapshot(window, cx);
11781 let cursors = self
11782 .selections
11783 .disjoint_anchors_arc()
11784 .iter()
11785 .map(|selection| {
11786 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11787
11788 let breakpoint_position = self
11789 .breakpoint_at_row(cursor_position.row, window, cx)
11790 .map(|bp| bp.0)
11791 .unwrap_or_else(|| {
11792 snapshot
11793 .display_snapshot
11794 .buffer_snapshot()
11795 .anchor_after(Point::new(cursor_position.row, 0))
11796 });
11797
11798 let breakpoint = self
11799 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11800 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11801
11802 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11803 })
11804 // 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.
11805 .collect::<HashMap<Anchor, _>>();
11806
11807 cursors.into_iter().collect()
11808 }
11809
11810 pub fn enable_breakpoint(
11811 &mut self,
11812 _: &crate::actions::EnableBreakpoint,
11813 window: &mut Window,
11814 cx: &mut Context<Self>,
11815 ) {
11816 if self.breakpoint_store.is_none() {
11817 return;
11818 }
11819
11820 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11821 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11822 continue;
11823 };
11824 self.edit_breakpoint_at_anchor(
11825 anchor,
11826 breakpoint,
11827 BreakpointEditAction::InvertState,
11828 cx,
11829 );
11830 }
11831 }
11832
11833 pub fn disable_breakpoint(
11834 &mut self,
11835 _: &crate::actions::DisableBreakpoint,
11836 window: &mut Window,
11837 cx: &mut Context<Self>,
11838 ) {
11839 if self.breakpoint_store.is_none() {
11840 return;
11841 }
11842
11843 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11844 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11845 continue;
11846 };
11847 self.edit_breakpoint_at_anchor(
11848 anchor,
11849 breakpoint,
11850 BreakpointEditAction::InvertState,
11851 cx,
11852 );
11853 }
11854 }
11855
11856 pub fn toggle_breakpoint(
11857 &mut self,
11858 _: &crate::actions::ToggleBreakpoint,
11859 window: &mut Window,
11860 cx: &mut Context<Self>,
11861 ) {
11862 if self.breakpoint_store.is_none() {
11863 return;
11864 }
11865
11866 let snapshot = self.snapshot(window, cx);
11867 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11868 if self.gutter_breakpoint_indicator.0.is_some() {
11869 let display_row = anchor
11870 .to_point(snapshot.buffer_snapshot())
11871 .to_display_point(&snapshot.display_snapshot)
11872 .row();
11873 self.update_breakpoint_collision_on_toggle(
11874 display_row,
11875 &BreakpointEditAction::Toggle,
11876 );
11877 }
11878
11879 if let Some(breakpoint) = breakpoint {
11880 self.edit_breakpoint_at_anchor(
11881 anchor,
11882 breakpoint,
11883 BreakpointEditAction::Toggle,
11884 cx,
11885 );
11886 } else {
11887 self.edit_breakpoint_at_anchor(
11888 anchor,
11889 Breakpoint::new_standard(),
11890 BreakpointEditAction::Toggle,
11891 cx,
11892 );
11893 }
11894 }
11895 }
11896
11897 fn update_breakpoint_collision_on_toggle(
11898 &mut self,
11899 display_row: DisplayRow,
11900 edit_action: &BreakpointEditAction,
11901 ) {
11902 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
11903 if breakpoint_indicator.display_row == display_row
11904 && matches!(edit_action, BreakpointEditAction::Toggle)
11905 {
11906 breakpoint_indicator.collides_with_existing_breakpoint =
11907 !breakpoint_indicator.collides_with_existing_breakpoint;
11908 }
11909 }
11910 }
11911
11912 pub fn edit_breakpoint_at_anchor(
11913 &mut self,
11914 breakpoint_position: Anchor,
11915 breakpoint: Breakpoint,
11916 edit_action: BreakpointEditAction,
11917 cx: &mut Context<Self>,
11918 ) {
11919 let Some(breakpoint_store) = &self.breakpoint_store else {
11920 return;
11921 };
11922
11923 let Some(buffer) = self
11924 .buffer
11925 .read(cx)
11926 .buffer_for_anchor(breakpoint_position, cx)
11927 else {
11928 return;
11929 };
11930
11931 breakpoint_store.update(cx, |breakpoint_store, cx| {
11932 breakpoint_store.toggle_breakpoint(
11933 buffer,
11934 BreakpointWithPosition {
11935 position: breakpoint_position.text_anchor,
11936 bp: breakpoint,
11937 },
11938 edit_action,
11939 cx,
11940 );
11941 });
11942
11943 cx.notify();
11944 }
11945
11946 #[cfg(any(test, feature = "test-support"))]
11947 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11948 self.breakpoint_store.clone()
11949 }
11950
11951 pub fn prepare_restore_change(
11952 &self,
11953 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11954 hunk: &MultiBufferDiffHunk,
11955 cx: &mut App,
11956 ) -> Option<()> {
11957 if hunk.is_created_file() {
11958 return None;
11959 }
11960 let buffer = self.buffer.read(cx);
11961 let diff = buffer.diff_for(hunk.buffer_id)?;
11962 let buffer = buffer.buffer(hunk.buffer_id)?;
11963 let buffer = buffer.read(cx);
11964 let original_text = diff
11965 .read(cx)
11966 .base_text(cx)
11967 .as_rope()
11968 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11969 let buffer_snapshot = buffer.snapshot();
11970 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11971 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11972 probe
11973 .0
11974 .start
11975 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11976 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11977 }) {
11978 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11979 Some(())
11980 } else {
11981 None
11982 }
11983 }
11984
11985 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11986 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11987 }
11988
11989 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11990 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11991 }
11992
11993 pub fn rotate_selections_forward(
11994 &mut self,
11995 _: &RotateSelectionsForward,
11996 window: &mut Window,
11997 cx: &mut Context<Self>,
11998 ) {
11999 self.rotate_selections(window, cx, false)
12000 }
12001
12002 pub fn rotate_selections_backward(
12003 &mut self,
12004 _: &RotateSelectionsBackward,
12005 window: &mut Window,
12006 cx: &mut Context<Self>,
12007 ) {
12008 self.rotate_selections(window, cx, true)
12009 }
12010
12011 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12013 let display_snapshot = self.display_snapshot(cx);
12014 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12015
12016 if selections.len() < 2 {
12017 return;
12018 }
12019
12020 let (edits, new_selections) = {
12021 let buffer = self.buffer.read(cx).read(cx);
12022 let has_selections = selections.iter().any(|s| !s.is_empty());
12023 if has_selections {
12024 let mut selected_texts: Vec<String> = selections
12025 .iter()
12026 .map(|selection| {
12027 buffer
12028 .text_for_range(selection.start..selection.end)
12029 .collect()
12030 })
12031 .collect();
12032
12033 if reverse {
12034 selected_texts.rotate_left(1);
12035 } else {
12036 selected_texts.rotate_right(1);
12037 }
12038
12039 let mut offset_delta: i64 = 0;
12040 let mut new_selections = Vec::new();
12041 let edits: Vec<_> = selections
12042 .iter()
12043 .zip(selected_texts.iter())
12044 .map(|(selection, new_text)| {
12045 let old_len = (selection.end.0 - selection.start.0) as i64;
12046 let new_len = new_text.len() as i64;
12047 let adjusted_start =
12048 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12049 let adjusted_end =
12050 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12051
12052 new_selections.push(Selection {
12053 id: selection.id,
12054 start: adjusted_start,
12055 end: adjusted_end,
12056 reversed: selection.reversed,
12057 goal: selection.goal,
12058 });
12059
12060 offset_delta += new_len - old_len;
12061 (selection.start..selection.end, new_text.clone())
12062 })
12063 .collect();
12064 (edits, new_selections)
12065 } else {
12066 let mut all_rows: Vec<u32> = selections
12067 .iter()
12068 .map(|selection| buffer.offset_to_point(selection.start).row)
12069 .collect();
12070 all_rows.sort_unstable();
12071 all_rows.dedup();
12072
12073 if all_rows.len() < 2 {
12074 return;
12075 }
12076
12077 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12078 .iter()
12079 .map(|&row| {
12080 let start = Point::new(row, 0);
12081 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12082 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12083 })
12084 .collect();
12085
12086 let mut line_texts: Vec<String> = line_ranges
12087 .iter()
12088 .map(|range| buffer.text_for_range(range.clone()).collect())
12089 .collect();
12090
12091 if reverse {
12092 line_texts.rotate_left(1);
12093 } else {
12094 line_texts.rotate_right(1);
12095 }
12096
12097 let edits = line_ranges
12098 .iter()
12099 .zip(line_texts.iter())
12100 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12101 .collect();
12102
12103 let num_rows = all_rows.len();
12104 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12105 .iter()
12106 .enumerate()
12107 .map(|(i, &row)| (row, i))
12108 .collect();
12109
12110 // Compute new line start offsets after rotation (handles CRLF)
12111 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12112 let first_line_start = line_ranges[0].start.0;
12113 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12114 for text in line_texts.iter().take(num_rows - 1) {
12115 let prev_start = *new_line_starts.last().unwrap();
12116 new_line_starts.push(prev_start + text.len() + newline_len);
12117 }
12118
12119 let new_selections = selections
12120 .iter()
12121 .map(|selection| {
12122 let point = buffer.offset_to_point(selection.start);
12123 let old_index = row_to_index[&point.row];
12124 let new_index = if reverse {
12125 (old_index + num_rows - 1) % num_rows
12126 } else {
12127 (old_index + 1) % num_rows
12128 };
12129 let new_offset =
12130 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12131 Selection {
12132 id: selection.id,
12133 start: new_offset,
12134 end: new_offset,
12135 reversed: selection.reversed,
12136 goal: selection.goal,
12137 }
12138 })
12139 .collect();
12140
12141 (edits, new_selections)
12142 }
12143 };
12144
12145 self.transact(window, cx, |this, window, cx| {
12146 this.buffer.update(cx, |buffer, cx| {
12147 buffer.edit(edits, None, cx);
12148 });
12149 this.change_selections(Default::default(), window, cx, |s| {
12150 s.select(new_selections);
12151 });
12152 });
12153 }
12154
12155 fn manipulate_lines<M>(
12156 &mut self,
12157 window: &mut Window,
12158 cx: &mut Context<Self>,
12159 mut manipulate: M,
12160 ) where
12161 M: FnMut(&str) -> LineManipulationResult,
12162 {
12163 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12164
12165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12166 let buffer = self.buffer.read(cx).snapshot(cx);
12167
12168 let mut edits = Vec::new();
12169
12170 let selections = self.selections.all::<Point>(&display_map);
12171 let mut selections = selections.iter().peekable();
12172 let mut contiguous_row_selections = Vec::new();
12173 let mut new_selections = Vec::new();
12174 let mut added_lines = 0;
12175 let mut removed_lines = 0;
12176
12177 while let Some(selection) = selections.next() {
12178 let (start_row, end_row) = consume_contiguous_rows(
12179 &mut contiguous_row_selections,
12180 selection,
12181 &display_map,
12182 &mut selections,
12183 );
12184
12185 let start_point = Point::new(start_row.0, 0);
12186 let end_point = Point::new(
12187 end_row.previous_row().0,
12188 buffer.line_len(end_row.previous_row()),
12189 );
12190 let text = buffer
12191 .text_for_range(start_point..end_point)
12192 .collect::<String>();
12193
12194 let LineManipulationResult {
12195 new_text,
12196 line_count_before,
12197 line_count_after,
12198 } = manipulate(&text);
12199
12200 edits.push((start_point..end_point, new_text));
12201
12202 // Selections must change based on added and removed line count
12203 let start_row =
12204 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12205 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12206 new_selections.push(Selection {
12207 id: selection.id,
12208 start: start_row,
12209 end: end_row,
12210 goal: SelectionGoal::None,
12211 reversed: selection.reversed,
12212 });
12213
12214 if line_count_after > line_count_before {
12215 added_lines += line_count_after - line_count_before;
12216 } else if line_count_before > line_count_after {
12217 removed_lines += line_count_before - line_count_after;
12218 }
12219 }
12220
12221 self.transact(window, cx, |this, window, cx| {
12222 let buffer = this.buffer.update(cx, |buffer, cx| {
12223 buffer.edit(edits, None, cx);
12224 buffer.snapshot(cx)
12225 });
12226
12227 // Recalculate offsets on newly edited buffer
12228 let new_selections = new_selections
12229 .iter()
12230 .map(|s| {
12231 let start_point = Point::new(s.start.0, 0);
12232 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12233 Selection {
12234 id: s.id,
12235 start: buffer.point_to_offset(start_point),
12236 end: buffer.point_to_offset(end_point),
12237 goal: s.goal,
12238 reversed: s.reversed,
12239 }
12240 })
12241 .collect();
12242
12243 this.change_selections(Default::default(), window, cx, |s| {
12244 s.select(new_selections);
12245 });
12246
12247 this.request_autoscroll(Autoscroll::fit(), cx);
12248 });
12249 }
12250
12251 fn manipulate_immutable_lines<Fn>(
12252 &mut self,
12253 window: &mut Window,
12254 cx: &mut Context<Self>,
12255 mut callback: Fn,
12256 ) where
12257 Fn: FnMut(&mut Vec<&str>),
12258 {
12259 self.manipulate_lines(window, cx, |text| {
12260 let mut lines: Vec<&str> = text.split('\n').collect();
12261 let line_count_before = lines.len();
12262
12263 callback(&mut lines);
12264
12265 LineManipulationResult {
12266 new_text: lines.join("\n"),
12267 line_count_before,
12268 line_count_after: lines.len(),
12269 }
12270 });
12271 }
12272
12273 fn manipulate_mutable_lines<Fn>(
12274 &mut self,
12275 window: &mut Window,
12276 cx: &mut Context<Self>,
12277 mut callback: Fn,
12278 ) where
12279 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12280 {
12281 self.manipulate_lines(window, cx, |text| {
12282 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12283 let line_count_before = lines.len();
12284
12285 callback(&mut lines);
12286
12287 LineManipulationResult {
12288 new_text: lines.join("\n"),
12289 line_count_before,
12290 line_count_after: lines.len(),
12291 }
12292 });
12293 }
12294
12295 pub fn convert_indentation_to_spaces(
12296 &mut self,
12297 _: &ConvertIndentationToSpaces,
12298 window: &mut Window,
12299 cx: &mut Context<Self>,
12300 ) {
12301 let settings = self.buffer.read(cx).language_settings(cx);
12302 let tab_size = settings.tab_size.get() as usize;
12303
12304 self.manipulate_mutable_lines(window, cx, |lines| {
12305 // Allocates a reasonably sized scratch buffer once for the whole loop
12306 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12307 // Avoids recomputing spaces that could be inserted many times
12308 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12309 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12310 .collect();
12311
12312 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12313 let mut chars = line.as_ref().chars();
12314 let mut col = 0;
12315 let mut changed = false;
12316
12317 for ch in chars.by_ref() {
12318 match ch {
12319 ' ' => {
12320 reindented_line.push(' ');
12321 col += 1;
12322 }
12323 '\t' => {
12324 // \t are converted to spaces depending on the current column
12325 let spaces_len = tab_size - (col % tab_size);
12326 reindented_line.extend(&space_cache[spaces_len - 1]);
12327 col += spaces_len;
12328 changed = true;
12329 }
12330 _ => {
12331 // If we dont append before break, the character is consumed
12332 reindented_line.push(ch);
12333 break;
12334 }
12335 }
12336 }
12337
12338 if !changed {
12339 reindented_line.clear();
12340 continue;
12341 }
12342 // Append the rest of the line and replace old reference with new one
12343 reindented_line.extend(chars);
12344 *line = Cow::Owned(reindented_line.clone());
12345 reindented_line.clear();
12346 }
12347 });
12348 }
12349
12350 pub fn convert_indentation_to_tabs(
12351 &mut self,
12352 _: &ConvertIndentationToTabs,
12353 window: &mut Window,
12354 cx: &mut Context<Self>,
12355 ) {
12356 let settings = self.buffer.read(cx).language_settings(cx);
12357 let tab_size = settings.tab_size.get() as usize;
12358
12359 self.manipulate_mutable_lines(window, cx, |lines| {
12360 // Allocates a reasonably sized buffer once for the whole loop
12361 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12362 // Avoids recomputing spaces that could be inserted many times
12363 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12364 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12365 .collect();
12366
12367 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12368 let mut chars = line.chars();
12369 let mut spaces_count = 0;
12370 let mut first_non_indent_char = None;
12371 let mut changed = false;
12372
12373 for ch in chars.by_ref() {
12374 match ch {
12375 ' ' => {
12376 // Keep track of spaces. Append \t when we reach tab_size
12377 spaces_count += 1;
12378 changed = true;
12379 if spaces_count == tab_size {
12380 reindented_line.push('\t');
12381 spaces_count = 0;
12382 }
12383 }
12384 '\t' => {
12385 reindented_line.push('\t');
12386 spaces_count = 0;
12387 }
12388 _ => {
12389 // Dont append it yet, we might have remaining spaces
12390 first_non_indent_char = Some(ch);
12391 break;
12392 }
12393 }
12394 }
12395
12396 if !changed {
12397 reindented_line.clear();
12398 continue;
12399 }
12400 // Remaining spaces that didn't make a full tab stop
12401 if spaces_count > 0 {
12402 reindented_line.extend(&space_cache[spaces_count - 1]);
12403 }
12404 // If we consume an extra character that was not indentation, add it back
12405 if let Some(extra_char) = first_non_indent_char {
12406 reindented_line.push(extra_char);
12407 }
12408 // Append the rest of the line and replace old reference with new one
12409 reindented_line.extend(chars);
12410 *line = Cow::Owned(reindented_line.clone());
12411 reindented_line.clear();
12412 }
12413 });
12414 }
12415
12416 pub fn convert_to_upper_case(
12417 &mut self,
12418 _: &ConvertToUpperCase,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) {
12422 self.manipulate_text(window, cx, |text| text.to_uppercase())
12423 }
12424
12425 pub fn convert_to_lower_case(
12426 &mut self,
12427 _: &ConvertToLowerCase,
12428 window: &mut Window,
12429 cx: &mut Context<Self>,
12430 ) {
12431 self.manipulate_text(window, cx, |text| text.to_lowercase())
12432 }
12433
12434 pub fn convert_to_title_case(
12435 &mut self,
12436 _: &ConvertToTitleCase,
12437 window: &mut Window,
12438 cx: &mut Context<Self>,
12439 ) {
12440 self.manipulate_text(window, cx, |text| {
12441 Self::convert_text_case(text, Case::Title)
12442 })
12443 }
12444
12445 pub fn convert_to_snake_case(
12446 &mut self,
12447 _: &ConvertToSnakeCase,
12448 window: &mut Window,
12449 cx: &mut Context<Self>,
12450 ) {
12451 self.manipulate_text(window, cx, |text| {
12452 Self::convert_text_case(text, Case::Snake)
12453 })
12454 }
12455
12456 pub fn convert_to_kebab_case(
12457 &mut self,
12458 _: &ConvertToKebabCase,
12459 window: &mut Window,
12460 cx: &mut Context<Self>,
12461 ) {
12462 self.manipulate_text(window, cx, |text| {
12463 Self::convert_text_case(text, Case::Kebab)
12464 })
12465 }
12466
12467 pub fn convert_to_upper_camel_case(
12468 &mut self,
12469 _: &ConvertToUpperCamelCase,
12470 window: &mut Window,
12471 cx: &mut Context<Self>,
12472 ) {
12473 self.manipulate_text(window, cx, |text| {
12474 Self::convert_text_case(text, Case::UpperCamel)
12475 })
12476 }
12477
12478 pub fn convert_to_lower_camel_case(
12479 &mut self,
12480 _: &ConvertToLowerCamelCase,
12481 window: &mut Window,
12482 cx: &mut Context<Self>,
12483 ) {
12484 self.manipulate_text(window, cx, |text| {
12485 Self::convert_text_case(text, Case::Camel)
12486 })
12487 }
12488
12489 pub fn convert_to_opposite_case(
12490 &mut self,
12491 _: &ConvertToOppositeCase,
12492 window: &mut Window,
12493 cx: &mut Context<Self>,
12494 ) {
12495 self.manipulate_text(window, cx, |text| {
12496 text.chars()
12497 .fold(String::with_capacity(text.len()), |mut t, c| {
12498 if c.is_uppercase() {
12499 t.extend(c.to_lowercase());
12500 } else {
12501 t.extend(c.to_uppercase());
12502 }
12503 t
12504 })
12505 })
12506 }
12507
12508 pub fn convert_to_sentence_case(
12509 &mut self,
12510 _: &ConvertToSentenceCase,
12511 window: &mut Window,
12512 cx: &mut Context<Self>,
12513 ) {
12514 self.manipulate_text(window, cx, |text| {
12515 Self::convert_text_case(text, Case::Sentence)
12516 })
12517 }
12518
12519 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12520 self.manipulate_text(window, cx, |text| {
12521 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12522 if has_upper_case_characters {
12523 text.to_lowercase()
12524 } else {
12525 text.to_uppercase()
12526 }
12527 })
12528 }
12529
12530 pub fn convert_to_rot13(
12531 &mut self,
12532 _: &ConvertToRot13,
12533 window: &mut Window,
12534 cx: &mut Context<Self>,
12535 ) {
12536 self.manipulate_text(window, cx, |text| {
12537 text.chars()
12538 .map(|c| match c {
12539 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12540 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12541 _ => c,
12542 })
12543 .collect()
12544 })
12545 }
12546
12547 fn convert_text_case(text: &str, case: Case) -> String {
12548 text.lines()
12549 .map(|line| {
12550 let trimmed_start = line.trim_start();
12551 let leading = &line[..line.len() - trimmed_start.len()];
12552 let trimmed = trimmed_start.trim_end();
12553 let trailing = &trimmed_start[trimmed.len()..];
12554 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12555 })
12556 .join("\n")
12557 }
12558
12559 pub fn convert_to_rot47(
12560 &mut self,
12561 _: &ConvertToRot47,
12562 window: &mut Window,
12563 cx: &mut Context<Self>,
12564 ) {
12565 self.manipulate_text(window, cx, |text| {
12566 text.chars()
12567 .map(|c| {
12568 let code_point = c as u32;
12569 if code_point >= 33 && code_point <= 126 {
12570 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12571 }
12572 c
12573 })
12574 .collect()
12575 })
12576 }
12577
12578 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12579 where
12580 Fn: FnMut(&str) -> String,
12581 {
12582 let buffer = self.buffer.read(cx).snapshot(cx);
12583
12584 let mut new_selections = Vec::new();
12585 let mut edits = Vec::new();
12586 let mut selection_adjustment = 0isize;
12587
12588 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12589 let selection_is_empty = selection.is_empty();
12590
12591 let (start, end) = if selection_is_empty {
12592 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12593 (word_range.start, word_range.end)
12594 } else {
12595 (
12596 buffer.point_to_offset(selection.start),
12597 buffer.point_to_offset(selection.end),
12598 )
12599 };
12600
12601 let text = buffer.text_for_range(start..end).collect::<String>();
12602 let old_length = text.len() as isize;
12603 let text = callback(&text);
12604
12605 new_selections.push(Selection {
12606 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12607 end: MultiBufferOffset(
12608 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12609 ),
12610 goal: SelectionGoal::None,
12611 id: selection.id,
12612 reversed: selection.reversed,
12613 });
12614
12615 selection_adjustment += old_length - text.len() as isize;
12616
12617 edits.push((start..end, text));
12618 }
12619
12620 self.transact(window, cx, |this, window, cx| {
12621 this.buffer.update(cx, |buffer, cx| {
12622 buffer.edit(edits, None, cx);
12623 });
12624
12625 this.change_selections(Default::default(), window, cx, |s| {
12626 s.select(new_selections);
12627 });
12628
12629 this.request_autoscroll(Autoscroll::fit(), cx);
12630 });
12631 }
12632
12633 pub fn move_selection_on_drop(
12634 &mut self,
12635 selection: &Selection<Anchor>,
12636 target: DisplayPoint,
12637 is_cut: bool,
12638 window: &mut Window,
12639 cx: &mut Context<Self>,
12640 ) {
12641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12642 let buffer = display_map.buffer_snapshot();
12643 let mut edits = Vec::new();
12644 let insert_point = display_map
12645 .clip_point(target, Bias::Left)
12646 .to_point(&display_map);
12647 let text = buffer
12648 .text_for_range(selection.start..selection.end)
12649 .collect::<String>();
12650 if is_cut {
12651 edits.push(((selection.start..selection.end), String::new()));
12652 }
12653 let insert_anchor = buffer.anchor_before(insert_point);
12654 edits.push(((insert_anchor..insert_anchor), text));
12655 let last_edit_start = insert_anchor.bias_left(buffer);
12656 let last_edit_end = insert_anchor.bias_right(buffer);
12657 self.transact(window, cx, |this, window, cx| {
12658 this.buffer.update(cx, |buffer, cx| {
12659 buffer.edit(edits, None, cx);
12660 });
12661 this.change_selections(Default::default(), window, cx, |s| {
12662 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12663 });
12664 });
12665 }
12666
12667 pub fn clear_selection_drag_state(&mut self) {
12668 self.selection_drag_state = SelectionDragState::None;
12669 }
12670
12671 pub fn duplicate(
12672 &mut self,
12673 upwards: bool,
12674 whole_lines: bool,
12675 window: &mut Window,
12676 cx: &mut Context<Self>,
12677 ) {
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12679
12680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12681 let buffer = display_map.buffer_snapshot();
12682 let selections = self.selections.all::<Point>(&display_map);
12683
12684 let mut edits = Vec::new();
12685 let mut selections_iter = selections.iter().peekable();
12686 while let Some(selection) = selections_iter.next() {
12687 let mut rows = selection.spanned_rows(false, &display_map);
12688 // duplicate line-wise
12689 if whole_lines || selection.start == selection.end {
12690 // Avoid duplicating the same lines twice.
12691 while let Some(next_selection) = selections_iter.peek() {
12692 let next_rows = next_selection.spanned_rows(false, &display_map);
12693 if next_rows.start < rows.end {
12694 rows.end = next_rows.end;
12695 selections_iter.next().unwrap();
12696 } else {
12697 break;
12698 }
12699 }
12700
12701 // Copy the text from the selected row region and splice it either at the start
12702 // or end of the region.
12703 let start = Point::new(rows.start.0, 0);
12704 let end = Point::new(
12705 rows.end.previous_row().0,
12706 buffer.line_len(rows.end.previous_row()),
12707 );
12708
12709 let mut text = buffer.text_for_range(start..end).collect::<String>();
12710
12711 let insert_location = if upwards {
12712 // When duplicating upward, we need to insert before the current line.
12713 // If we're on the last line and it doesn't end with a newline,
12714 // we need to add a newline before the duplicated content.
12715 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12716 && buffer.max_point().column > 0
12717 && !text.ends_with('\n');
12718
12719 if needs_leading_newline {
12720 text.insert(0, '\n');
12721 end
12722 } else {
12723 text.push('\n');
12724 Point::new(rows.start.0, 0)
12725 }
12726 } else {
12727 text.push('\n');
12728 start
12729 };
12730 edits.push((insert_location..insert_location, text));
12731 } else {
12732 // duplicate character-wise
12733 let start = selection.start;
12734 let end = selection.end;
12735 let text = buffer.text_for_range(start..end).collect::<String>();
12736 edits.push((selection.end..selection.end, text));
12737 }
12738 }
12739
12740 self.transact(window, cx, |this, window, cx| {
12741 this.buffer.update(cx, |buffer, cx| {
12742 buffer.edit(edits, None, cx);
12743 });
12744
12745 // When duplicating upward with whole lines, move the cursor to the duplicated line
12746 if upwards && whole_lines {
12747 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12748
12749 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12750 let mut new_ranges = Vec::new();
12751 let selections = s.all::<Point>(&display_map);
12752 let mut selections_iter = selections.iter().peekable();
12753
12754 while let Some(first_selection) = selections_iter.next() {
12755 // Group contiguous selections together to find the total row span
12756 let mut group_selections = vec![first_selection];
12757 let mut rows = first_selection.spanned_rows(false, &display_map);
12758
12759 while let Some(next_selection) = selections_iter.peek() {
12760 let next_rows = next_selection.spanned_rows(false, &display_map);
12761 if next_rows.start < rows.end {
12762 rows.end = next_rows.end;
12763 group_selections.push(selections_iter.next().unwrap());
12764 } else {
12765 break;
12766 }
12767 }
12768
12769 let row_count = rows.end.0 - rows.start.0;
12770
12771 // Move all selections in this group up by the total number of duplicated rows
12772 for selection in group_selections {
12773 let new_start = Point::new(
12774 selection.start.row.saturating_sub(row_count),
12775 selection.start.column,
12776 );
12777
12778 let new_end = Point::new(
12779 selection.end.row.saturating_sub(row_count),
12780 selection.end.column,
12781 );
12782
12783 new_ranges.push(new_start..new_end);
12784 }
12785 }
12786
12787 s.select_ranges(new_ranges);
12788 });
12789 }
12790
12791 this.request_autoscroll(Autoscroll::fit(), cx);
12792 });
12793 }
12794
12795 pub fn duplicate_line_up(
12796 &mut self,
12797 _: &DuplicateLineUp,
12798 window: &mut Window,
12799 cx: &mut Context<Self>,
12800 ) {
12801 self.duplicate(true, true, window, cx);
12802 }
12803
12804 pub fn duplicate_line_down(
12805 &mut self,
12806 _: &DuplicateLineDown,
12807 window: &mut Window,
12808 cx: &mut Context<Self>,
12809 ) {
12810 self.duplicate(false, true, window, cx);
12811 }
12812
12813 pub fn duplicate_selection(
12814 &mut self,
12815 _: &DuplicateSelection,
12816 window: &mut Window,
12817 cx: &mut Context<Self>,
12818 ) {
12819 self.duplicate(false, false, window, cx);
12820 }
12821
12822 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12824 if self.mode.is_single_line() {
12825 cx.propagate();
12826 return;
12827 }
12828
12829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12830 let buffer = self.buffer.read(cx).snapshot(cx);
12831
12832 let mut edits = Vec::new();
12833 let mut unfold_ranges = Vec::new();
12834 let mut refold_creases = Vec::new();
12835
12836 let selections = self.selections.all::<Point>(&display_map);
12837 let mut selections = selections.iter().peekable();
12838 let mut contiguous_row_selections = Vec::new();
12839 let mut new_selections = Vec::new();
12840
12841 while let Some(selection) = selections.next() {
12842 // Find all the selections that span a contiguous row range
12843 let (start_row, end_row) = consume_contiguous_rows(
12844 &mut contiguous_row_selections,
12845 selection,
12846 &display_map,
12847 &mut selections,
12848 );
12849
12850 // Move the text spanned by the row range to be before the line preceding the row range
12851 if start_row.0 > 0 {
12852 let range_to_move = Point::new(
12853 start_row.previous_row().0,
12854 buffer.line_len(start_row.previous_row()),
12855 )
12856 ..Point::new(
12857 end_row.previous_row().0,
12858 buffer.line_len(end_row.previous_row()),
12859 );
12860 let insertion_point = display_map
12861 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12862 .0;
12863
12864 // Don't move lines across excerpts
12865 if buffer
12866 .excerpt_containing(insertion_point..range_to_move.end)
12867 .is_some()
12868 {
12869 let text = buffer
12870 .text_for_range(range_to_move.clone())
12871 .flat_map(|s| s.chars())
12872 .skip(1)
12873 .chain(['\n'])
12874 .collect::<String>();
12875
12876 edits.push((
12877 buffer.anchor_after(range_to_move.start)
12878 ..buffer.anchor_before(range_to_move.end),
12879 String::new(),
12880 ));
12881 let insertion_anchor = buffer.anchor_after(insertion_point);
12882 edits.push((insertion_anchor..insertion_anchor, text));
12883
12884 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12885
12886 // Move selections up
12887 new_selections.extend(contiguous_row_selections.drain(..).map(
12888 |mut selection| {
12889 selection.start.row -= row_delta;
12890 selection.end.row -= row_delta;
12891 selection
12892 },
12893 ));
12894
12895 // Move folds up
12896 unfold_ranges.push(range_to_move.clone());
12897 for fold in display_map.folds_in_range(
12898 buffer.anchor_before(range_to_move.start)
12899 ..buffer.anchor_after(range_to_move.end),
12900 ) {
12901 let mut start = fold.range.start.to_point(&buffer);
12902 let mut end = fold.range.end.to_point(&buffer);
12903 start.row -= row_delta;
12904 end.row -= row_delta;
12905 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12906 }
12907 }
12908 }
12909
12910 // If we didn't move line(s), preserve the existing selections
12911 new_selections.append(&mut contiguous_row_selections);
12912 }
12913
12914 self.transact(window, cx, |this, window, cx| {
12915 this.unfold_ranges(&unfold_ranges, true, true, cx);
12916 this.buffer.update(cx, |buffer, cx| {
12917 for (range, text) in edits {
12918 buffer.edit([(range, text)], None, cx);
12919 }
12920 });
12921 this.fold_creases(refold_creases, true, window, cx);
12922 this.change_selections(Default::default(), window, cx, |s| {
12923 s.select(new_selections);
12924 })
12925 });
12926 }
12927
12928 pub fn move_line_down(
12929 &mut self,
12930 _: &MoveLineDown,
12931 window: &mut Window,
12932 cx: &mut Context<Self>,
12933 ) {
12934 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12935 if self.mode.is_single_line() {
12936 cx.propagate();
12937 return;
12938 }
12939
12940 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12941 let buffer = self.buffer.read(cx).snapshot(cx);
12942
12943 let mut edits = Vec::new();
12944 let mut unfold_ranges = Vec::new();
12945 let mut refold_creases = Vec::new();
12946
12947 let selections = self.selections.all::<Point>(&display_map);
12948 let mut selections = selections.iter().peekable();
12949 let mut contiguous_row_selections = Vec::new();
12950 let mut new_selections = Vec::new();
12951
12952 while let Some(selection) = selections.next() {
12953 // Find all the selections that span a contiguous row range
12954 let (start_row, end_row) = consume_contiguous_rows(
12955 &mut contiguous_row_selections,
12956 selection,
12957 &display_map,
12958 &mut selections,
12959 );
12960
12961 // Move the text spanned by the row range to be after the last line of the row range
12962 if end_row.0 <= buffer.max_point().row {
12963 let range_to_move =
12964 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12965 let insertion_point = display_map
12966 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12967 .0;
12968
12969 // Don't move lines across excerpt boundaries
12970 if buffer
12971 .excerpt_containing(range_to_move.start..insertion_point)
12972 .is_some()
12973 {
12974 let mut text = String::from("\n");
12975 text.extend(buffer.text_for_range(range_to_move.clone()));
12976 text.pop(); // Drop trailing newline
12977 edits.push((
12978 buffer.anchor_after(range_to_move.start)
12979 ..buffer.anchor_before(range_to_move.end),
12980 String::new(),
12981 ));
12982 let insertion_anchor = buffer.anchor_after(insertion_point);
12983 edits.push((insertion_anchor..insertion_anchor, text));
12984
12985 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12986
12987 // Move selections down
12988 new_selections.extend(contiguous_row_selections.drain(..).map(
12989 |mut selection| {
12990 selection.start.row += row_delta;
12991 selection.end.row += row_delta;
12992 selection
12993 },
12994 ));
12995
12996 // Move folds down
12997 unfold_ranges.push(range_to_move.clone());
12998 for fold in display_map.folds_in_range(
12999 buffer.anchor_before(range_to_move.start)
13000 ..buffer.anchor_after(range_to_move.end),
13001 ) {
13002 let mut start = fold.range.start.to_point(&buffer);
13003 let mut end = fold.range.end.to_point(&buffer);
13004 start.row += row_delta;
13005 end.row += row_delta;
13006 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13007 }
13008 }
13009 }
13010
13011 // If we didn't move line(s), preserve the existing selections
13012 new_selections.append(&mut contiguous_row_selections);
13013 }
13014
13015 self.transact(window, cx, |this, window, cx| {
13016 this.unfold_ranges(&unfold_ranges, true, true, cx);
13017 this.buffer.update(cx, |buffer, cx| {
13018 for (range, text) in edits {
13019 buffer.edit([(range, text)], None, cx);
13020 }
13021 });
13022 this.fold_creases(refold_creases, true, window, cx);
13023 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13024 });
13025 }
13026
13027 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13029 let text_layout_details = &self.text_layout_details(window, cx);
13030 self.transact(window, cx, |this, window, cx| {
13031 let edits = this.change_selections(Default::default(), window, cx, |s| {
13032 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13033 s.move_with(&mut |display_map, selection| {
13034 if !selection.is_empty() {
13035 return;
13036 }
13037
13038 let mut head = selection.head();
13039 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13040 if head.column() == display_map.line_len(head.row()) {
13041 transpose_offset = display_map
13042 .buffer_snapshot()
13043 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13044 }
13045
13046 if transpose_offset == MultiBufferOffset(0) {
13047 return;
13048 }
13049
13050 *head.column_mut() += 1;
13051 head = display_map.clip_point(head, Bias::Right);
13052 let goal = SelectionGoal::HorizontalPosition(
13053 display_map
13054 .x_for_display_point(head, text_layout_details)
13055 .into(),
13056 );
13057 selection.collapse_to(head, goal);
13058
13059 let transpose_start = display_map
13060 .buffer_snapshot()
13061 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13062 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13063 let transpose_end = display_map
13064 .buffer_snapshot()
13065 .clip_offset(transpose_offset + 1usize, Bias::Right);
13066 if let Some(ch) = display_map
13067 .buffer_snapshot()
13068 .chars_at(transpose_start)
13069 .next()
13070 {
13071 edits.push((transpose_start..transpose_offset, String::new()));
13072 edits.push((transpose_end..transpose_end, ch.to_string()));
13073 }
13074 }
13075 });
13076 edits
13077 });
13078 this.buffer
13079 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13080 let selections = this
13081 .selections
13082 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13083 this.change_selections(Default::default(), window, cx, |s| {
13084 s.select(selections);
13085 });
13086 });
13087 }
13088
13089 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13090 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13091 if self.mode.is_single_line() {
13092 cx.propagate();
13093 return;
13094 }
13095
13096 self.rewrap_impl(RewrapOptions::default(), cx)
13097 }
13098
13099 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13100 let buffer = self.buffer.read(cx).snapshot(cx);
13101 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13102
13103 #[derive(Clone, Debug, PartialEq)]
13104 enum CommentFormat {
13105 /// single line comment, with prefix for line
13106 Line(String),
13107 /// single line within a block comment, with prefix for line
13108 BlockLine(String),
13109 /// a single line of a block comment that includes the initial delimiter
13110 BlockCommentWithStart(BlockCommentConfig),
13111 /// a single line of a block comment that includes the ending delimiter
13112 BlockCommentWithEnd(BlockCommentConfig),
13113 }
13114
13115 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13116 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13117 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13118 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13119 .peekable();
13120
13121 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13122 row
13123 } else {
13124 return Vec::new();
13125 };
13126
13127 let language_settings = buffer.language_settings_at(selection.head(), cx);
13128 let language_scope = buffer.language_scope_at(selection.head());
13129
13130 let indent_and_prefix_for_row =
13131 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13132 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13133 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13134 &language_scope
13135 {
13136 let indent_end = Point::new(row, indent.len);
13137 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13138 let line_text_after_indent = buffer
13139 .text_for_range(indent_end..line_end)
13140 .collect::<String>();
13141
13142 let is_within_comment_override = buffer
13143 .language_scope_at(indent_end)
13144 .is_some_and(|scope| scope.override_name() == Some("comment"));
13145 let comment_delimiters = if is_within_comment_override {
13146 // we are within a comment syntax node, but we don't
13147 // yet know what kind of comment: block, doc or line
13148 match (
13149 language_scope.documentation_comment(),
13150 language_scope.block_comment(),
13151 ) {
13152 (Some(config), _) | (_, Some(config))
13153 if buffer.contains_str_at(indent_end, &config.start) =>
13154 {
13155 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13156 }
13157 (Some(config), _) | (_, Some(config))
13158 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13159 {
13160 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13161 }
13162 (Some(config), _) | (_, Some(config))
13163 if buffer.contains_str_at(indent_end, &config.prefix) =>
13164 {
13165 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13166 }
13167 (_, _) => language_scope
13168 .line_comment_prefixes()
13169 .iter()
13170 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13171 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13172 }
13173 } else {
13174 // we not in an overridden comment node, but we may
13175 // be within a non-overridden line comment node
13176 language_scope
13177 .line_comment_prefixes()
13178 .iter()
13179 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13180 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13181 };
13182
13183 let rewrap_prefix = language_scope
13184 .rewrap_prefixes()
13185 .iter()
13186 .find_map(|prefix_regex| {
13187 prefix_regex.find(&line_text_after_indent).map(|mat| {
13188 if mat.start() == 0 {
13189 Some(mat.as_str().to_string())
13190 } else {
13191 None
13192 }
13193 })
13194 })
13195 .flatten();
13196 (comment_delimiters, rewrap_prefix)
13197 } else {
13198 (None, None)
13199 };
13200 (indent, comment_prefix, rewrap_prefix)
13201 };
13202
13203 let mut ranges = Vec::new();
13204 let from_empty_selection = selection.is_empty();
13205
13206 let mut current_range_start = first_row;
13207 let mut prev_row = first_row;
13208 let (
13209 mut current_range_indent,
13210 mut current_range_comment_delimiters,
13211 mut current_range_rewrap_prefix,
13212 ) = indent_and_prefix_for_row(first_row);
13213
13214 for row in non_blank_rows_iter.skip(1) {
13215 let has_paragraph_break = row > prev_row + 1;
13216
13217 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13218 indent_and_prefix_for_row(row);
13219
13220 let has_indent_change = row_indent != current_range_indent;
13221 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13222
13223 let has_boundary_change = has_comment_change
13224 || row_rewrap_prefix.is_some()
13225 || (has_indent_change && current_range_comment_delimiters.is_some());
13226
13227 if has_paragraph_break || has_boundary_change {
13228 ranges.push((
13229 language_settings.clone(),
13230 Point::new(current_range_start, 0)
13231 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13232 current_range_indent,
13233 current_range_comment_delimiters.clone(),
13234 current_range_rewrap_prefix.clone(),
13235 from_empty_selection,
13236 ));
13237 current_range_start = row;
13238 current_range_indent = row_indent;
13239 current_range_comment_delimiters = row_comment_delimiters;
13240 current_range_rewrap_prefix = row_rewrap_prefix;
13241 }
13242 prev_row = row;
13243 }
13244
13245 ranges.push((
13246 language_settings.clone(),
13247 Point::new(current_range_start, 0)
13248 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13249 current_range_indent,
13250 current_range_comment_delimiters,
13251 current_range_rewrap_prefix,
13252 from_empty_selection,
13253 ));
13254
13255 ranges
13256 });
13257
13258 let mut edits = Vec::new();
13259 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13260
13261 for (
13262 language_settings,
13263 wrap_range,
13264 mut indent_size,
13265 comment_prefix,
13266 rewrap_prefix,
13267 from_empty_selection,
13268 ) in wrap_ranges
13269 {
13270 let mut start_row = wrap_range.start.row;
13271 let mut end_row = wrap_range.end.row;
13272
13273 // Skip selections that overlap with a range that has already been rewrapped.
13274 let selection_range = start_row..end_row;
13275 if rewrapped_row_ranges
13276 .iter()
13277 .any(|range| range.overlaps(&selection_range))
13278 {
13279 continue;
13280 }
13281
13282 let tab_size = language_settings.tab_size;
13283
13284 let (line_prefix, inside_comment) = match &comment_prefix {
13285 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13286 (Some(prefix.as_str()), true)
13287 }
13288 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13289 (Some(prefix.as_ref()), true)
13290 }
13291 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13292 start: _,
13293 end: _,
13294 prefix,
13295 tab_size,
13296 })) => {
13297 indent_size.len += tab_size;
13298 (Some(prefix.as_ref()), true)
13299 }
13300 None => (None, false),
13301 };
13302 let indent_prefix = indent_size.chars().collect::<String>();
13303 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13304
13305 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13306 RewrapBehavior::InComments => inside_comment,
13307 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13308 RewrapBehavior::Anywhere => true,
13309 };
13310
13311 let should_rewrap = options.override_language_settings
13312 || allow_rewrap_based_on_language
13313 || self.hard_wrap.is_some();
13314 if !should_rewrap {
13315 continue;
13316 }
13317
13318 if from_empty_selection {
13319 'expand_upwards: while start_row > 0 {
13320 let prev_row = start_row - 1;
13321 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13322 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13323 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13324 {
13325 start_row = prev_row;
13326 } else {
13327 break 'expand_upwards;
13328 }
13329 }
13330
13331 'expand_downwards: while end_row < buffer.max_point().row {
13332 let next_row = end_row + 1;
13333 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13334 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13335 && !buffer.is_line_blank(MultiBufferRow(next_row))
13336 {
13337 end_row = next_row;
13338 } else {
13339 break 'expand_downwards;
13340 }
13341 }
13342 }
13343
13344 let start = Point::new(start_row, 0);
13345 let start_offset = ToOffset::to_offset(&start, &buffer);
13346 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13347 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13348 let mut first_line_delimiter = None;
13349 let mut last_line_delimiter = None;
13350 let Some(lines_without_prefixes) = selection_text
13351 .lines()
13352 .enumerate()
13353 .map(|(ix, line)| {
13354 let line_trimmed = line.trim_start();
13355 if rewrap_prefix.is_some() && ix > 0 {
13356 Ok(line_trimmed)
13357 } else if let Some(
13358 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13359 start,
13360 prefix,
13361 end,
13362 tab_size,
13363 })
13364 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13365 start,
13366 prefix,
13367 end,
13368 tab_size,
13369 }),
13370 ) = &comment_prefix
13371 {
13372 let line_trimmed = line_trimmed
13373 .strip_prefix(start.as_ref())
13374 .map(|s| {
13375 let mut indent_size = indent_size;
13376 indent_size.len -= tab_size;
13377 let indent_prefix: String = indent_size.chars().collect();
13378 first_line_delimiter = Some((indent_prefix, start));
13379 s.trim_start()
13380 })
13381 .unwrap_or(line_trimmed);
13382 let line_trimmed = line_trimmed
13383 .strip_suffix(end.as_ref())
13384 .map(|s| {
13385 last_line_delimiter = Some(end);
13386 s.trim_end()
13387 })
13388 .unwrap_or(line_trimmed);
13389 let line_trimmed = line_trimmed
13390 .strip_prefix(prefix.as_ref())
13391 .unwrap_or(line_trimmed);
13392 Ok(line_trimmed)
13393 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13394 line_trimmed.strip_prefix(prefix).with_context(|| {
13395 format!("line did not start with prefix {prefix:?}: {line:?}")
13396 })
13397 } else {
13398 line_trimmed
13399 .strip_prefix(&line_prefix.trim_start())
13400 .with_context(|| {
13401 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13402 })
13403 }
13404 })
13405 .collect::<Result<Vec<_>, _>>()
13406 .log_err()
13407 else {
13408 continue;
13409 };
13410
13411 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13412 buffer
13413 .language_settings_at(Point::new(start_row, 0), cx)
13414 .preferred_line_length as usize
13415 });
13416
13417 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13418 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13419 } else {
13420 line_prefix.clone()
13421 };
13422
13423 let wrapped_text = {
13424 let mut wrapped_text = wrap_with_prefix(
13425 line_prefix,
13426 subsequent_lines_prefix,
13427 lines_without_prefixes.join("\n"),
13428 wrap_column,
13429 tab_size,
13430 options.preserve_existing_whitespace,
13431 );
13432
13433 if let Some((indent, delimiter)) = first_line_delimiter {
13434 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13435 }
13436 if let Some(last_line) = last_line_delimiter {
13437 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13438 }
13439
13440 wrapped_text
13441 };
13442
13443 // TODO: should always use char-based diff while still supporting cursor behavior that
13444 // matches vim.
13445 let mut diff_options = DiffOptions::default();
13446 if options.override_language_settings {
13447 diff_options.max_word_diff_len = 0;
13448 diff_options.max_word_diff_line_count = 0;
13449 } else {
13450 diff_options.max_word_diff_len = usize::MAX;
13451 diff_options.max_word_diff_line_count = usize::MAX;
13452 }
13453
13454 for (old_range, new_text) in
13455 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13456 {
13457 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13458 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13459 edits.push((edit_start..edit_end, new_text));
13460 }
13461
13462 rewrapped_row_ranges.push(start_row..=end_row);
13463 }
13464
13465 self.buffer
13466 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13467 }
13468
13469 pub fn cut_common(
13470 &mut self,
13471 cut_no_selection_line: bool,
13472 window: &mut Window,
13473 cx: &mut Context<Self>,
13474 ) -> ClipboardItem {
13475 let mut text = String::new();
13476 let buffer = self.buffer.read(cx).snapshot(cx);
13477 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13478 let mut clipboard_selections = Vec::with_capacity(selections.len());
13479 {
13480 let max_point = buffer.max_point();
13481 let mut is_first = true;
13482 let mut prev_selection_was_entire_line = false;
13483 for selection in &mut selections {
13484 let is_entire_line =
13485 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13486 if is_entire_line {
13487 selection.start = Point::new(selection.start.row, 0);
13488 if !selection.is_empty() && selection.end.column == 0 {
13489 selection.end = cmp::min(max_point, selection.end);
13490 } else {
13491 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13492 }
13493 selection.goal = SelectionGoal::None;
13494 }
13495 if is_first {
13496 is_first = false;
13497 } else if !prev_selection_was_entire_line {
13498 text += "\n";
13499 }
13500 prev_selection_was_entire_line = is_entire_line;
13501 let mut len = 0;
13502 for chunk in buffer.text_for_range(selection.start..selection.end) {
13503 text.push_str(chunk);
13504 len += chunk.len();
13505 }
13506
13507 clipboard_selections.push(ClipboardSelection::for_buffer(
13508 len,
13509 is_entire_line,
13510 selection.range(),
13511 &buffer,
13512 self.project.as_ref(),
13513 cx,
13514 ));
13515 }
13516 }
13517
13518 self.transact(window, cx, |this, window, cx| {
13519 this.change_selections(Default::default(), window, cx, |s| {
13520 s.select(selections);
13521 });
13522 this.insert("", window, cx);
13523 });
13524 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13525 }
13526
13527 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13529 let item = self.cut_common(true, window, cx);
13530 cx.write_to_clipboard(item);
13531 }
13532
13533 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13534 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13535 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13536 s.move_with(&mut |snapshot, sel| {
13537 if sel.is_empty() {
13538 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13539 }
13540 if sel.is_empty() {
13541 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13542 }
13543 });
13544 });
13545 let item = self.cut_common(false, window, cx);
13546 cx.set_global(KillRing(item))
13547 }
13548
13549 pub fn kill_ring_yank(
13550 &mut self,
13551 _: &KillRingYank,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13556 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13557 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13558 (kill_ring.text().to_string(), kill_ring.metadata_json())
13559 } else {
13560 return;
13561 }
13562 } else {
13563 return;
13564 };
13565 self.do_paste(&text, metadata, false, window, cx);
13566 }
13567
13568 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13569 self.do_copy(true, cx);
13570 }
13571
13572 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13573 self.do_copy(false, cx);
13574 }
13575
13576 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13577 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13578 let buffer = self.buffer.read(cx).read(cx);
13579 let mut text = String::new();
13580 let mut clipboard_selections = Vec::with_capacity(selections.len());
13581
13582 let max_point = buffer.max_point();
13583 let mut is_first = true;
13584 for selection in &selections {
13585 let mut start = selection.start;
13586 let mut end = selection.end;
13587 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13588 let mut add_trailing_newline = false;
13589 if is_entire_line {
13590 start = Point::new(start.row, 0);
13591 let next_line_start = Point::new(end.row + 1, 0);
13592 if next_line_start <= max_point {
13593 end = next_line_start;
13594 } else {
13595 // We're on the last line without a trailing newline.
13596 // Copy to the end of the line and add a newline afterwards.
13597 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13598 add_trailing_newline = true;
13599 }
13600 }
13601
13602 let mut trimmed_selections = Vec::new();
13603 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13604 let row = MultiBufferRow(start.row);
13605 let first_indent = buffer.indent_size_for_line(row);
13606 if first_indent.len == 0 || start.column > first_indent.len {
13607 trimmed_selections.push(start..end);
13608 } else {
13609 trimmed_selections.push(
13610 Point::new(row.0, first_indent.len)
13611 ..Point::new(row.0, buffer.line_len(row)),
13612 );
13613 for row in start.row + 1..=end.row {
13614 let mut line_len = buffer.line_len(MultiBufferRow(row));
13615 if row == end.row {
13616 line_len = end.column;
13617 }
13618 if line_len == 0 {
13619 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13620 continue;
13621 }
13622 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13623 if row_indent_size.len >= first_indent.len {
13624 trimmed_selections
13625 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13626 } else {
13627 trimmed_selections.clear();
13628 trimmed_selections.push(start..end);
13629 break;
13630 }
13631 }
13632 }
13633 } else {
13634 trimmed_selections.push(start..end);
13635 }
13636
13637 let is_multiline_trim = trimmed_selections.len() > 1;
13638 let mut selection_len: usize = 0;
13639 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13640
13641 for trimmed_range in trimmed_selections {
13642 if is_first {
13643 is_first = false;
13644 } else if is_multiline_trim || !prev_selection_was_entire_line {
13645 text.push('\n');
13646 if is_multiline_trim {
13647 selection_len += 1;
13648 }
13649 }
13650 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13651 text.push_str(chunk);
13652 selection_len += chunk.len();
13653 }
13654 if add_trailing_newline {
13655 text.push('\n');
13656 selection_len += 1;
13657 }
13658 }
13659
13660 clipboard_selections.push(ClipboardSelection::for_buffer(
13661 selection_len,
13662 is_entire_line,
13663 start..end,
13664 &buffer,
13665 self.project.as_ref(),
13666 cx,
13667 ));
13668 }
13669
13670 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13671 text,
13672 clipboard_selections,
13673 ));
13674 }
13675
13676 pub fn do_paste(
13677 &mut self,
13678 text: &String,
13679 clipboard_selections: Option<Vec<ClipboardSelection>>,
13680 handle_entire_lines: bool,
13681 window: &mut Window,
13682 cx: &mut Context<Self>,
13683 ) {
13684 if self.read_only(cx) {
13685 return;
13686 }
13687
13688 let clipboard_text = Cow::Borrowed(text.as_str());
13689
13690 self.transact(window, cx, |this, window, cx| {
13691 let had_active_edit_prediction = this.has_active_edit_prediction();
13692 let display_map = this.display_snapshot(cx);
13693 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13694 let cursor_offset = this
13695 .selections
13696 .last::<MultiBufferOffset>(&display_map)
13697 .head();
13698
13699 if let Some(mut clipboard_selections) = clipboard_selections {
13700 let all_selections_were_entire_line =
13701 clipboard_selections.iter().all(|s| s.is_entire_line);
13702 let first_selection_indent_column =
13703 clipboard_selections.first().map(|s| s.first_line_indent);
13704 if clipboard_selections.len() != old_selections.len() {
13705 clipboard_selections.drain(..);
13706 }
13707 let mut auto_indent_on_paste = true;
13708
13709 this.buffer.update(cx, |buffer, cx| {
13710 let snapshot = buffer.read(cx);
13711 auto_indent_on_paste = snapshot
13712 .language_settings_at(cursor_offset, cx)
13713 .auto_indent_on_paste;
13714
13715 let mut start_offset = 0;
13716 let mut edits = Vec::new();
13717 let mut original_indent_columns = Vec::new();
13718 for (ix, selection) in old_selections.iter().enumerate() {
13719 let to_insert;
13720 let entire_line;
13721 let original_indent_column;
13722 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13723 let end_offset = start_offset + clipboard_selection.len;
13724 to_insert = &clipboard_text[start_offset..end_offset];
13725 entire_line = clipboard_selection.is_entire_line;
13726 start_offset = if entire_line {
13727 end_offset
13728 } else {
13729 end_offset + 1
13730 };
13731 original_indent_column = Some(clipboard_selection.first_line_indent);
13732 } else {
13733 to_insert = &*clipboard_text;
13734 entire_line = all_selections_were_entire_line;
13735 original_indent_column = first_selection_indent_column
13736 }
13737
13738 let (range, to_insert) =
13739 if selection.is_empty() && handle_entire_lines && entire_line {
13740 // If the corresponding selection was empty when this slice of the
13741 // clipboard text was written, then the entire line containing the
13742 // selection was copied. If this selection is also currently empty,
13743 // then paste the line before the current line of the buffer.
13744 let column = selection.start.to_point(&snapshot).column as usize;
13745 let line_start = selection.start - column;
13746 (line_start..line_start, Cow::Borrowed(to_insert))
13747 } else {
13748 let language = snapshot.language_at(selection.head());
13749 let range = selection.range();
13750 if let Some(language) = language
13751 && language.name() == "Markdown"
13752 {
13753 edit_for_markdown_paste(
13754 &snapshot,
13755 range,
13756 to_insert,
13757 url::Url::parse(to_insert).ok(),
13758 )
13759 } else {
13760 (range, Cow::Borrowed(to_insert))
13761 }
13762 };
13763
13764 edits.push((range, to_insert));
13765 original_indent_columns.push(original_indent_column);
13766 }
13767 drop(snapshot);
13768
13769 buffer.edit(
13770 edits,
13771 if auto_indent_on_paste {
13772 Some(AutoindentMode::Block {
13773 original_indent_columns,
13774 })
13775 } else {
13776 None
13777 },
13778 cx,
13779 );
13780 });
13781
13782 let selections = this
13783 .selections
13784 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13785 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13786 } else {
13787 let url = url::Url::parse(&clipboard_text).ok();
13788
13789 let auto_indent_mode = if !clipboard_text.is_empty() {
13790 Some(AutoindentMode::Block {
13791 original_indent_columns: Vec::new(),
13792 })
13793 } else {
13794 None
13795 };
13796
13797 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13798 let snapshot = buffer.snapshot(cx);
13799
13800 let anchors = old_selections
13801 .iter()
13802 .map(|s| {
13803 let anchor = snapshot.anchor_after(s.head());
13804 s.map(|_| anchor)
13805 })
13806 .collect::<Vec<_>>();
13807
13808 let mut edits = Vec::new();
13809
13810 // When pasting text without metadata (e.g. copied from an
13811 // external editor using multiple cursors) and the number of
13812 // lines matches the number of selections, distribute one
13813 // line per cursor instead of pasting the whole text at each.
13814 let lines: Vec<&str> = clipboard_text.split('\n').collect();
13815 let distribute_lines =
13816 old_selections.len() > 1 && lines.len() == old_selections.len();
13817
13818 for (ix, selection) in old_selections.iter().enumerate() {
13819 let language = snapshot.language_at(selection.head());
13820 let range = selection.range();
13821
13822 let text_for_cursor: &str = if distribute_lines {
13823 lines[ix]
13824 } else {
13825 &clipboard_text
13826 };
13827
13828 let (edit_range, edit_text) = if let Some(language) = language
13829 && language.name() == "Markdown"
13830 {
13831 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
13832 } else {
13833 (range, Cow::Borrowed(text_for_cursor))
13834 };
13835
13836 edits.push((edit_range, edit_text));
13837 }
13838
13839 drop(snapshot);
13840 buffer.edit(edits, auto_indent_mode, cx);
13841
13842 anchors
13843 });
13844
13845 this.change_selections(Default::default(), window, cx, |s| {
13846 s.select_anchors(selection_anchors);
13847 });
13848 }
13849
13850 // 🤔 | .. | show_in_menu |
13851 // | .. | true true
13852 // | had_edit_prediction | false true
13853
13854 let trigger_in_words =
13855 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13856
13857 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13858 });
13859 }
13860
13861 pub fn diff_clipboard_with_selection(
13862 &mut self,
13863 _: &DiffClipboardWithSelection,
13864 window: &mut Window,
13865 cx: &mut Context<Self>,
13866 ) {
13867 let selections = self
13868 .selections
13869 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13870
13871 if selections.is_empty() {
13872 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13873 return;
13874 };
13875
13876 let clipboard_text = match cx.read_from_clipboard() {
13877 Some(item) => match item.entries().first() {
13878 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13879 _ => None,
13880 },
13881 None => None,
13882 };
13883
13884 let Some(clipboard_text) = clipboard_text else {
13885 log::warn!("Clipboard doesn't contain text.");
13886 return;
13887 };
13888
13889 window.dispatch_action(
13890 Box::new(DiffClipboardWithSelectionData {
13891 clipboard_text,
13892 editor: cx.entity(),
13893 }),
13894 cx,
13895 );
13896 }
13897
13898 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13899 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13900 if let Some(item) = cx.read_from_clipboard() {
13901 let entries = item.entries();
13902
13903 match entries.first() {
13904 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13905 // of all the pasted entries.
13906 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13907 .do_paste(
13908 clipboard_string.text(),
13909 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13910 true,
13911 window,
13912 cx,
13913 ),
13914 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13915 }
13916 }
13917 }
13918
13919 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13920 if self.read_only(cx) {
13921 return;
13922 }
13923
13924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13925
13926 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13927 if let Some((selections, _)) =
13928 self.selection_history.transaction(transaction_id).cloned()
13929 {
13930 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13931 s.select_anchors(selections.to_vec());
13932 });
13933 } else {
13934 log::error!(
13935 "No entry in selection_history found for undo. \
13936 This may correspond to a bug where undo does not update the selection. \
13937 If this is occurring, please add details to \
13938 https://github.com/zed-industries/zed/issues/22692"
13939 );
13940 }
13941 self.request_autoscroll(Autoscroll::fit(), cx);
13942 self.unmark_text(window, cx);
13943 self.refresh_edit_prediction(true, false, window, cx);
13944 cx.emit(EditorEvent::Edited { transaction_id });
13945 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13946 }
13947 }
13948
13949 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13950 if self.read_only(cx) {
13951 return;
13952 }
13953
13954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13955
13956 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13957 if let Some((_, Some(selections))) =
13958 self.selection_history.transaction(transaction_id).cloned()
13959 {
13960 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13961 s.select_anchors(selections.to_vec());
13962 });
13963 } else {
13964 log::error!(
13965 "No entry in selection_history found for redo. \
13966 This may correspond to a bug where undo does not update the selection. \
13967 If this is occurring, please add details to \
13968 https://github.com/zed-industries/zed/issues/22692"
13969 );
13970 }
13971 self.request_autoscroll(Autoscroll::fit(), cx);
13972 self.unmark_text(window, cx);
13973 self.refresh_edit_prediction(true, false, window, cx);
13974 cx.emit(EditorEvent::Edited { transaction_id });
13975 }
13976 }
13977
13978 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13979 self.buffer
13980 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13981 }
13982
13983 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13984 self.buffer
13985 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13986 }
13987
13988 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13990 self.change_selections(Default::default(), window, cx, |s| {
13991 s.move_with(&mut |map, selection| {
13992 let cursor = if selection.is_empty() {
13993 movement::left(map, selection.start)
13994 } else {
13995 selection.start
13996 };
13997 selection.collapse_to(cursor, SelectionGoal::None);
13998 });
13999 })
14000 }
14001
14002 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14003 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14004 self.change_selections(Default::default(), window, cx, |s| {
14005 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14006 })
14007 }
14008
14009 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14011 self.change_selections(Default::default(), window, cx, |s| {
14012 s.move_with(&mut |map, selection| {
14013 let cursor = if selection.is_empty() {
14014 movement::right(map, selection.end)
14015 } else {
14016 selection.end
14017 };
14018 selection.collapse_to(cursor, SelectionGoal::None)
14019 });
14020 })
14021 }
14022
14023 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14025 self.change_selections(Default::default(), window, cx, |s| {
14026 s.move_heads_with(&mut |map, head, _| {
14027 (movement::right(map, head), SelectionGoal::None)
14028 });
14029 });
14030 }
14031
14032 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14033 if self.take_rename(true, window, cx).is_some() {
14034 return;
14035 }
14036
14037 if self.mode.is_single_line() {
14038 cx.propagate();
14039 return;
14040 }
14041
14042 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14043
14044 let text_layout_details = &self.text_layout_details(window, cx);
14045 let selection_count = self.selections.count();
14046 let first_selection = self.selections.first_anchor();
14047
14048 self.change_selections(Default::default(), window, cx, |s| {
14049 s.move_with(&mut |map, selection| {
14050 if !selection.is_empty() {
14051 selection.goal = SelectionGoal::None;
14052 }
14053 let (cursor, goal) = movement::up(
14054 map,
14055 selection.start,
14056 selection.goal,
14057 false,
14058 text_layout_details,
14059 );
14060 selection.collapse_to(cursor, goal);
14061 });
14062 });
14063
14064 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14065 {
14066 cx.propagate();
14067 }
14068 }
14069
14070 pub fn move_up_by_lines(
14071 &mut self,
14072 action: &MoveUpByLines,
14073 window: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) {
14076 if self.take_rename(true, window, cx).is_some() {
14077 return;
14078 }
14079
14080 if self.mode.is_single_line() {
14081 cx.propagate();
14082 return;
14083 }
14084
14085 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14086
14087 let text_layout_details = &self.text_layout_details(window, cx);
14088
14089 self.change_selections(Default::default(), window, cx, |s| {
14090 s.move_with(&mut |map, selection| {
14091 if !selection.is_empty() {
14092 selection.goal = SelectionGoal::None;
14093 }
14094 let (cursor, goal) = movement::up_by_rows(
14095 map,
14096 selection.start,
14097 action.lines,
14098 selection.goal,
14099 false,
14100 text_layout_details,
14101 );
14102 selection.collapse_to(cursor, goal);
14103 });
14104 })
14105 }
14106
14107 pub fn move_down_by_lines(
14108 &mut self,
14109 action: &MoveDownByLines,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) {
14113 if self.take_rename(true, window, cx).is_some() {
14114 return;
14115 }
14116
14117 if self.mode.is_single_line() {
14118 cx.propagate();
14119 return;
14120 }
14121
14122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14123
14124 let text_layout_details = &self.text_layout_details(window, cx);
14125
14126 self.change_selections(Default::default(), window, cx, |s| {
14127 s.move_with(&mut |map, selection| {
14128 if !selection.is_empty() {
14129 selection.goal = SelectionGoal::None;
14130 }
14131 let (cursor, goal) = movement::down_by_rows(
14132 map,
14133 selection.start,
14134 action.lines,
14135 selection.goal,
14136 false,
14137 text_layout_details,
14138 );
14139 selection.collapse_to(cursor, goal);
14140 });
14141 })
14142 }
14143
14144 pub fn select_down_by_lines(
14145 &mut self,
14146 action: &SelectDownByLines,
14147 window: &mut Window,
14148 cx: &mut Context<Self>,
14149 ) {
14150 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14151 let text_layout_details = &self.text_layout_details(window, cx);
14152 self.change_selections(Default::default(), window, cx, |s| {
14153 s.move_heads_with(&mut |map, head, goal| {
14154 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14155 })
14156 })
14157 }
14158
14159 pub fn select_up_by_lines(
14160 &mut self,
14161 action: &SelectUpByLines,
14162 window: &mut Window,
14163 cx: &mut Context<Self>,
14164 ) {
14165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14166 let text_layout_details = &self.text_layout_details(window, cx);
14167 self.change_selections(Default::default(), window, cx, |s| {
14168 s.move_heads_with(&mut |map, head, goal| {
14169 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14170 })
14171 })
14172 }
14173
14174 pub fn select_page_up(
14175 &mut self,
14176 _: &SelectPageUp,
14177 window: &mut Window,
14178 cx: &mut Context<Self>,
14179 ) {
14180 let Some(row_count) = self.visible_row_count() else {
14181 return;
14182 };
14183
14184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14185
14186 let text_layout_details = &self.text_layout_details(window, cx);
14187
14188 self.change_selections(Default::default(), window, cx, |s| {
14189 s.move_heads_with(&mut |map, head, goal| {
14190 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14191 })
14192 })
14193 }
14194
14195 pub fn move_page_up(
14196 &mut self,
14197 action: &MovePageUp,
14198 window: &mut Window,
14199 cx: &mut Context<Self>,
14200 ) {
14201 if self.take_rename(true, window, cx).is_some() {
14202 return;
14203 }
14204
14205 if self
14206 .context_menu
14207 .borrow_mut()
14208 .as_mut()
14209 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14210 .unwrap_or(false)
14211 {
14212 return;
14213 }
14214
14215 if matches!(self.mode, EditorMode::SingleLine) {
14216 cx.propagate();
14217 return;
14218 }
14219
14220 let Some(row_count) = self.visible_row_count() else {
14221 return;
14222 };
14223
14224 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14225
14226 let effects = if action.center_cursor {
14227 SelectionEffects::scroll(Autoscroll::center())
14228 } else {
14229 SelectionEffects::default()
14230 };
14231
14232 let text_layout_details = &self.text_layout_details(window, cx);
14233
14234 self.change_selections(effects, window, cx, |s| {
14235 s.move_with(&mut |map, selection| {
14236 if !selection.is_empty() {
14237 selection.goal = SelectionGoal::None;
14238 }
14239 let (cursor, goal) = movement::up_by_rows(
14240 map,
14241 selection.end,
14242 row_count,
14243 selection.goal,
14244 false,
14245 text_layout_details,
14246 );
14247 selection.collapse_to(cursor, goal);
14248 });
14249 });
14250 }
14251
14252 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14254 let text_layout_details = &self.text_layout_details(window, cx);
14255 self.change_selections(Default::default(), window, cx, |s| {
14256 s.move_heads_with(&mut |map, head, goal| {
14257 movement::up(map, head, goal, false, text_layout_details)
14258 })
14259 })
14260 }
14261
14262 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14263 self.take_rename(true, window, cx);
14264
14265 if self.mode.is_single_line() {
14266 cx.propagate();
14267 return;
14268 }
14269
14270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14271
14272 let text_layout_details = &self.text_layout_details(window, cx);
14273 let selection_count = self.selections.count();
14274 let first_selection = self.selections.first_anchor();
14275
14276 self.change_selections(Default::default(), window, cx, |s| {
14277 s.move_with(&mut |map, selection| {
14278 if !selection.is_empty() {
14279 selection.goal = SelectionGoal::None;
14280 }
14281 let (cursor, goal) = movement::down(
14282 map,
14283 selection.end,
14284 selection.goal,
14285 false,
14286 text_layout_details,
14287 );
14288 selection.collapse_to(cursor, goal);
14289 });
14290 });
14291
14292 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14293 {
14294 cx.propagate();
14295 }
14296 }
14297
14298 pub fn select_page_down(
14299 &mut self,
14300 _: &SelectPageDown,
14301 window: &mut Window,
14302 cx: &mut Context<Self>,
14303 ) {
14304 let Some(row_count) = self.visible_row_count() else {
14305 return;
14306 };
14307
14308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14309
14310 let text_layout_details = &self.text_layout_details(window, cx);
14311
14312 self.change_selections(Default::default(), window, cx, |s| {
14313 s.move_heads_with(&mut |map, head, goal| {
14314 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14315 })
14316 })
14317 }
14318
14319 pub fn move_page_down(
14320 &mut self,
14321 action: &MovePageDown,
14322 window: &mut Window,
14323 cx: &mut Context<Self>,
14324 ) {
14325 if self.take_rename(true, window, cx).is_some() {
14326 return;
14327 }
14328
14329 if self
14330 .context_menu
14331 .borrow_mut()
14332 .as_mut()
14333 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14334 .unwrap_or(false)
14335 {
14336 return;
14337 }
14338
14339 if matches!(self.mode, EditorMode::SingleLine) {
14340 cx.propagate();
14341 return;
14342 }
14343
14344 let Some(row_count) = self.visible_row_count() else {
14345 return;
14346 };
14347
14348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14349
14350 let effects = if action.center_cursor {
14351 SelectionEffects::scroll(Autoscroll::center())
14352 } else {
14353 SelectionEffects::default()
14354 };
14355
14356 let text_layout_details = &self.text_layout_details(window, cx);
14357 self.change_selections(effects, window, cx, |s| {
14358 s.move_with(&mut |map, selection| {
14359 if !selection.is_empty() {
14360 selection.goal = SelectionGoal::None;
14361 }
14362 let (cursor, goal) = movement::down_by_rows(
14363 map,
14364 selection.end,
14365 row_count,
14366 selection.goal,
14367 false,
14368 text_layout_details,
14369 );
14370 selection.collapse_to(cursor, goal);
14371 });
14372 });
14373 }
14374
14375 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14377 let text_layout_details = &self.text_layout_details(window, cx);
14378 self.change_selections(Default::default(), window, cx, |s| {
14379 s.move_heads_with(&mut |map, head, goal| {
14380 movement::down(map, head, goal, false, text_layout_details)
14381 })
14382 });
14383 }
14384
14385 pub fn context_menu_first(
14386 &mut self,
14387 _: &ContextMenuFirst,
14388 window: &mut Window,
14389 cx: &mut Context<Self>,
14390 ) {
14391 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14392 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14393 }
14394 }
14395
14396 pub fn context_menu_prev(
14397 &mut self,
14398 _: &ContextMenuPrevious,
14399 window: &mut Window,
14400 cx: &mut Context<Self>,
14401 ) {
14402 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14403 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14404 }
14405 }
14406
14407 pub fn context_menu_next(
14408 &mut self,
14409 _: &ContextMenuNext,
14410 window: &mut Window,
14411 cx: &mut Context<Self>,
14412 ) {
14413 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14414 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14415 }
14416 }
14417
14418 pub fn context_menu_last(
14419 &mut self,
14420 _: &ContextMenuLast,
14421 window: &mut Window,
14422 cx: &mut Context<Self>,
14423 ) {
14424 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14425 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14426 }
14427 }
14428
14429 pub fn signature_help_prev(
14430 &mut self,
14431 _: &SignatureHelpPrevious,
14432 _: &mut Window,
14433 cx: &mut Context<Self>,
14434 ) {
14435 if let Some(popover) = self.signature_help_state.popover_mut() {
14436 if popover.current_signature == 0 {
14437 popover.current_signature = popover.signatures.len() - 1;
14438 } else {
14439 popover.current_signature -= 1;
14440 }
14441 cx.notify();
14442 }
14443 }
14444
14445 pub fn signature_help_next(
14446 &mut self,
14447 _: &SignatureHelpNext,
14448 _: &mut Window,
14449 cx: &mut Context<Self>,
14450 ) {
14451 if let Some(popover) = self.signature_help_state.popover_mut() {
14452 if popover.current_signature + 1 == popover.signatures.len() {
14453 popover.current_signature = 0;
14454 } else {
14455 popover.current_signature += 1;
14456 }
14457 cx.notify();
14458 }
14459 }
14460
14461 pub fn move_to_previous_word_start(
14462 &mut self,
14463 _: &MoveToPreviousWordStart,
14464 window: &mut Window,
14465 cx: &mut Context<Self>,
14466 ) {
14467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14468 self.change_selections(Default::default(), window, cx, |s| {
14469 s.move_cursors_with(&mut |map, head, _| {
14470 (
14471 movement::previous_word_start(map, head),
14472 SelectionGoal::None,
14473 )
14474 });
14475 })
14476 }
14477
14478 pub fn move_to_previous_subword_start(
14479 &mut self,
14480 _: &MoveToPreviousSubwordStart,
14481 window: &mut Window,
14482 cx: &mut Context<Self>,
14483 ) {
14484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14485 self.change_selections(Default::default(), window, cx, |s| {
14486 s.move_cursors_with(&mut |map, head, _| {
14487 (
14488 movement::previous_subword_start(map, head),
14489 SelectionGoal::None,
14490 )
14491 });
14492 })
14493 }
14494
14495 pub fn select_to_previous_word_start(
14496 &mut self,
14497 _: &SelectToPreviousWordStart,
14498 window: &mut Window,
14499 cx: &mut Context<Self>,
14500 ) {
14501 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14502 self.change_selections(Default::default(), window, cx, |s| {
14503 s.move_heads_with(&mut |map, head, _| {
14504 (
14505 movement::previous_word_start(map, head),
14506 SelectionGoal::None,
14507 )
14508 });
14509 })
14510 }
14511
14512 pub fn select_to_previous_subword_start(
14513 &mut self,
14514 _: &SelectToPreviousSubwordStart,
14515 window: &mut Window,
14516 cx: &mut Context<Self>,
14517 ) {
14518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14519 self.change_selections(Default::default(), window, cx, |s| {
14520 s.move_heads_with(&mut |map, head, _| {
14521 (
14522 movement::previous_subword_start(map, head),
14523 SelectionGoal::None,
14524 )
14525 });
14526 })
14527 }
14528
14529 pub fn delete_to_previous_word_start(
14530 &mut self,
14531 action: &DeleteToPreviousWordStart,
14532 window: &mut Window,
14533 cx: &mut Context<Self>,
14534 ) {
14535 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14536 self.transact(window, cx, |this, window, cx| {
14537 this.select_autoclose_pair(window, cx);
14538 this.change_selections(Default::default(), window, cx, |s| {
14539 s.move_with(&mut |map, selection| {
14540 if selection.is_empty() {
14541 let mut cursor = if action.ignore_newlines {
14542 movement::previous_word_start(map, selection.head())
14543 } else {
14544 movement::previous_word_start_or_newline(map, selection.head())
14545 };
14546 cursor = movement::adjust_greedy_deletion(
14547 map,
14548 selection.head(),
14549 cursor,
14550 action.ignore_brackets,
14551 );
14552 selection.set_head(cursor, SelectionGoal::None);
14553 }
14554 });
14555 });
14556 this.insert("", window, cx);
14557 });
14558 }
14559
14560 pub fn delete_to_previous_subword_start(
14561 &mut self,
14562 action: &DeleteToPreviousSubwordStart,
14563 window: &mut Window,
14564 cx: &mut Context<Self>,
14565 ) {
14566 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14567 self.transact(window, cx, |this, window, cx| {
14568 this.select_autoclose_pair(window, cx);
14569 this.change_selections(Default::default(), window, cx, |s| {
14570 s.move_with(&mut |map, selection| {
14571 if selection.is_empty() {
14572 let mut cursor = if action.ignore_newlines {
14573 movement::previous_subword_start(map, selection.head())
14574 } else {
14575 movement::previous_subword_start_or_newline(map, selection.head())
14576 };
14577 cursor = movement::adjust_greedy_deletion(
14578 map,
14579 selection.head(),
14580 cursor,
14581 action.ignore_brackets,
14582 );
14583 selection.set_head(cursor, SelectionGoal::None);
14584 }
14585 });
14586 });
14587 this.insert("", window, cx);
14588 });
14589 }
14590
14591 pub fn move_to_next_word_end(
14592 &mut self,
14593 _: &MoveToNextWordEnd,
14594 window: &mut Window,
14595 cx: &mut Context<Self>,
14596 ) {
14597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14598 self.change_selections(Default::default(), window, cx, |s| {
14599 s.move_cursors_with(&mut |map, head, _| {
14600 (movement::next_word_end(map, head), SelectionGoal::None)
14601 });
14602 })
14603 }
14604
14605 pub fn move_to_next_subword_end(
14606 &mut self,
14607 _: &MoveToNextSubwordEnd,
14608 window: &mut Window,
14609 cx: &mut Context<Self>,
14610 ) {
14611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14612 self.change_selections(Default::default(), window, cx, |s| {
14613 s.move_cursors_with(&mut |map, head, _| {
14614 (movement::next_subword_end(map, head), SelectionGoal::None)
14615 });
14616 })
14617 }
14618
14619 pub fn select_to_next_word_end(
14620 &mut self,
14621 _: &SelectToNextWordEnd,
14622 window: &mut Window,
14623 cx: &mut Context<Self>,
14624 ) {
14625 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14626 self.change_selections(Default::default(), window, cx, |s| {
14627 s.move_heads_with(&mut |map, head, _| {
14628 (movement::next_word_end(map, head), SelectionGoal::None)
14629 });
14630 })
14631 }
14632
14633 pub fn select_to_next_subword_end(
14634 &mut self,
14635 _: &SelectToNextSubwordEnd,
14636 window: &mut Window,
14637 cx: &mut Context<Self>,
14638 ) {
14639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14640 self.change_selections(Default::default(), window, cx, |s| {
14641 s.move_heads_with(&mut |map, head, _| {
14642 (movement::next_subword_end(map, head), SelectionGoal::None)
14643 });
14644 })
14645 }
14646
14647 pub fn delete_to_next_word_end(
14648 &mut self,
14649 action: &DeleteToNextWordEnd,
14650 window: &mut Window,
14651 cx: &mut Context<Self>,
14652 ) {
14653 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14654 self.transact(window, cx, |this, window, cx| {
14655 this.change_selections(Default::default(), window, cx, |s| {
14656 s.move_with(&mut |map, selection| {
14657 if selection.is_empty() {
14658 let mut cursor = if action.ignore_newlines {
14659 movement::next_word_end(map, selection.head())
14660 } else {
14661 movement::next_word_end_or_newline(map, selection.head())
14662 };
14663 cursor = movement::adjust_greedy_deletion(
14664 map,
14665 selection.head(),
14666 cursor,
14667 action.ignore_brackets,
14668 );
14669 selection.set_head(cursor, SelectionGoal::None);
14670 }
14671 });
14672 });
14673 this.insert("", window, cx);
14674 });
14675 }
14676
14677 pub fn delete_to_next_subword_end(
14678 &mut self,
14679 action: &DeleteToNextSubwordEnd,
14680 window: &mut Window,
14681 cx: &mut Context<Self>,
14682 ) {
14683 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14684 self.transact(window, cx, |this, window, cx| {
14685 this.change_selections(Default::default(), window, cx, |s| {
14686 s.move_with(&mut |map, selection| {
14687 if selection.is_empty() {
14688 let mut cursor = if action.ignore_newlines {
14689 movement::next_subword_end(map, selection.head())
14690 } else {
14691 movement::next_subword_end_or_newline(map, selection.head())
14692 };
14693 cursor = movement::adjust_greedy_deletion(
14694 map,
14695 selection.head(),
14696 cursor,
14697 action.ignore_brackets,
14698 );
14699 selection.set_head(cursor, SelectionGoal::None);
14700 }
14701 });
14702 });
14703 this.insert("", window, cx);
14704 });
14705 }
14706
14707 pub fn move_to_beginning_of_line(
14708 &mut self,
14709 action: &MoveToBeginningOfLine,
14710 window: &mut Window,
14711 cx: &mut Context<Self>,
14712 ) {
14713 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
14714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14715 self.change_selections(Default::default(), window, cx, |s| {
14716 s.move_cursors_with(&mut |map, head, _| {
14717 (
14718 movement::indented_line_beginning(
14719 map,
14720 head,
14721 action.stop_at_soft_wraps,
14722 stop_at_indent,
14723 ),
14724 SelectionGoal::None,
14725 )
14726 });
14727 })
14728 }
14729
14730 pub fn select_to_beginning_of_line(
14731 &mut self,
14732 action: &SelectToBeginningOfLine,
14733 window: &mut Window,
14734 cx: &mut Context<Self>,
14735 ) {
14736 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
14737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14738 self.change_selections(Default::default(), window, cx, |s| {
14739 s.move_heads_with(&mut |map, head, _| {
14740 (
14741 movement::indented_line_beginning(
14742 map,
14743 head,
14744 action.stop_at_soft_wraps,
14745 stop_at_indent,
14746 ),
14747 SelectionGoal::None,
14748 )
14749 });
14750 });
14751 }
14752
14753 pub fn delete_to_beginning_of_line(
14754 &mut self,
14755 action: &DeleteToBeginningOfLine,
14756 window: &mut Window,
14757 cx: &mut Context<Self>,
14758 ) {
14759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14760 self.transact(window, cx, |this, window, cx| {
14761 this.change_selections(Default::default(), window, cx, |s| {
14762 s.move_with(&mut |_, selection| {
14763 selection.reversed = true;
14764 });
14765 });
14766
14767 this.select_to_beginning_of_line(
14768 &SelectToBeginningOfLine {
14769 stop_at_soft_wraps: false,
14770 stop_at_indent: action.stop_at_indent,
14771 },
14772 window,
14773 cx,
14774 );
14775 this.backspace(&Backspace, window, cx);
14776 });
14777 }
14778
14779 pub fn move_to_end_of_line(
14780 &mut self,
14781 action: &MoveToEndOfLine,
14782 window: &mut Window,
14783 cx: &mut Context<Self>,
14784 ) {
14785 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14786 self.change_selections(Default::default(), window, cx, |s| {
14787 s.move_cursors_with(&mut |map, head, _| {
14788 (
14789 movement::line_end(map, head, action.stop_at_soft_wraps),
14790 SelectionGoal::None,
14791 )
14792 });
14793 })
14794 }
14795
14796 pub fn select_to_end_of_line(
14797 &mut self,
14798 action: &SelectToEndOfLine,
14799 window: &mut Window,
14800 cx: &mut Context<Self>,
14801 ) {
14802 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14803 self.change_selections(Default::default(), window, cx, |s| {
14804 s.move_heads_with(&mut |map, head, _| {
14805 (
14806 movement::line_end(map, head, action.stop_at_soft_wraps),
14807 SelectionGoal::None,
14808 )
14809 });
14810 })
14811 }
14812
14813 pub fn delete_to_end_of_line(
14814 &mut self,
14815 _: &DeleteToEndOfLine,
14816 window: &mut Window,
14817 cx: &mut Context<Self>,
14818 ) {
14819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14820 self.transact(window, cx, |this, window, cx| {
14821 this.select_to_end_of_line(
14822 &SelectToEndOfLine {
14823 stop_at_soft_wraps: false,
14824 },
14825 window,
14826 cx,
14827 );
14828 this.delete(&Delete, window, cx);
14829 });
14830 }
14831
14832 pub fn cut_to_end_of_line(
14833 &mut self,
14834 action: &CutToEndOfLine,
14835 window: &mut Window,
14836 cx: &mut Context<Self>,
14837 ) {
14838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14839 self.transact(window, cx, |this, window, cx| {
14840 this.select_to_end_of_line(
14841 &SelectToEndOfLine {
14842 stop_at_soft_wraps: false,
14843 },
14844 window,
14845 cx,
14846 );
14847 if !action.stop_at_newlines {
14848 this.change_selections(Default::default(), window, cx, |s| {
14849 s.move_with(&mut |_, sel| {
14850 if sel.is_empty() {
14851 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14852 }
14853 });
14854 });
14855 }
14856 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14857 let item = this.cut_common(false, window, cx);
14858 cx.write_to_clipboard(item);
14859 });
14860 }
14861
14862 pub fn move_to_start_of_paragraph(
14863 &mut self,
14864 _: &MoveToStartOfParagraph,
14865 window: &mut Window,
14866 cx: &mut Context<Self>,
14867 ) {
14868 if matches!(self.mode, EditorMode::SingleLine) {
14869 cx.propagate();
14870 return;
14871 }
14872 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14873 self.change_selections(Default::default(), window, cx, |s| {
14874 s.move_with(&mut |map, selection| {
14875 selection.collapse_to(
14876 movement::start_of_paragraph(map, selection.head(), 1),
14877 SelectionGoal::None,
14878 )
14879 });
14880 })
14881 }
14882
14883 pub fn move_to_end_of_paragraph(
14884 &mut self,
14885 _: &MoveToEndOfParagraph,
14886 window: &mut Window,
14887 cx: &mut Context<Self>,
14888 ) {
14889 if matches!(self.mode, EditorMode::SingleLine) {
14890 cx.propagate();
14891 return;
14892 }
14893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14894 self.change_selections(Default::default(), window, cx, |s| {
14895 s.move_with(&mut |map, selection| {
14896 selection.collapse_to(
14897 movement::end_of_paragraph(map, selection.head(), 1),
14898 SelectionGoal::None,
14899 )
14900 });
14901 })
14902 }
14903
14904 pub fn select_to_start_of_paragraph(
14905 &mut self,
14906 _: &SelectToStartOfParagraph,
14907 window: &mut Window,
14908 cx: &mut Context<Self>,
14909 ) {
14910 if matches!(self.mode, EditorMode::SingleLine) {
14911 cx.propagate();
14912 return;
14913 }
14914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14915 self.change_selections(Default::default(), window, cx, |s| {
14916 s.move_heads_with(&mut |map, head, _| {
14917 (
14918 movement::start_of_paragraph(map, head, 1),
14919 SelectionGoal::None,
14920 )
14921 });
14922 })
14923 }
14924
14925 pub fn select_to_end_of_paragraph(
14926 &mut self,
14927 _: &SelectToEndOfParagraph,
14928 window: &mut Window,
14929 cx: &mut Context<Self>,
14930 ) {
14931 if matches!(self.mode, EditorMode::SingleLine) {
14932 cx.propagate();
14933 return;
14934 }
14935 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14936 self.change_selections(Default::default(), window, cx, |s| {
14937 s.move_heads_with(&mut |map, head, _| {
14938 (
14939 movement::end_of_paragraph(map, head, 1),
14940 SelectionGoal::None,
14941 )
14942 });
14943 })
14944 }
14945
14946 pub fn move_to_start_of_excerpt(
14947 &mut self,
14948 _: &MoveToStartOfExcerpt,
14949 window: &mut Window,
14950 cx: &mut Context<Self>,
14951 ) {
14952 if matches!(self.mode, EditorMode::SingleLine) {
14953 cx.propagate();
14954 return;
14955 }
14956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14957 self.change_selections(Default::default(), window, cx, |s| {
14958 s.move_with(&mut |map, selection| {
14959 selection.collapse_to(
14960 movement::start_of_excerpt(
14961 map,
14962 selection.head(),
14963 workspace::searchable::Direction::Prev,
14964 ),
14965 SelectionGoal::None,
14966 )
14967 });
14968 })
14969 }
14970
14971 pub fn move_to_start_of_next_excerpt(
14972 &mut self,
14973 _: &MoveToStartOfNextExcerpt,
14974 window: &mut Window,
14975 cx: &mut Context<Self>,
14976 ) {
14977 if matches!(self.mode, EditorMode::SingleLine) {
14978 cx.propagate();
14979 return;
14980 }
14981
14982 self.change_selections(Default::default(), window, cx, |s| {
14983 s.move_with(&mut |map, selection| {
14984 selection.collapse_to(
14985 movement::start_of_excerpt(
14986 map,
14987 selection.head(),
14988 workspace::searchable::Direction::Next,
14989 ),
14990 SelectionGoal::None,
14991 )
14992 });
14993 })
14994 }
14995
14996 pub fn move_to_end_of_excerpt(
14997 &mut self,
14998 _: &MoveToEndOfExcerpt,
14999 window: &mut Window,
15000 cx: &mut Context<Self>,
15001 ) {
15002 if matches!(self.mode, EditorMode::SingleLine) {
15003 cx.propagate();
15004 return;
15005 }
15006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15007 self.change_selections(Default::default(), window, cx, |s| {
15008 s.move_with(&mut |map, selection| {
15009 selection.collapse_to(
15010 movement::end_of_excerpt(
15011 map,
15012 selection.head(),
15013 workspace::searchable::Direction::Next,
15014 ),
15015 SelectionGoal::None,
15016 )
15017 });
15018 })
15019 }
15020
15021 pub fn move_to_end_of_previous_excerpt(
15022 &mut self,
15023 _: &MoveToEndOfPreviousExcerpt,
15024 window: &mut Window,
15025 cx: &mut Context<Self>,
15026 ) {
15027 if matches!(self.mode, EditorMode::SingleLine) {
15028 cx.propagate();
15029 return;
15030 }
15031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15032 self.change_selections(Default::default(), window, cx, |s| {
15033 s.move_with(&mut |map, selection| {
15034 selection.collapse_to(
15035 movement::end_of_excerpt(
15036 map,
15037 selection.head(),
15038 workspace::searchable::Direction::Prev,
15039 ),
15040 SelectionGoal::None,
15041 )
15042 });
15043 })
15044 }
15045
15046 pub fn select_to_start_of_excerpt(
15047 &mut self,
15048 _: &SelectToStartOfExcerpt,
15049 window: &mut Window,
15050 cx: &mut Context<Self>,
15051 ) {
15052 if matches!(self.mode, EditorMode::SingleLine) {
15053 cx.propagate();
15054 return;
15055 }
15056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15057 self.change_selections(Default::default(), window, cx, |s| {
15058 s.move_heads_with(&mut |map, head, _| {
15059 (
15060 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15061 SelectionGoal::None,
15062 )
15063 });
15064 })
15065 }
15066
15067 pub fn select_to_start_of_next_excerpt(
15068 &mut self,
15069 _: &SelectToStartOfNextExcerpt,
15070 window: &mut Window,
15071 cx: &mut Context<Self>,
15072 ) {
15073 if matches!(self.mode, EditorMode::SingleLine) {
15074 cx.propagate();
15075 return;
15076 }
15077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15078 self.change_selections(Default::default(), window, cx, |s| {
15079 s.move_heads_with(&mut |map, head, _| {
15080 (
15081 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15082 SelectionGoal::None,
15083 )
15084 });
15085 })
15086 }
15087
15088 pub fn select_to_end_of_excerpt(
15089 &mut self,
15090 _: &SelectToEndOfExcerpt,
15091 window: &mut Window,
15092 cx: &mut Context<Self>,
15093 ) {
15094 if matches!(self.mode, EditorMode::SingleLine) {
15095 cx.propagate();
15096 return;
15097 }
15098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15099 self.change_selections(Default::default(), window, cx, |s| {
15100 s.move_heads_with(&mut |map, head, _| {
15101 (
15102 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15103 SelectionGoal::None,
15104 )
15105 });
15106 })
15107 }
15108
15109 pub fn select_to_end_of_previous_excerpt(
15110 &mut self,
15111 _: &SelectToEndOfPreviousExcerpt,
15112 window: &mut Window,
15113 cx: &mut Context<Self>,
15114 ) {
15115 if matches!(self.mode, EditorMode::SingleLine) {
15116 cx.propagate();
15117 return;
15118 }
15119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15120 self.change_selections(Default::default(), window, cx, |s| {
15121 s.move_heads_with(&mut |map, head, _| {
15122 (
15123 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15124 SelectionGoal::None,
15125 )
15126 });
15127 })
15128 }
15129
15130 pub fn move_to_beginning(
15131 &mut self,
15132 _: &MoveToBeginning,
15133 window: &mut Window,
15134 cx: &mut Context<Self>,
15135 ) {
15136 if matches!(self.mode, EditorMode::SingleLine) {
15137 cx.propagate();
15138 return;
15139 }
15140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15141 self.change_selections(Default::default(), window, cx, |s| {
15142 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15143 });
15144 }
15145
15146 pub fn select_to_beginning(
15147 &mut self,
15148 _: &SelectToBeginning,
15149 window: &mut Window,
15150 cx: &mut Context<Self>,
15151 ) {
15152 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15153 selection.set_head(Point::zero(), SelectionGoal::None);
15154 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15155 self.change_selections(Default::default(), window, cx, |s| {
15156 s.select(vec![selection]);
15157 });
15158 }
15159
15160 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15161 if matches!(self.mode, EditorMode::SingleLine) {
15162 cx.propagate();
15163 return;
15164 }
15165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15166 let cursor = self.buffer.read(cx).read(cx).len();
15167 self.change_selections(Default::default(), window, cx, |s| {
15168 s.select_ranges(vec![cursor..cursor])
15169 });
15170 }
15171
15172 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15173 self.nav_history = nav_history;
15174 }
15175
15176 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15177 self.nav_history.as_ref()
15178 }
15179
15180 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15181 self.push_to_nav_history(
15182 self.selections.newest_anchor().head(),
15183 None,
15184 false,
15185 true,
15186 cx,
15187 );
15188 }
15189
15190 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15191 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15192 let buffer = self.buffer.read(cx).read(cx);
15193 let cursor_position = cursor_anchor.to_point(&buffer);
15194 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15195 let scroll_top_row = scroll_anchor.top_row(&buffer);
15196 drop(buffer);
15197
15198 NavigationData {
15199 cursor_anchor,
15200 cursor_position,
15201 scroll_anchor,
15202 scroll_top_row,
15203 }
15204 }
15205
15206 fn navigation_entry(
15207 &self,
15208 cursor_anchor: Anchor,
15209 cx: &mut Context<Self>,
15210 ) -> Option<NavigationEntry> {
15211 let Some(history) = self.nav_history.clone() else {
15212 return None;
15213 };
15214 let data = self.navigation_data(cursor_anchor, cx);
15215 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15216 }
15217
15218 fn push_to_nav_history(
15219 &mut self,
15220 cursor_anchor: Anchor,
15221 new_position: Option<Point>,
15222 is_deactivate: bool,
15223 always: bool,
15224 cx: &mut Context<Self>,
15225 ) {
15226 let data = self.navigation_data(cursor_anchor, cx);
15227 if let Some(nav_history) = self.nav_history.as_mut() {
15228 if let Some(new_position) = new_position {
15229 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15230 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15231 return;
15232 }
15233 }
15234
15235 nav_history.push(Some(data), cx);
15236 cx.emit(EditorEvent::PushedToNavHistory {
15237 anchor: cursor_anchor,
15238 is_deactivate,
15239 })
15240 }
15241 }
15242
15243 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15245 let buffer = self.buffer.read(cx).snapshot(cx);
15246 let mut selection = self
15247 .selections
15248 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15249 selection.set_head(buffer.len(), SelectionGoal::None);
15250 self.change_selections(Default::default(), window, cx, |s| {
15251 s.select(vec![selection]);
15252 });
15253 }
15254
15255 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15257 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15258 s.select_ranges([Anchor::min()..Anchor::max()]);
15259 });
15260 }
15261
15262 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15264 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15265 let mut selections = self.selections.all::<Point>(&display_map);
15266 let max_point = display_map.buffer_snapshot().max_point();
15267 for selection in &mut selections {
15268 let rows = selection.spanned_rows(true, &display_map);
15269 selection.start = Point::new(rows.start.0, 0);
15270 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15271 selection.reversed = false;
15272 }
15273 self.change_selections(Default::default(), window, cx, |s| {
15274 s.select(selections);
15275 });
15276 }
15277
15278 pub fn split_selection_into_lines(
15279 &mut self,
15280 action: &SplitSelectionIntoLines,
15281 window: &mut Window,
15282 cx: &mut Context<Self>,
15283 ) {
15284 let selections = self
15285 .selections
15286 .all::<Point>(&self.display_snapshot(cx))
15287 .into_iter()
15288 .map(|selection| selection.start..selection.end)
15289 .collect::<Vec<_>>();
15290 self.unfold_ranges(&selections, true, false, cx);
15291
15292 let mut new_selection_ranges = Vec::new();
15293 {
15294 let buffer = self.buffer.read(cx).read(cx);
15295 for selection in selections {
15296 for row in selection.start.row..selection.end.row {
15297 let line_start = Point::new(row, 0);
15298 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15299
15300 if action.keep_selections {
15301 // Keep the selection range for each line
15302 let selection_start = if row == selection.start.row {
15303 selection.start
15304 } else {
15305 line_start
15306 };
15307 new_selection_ranges.push(selection_start..line_end);
15308 } else {
15309 // Collapse to cursor at end of line
15310 new_selection_ranges.push(line_end..line_end);
15311 }
15312 }
15313
15314 let is_multiline_selection = selection.start.row != selection.end.row;
15315 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15316 // so this action feels more ergonomic when paired with other selection operations
15317 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15318 if !should_skip_last {
15319 if action.keep_selections {
15320 if is_multiline_selection {
15321 let line_start = Point::new(selection.end.row, 0);
15322 new_selection_ranges.push(line_start..selection.end);
15323 } else {
15324 new_selection_ranges.push(selection.start..selection.end);
15325 }
15326 } else {
15327 new_selection_ranges.push(selection.end..selection.end);
15328 }
15329 }
15330 }
15331 }
15332 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15333 s.select_ranges(new_selection_ranges);
15334 });
15335 }
15336
15337 pub fn add_selection_above(
15338 &mut self,
15339 action: &AddSelectionAbove,
15340 window: &mut Window,
15341 cx: &mut Context<Self>,
15342 ) {
15343 self.add_selection(true, action.skip_soft_wrap, window, cx);
15344 }
15345
15346 pub fn add_selection_below(
15347 &mut self,
15348 action: &AddSelectionBelow,
15349 window: &mut Window,
15350 cx: &mut Context<Self>,
15351 ) {
15352 self.add_selection(false, action.skip_soft_wrap, window, cx);
15353 }
15354
15355 fn add_selection(
15356 &mut self,
15357 above: bool,
15358 skip_soft_wrap: bool,
15359 window: &mut Window,
15360 cx: &mut Context<Self>,
15361 ) {
15362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15363
15364 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15365 let all_selections = self.selections.all::<Point>(&display_map);
15366 let text_layout_details = self.text_layout_details(window, cx);
15367
15368 let (mut columnar_selections, new_selections_to_columnarize) = {
15369 if let Some(state) = self.add_selections_state.as_ref() {
15370 let columnar_selection_ids: HashSet<_> = state
15371 .groups
15372 .iter()
15373 .flat_map(|group| group.stack.iter())
15374 .copied()
15375 .collect();
15376
15377 all_selections
15378 .into_iter()
15379 .partition(|s| columnar_selection_ids.contains(&s.id))
15380 } else {
15381 (Vec::new(), all_selections)
15382 }
15383 };
15384
15385 let mut state = self
15386 .add_selections_state
15387 .take()
15388 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15389
15390 for selection in new_selections_to_columnarize {
15391 let range = selection.display_range(&display_map).sorted();
15392 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15393 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15394 let positions = start_x.min(end_x)..start_x.max(end_x);
15395 let mut stack = Vec::new();
15396 for row in range.start.row().0..=range.end.row().0 {
15397 if let Some(selection) = self.selections.build_columnar_selection(
15398 &display_map,
15399 DisplayRow(row),
15400 &positions,
15401 selection.reversed,
15402 &text_layout_details,
15403 ) {
15404 stack.push(selection.id);
15405 columnar_selections.push(selection);
15406 }
15407 }
15408 if !stack.is_empty() {
15409 if above {
15410 stack.reverse();
15411 }
15412 state.groups.push(AddSelectionsGroup { above, stack });
15413 }
15414 }
15415
15416 let mut final_selections = Vec::new();
15417 let end_row = if above {
15418 DisplayRow(0)
15419 } else {
15420 display_map.max_point().row()
15421 };
15422
15423 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15424 // positions to place new selections, so we need to keep track of the
15425 // column range of the oldest selection in each group, because
15426 // intermediate selections may have been clamped to shorter lines.
15427 // selections may have been clamped to shorter lines.
15428 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15429 let mut map = HashMap::default();
15430 for group in state.groups.iter() {
15431 if let Some(oldest_id) = group.stack.first() {
15432 if let Some(oldest_selection) =
15433 columnar_selections.iter().find(|s| s.id == *oldest_id)
15434 {
15435 let start_col = oldest_selection.start.column;
15436 let end_col = oldest_selection.end.column;
15437 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15438 for id in &group.stack {
15439 map.insert(*id, goal_columns.clone());
15440 }
15441 }
15442 }
15443 }
15444 map
15445 } else {
15446 HashMap::default()
15447 };
15448
15449 let mut last_added_item_per_group = HashMap::default();
15450 for group in state.groups.iter_mut() {
15451 if let Some(last_id) = group.stack.last() {
15452 last_added_item_per_group.insert(*last_id, group);
15453 }
15454 }
15455
15456 for selection in columnar_selections {
15457 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15458 if above == group.above {
15459 let range = selection.display_range(&display_map).sorted();
15460 debug_assert_eq!(range.start.row(), range.end.row());
15461 let row = range.start.row();
15462 let positions =
15463 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15464 Pixels::from(start)..Pixels::from(end)
15465 } else {
15466 let start_x =
15467 display_map.x_for_display_point(range.start, &text_layout_details);
15468 let end_x =
15469 display_map.x_for_display_point(range.end, &text_layout_details);
15470 start_x.min(end_x)..start_x.max(end_x)
15471 };
15472
15473 let maybe_new_selection = if skip_soft_wrap {
15474 let goal_columns = goal_columns_by_selection_id
15475 .remove(&selection.id)
15476 .unwrap_or_else(|| {
15477 let start_col = selection.start.column;
15478 let end_col = selection.end.column;
15479 start_col.min(end_col)..start_col.max(end_col)
15480 });
15481 self.selections.find_next_columnar_selection_by_buffer_row(
15482 &display_map,
15483 row,
15484 end_row,
15485 above,
15486 &goal_columns,
15487 selection.reversed,
15488 &text_layout_details,
15489 )
15490 } else {
15491 self.selections.find_next_columnar_selection_by_display_row(
15492 &display_map,
15493 row,
15494 end_row,
15495 above,
15496 &positions,
15497 selection.reversed,
15498 &text_layout_details,
15499 )
15500 };
15501
15502 if let Some(new_selection) = maybe_new_selection {
15503 group.stack.push(new_selection.id);
15504 if above {
15505 final_selections.push(new_selection);
15506 final_selections.push(selection);
15507 } else {
15508 final_selections.push(selection);
15509 final_selections.push(new_selection);
15510 }
15511 } else {
15512 final_selections.push(selection);
15513 }
15514 } else {
15515 group.stack.pop();
15516 }
15517 } else {
15518 final_selections.push(selection);
15519 }
15520 }
15521
15522 self.change_selections(Default::default(), window, cx, |s| {
15523 s.select(final_selections);
15524 });
15525
15526 let final_selection_ids: HashSet<_> = self
15527 .selections
15528 .all::<Point>(&display_map)
15529 .iter()
15530 .map(|s| s.id)
15531 .collect();
15532 state.groups.retain_mut(|group| {
15533 // selections might get merged above so we remove invalid items from stacks
15534 group.stack.retain(|id| final_selection_ids.contains(id));
15535
15536 // single selection in stack can be treated as initial state
15537 group.stack.len() > 1
15538 });
15539
15540 if !state.groups.is_empty() {
15541 self.add_selections_state = Some(state);
15542 }
15543 }
15544
15545 pub fn insert_snippet_at_selections(
15546 &mut self,
15547 action: &InsertSnippet,
15548 window: &mut Window,
15549 cx: &mut Context<Self>,
15550 ) {
15551 self.try_insert_snippet_at_selections(action, window, cx)
15552 .log_err();
15553 }
15554
15555 fn try_insert_snippet_at_selections(
15556 &mut self,
15557 action: &InsertSnippet,
15558 window: &mut Window,
15559 cx: &mut Context<Self>,
15560 ) -> Result<()> {
15561 let insertion_ranges = self
15562 .selections
15563 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15564 .into_iter()
15565 .map(|selection| selection.range())
15566 .collect_vec();
15567
15568 let snippet = if let Some(snippet_body) = &action.snippet {
15569 if action.language.is_none() && action.name.is_none() {
15570 Snippet::parse(snippet_body)?
15571 } else {
15572 bail!("`snippet` is mutually exclusive with `language` and `name`")
15573 }
15574 } else if let Some(name) = &action.name {
15575 let project = self.project().context("no project")?;
15576 let snippet_store = project.read(cx).snippets().read(cx);
15577 let snippet = snippet_store
15578 .snippets_for(action.language.clone(), cx)
15579 .into_iter()
15580 .find(|snippet| snippet.name == *name)
15581 .context("snippet not found")?;
15582 Snippet::parse(&snippet.body)?
15583 } else {
15584 // todo(andrew): open modal to select snippet
15585 bail!("`name` or `snippet` is required")
15586 };
15587
15588 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15589 }
15590
15591 fn select_match_ranges(
15592 &mut self,
15593 range: Range<MultiBufferOffset>,
15594 reversed: bool,
15595 replace_newest: bool,
15596 auto_scroll: Option<Autoscroll>,
15597 window: &mut Window,
15598 cx: &mut Context<Editor>,
15599 ) {
15600 self.unfold_ranges(
15601 std::slice::from_ref(&range),
15602 false,
15603 auto_scroll.is_some(),
15604 cx,
15605 );
15606 let effects = if let Some(scroll) = auto_scroll {
15607 SelectionEffects::scroll(scroll)
15608 } else {
15609 SelectionEffects::no_scroll()
15610 };
15611 self.change_selections(effects, window, cx, |s| {
15612 if replace_newest {
15613 s.delete(s.newest_anchor().id);
15614 }
15615 if reversed {
15616 s.insert_range(range.end..range.start);
15617 } else {
15618 s.insert_range(range);
15619 }
15620 });
15621 }
15622
15623 pub fn select_next_match_internal(
15624 &mut self,
15625 display_map: &DisplaySnapshot,
15626 replace_newest: bool,
15627 autoscroll: Option<Autoscroll>,
15628 window: &mut Window,
15629 cx: &mut Context<Self>,
15630 ) -> Result<()> {
15631 let buffer = display_map.buffer_snapshot();
15632 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15633 if let Some(mut select_next_state) = self.select_next_state.take() {
15634 let query = &select_next_state.query;
15635 if !select_next_state.done {
15636 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15637 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15638 let mut next_selected_range = None;
15639
15640 let bytes_after_last_selection =
15641 buffer.bytes_in_range(last_selection.end..buffer.len());
15642 let bytes_before_first_selection =
15643 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15644 let query_matches = query
15645 .stream_find_iter(bytes_after_last_selection)
15646 .map(|result| (last_selection.end, result))
15647 .chain(
15648 query
15649 .stream_find_iter(bytes_before_first_selection)
15650 .map(|result| (MultiBufferOffset(0), result)),
15651 );
15652
15653 for (start_offset, query_match) in query_matches {
15654 let query_match = query_match.unwrap(); // can only fail due to I/O
15655 let offset_range =
15656 start_offset + query_match.start()..start_offset + query_match.end();
15657
15658 if !select_next_state.wordwise
15659 || (!buffer.is_inside_word(offset_range.start, None)
15660 && !buffer.is_inside_word(offset_range.end, None))
15661 {
15662 let idx = selections
15663 .partition_point(|selection| selection.end <= offset_range.start);
15664 let overlaps = selections
15665 .get(idx)
15666 .map_or(false, |selection| selection.start < offset_range.end);
15667
15668 if !overlaps {
15669 next_selected_range = Some(offset_range);
15670 break;
15671 }
15672 }
15673 }
15674
15675 if let Some(next_selected_range) = next_selected_range {
15676 self.select_match_ranges(
15677 next_selected_range,
15678 last_selection.reversed,
15679 replace_newest,
15680 autoscroll,
15681 window,
15682 cx,
15683 );
15684 } else {
15685 select_next_state.done = true;
15686 }
15687 }
15688
15689 self.select_next_state = Some(select_next_state);
15690 } else {
15691 let mut only_carets = true;
15692 let mut same_text_selected = true;
15693 let mut selected_text = None;
15694
15695 let mut selections_iter = selections.iter().peekable();
15696 while let Some(selection) = selections_iter.next() {
15697 if selection.start != selection.end {
15698 only_carets = false;
15699 }
15700
15701 if same_text_selected {
15702 if selected_text.is_none() {
15703 selected_text =
15704 Some(buffer.text_for_range(selection.range()).collect::<String>());
15705 }
15706
15707 if let Some(next_selection) = selections_iter.peek() {
15708 if next_selection.len() == selection.len() {
15709 let next_selected_text = buffer
15710 .text_for_range(next_selection.range())
15711 .collect::<String>();
15712 if Some(next_selected_text) != selected_text {
15713 same_text_selected = false;
15714 selected_text = None;
15715 }
15716 } else {
15717 same_text_selected = false;
15718 selected_text = None;
15719 }
15720 }
15721 }
15722 }
15723
15724 if only_carets {
15725 for selection in &mut selections {
15726 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15727 selection.start = word_range.start;
15728 selection.end = word_range.end;
15729 selection.goal = SelectionGoal::None;
15730 selection.reversed = false;
15731 self.select_match_ranges(
15732 selection.start..selection.end,
15733 selection.reversed,
15734 replace_newest,
15735 autoscroll,
15736 window,
15737 cx,
15738 );
15739 }
15740
15741 if selections.len() == 1 {
15742 let selection = selections
15743 .last()
15744 .expect("ensured that there's only one selection");
15745 let query = buffer
15746 .text_for_range(selection.start..selection.end)
15747 .collect::<String>();
15748 let is_empty = query.is_empty();
15749 let select_state = SelectNextState {
15750 query: self.build_query(&[query], cx)?,
15751 wordwise: true,
15752 done: is_empty,
15753 };
15754 self.select_next_state = Some(select_state);
15755 } else {
15756 self.select_next_state = None;
15757 }
15758 } else if let Some(selected_text) = selected_text {
15759 self.select_next_state = Some(SelectNextState {
15760 query: self.build_query(&[selected_text], cx)?,
15761 wordwise: false,
15762 done: false,
15763 });
15764 self.select_next_match_internal(
15765 display_map,
15766 replace_newest,
15767 autoscroll,
15768 window,
15769 cx,
15770 )?;
15771 }
15772 }
15773 Ok(())
15774 }
15775
15776 pub fn select_all_matches(
15777 &mut self,
15778 _action: &SelectAllMatches,
15779 window: &mut Window,
15780 cx: &mut Context<Self>,
15781 ) -> Result<()> {
15782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15783
15784 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15785
15786 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15787 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15788 else {
15789 return Ok(());
15790 };
15791
15792 let mut new_selections = Vec::new();
15793
15794 let reversed = self
15795 .selections
15796 .oldest::<MultiBufferOffset>(&display_map)
15797 .reversed;
15798 let buffer = display_map.buffer_snapshot();
15799 let query_matches = select_next_state
15800 .query
15801 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15802
15803 for query_match in query_matches.into_iter() {
15804 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15805 let offset_range = if reversed {
15806 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15807 } else {
15808 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15809 };
15810
15811 if !select_next_state.wordwise
15812 || (!buffer.is_inside_word(offset_range.start, None)
15813 && !buffer.is_inside_word(offset_range.end, None))
15814 {
15815 new_selections.push(offset_range.start..offset_range.end);
15816 }
15817 }
15818
15819 select_next_state.done = true;
15820
15821 if new_selections.is_empty() {
15822 log::error!("bug: new_selections is empty in select_all_matches");
15823 return Ok(());
15824 }
15825
15826 self.unfold_ranges(&new_selections, false, false, cx);
15827 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15828 selections.select_ranges(new_selections)
15829 });
15830
15831 Ok(())
15832 }
15833
15834 pub fn select_next(
15835 &mut self,
15836 action: &SelectNext,
15837 window: &mut Window,
15838 cx: &mut Context<Self>,
15839 ) -> Result<()> {
15840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15841 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15842 self.select_next_match_internal(
15843 &display_map,
15844 action.replace_newest,
15845 Some(Autoscroll::newest()),
15846 window,
15847 cx,
15848 )
15849 }
15850
15851 pub fn select_previous(
15852 &mut self,
15853 action: &SelectPrevious,
15854 window: &mut Window,
15855 cx: &mut Context<Self>,
15856 ) -> Result<()> {
15857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15858 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15859 let buffer = display_map.buffer_snapshot();
15860 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15861 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15862 let query = &select_prev_state.query;
15863 if !select_prev_state.done {
15864 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15865 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15866 let mut next_selected_range = None;
15867 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15868 let bytes_before_last_selection =
15869 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15870 let bytes_after_first_selection =
15871 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15872 let query_matches = query
15873 .stream_find_iter(bytes_before_last_selection)
15874 .map(|result| (last_selection.start, result))
15875 .chain(
15876 query
15877 .stream_find_iter(bytes_after_first_selection)
15878 .map(|result| (buffer.len(), result)),
15879 );
15880 for (end_offset, query_match) in query_matches {
15881 let query_match = query_match.unwrap(); // can only fail due to I/O
15882 let offset_range =
15883 end_offset - query_match.end()..end_offset - query_match.start();
15884
15885 if !select_prev_state.wordwise
15886 || (!buffer.is_inside_word(offset_range.start, None)
15887 && !buffer.is_inside_word(offset_range.end, None))
15888 {
15889 next_selected_range = Some(offset_range);
15890 break;
15891 }
15892 }
15893
15894 if let Some(next_selected_range) = next_selected_range {
15895 self.select_match_ranges(
15896 next_selected_range,
15897 last_selection.reversed,
15898 action.replace_newest,
15899 Some(Autoscroll::newest()),
15900 window,
15901 cx,
15902 );
15903 } else {
15904 select_prev_state.done = true;
15905 }
15906 }
15907
15908 self.select_prev_state = Some(select_prev_state);
15909 } else {
15910 let mut only_carets = true;
15911 let mut same_text_selected = true;
15912 let mut selected_text = None;
15913
15914 let mut selections_iter = selections.iter().peekable();
15915 while let Some(selection) = selections_iter.next() {
15916 if selection.start != selection.end {
15917 only_carets = false;
15918 }
15919
15920 if same_text_selected {
15921 if selected_text.is_none() {
15922 selected_text =
15923 Some(buffer.text_for_range(selection.range()).collect::<String>());
15924 }
15925
15926 if let Some(next_selection) = selections_iter.peek() {
15927 if next_selection.len() == selection.len() {
15928 let next_selected_text = buffer
15929 .text_for_range(next_selection.range())
15930 .collect::<String>();
15931 if Some(next_selected_text) != selected_text {
15932 same_text_selected = false;
15933 selected_text = None;
15934 }
15935 } else {
15936 same_text_selected = false;
15937 selected_text = None;
15938 }
15939 }
15940 }
15941 }
15942
15943 if only_carets {
15944 for selection in &mut selections {
15945 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15946 selection.start = word_range.start;
15947 selection.end = word_range.end;
15948 selection.goal = SelectionGoal::None;
15949 selection.reversed = false;
15950 self.select_match_ranges(
15951 selection.start..selection.end,
15952 selection.reversed,
15953 action.replace_newest,
15954 Some(Autoscroll::newest()),
15955 window,
15956 cx,
15957 );
15958 }
15959 if selections.len() == 1 {
15960 let selection = selections
15961 .last()
15962 .expect("ensured that there's only one selection");
15963 let query = buffer
15964 .text_for_range(selection.start..selection.end)
15965 .collect::<String>();
15966 let is_empty = query.is_empty();
15967 let select_state = SelectNextState {
15968 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15969 wordwise: true,
15970 done: is_empty,
15971 };
15972 self.select_prev_state = Some(select_state);
15973 } else {
15974 self.select_prev_state = None;
15975 }
15976 } else if let Some(selected_text) = selected_text {
15977 self.select_prev_state = Some(SelectNextState {
15978 query: self
15979 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15980 wordwise: false,
15981 done: false,
15982 });
15983 self.select_previous(action, window, cx)?;
15984 }
15985 }
15986 Ok(())
15987 }
15988
15989 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15990 /// setting the case sensitivity based on the global
15991 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15992 /// editor's settings.
15993 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15994 where
15995 I: IntoIterator<Item = P>,
15996 P: AsRef<[u8]>,
15997 {
15998 let case_sensitive = self
15999 .select_next_is_case_sensitive
16000 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16001
16002 let mut builder = AhoCorasickBuilder::new();
16003 builder.ascii_case_insensitive(!case_sensitive);
16004 builder.build(patterns)
16005 }
16006
16007 pub fn find_next_match(
16008 &mut self,
16009 _: &FindNextMatch,
16010 window: &mut Window,
16011 cx: &mut Context<Self>,
16012 ) -> Result<()> {
16013 let selections = self.selections.disjoint_anchors_arc();
16014 match selections.first() {
16015 Some(first) if selections.len() >= 2 => {
16016 self.change_selections(Default::default(), window, cx, |s| {
16017 s.select_ranges([first.range()]);
16018 });
16019 }
16020 _ => self.select_next(
16021 &SelectNext {
16022 replace_newest: true,
16023 },
16024 window,
16025 cx,
16026 )?,
16027 }
16028 Ok(())
16029 }
16030
16031 pub fn find_previous_match(
16032 &mut self,
16033 _: &FindPreviousMatch,
16034 window: &mut Window,
16035 cx: &mut Context<Self>,
16036 ) -> Result<()> {
16037 let selections = self.selections.disjoint_anchors_arc();
16038 match selections.last() {
16039 Some(last) if selections.len() >= 2 => {
16040 self.change_selections(Default::default(), window, cx, |s| {
16041 s.select_ranges([last.range()]);
16042 });
16043 }
16044 _ => self.select_previous(
16045 &SelectPrevious {
16046 replace_newest: true,
16047 },
16048 window,
16049 cx,
16050 )?,
16051 }
16052 Ok(())
16053 }
16054
16055 pub fn toggle_comments(
16056 &mut self,
16057 action: &ToggleComments,
16058 window: &mut Window,
16059 cx: &mut Context<Self>,
16060 ) {
16061 if self.read_only(cx) {
16062 return;
16063 }
16064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16065 let text_layout_details = &self.text_layout_details(window, cx);
16066 self.transact(window, cx, |this, window, cx| {
16067 let mut selections = this
16068 .selections
16069 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16070 let mut edits = Vec::new();
16071 let mut selection_edit_ranges = Vec::new();
16072 let mut last_toggled_row = None;
16073 let snapshot = this.buffer.read(cx).read(cx);
16074 let empty_str: Arc<str> = Arc::default();
16075 let mut suffixes_inserted = Vec::new();
16076 let ignore_indent = action.ignore_indent;
16077
16078 fn comment_prefix_range(
16079 snapshot: &MultiBufferSnapshot,
16080 row: MultiBufferRow,
16081 comment_prefix: &str,
16082 comment_prefix_whitespace: &str,
16083 ignore_indent: bool,
16084 ) -> Range<Point> {
16085 let indent_size = if ignore_indent {
16086 0
16087 } else {
16088 snapshot.indent_size_for_line(row).len
16089 };
16090
16091 let start = Point::new(row.0, indent_size);
16092
16093 let mut line_bytes = snapshot
16094 .bytes_in_range(start..snapshot.max_point())
16095 .flatten()
16096 .copied();
16097
16098 // If this line currently begins with the line comment prefix, then record
16099 // the range containing the prefix.
16100 if line_bytes
16101 .by_ref()
16102 .take(comment_prefix.len())
16103 .eq(comment_prefix.bytes())
16104 {
16105 // Include any whitespace that matches the comment prefix.
16106 let matching_whitespace_len = line_bytes
16107 .zip(comment_prefix_whitespace.bytes())
16108 .take_while(|(a, b)| a == b)
16109 .count() as u32;
16110 let end = Point::new(
16111 start.row,
16112 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16113 );
16114 start..end
16115 } else {
16116 start..start
16117 }
16118 }
16119
16120 fn comment_suffix_range(
16121 snapshot: &MultiBufferSnapshot,
16122 row: MultiBufferRow,
16123 comment_suffix: &str,
16124 comment_suffix_has_leading_space: bool,
16125 ) -> Range<Point> {
16126 let end = Point::new(row.0, snapshot.line_len(row));
16127 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16128
16129 let mut line_end_bytes = snapshot
16130 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16131 .flatten()
16132 .copied();
16133
16134 let leading_space_len = if suffix_start_column > 0
16135 && line_end_bytes.next() == Some(b' ')
16136 && comment_suffix_has_leading_space
16137 {
16138 1
16139 } else {
16140 0
16141 };
16142
16143 // If this line currently begins with the line comment prefix, then record
16144 // the range containing the prefix.
16145 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16146 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16147 start..end
16148 } else {
16149 end..end
16150 }
16151 }
16152
16153 // TODO: Handle selections that cross excerpts
16154 for selection in &mut selections {
16155 let start_column = snapshot
16156 .indent_size_for_line(MultiBufferRow(selection.start.row))
16157 .len;
16158 let language = if let Some(language) =
16159 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16160 {
16161 language
16162 } else {
16163 continue;
16164 };
16165
16166 selection_edit_ranges.clear();
16167
16168 // If multiple selections contain a given row, avoid processing that
16169 // row more than once.
16170 let mut start_row = MultiBufferRow(selection.start.row);
16171 if last_toggled_row == Some(start_row) {
16172 start_row = start_row.next_row();
16173 }
16174 let end_row =
16175 if selection.end.row > selection.start.row && selection.end.column == 0 {
16176 MultiBufferRow(selection.end.row - 1)
16177 } else {
16178 MultiBufferRow(selection.end.row)
16179 };
16180 last_toggled_row = Some(end_row);
16181
16182 if start_row > end_row {
16183 continue;
16184 }
16185
16186 // If the language has line comments, toggle those.
16187 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16188
16189 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16190 if ignore_indent {
16191 full_comment_prefixes = full_comment_prefixes
16192 .into_iter()
16193 .map(|s| Arc::from(s.trim_end()))
16194 .collect();
16195 }
16196
16197 if !full_comment_prefixes.is_empty() {
16198 let first_prefix = full_comment_prefixes
16199 .first()
16200 .expect("prefixes is non-empty");
16201 let prefix_trimmed_lengths = full_comment_prefixes
16202 .iter()
16203 .map(|p| p.trim_end_matches(' ').len())
16204 .collect::<SmallVec<[usize; 4]>>();
16205
16206 let mut all_selection_lines_are_comments = true;
16207
16208 for row in start_row.0..=end_row.0 {
16209 let row = MultiBufferRow(row);
16210 if start_row < end_row && snapshot.is_line_blank(row) {
16211 continue;
16212 }
16213
16214 let prefix_range = full_comment_prefixes
16215 .iter()
16216 .zip(prefix_trimmed_lengths.iter().copied())
16217 .map(|(prefix, trimmed_prefix_len)| {
16218 comment_prefix_range(
16219 snapshot.deref(),
16220 row,
16221 &prefix[..trimmed_prefix_len],
16222 &prefix[trimmed_prefix_len..],
16223 ignore_indent,
16224 )
16225 })
16226 .max_by_key(|range| range.end.column - range.start.column)
16227 .expect("prefixes is non-empty");
16228
16229 if prefix_range.is_empty() {
16230 all_selection_lines_are_comments = false;
16231 }
16232
16233 selection_edit_ranges.push(prefix_range);
16234 }
16235
16236 if all_selection_lines_are_comments {
16237 edits.extend(
16238 selection_edit_ranges
16239 .iter()
16240 .cloned()
16241 .map(|range| (range, empty_str.clone())),
16242 );
16243 } else {
16244 let min_column = selection_edit_ranges
16245 .iter()
16246 .map(|range| range.start.column)
16247 .min()
16248 .unwrap_or(0);
16249 edits.extend(selection_edit_ranges.iter().map(|range| {
16250 let position = Point::new(range.start.row, min_column);
16251 (position..position, first_prefix.clone())
16252 }));
16253 }
16254 } else if let Some(BlockCommentConfig {
16255 start: full_comment_prefix,
16256 end: comment_suffix,
16257 ..
16258 }) = language.block_comment()
16259 {
16260 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16261 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16262 let prefix_range = comment_prefix_range(
16263 snapshot.deref(),
16264 start_row,
16265 comment_prefix,
16266 comment_prefix_whitespace,
16267 ignore_indent,
16268 );
16269 let suffix_range = comment_suffix_range(
16270 snapshot.deref(),
16271 end_row,
16272 comment_suffix.trim_start_matches(' '),
16273 comment_suffix.starts_with(' '),
16274 );
16275
16276 if prefix_range.is_empty() || suffix_range.is_empty() {
16277 edits.push((
16278 prefix_range.start..prefix_range.start,
16279 full_comment_prefix.clone(),
16280 ));
16281 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16282 suffixes_inserted.push((end_row, comment_suffix.len()));
16283 } else {
16284 edits.push((prefix_range, empty_str.clone()));
16285 edits.push((suffix_range, empty_str.clone()));
16286 }
16287 } else {
16288 continue;
16289 }
16290 }
16291
16292 drop(snapshot);
16293 this.buffer.update(cx, |buffer, cx| {
16294 buffer.edit(edits, None, cx);
16295 });
16296
16297 // Adjust selections so that they end before any comment suffixes that
16298 // were inserted.
16299 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16300 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16301 let snapshot = this.buffer.read(cx).read(cx);
16302 for selection in &mut selections {
16303 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16304 match row.cmp(&MultiBufferRow(selection.end.row)) {
16305 Ordering::Less => {
16306 suffixes_inserted.next();
16307 continue;
16308 }
16309 Ordering::Greater => break,
16310 Ordering::Equal => {
16311 if selection.end.column == snapshot.line_len(row) {
16312 if selection.is_empty() {
16313 selection.start.column -= suffix_len as u32;
16314 }
16315 selection.end.column -= suffix_len as u32;
16316 }
16317 break;
16318 }
16319 }
16320 }
16321 }
16322
16323 drop(snapshot);
16324 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16325
16326 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16327 let selections_on_single_row = selections.windows(2).all(|selections| {
16328 selections[0].start.row == selections[1].start.row
16329 && selections[0].end.row == selections[1].end.row
16330 && selections[0].start.row == selections[0].end.row
16331 });
16332 let selections_selecting = selections
16333 .iter()
16334 .any(|selection| selection.start != selection.end);
16335 let advance_downwards = action.advance_downwards
16336 && selections_on_single_row
16337 && !selections_selecting
16338 && !matches!(this.mode, EditorMode::SingleLine);
16339
16340 if advance_downwards {
16341 let snapshot = this.buffer.read(cx).snapshot(cx);
16342
16343 this.change_selections(Default::default(), window, cx, |s| {
16344 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16345 let mut point = display_point.to_point(display_snapshot);
16346 point.row += 1;
16347 point = snapshot.clip_point(point, Bias::Left);
16348 let display_point = point.to_display_point(display_snapshot);
16349 let goal = SelectionGoal::HorizontalPosition(
16350 display_snapshot
16351 .x_for_display_point(display_point, text_layout_details)
16352 .into(),
16353 );
16354 (display_point, goal)
16355 })
16356 });
16357 }
16358 });
16359 }
16360
16361 pub fn select_enclosing_symbol(
16362 &mut self,
16363 _: &SelectEnclosingSymbol,
16364 window: &mut Window,
16365 cx: &mut Context<Self>,
16366 ) {
16367 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16368
16369 let buffer = self.buffer.read(cx).snapshot(cx);
16370 let old_selections = self
16371 .selections
16372 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16373 .into_boxed_slice();
16374
16375 fn update_selection(
16376 selection: &Selection<MultiBufferOffset>,
16377 buffer_snap: &MultiBufferSnapshot,
16378 ) -> Option<Selection<MultiBufferOffset>> {
16379 let cursor = selection.head();
16380 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16381 for symbol in symbols.iter().rev() {
16382 let start = symbol.range.start.to_offset(buffer_snap);
16383 let end = symbol.range.end.to_offset(buffer_snap);
16384 let new_range = start..end;
16385 if start < selection.start || end > selection.end {
16386 return Some(Selection {
16387 id: selection.id,
16388 start: new_range.start,
16389 end: new_range.end,
16390 goal: SelectionGoal::None,
16391 reversed: selection.reversed,
16392 });
16393 }
16394 }
16395 None
16396 }
16397
16398 let mut selected_larger_symbol = false;
16399 let new_selections = old_selections
16400 .iter()
16401 .map(|selection| match update_selection(selection, &buffer) {
16402 Some(new_selection) => {
16403 if new_selection.range() != selection.range() {
16404 selected_larger_symbol = true;
16405 }
16406 new_selection
16407 }
16408 None => selection.clone(),
16409 })
16410 .collect::<Vec<_>>();
16411
16412 if selected_larger_symbol {
16413 self.change_selections(Default::default(), window, cx, |s| {
16414 s.select(new_selections);
16415 });
16416 }
16417 }
16418
16419 pub fn select_larger_syntax_node(
16420 &mut self,
16421 _: &SelectLargerSyntaxNode,
16422 window: &mut Window,
16423 cx: &mut Context<Self>,
16424 ) {
16425 let Some(visible_row_count) = self.visible_row_count() else {
16426 return;
16427 };
16428 let old_selections: Box<[_]> = self
16429 .selections
16430 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16431 .into();
16432 if old_selections.is_empty() {
16433 return;
16434 }
16435
16436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16437
16438 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16439 let buffer = self.buffer.read(cx).snapshot(cx);
16440
16441 let mut selected_larger_node = false;
16442 let mut new_selections = old_selections
16443 .iter()
16444 .map(|selection| {
16445 let old_range = selection.start..selection.end;
16446
16447 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16448 // manually select word at selection
16449 if ["string_content", "inline"].contains(&node.kind()) {
16450 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16451 // ignore if word is already selected
16452 if !word_range.is_empty() && old_range != word_range {
16453 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16454 // only select word if start and end point belongs to same word
16455 if word_range == last_word_range {
16456 selected_larger_node = true;
16457 return Selection {
16458 id: selection.id,
16459 start: word_range.start,
16460 end: word_range.end,
16461 goal: SelectionGoal::None,
16462 reversed: selection.reversed,
16463 };
16464 }
16465 }
16466 }
16467 }
16468
16469 let mut new_range = old_range.clone();
16470 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16471 new_range = range;
16472 if !node.is_named() {
16473 continue;
16474 }
16475 if !display_map.intersects_fold(new_range.start)
16476 && !display_map.intersects_fold(new_range.end)
16477 {
16478 break;
16479 }
16480 }
16481
16482 selected_larger_node |= new_range != old_range;
16483 Selection {
16484 id: selection.id,
16485 start: new_range.start,
16486 end: new_range.end,
16487 goal: SelectionGoal::None,
16488 reversed: selection.reversed,
16489 }
16490 })
16491 .collect::<Vec<_>>();
16492
16493 if !selected_larger_node {
16494 return; // don't put this call in the history
16495 }
16496
16497 // scroll based on transformation done to the last selection created by the user
16498 let (last_old, last_new) = old_selections
16499 .last()
16500 .zip(new_selections.last().cloned())
16501 .expect("old_selections isn't empty");
16502
16503 // revert selection
16504 let is_selection_reversed = {
16505 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16506 new_selections.last_mut().expect("checked above").reversed =
16507 should_newest_selection_be_reversed;
16508 should_newest_selection_be_reversed
16509 };
16510
16511 if selected_larger_node {
16512 self.select_syntax_node_history.disable_clearing = true;
16513 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16514 s.select(new_selections.clone());
16515 });
16516 self.select_syntax_node_history.disable_clearing = false;
16517 }
16518
16519 let start_row = last_new.start.to_display_point(&display_map).row().0;
16520 let end_row = last_new.end.to_display_point(&display_map).row().0;
16521 let selection_height = end_row - start_row + 1;
16522 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16523
16524 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16525 let scroll_behavior = if fits_on_the_screen {
16526 self.request_autoscroll(Autoscroll::fit(), cx);
16527 SelectSyntaxNodeScrollBehavior::FitSelection
16528 } else if is_selection_reversed {
16529 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16530 SelectSyntaxNodeScrollBehavior::CursorTop
16531 } else {
16532 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16533 SelectSyntaxNodeScrollBehavior::CursorBottom
16534 };
16535
16536 let old_selections: Box<[Selection<Anchor>]> = old_selections
16537 .iter()
16538 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16539 .collect();
16540 self.select_syntax_node_history.push((
16541 old_selections,
16542 scroll_behavior,
16543 is_selection_reversed,
16544 ));
16545 }
16546
16547 pub fn select_smaller_syntax_node(
16548 &mut self,
16549 _: &SelectSmallerSyntaxNode,
16550 window: &mut Window,
16551 cx: &mut Context<Self>,
16552 ) {
16553 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16554
16555 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16556 self.select_syntax_node_history.pop()
16557 {
16558 if let Some(selection) = selections.last_mut() {
16559 selection.reversed = is_selection_reversed;
16560 }
16561
16562 let snapshot = self.buffer.read(cx).snapshot(cx);
16563 let selections: Vec<Selection<MultiBufferOffset>> = selections
16564 .iter()
16565 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16566 .collect();
16567
16568 self.select_syntax_node_history.disable_clearing = true;
16569 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16570 s.select(selections);
16571 });
16572 self.select_syntax_node_history.disable_clearing = false;
16573
16574 match scroll_behavior {
16575 SelectSyntaxNodeScrollBehavior::CursorTop => {
16576 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16577 }
16578 SelectSyntaxNodeScrollBehavior::FitSelection => {
16579 self.request_autoscroll(Autoscroll::fit(), cx);
16580 }
16581 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16582 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16583 }
16584 }
16585 }
16586 }
16587
16588 pub fn unwrap_syntax_node(
16589 &mut self,
16590 _: &UnwrapSyntaxNode,
16591 window: &mut Window,
16592 cx: &mut Context<Self>,
16593 ) {
16594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16595
16596 let buffer = self.buffer.read(cx).snapshot(cx);
16597 let selections = self
16598 .selections
16599 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16600 .into_iter()
16601 // subtracting the offset requires sorting
16602 .sorted_by_key(|i| i.start);
16603
16604 let full_edits = selections
16605 .into_iter()
16606 .filter_map(|selection| {
16607 let child = if selection.is_empty()
16608 && let Some((_, ancestor_range)) =
16609 buffer.syntax_ancestor(selection.start..selection.end)
16610 {
16611 ancestor_range
16612 } else {
16613 selection.range()
16614 };
16615
16616 let mut parent = child.clone();
16617 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16618 parent = ancestor_range;
16619 if parent.start < child.start || parent.end > child.end {
16620 break;
16621 }
16622 }
16623
16624 if parent == child {
16625 return None;
16626 }
16627 let text = buffer.text_for_range(child).collect::<String>();
16628 Some((selection.id, parent, text))
16629 })
16630 .collect::<Vec<_>>();
16631 if full_edits.is_empty() {
16632 return;
16633 }
16634
16635 self.transact(window, cx, |this, window, cx| {
16636 this.buffer.update(cx, |buffer, cx| {
16637 buffer.edit(
16638 full_edits
16639 .iter()
16640 .map(|(_, p, t)| (p.clone(), t.clone()))
16641 .collect::<Vec<_>>(),
16642 None,
16643 cx,
16644 );
16645 });
16646 this.change_selections(Default::default(), window, cx, |s| {
16647 let mut offset = 0;
16648 let mut selections = vec![];
16649 for (id, parent, text) in full_edits {
16650 let start = parent.start - offset;
16651 offset += (parent.end - parent.start) - text.len();
16652 selections.push(Selection {
16653 id,
16654 start,
16655 end: start + text.len(),
16656 reversed: false,
16657 goal: Default::default(),
16658 });
16659 }
16660 s.select(selections);
16661 });
16662 });
16663 }
16664
16665 pub fn select_next_syntax_node(
16666 &mut self,
16667 _: &SelectNextSyntaxNode,
16668 window: &mut Window,
16669 cx: &mut Context<Self>,
16670 ) {
16671 let old_selections: Box<[_]> = self
16672 .selections
16673 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16674 .into();
16675 if old_selections.is_empty() {
16676 return;
16677 }
16678
16679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16680
16681 let buffer = self.buffer.read(cx).snapshot(cx);
16682 let mut selected_sibling = false;
16683
16684 let new_selections = old_selections
16685 .iter()
16686 .map(|selection| {
16687 let old_range = selection.start..selection.end;
16688
16689 let old_range =
16690 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16691 let excerpt = buffer.excerpt_containing(old_range.clone());
16692
16693 if let Some(mut excerpt) = excerpt
16694 && let Some(node) = excerpt
16695 .buffer()
16696 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16697 {
16698 let new_range = excerpt.map_range_from_buffer(
16699 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16700 );
16701 selected_sibling = true;
16702 Selection {
16703 id: selection.id,
16704 start: new_range.start,
16705 end: new_range.end,
16706 goal: SelectionGoal::None,
16707 reversed: selection.reversed,
16708 }
16709 } else {
16710 selection.clone()
16711 }
16712 })
16713 .collect::<Vec<_>>();
16714
16715 if selected_sibling {
16716 self.change_selections(
16717 SelectionEffects::scroll(Autoscroll::fit()),
16718 window,
16719 cx,
16720 |s| {
16721 s.select(new_selections);
16722 },
16723 );
16724 }
16725 }
16726
16727 pub fn select_prev_syntax_node(
16728 &mut self,
16729 _: &SelectPreviousSyntaxNode,
16730 window: &mut Window,
16731 cx: &mut Context<Self>,
16732 ) {
16733 let old_selections: Box<[_]> = self
16734 .selections
16735 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16736 .into();
16737 if old_selections.is_empty() {
16738 return;
16739 }
16740
16741 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16742
16743 let buffer = self.buffer.read(cx).snapshot(cx);
16744 let mut selected_sibling = false;
16745
16746 let new_selections = old_selections
16747 .iter()
16748 .map(|selection| {
16749 let old_range = selection.start..selection.end;
16750 let old_range =
16751 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16752 let excerpt = buffer.excerpt_containing(old_range.clone());
16753
16754 if let Some(mut excerpt) = excerpt
16755 && let Some(node) = excerpt
16756 .buffer()
16757 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16758 {
16759 let new_range = excerpt.map_range_from_buffer(
16760 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16761 );
16762 selected_sibling = true;
16763 Selection {
16764 id: selection.id,
16765 start: new_range.start,
16766 end: new_range.end,
16767 goal: SelectionGoal::None,
16768 reversed: selection.reversed,
16769 }
16770 } else {
16771 selection.clone()
16772 }
16773 })
16774 .collect::<Vec<_>>();
16775
16776 if selected_sibling {
16777 self.change_selections(
16778 SelectionEffects::scroll(Autoscroll::fit()),
16779 window,
16780 cx,
16781 |s| {
16782 s.select(new_selections);
16783 },
16784 );
16785 }
16786 }
16787
16788 pub fn move_to_start_of_larger_syntax_node(
16789 &mut self,
16790 _: &MoveToStartOfLargerSyntaxNode,
16791 window: &mut Window,
16792 cx: &mut Context<Self>,
16793 ) {
16794 self.move_cursors_to_syntax_nodes(window, cx, false);
16795 }
16796
16797 pub fn move_to_end_of_larger_syntax_node(
16798 &mut self,
16799 _: &MoveToEndOfLargerSyntaxNode,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) {
16803 self.move_cursors_to_syntax_nodes(window, cx, true);
16804 }
16805
16806 fn find_syntax_node_boundary(
16807 &self,
16808 selection_pos: MultiBufferOffset,
16809 move_to_end: bool,
16810 display_map: &DisplaySnapshot,
16811 buffer: &MultiBufferSnapshot,
16812 ) -> MultiBufferOffset {
16813 let old_range = selection_pos..selection_pos;
16814 let mut new_pos = selection_pos;
16815 let mut search_range = old_range;
16816 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16817 search_range = range.clone();
16818 if !node.is_named()
16819 || display_map.intersects_fold(range.start)
16820 || display_map.intersects_fold(range.end)
16821 // If cursor is already at the end of the syntax node, continue searching
16822 || (move_to_end && range.end == selection_pos)
16823 // If cursor is already at the start of the syntax node, continue searching
16824 || (!move_to_end && range.start == selection_pos)
16825 {
16826 continue;
16827 }
16828
16829 // If we found a string_content node, find the largest parent that is still string_content
16830 // Enables us to skip to the end of strings without taking multiple steps inside the string
16831 let (_, final_range) = if node.kind() == "string_content" {
16832 let mut current_node = node;
16833 let mut current_range = range;
16834 while let Some((parent, parent_range)) =
16835 buffer.syntax_ancestor(current_range.clone())
16836 {
16837 if parent.kind() == "string_content" {
16838 current_node = parent;
16839 current_range = parent_range;
16840 } else {
16841 break;
16842 }
16843 }
16844
16845 (current_node, current_range)
16846 } else {
16847 (node, range)
16848 };
16849
16850 new_pos = if move_to_end {
16851 final_range.end
16852 } else {
16853 final_range.start
16854 };
16855
16856 break;
16857 }
16858
16859 new_pos
16860 }
16861
16862 fn move_cursors_to_syntax_nodes(
16863 &mut self,
16864 window: &mut Window,
16865 cx: &mut Context<Self>,
16866 move_to_end: bool,
16867 ) -> bool {
16868 let old_selections: Box<[_]> = self
16869 .selections
16870 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16871 .into();
16872 if old_selections.is_empty() {
16873 return false;
16874 }
16875
16876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16877
16878 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16879 let buffer = self.buffer.read(cx).snapshot(cx);
16880
16881 let mut any_cursor_moved = false;
16882 let new_selections = old_selections
16883 .iter()
16884 .map(|selection| {
16885 if !selection.is_empty() {
16886 return selection.clone();
16887 }
16888
16889 let selection_pos = selection.head();
16890 let new_pos = self.find_syntax_node_boundary(
16891 selection_pos,
16892 move_to_end,
16893 &display_map,
16894 &buffer,
16895 );
16896
16897 any_cursor_moved |= new_pos != selection_pos;
16898
16899 Selection {
16900 id: selection.id,
16901 start: new_pos,
16902 end: new_pos,
16903 goal: SelectionGoal::None,
16904 reversed: false,
16905 }
16906 })
16907 .collect::<Vec<_>>();
16908
16909 self.change_selections(Default::default(), window, cx, |s| {
16910 s.select(new_selections);
16911 });
16912 self.request_autoscroll(Autoscroll::newest(), cx);
16913
16914 any_cursor_moved
16915 }
16916
16917 pub fn select_to_start_of_larger_syntax_node(
16918 &mut self,
16919 _: &SelectToStartOfLargerSyntaxNode,
16920 window: &mut Window,
16921 cx: &mut Context<Self>,
16922 ) {
16923 self.select_to_syntax_nodes(window, cx, false);
16924 }
16925
16926 pub fn select_to_end_of_larger_syntax_node(
16927 &mut self,
16928 _: &SelectToEndOfLargerSyntaxNode,
16929 window: &mut Window,
16930 cx: &mut Context<Self>,
16931 ) {
16932 self.select_to_syntax_nodes(window, cx, true);
16933 }
16934
16935 fn select_to_syntax_nodes(
16936 &mut self,
16937 window: &mut Window,
16938 cx: &mut Context<Self>,
16939 move_to_end: bool,
16940 ) {
16941 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16942
16943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16944 let buffer = self.buffer.read(cx).snapshot(cx);
16945 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
16946
16947 let new_selections = old_selections
16948 .iter()
16949 .map(|selection| {
16950 let new_pos = self.find_syntax_node_boundary(
16951 selection.head(),
16952 move_to_end,
16953 &display_map,
16954 &buffer,
16955 );
16956
16957 let mut new_selection = selection.clone();
16958 new_selection.set_head(new_pos, SelectionGoal::None);
16959 new_selection
16960 })
16961 .collect::<Vec<_>>();
16962
16963 self.change_selections(Default::default(), window, cx, |s| {
16964 s.select(new_selections);
16965 });
16966 }
16967
16968 pub fn move_to_enclosing_bracket(
16969 &mut self,
16970 _: &MoveToEnclosingBracket,
16971 window: &mut Window,
16972 cx: &mut Context<Self>,
16973 ) {
16974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16975 self.change_selections(Default::default(), window, cx, |s| {
16976 s.move_offsets_with(&mut |snapshot, selection| {
16977 let Some(enclosing_bracket_ranges) =
16978 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16979 else {
16980 return;
16981 };
16982
16983 let mut best_length = usize::MAX;
16984 let mut best_inside = false;
16985 let mut best_in_bracket_range = false;
16986 let mut best_destination = None;
16987 for (open, close) in enclosing_bracket_ranges {
16988 let close = close.to_inclusive();
16989 let length = *close.end() - open.start;
16990 let inside = selection.start >= open.end && selection.end <= *close.start();
16991 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16992 || close.contains(&selection.head());
16993
16994 // If best is next to a bracket and current isn't, skip
16995 if !in_bracket_range && best_in_bracket_range {
16996 continue;
16997 }
16998
16999 // Prefer smaller lengths unless best is inside and current isn't
17000 if length > best_length && (best_inside || !inside) {
17001 continue;
17002 }
17003
17004 best_length = length;
17005 best_inside = inside;
17006 best_in_bracket_range = in_bracket_range;
17007 best_destination = Some(
17008 if close.contains(&selection.start) && close.contains(&selection.end) {
17009 if inside { open.end } else { open.start }
17010 } else if inside {
17011 *close.start()
17012 } else {
17013 *close.end()
17014 },
17015 );
17016 }
17017
17018 if let Some(destination) = best_destination {
17019 selection.collapse_to(destination, SelectionGoal::None);
17020 }
17021 })
17022 });
17023 }
17024
17025 pub fn undo_selection(
17026 &mut self,
17027 _: &UndoSelection,
17028 window: &mut Window,
17029 cx: &mut Context<Self>,
17030 ) {
17031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17032 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17033 self.selection_history.mode = SelectionHistoryMode::Undoing;
17034 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17035 this.end_selection(window, cx);
17036 this.change_selections(
17037 SelectionEffects::scroll(Autoscroll::newest()),
17038 window,
17039 cx,
17040 |s| s.select_anchors(entry.selections.to_vec()),
17041 );
17042 });
17043 self.selection_history.mode = SelectionHistoryMode::Normal;
17044
17045 self.select_next_state = entry.select_next_state;
17046 self.select_prev_state = entry.select_prev_state;
17047 self.add_selections_state = entry.add_selections_state;
17048 }
17049 }
17050
17051 pub fn redo_selection(
17052 &mut self,
17053 _: &RedoSelection,
17054 window: &mut Window,
17055 cx: &mut Context<Self>,
17056 ) {
17057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17058 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17059 self.selection_history.mode = SelectionHistoryMode::Redoing;
17060 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17061 this.end_selection(window, cx);
17062 this.change_selections(
17063 SelectionEffects::scroll(Autoscroll::newest()),
17064 window,
17065 cx,
17066 |s| s.select_anchors(entry.selections.to_vec()),
17067 );
17068 });
17069 self.selection_history.mode = SelectionHistoryMode::Normal;
17070
17071 self.select_next_state = entry.select_next_state;
17072 self.select_prev_state = entry.select_prev_state;
17073 self.add_selections_state = entry.add_selections_state;
17074 }
17075 }
17076
17077 pub fn expand_excerpts(
17078 &mut self,
17079 action: &ExpandExcerpts,
17080 _: &mut Window,
17081 cx: &mut Context<Self>,
17082 ) {
17083 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17084 }
17085
17086 pub fn expand_excerpts_down(
17087 &mut self,
17088 action: &ExpandExcerptsDown,
17089 _: &mut Window,
17090 cx: &mut Context<Self>,
17091 ) {
17092 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17093 }
17094
17095 pub fn expand_excerpts_up(
17096 &mut self,
17097 action: &ExpandExcerptsUp,
17098 _: &mut Window,
17099 cx: &mut Context<Self>,
17100 ) {
17101 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17102 }
17103
17104 pub fn expand_excerpts_for_direction(
17105 &mut self,
17106 lines: u32,
17107 direction: ExpandExcerptDirection,
17108 cx: &mut Context<Self>,
17109 ) {
17110 let selections = self.selections.disjoint_anchors_arc();
17111
17112 let lines = if lines == 0 {
17113 EditorSettings::get_global(cx).expand_excerpt_lines
17114 } else {
17115 lines
17116 };
17117
17118 let snapshot = self.buffer.read(cx).snapshot(cx);
17119 let excerpt_ids = selections
17120 .iter()
17121 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17122 .unique()
17123 .sorted()
17124 .collect::<Vec<_>>();
17125
17126 if self.delegate_expand_excerpts {
17127 cx.emit(EditorEvent::ExpandExcerptsRequested {
17128 excerpt_ids,
17129 lines,
17130 direction,
17131 });
17132 return;
17133 }
17134
17135 self.buffer.update(cx, |buffer, cx| {
17136 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17137 })
17138 }
17139
17140 pub fn expand_excerpt(
17141 &mut self,
17142 excerpt: ExcerptId,
17143 direction: ExpandExcerptDirection,
17144 window: &mut Window,
17145 cx: &mut Context<Self>,
17146 ) {
17147 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17148
17149 if self.delegate_expand_excerpts {
17150 cx.emit(EditorEvent::ExpandExcerptsRequested {
17151 excerpt_ids: vec![excerpt],
17152 lines: lines_to_expand,
17153 direction,
17154 });
17155 return;
17156 }
17157
17158 let current_scroll_position = self.scroll_position(cx);
17159 let mut scroll = None;
17160
17161 if direction == ExpandExcerptDirection::Down {
17162 let multi_buffer = self.buffer.read(cx);
17163 let snapshot = multi_buffer.snapshot(cx);
17164 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17165 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17166 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17167 {
17168 let buffer_snapshot = buffer.read(cx).snapshot();
17169 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17170 let last_row = buffer_snapshot.max_point().row;
17171 let lines_below = last_row.saturating_sub(excerpt_end_row);
17172 if lines_below >= lines_to_expand {
17173 scroll = Some(
17174 current_scroll_position
17175 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17176 );
17177 }
17178 }
17179 }
17180 if direction == ExpandExcerptDirection::Up
17181 && self
17182 .buffer
17183 .read(cx)
17184 .snapshot(cx)
17185 .excerpt_before(excerpt)
17186 .is_none()
17187 {
17188 scroll = Some(current_scroll_position);
17189 }
17190
17191 self.buffer.update(cx, |buffer, cx| {
17192 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17193 });
17194
17195 if let Some(new_scroll_position) = scroll {
17196 self.set_scroll_position(new_scroll_position, window, cx);
17197 }
17198 }
17199
17200 pub fn go_to_singleton_buffer_point(
17201 &mut self,
17202 point: Point,
17203 window: &mut Window,
17204 cx: &mut Context<Self>,
17205 ) {
17206 self.go_to_singleton_buffer_range(point..point, window, cx);
17207 }
17208
17209 pub fn go_to_singleton_buffer_range(
17210 &mut self,
17211 range: Range<Point>,
17212 window: &mut Window,
17213 cx: &mut Context<Self>,
17214 ) {
17215 let multibuffer = self.buffer().read(cx);
17216 let Some(buffer) = multibuffer.as_singleton() else {
17217 return;
17218 };
17219 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17220 return;
17221 };
17222 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17223 return;
17224 };
17225 self.change_selections(
17226 SelectionEffects::default().nav_history(true),
17227 window,
17228 cx,
17229 |s| s.select_anchor_ranges([start..end]),
17230 );
17231 }
17232
17233 pub fn go_to_diagnostic(
17234 &mut self,
17235 action: &GoToDiagnostic,
17236 window: &mut Window,
17237 cx: &mut Context<Self>,
17238 ) {
17239 if !self.diagnostics_enabled() {
17240 return;
17241 }
17242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17243 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17244 }
17245
17246 pub fn go_to_prev_diagnostic(
17247 &mut self,
17248 action: &GoToPreviousDiagnostic,
17249 window: &mut Window,
17250 cx: &mut Context<Self>,
17251 ) {
17252 if !self.diagnostics_enabled() {
17253 return;
17254 }
17255 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17256 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17257 }
17258
17259 pub fn go_to_diagnostic_impl(
17260 &mut self,
17261 direction: Direction,
17262 severity: GoToDiagnosticSeverityFilter,
17263 window: &mut Window,
17264 cx: &mut Context<Self>,
17265 ) {
17266 let buffer = self.buffer.read(cx).snapshot(cx);
17267 let selection = self
17268 .selections
17269 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17270
17271 let mut active_group_id = None;
17272 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17273 && active_group.active_range.start.to_offset(&buffer) == selection.start
17274 {
17275 active_group_id = Some(active_group.group_id);
17276 }
17277
17278 fn filtered<'a>(
17279 severity: GoToDiagnosticSeverityFilter,
17280 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17281 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17282 diagnostics
17283 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17284 .filter(|entry| entry.range.start != entry.range.end)
17285 .filter(|entry| !entry.diagnostic.is_unnecessary)
17286 }
17287
17288 let before = filtered(
17289 severity,
17290 buffer
17291 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17292 .filter(|entry| entry.range.start <= selection.start),
17293 );
17294 let after = filtered(
17295 severity,
17296 buffer
17297 .diagnostics_in_range(selection.start..buffer.len())
17298 .filter(|entry| entry.range.start >= selection.start),
17299 );
17300
17301 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17302 if direction == Direction::Prev {
17303 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17304 {
17305 for diagnostic in prev_diagnostics.into_iter().rev() {
17306 if diagnostic.range.start != selection.start
17307 || active_group_id
17308 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17309 {
17310 found = Some(diagnostic);
17311 break 'outer;
17312 }
17313 }
17314 }
17315 } else {
17316 for diagnostic in after.chain(before) {
17317 if diagnostic.range.start != selection.start
17318 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17319 {
17320 found = Some(diagnostic);
17321 break;
17322 }
17323 }
17324 }
17325 let Some(next_diagnostic) = found else {
17326 return;
17327 };
17328
17329 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17330 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17331 return;
17332 };
17333 let snapshot = self.snapshot(window, cx);
17334 if snapshot.intersects_fold(next_diagnostic.range.start) {
17335 self.unfold_ranges(
17336 std::slice::from_ref(&next_diagnostic.range),
17337 true,
17338 false,
17339 cx,
17340 );
17341 }
17342 self.change_selections(Default::default(), window, cx, |s| {
17343 s.select_ranges(vec![
17344 next_diagnostic.range.start..next_diagnostic.range.start,
17345 ])
17346 });
17347 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17348 self.refresh_edit_prediction(false, true, window, cx);
17349 }
17350
17351 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17353 let snapshot = self.snapshot(window, cx);
17354 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17355 self.go_to_hunk_before_or_after_position(
17356 &snapshot,
17357 selection.head(),
17358 Direction::Next,
17359 true,
17360 window,
17361 cx,
17362 );
17363 }
17364
17365 pub fn go_to_hunk_before_or_after_position(
17366 &mut self,
17367 snapshot: &EditorSnapshot,
17368 position: Point,
17369 direction: Direction,
17370 wrap_around: bool,
17371 window: &mut Window,
17372 cx: &mut Context<Editor>,
17373 ) {
17374 let row = if direction == Direction::Next {
17375 self.hunk_after_position(snapshot, position, wrap_around)
17376 .map(|hunk| hunk.row_range.start)
17377 } else {
17378 self.hunk_before_position(snapshot, position, wrap_around)
17379 };
17380
17381 if let Some(row) = row {
17382 let destination = Point::new(row.0, 0);
17383 let autoscroll = Autoscroll::center();
17384
17385 self.unfold_ranges(&[destination..destination], false, false, cx);
17386 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17387 s.select_ranges([destination..destination]);
17388 });
17389 }
17390 }
17391
17392 fn hunk_after_position(
17393 &mut self,
17394 snapshot: &EditorSnapshot,
17395 position: Point,
17396 wrap_around: bool,
17397 ) -> Option<MultiBufferDiffHunk> {
17398 let result = snapshot
17399 .buffer_snapshot()
17400 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17401 .find(|hunk| hunk.row_range.start.0 > position.row);
17402
17403 if wrap_around {
17404 result.or_else(|| {
17405 snapshot
17406 .buffer_snapshot()
17407 .diff_hunks_in_range(Point::zero()..position)
17408 .find(|hunk| hunk.row_range.end.0 < position.row)
17409 })
17410 } else {
17411 result
17412 }
17413 }
17414
17415 fn go_to_prev_hunk(
17416 &mut self,
17417 _: &GoToPreviousHunk,
17418 window: &mut Window,
17419 cx: &mut Context<Self>,
17420 ) {
17421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17422 let snapshot = self.snapshot(window, cx);
17423 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17424 self.go_to_hunk_before_or_after_position(
17425 &snapshot,
17426 selection.head(),
17427 Direction::Prev,
17428 true,
17429 window,
17430 cx,
17431 );
17432 }
17433
17434 fn hunk_before_position(
17435 &mut self,
17436 snapshot: &EditorSnapshot,
17437 position: Point,
17438 wrap_around: bool,
17439 ) -> Option<MultiBufferRow> {
17440 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17441
17442 if wrap_around {
17443 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17444 } else {
17445 result
17446 }
17447 }
17448
17449 fn go_to_next_change(
17450 &mut self,
17451 _: &GoToNextChange,
17452 window: &mut Window,
17453 cx: &mut Context<Self>,
17454 ) {
17455 if let Some(selections) = self
17456 .change_list
17457 .next_change(1, Direction::Next)
17458 .map(|s| s.to_vec())
17459 {
17460 self.change_selections(Default::default(), window, cx, |s| {
17461 let map = s.display_snapshot();
17462 s.select_display_ranges(selections.iter().map(|a| {
17463 let point = a.to_display_point(&map);
17464 point..point
17465 }))
17466 })
17467 }
17468 }
17469
17470 fn go_to_previous_change(
17471 &mut self,
17472 _: &GoToPreviousChange,
17473 window: &mut Window,
17474 cx: &mut Context<Self>,
17475 ) {
17476 if let Some(selections) = self
17477 .change_list
17478 .next_change(1, Direction::Prev)
17479 .map(|s| s.to_vec())
17480 {
17481 self.change_selections(Default::default(), window, cx, |s| {
17482 let map = s.display_snapshot();
17483 s.select_display_ranges(selections.iter().map(|a| {
17484 let point = a.to_display_point(&map);
17485 point..point
17486 }))
17487 })
17488 }
17489 }
17490
17491 pub fn go_to_next_document_highlight(
17492 &mut self,
17493 _: &GoToNextDocumentHighlight,
17494 window: &mut Window,
17495 cx: &mut Context<Self>,
17496 ) {
17497 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17498 }
17499
17500 pub fn go_to_prev_document_highlight(
17501 &mut self,
17502 _: &GoToPreviousDocumentHighlight,
17503 window: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) {
17506 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17507 }
17508
17509 pub fn go_to_document_highlight_before_or_after_position(
17510 &mut self,
17511 direction: Direction,
17512 window: &mut Window,
17513 cx: &mut Context<Editor>,
17514 ) {
17515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17516 let snapshot = self.snapshot(window, cx);
17517 let buffer = &snapshot.buffer_snapshot();
17518 let position = self
17519 .selections
17520 .newest::<Point>(&snapshot.display_snapshot)
17521 .head();
17522 let anchor_position = buffer.anchor_after(position);
17523
17524 // Get all document highlights (both read and write)
17525 let mut all_highlights = Vec::new();
17526
17527 if let Some((_, read_highlights)) = self
17528 .background_highlights
17529 .get(&HighlightKey::DocumentHighlightRead)
17530 {
17531 all_highlights.extend(read_highlights.iter());
17532 }
17533
17534 if let Some((_, write_highlights)) = self
17535 .background_highlights
17536 .get(&HighlightKey::DocumentHighlightWrite)
17537 {
17538 all_highlights.extend(write_highlights.iter());
17539 }
17540
17541 if all_highlights.is_empty() {
17542 return;
17543 }
17544
17545 // Sort highlights by position
17546 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17547
17548 let target_highlight = match direction {
17549 Direction::Next => {
17550 // Find the first highlight after the current position
17551 all_highlights
17552 .iter()
17553 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17554 }
17555 Direction::Prev => {
17556 // Find the last highlight before the current position
17557 all_highlights
17558 .iter()
17559 .rev()
17560 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17561 }
17562 };
17563
17564 if let Some(highlight) = target_highlight {
17565 let destination = highlight.start.to_point(buffer);
17566 let autoscroll = Autoscroll::center();
17567
17568 self.unfold_ranges(&[destination..destination], false, false, cx);
17569 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17570 s.select_ranges([destination..destination]);
17571 });
17572 }
17573 }
17574
17575 fn go_to_line<T: 'static>(
17576 &mut self,
17577 position: Anchor,
17578 highlight_color: Option<Hsla>,
17579 window: &mut Window,
17580 cx: &mut Context<Self>,
17581 ) {
17582 let snapshot = self.snapshot(window, cx).display_snapshot;
17583 let position = position.to_point(&snapshot.buffer_snapshot());
17584 let start = snapshot
17585 .buffer_snapshot()
17586 .clip_point(Point::new(position.row, 0), Bias::Left);
17587 let end = start + Point::new(1, 0);
17588 let start = snapshot.buffer_snapshot().anchor_before(start);
17589 let end = snapshot.buffer_snapshot().anchor_before(end);
17590
17591 self.highlight_rows::<T>(
17592 start..end,
17593 highlight_color
17594 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17595 Default::default(),
17596 cx,
17597 );
17598
17599 if self.buffer.read(cx).is_singleton() {
17600 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17601 }
17602 }
17603
17604 pub fn go_to_definition(
17605 &mut self,
17606 _: &GoToDefinition,
17607 window: &mut Window,
17608 cx: &mut Context<Self>,
17609 ) -> Task<Result<Navigated>> {
17610 let definition =
17611 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17612 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17613 cx.spawn_in(window, async move |editor, cx| {
17614 if definition.await? == Navigated::Yes {
17615 return Ok(Navigated::Yes);
17616 }
17617 match fallback_strategy {
17618 GoToDefinitionFallback::None => Ok(Navigated::No),
17619 GoToDefinitionFallback::FindAllReferences => {
17620 match editor.update_in(cx, |editor, window, cx| {
17621 editor.find_all_references(&FindAllReferences::default(), window, cx)
17622 })? {
17623 Some(references) => references.await,
17624 None => Ok(Navigated::No),
17625 }
17626 }
17627 }
17628 })
17629 }
17630
17631 pub fn go_to_declaration(
17632 &mut self,
17633 _: &GoToDeclaration,
17634 window: &mut Window,
17635 cx: &mut Context<Self>,
17636 ) -> Task<Result<Navigated>> {
17637 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17638 }
17639
17640 pub fn go_to_declaration_split(
17641 &mut self,
17642 _: &GoToDeclaration,
17643 window: &mut Window,
17644 cx: &mut Context<Self>,
17645 ) -> Task<Result<Navigated>> {
17646 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17647 }
17648
17649 pub fn go_to_implementation(
17650 &mut self,
17651 _: &GoToImplementation,
17652 window: &mut Window,
17653 cx: &mut Context<Self>,
17654 ) -> Task<Result<Navigated>> {
17655 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17656 }
17657
17658 pub fn go_to_implementation_split(
17659 &mut self,
17660 _: &GoToImplementationSplit,
17661 window: &mut Window,
17662 cx: &mut Context<Self>,
17663 ) -> Task<Result<Navigated>> {
17664 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17665 }
17666
17667 pub fn go_to_type_definition(
17668 &mut self,
17669 _: &GoToTypeDefinition,
17670 window: &mut Window,
17671 cx: &mut Context<Self>,
17672 ) -> Task<Result<Navigated>> {
17673 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17674 }
17675
17676 pub fn go_to_definition_split(
17677 &mut self,
17678 _: &GoToDefinitionSplit,
17679 window: &mut Window,
17680 cx: &mut Context<Self>,
17681 ) -> Task<Result<Navigated>> {
17682 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17683 }
17684
17685 pub fn go_to_type_definition_split(
17686 &mut self,
17687 _: &GoToTypeDefinitionSplit,
17688 window: &mut Window,
17689 cx: &mut Context<Self>,
17690 ) -> Task<Result<Navigated>> {
17691 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17692 }
17693
17694 fn go_to_definition_of_kind(
17695 &mut self,
17696 kind: GotoDefinitionKind,
17697 split: bool,
17698 window: &mut Window,
17699 cx: &mut Context<Self>,
17700 ) -> Task<Result<Navigated>> {
17701 let Some(provider) = self.semantics_provider.clone() else {
17702 return Task::ready(Ok(Navigated::No));
17703 };
17704 let head = self
17705 .selections
17706 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17707 .head();
17708 let buffer = self.buffer.read(cx);
17709 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17710 return Task::ready(Ok(Navigated::No));
17711 };
17712 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17713 return Task::ready(Ok(Navigated::No));
17714 };
17715
17716 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17717
17718 cx.spawn_in(window, async move |editor, cx| {
17719 let Some(definitions) = definitions.await? else {
17720 return Ok(Navigated::No);
17721 };
17722 let navigated = editor
17723 .update_in(cx, |editor, window, cx| {
17724 editor.navigate_to_hover_links(
17725 Some(kind),
17726 definitions
17727 .into_iter()
17728 .filter(|location| {
17729 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17730 })
17731 .map(HoverLink::Text)
17732 .collect::<Vec<_>>(),
17733 nav_entry,
17734 split,
17735 window,
17736 cx,
17737 )
17738 })?
17739 .await?;
17740 anyhow::Ok(navigated)
17741 })
17742 }
17743
17744 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17745 let selection = self.selections.newest_anchor();
17746 let head = selection.head();
17747 let tail = selection.tail();
17748
17749 let Some((buffer, start_position)) =
17750 self.buffer.read(cx).text_anchor_for_position(head, cx)
17751 else {
17752 return;
17753 };
17754
17755 let end_position = if head != tail {
17756 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17757 return;
17758 };
17759 Some(pos)
17760 } else {
17761 None
17762 };
17763
17764 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17765 let url = if let Some(end_pos) = end_position {
17766 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17767 } else {
17768 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17769 };
17770
17771 if let Some(url) = url {
17772 cx.update(|window, cx| {
17773 if parse_zed_link(&url, cx).is_some() {
17774 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17775 } else {
17776 cx.open_url(&url);
17777 }
17778 })?;
17779 }
17780
17781 anyhow::Ok(())
17782 });
17783
17784 url_finder.detach();
17785 }
17786
17787 pub fn open_selected_filename(
17788 &mut self,
17789 _: &OpenSelectedFilename,
17790 window: &mut Window,
17791 cx: &mut Context<Self>,
17792 ) {
17793 let Some(workspace) = self.workspace() else {
17794 return;
17795 };
17796
17797 let position = self.selections.newest_anchor().head();
17798
17799 let Some((buffer, buffer_position)) =
17800 self.buffer.read(cx).text_anchor_for_position(position, cx)
17801 else {
17802 return;
17803 };
17804
17805 let project = self.project.clone();
17806
17807 cx.spawn_in(window, async move |_, cx| {
17808 let result = find_file(&buffer, project, buffer_position, cx).await;
17809
17810 if let Some((_, path)) = result {
17811 workspace
17812 .update_in(cx, |workspace, window, cx| {
17813 workspace.open_resolved_path(path, window, cx)
17814 })?
17815 .await?;
17816 }
17817 anyhow::Ok(())
17818 })
17819 .detach();
17820 }
17821
17822 pub(crate) fn navigate_to_hover_links(
17823 &mut self,
17824 kind: Option<GotoDefinitionKind>,
17825 definitions: Vec<HoverLink>,
17826 origin: Option<NavigationEntry>,
17827 split: bool,
17828 window: &mut Window,
17829 cx: &mut Context<Editor>,
17830 ) -> Task<Result<Navigated>> {
17831 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17832 let mut first_url_or_file = None;
17833 let definitions: Vec<_> = definitions
17834 .into_iter()
17835 .filter_map(|def| match def {
17836 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17837 HoverLink::InlayHint(lsp_location, server_id) => {
17838 let computation =
17839 self.compute_target_location(lsp_location, server_id, window, cx);
17840 Some(cx.background_spawn(computation))
17841 }
17842 HoverLink::Url(url) => {
17843 first_url_or_file = Some(Either::Left(url));
17844 None
17845 }
17846 HoverLink::File(path) => {
17847 first_url_or_file = Some(Either::Right(path));
17848 None
17849 }
17850 })
17851 .collect();
17852
17853 let workspace = self.workspace();
17854
17855 cx.spawn_in(window, async move |editor, cx| {
17856 let locations: Vec<Location> = future::join_all(definitions)
17857 .await
17858 .into_iter()
17859 .filter_map(|location| location.transpose())
17860 .collect::<Result<_>>()
17861 .context("location tasks")?;
17862 let mut locations = cx.update(|_, cx| {
17863 locations
17864 .into_iter()
17865 .map(|location| {
17866 let buffer = location.buffer.read(cx);
17867 (location.buffer, location.range.to_point(buffer))
17868 })
17869 .into_group_map()
17870 })?;
17871 let mut num_locations = 0;
17872 for ranges in locations.values_mut() {
17873 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17874 ranges.dedup();
17875 num_locations += ranges.len();
17876 }
17877
17878 if num_locations > 1 {
17879 let tab_kind = match kind {
17880 Some(GotoDefinitionKind::Implementation) => "Implementations",
17881 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17882 Some(GotoDefinitionKind::Declaration) => "Declarations",
17883 Some(GotoDefinitionKind::Type) => "Types",
17884 };
17885 let title = editor
17886 .update_in(cx, |_, _, cx| {
17887 let target = locations
17888 .iter()
17889 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17890 .map(|(buffer, location)| {
17891 buffer
17892 .read(cx)
17893 .text_for_range(location.clone())
17894 .collect::<String>()
17895 })
17896 .filter(|text| !text.contains('\n'))
17897 .unique()
17898 .take(3)
17899 .join(", ");
17900 if target.is_empty() {
17901 tab_kind.to_owned()
17902 } else {
17903 format!("{tab_kind} for {target}")
17904 }
17905 })
17906 .context("buffer title")?;
17907
17908 let Some(workspace) = workspace else {
17909 return Ok(Navigated::No);
17910 };
17911
17912 let opened = workspace
17913 .update_in(cx, |workspace, window, cx| {
17914 let allow_preview = PreviewTabsSettings::get_global(cx)
17915 .enable_preview_multibuffer_from_code_navigation;
17916 if let Some((target_editor, target_pane)) =
17917 Self::open_locations_in_multibuffer(
17918 workspace,
17919 locations,
17920 title,
17921 split,
17922 allow_preview,
17923 MultibufferSelectionMode::First,
17924 window,
17925 cx,
17926 )
17927 {
17928 // We create our own nav history instead of using
17929 // `target_editor.nav_history` because `nav_history`
17930 // seems to be populated asynchronously when an item
17931 // is added to a pane
17932 let mut nav_history = target_pane
17933 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17934 target_editor.update(cx, |editor, cx| {
17935 let nav_data = editor
17936 .navigation_data(editor.selections.newest_anchor().head(), cx);
17937 let target =
17938 Some(nav_history.navigation_entry(Some(
17939 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17940 )));
17941 nav_history.push_tag(origin, target);
17942 })
17943 }
17944 })
17945 .is_ok();
17946
17947 anyhow::Ok(Navigated::from_bool(opened))
17948 } else if num_locations == 0 {
17949 // If there is one url or file, open it directly
17950 match first_url_or_file {
17951 Some(Either::Left(url)) => {
17952 cx.update(|window, cx| {
17953 if parse_zed_link(&url, cx).is_some() {
17954 window
17955 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17956 } else {
17957 cx.open_url(&url);
17958 }
17959 })?;
17960 Ok(Navigated::Yes)
17961 }
17962 Some(Either::Right(path)) => {
17963 // TODO(andrew): respect preview tab settings
17964 // `enable_keep_preview_on_code_navigation` and
17965 // `enable_preview_file_from_code_navigation`
17966 let Some(workspace) = workspace else {
17967 return Ok(Navigated::No);
17968 };
17969 workspace
17970 .update_in(cx, |workspace, window, cx| {
17971 workspace.open_resolved_path(path, window, cx)
17972 })?
17973 .await?;
17974 Ok(Navigated::Yes)
17975 }
17976 None => Ok(Navigated::No),
17977 }
17978 } else {
17979 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17980 let target_range = target_ranges.first().unwrap().clone();
17981
17982 editor.update_in(cx, |editor, window, cx| {
17983 let range = editor.range_for_match(&target_range);
17984 let range = collapse_multiline_range(range);
17985
17986 if !split
17987 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17988 {
17989 editor.go_to_singleton_buffer_range(range, window, cx);
17990
17991 let target =
17992 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
17993 if let Some(mut nav_history) = editor.nav_history.clone() {
17994 nav_history.push_tag(origin, target);
17995 }
17996 } else {
17997 let Some(workspace) = workspace else {
17998 return Navigated::No;
17999 };
18000 let pane = workspace.read(cx).active_pane().clone();
18001 window.defer(cx, move |window, cx| {
18002 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18003 workspace.update(cx, |workspace, cx| {
18004 let pane = if split {
18005 workspace.adjacent_pane(window, cx)
18006 } else {
18007 workspace.active_pane().clone()
18008 };
18009
18010 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18011 let keep_old_preview = preview_tabs_settings
18012 .enable_keep_preview_on_code_navigation;
18013 let allow_new_preview = preview_tabs_settings
18014 .enable_preview_file_from_code_navigation;
18015
18016 let editor = workspace.open_project_item(
18017 pane.clone(),
18018 target_buffer.clone(),
18019 true,
18020 true,
18021 keep_old_preview,
18022 allow_new_preview,
18023 window,
18024 cx,
18025 );
18026 (editor, pane)
18027 });
18028 // We create our own nav history instead of using
18029 // `target_editor.nav_history` because `nav_history`
18030 // seems to be populated asynchronously when an item
18031 // is added to a pane
18032 let mut nav_history = target_pane
18033 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18034 target_editor.update(cx, |target_editor, cx| {
18035 // When selecting a definition in a different buffer, disable the nav history
18036 // to avoid creating a history entry at the previous cursor location.
18037 pane.update(cx, |pane, _| pane.disable_history());
18038 target_editor.go_to_singleton_buffer_range(range, window, cx);
18039
18040 let nav_data = target_editor.navigation_data(
18041 target_editor.selections.newest_anchor().head(),
18042 cx,
18043 );
18044 let target =
18045 Some(nav_history.navigation_entry(Some(
18046 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18047 )));
18048 nav_history.push_tag(origin, target);
18049 pane.update(cx, |pane, _| pane.enable_history());
18050 });
18051 });
18052 }
18053 Navigated::Yes
18054 })
18055 }
18056 })
18057 }
18058
18059 fn compute_target_location(
18060 &self,
18061 lsp_location: lsp::Location,
18062 server_id: LanguageServerId,
18063 window: &mut Window,
18064 cx: &mut Context<Self>,
18065 ) -> Task<anyhow::Result<Option<Location>>> {
18066 let Some(project) = self.project.clone() else {
18067 return Task::ready(Ok(None));
18068 };
18069
18070 cx.spawn_in(window, async move |editor, cx| {
18071 let location_task = editor.update(cx, |_, cx| {
18072 project.update(cx, |project, cx| {
18073 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18074 })
18075 })?;
18076 let location = Some({
18077 let target_buffer_handle = location_task.await.context("open local buffer")?;
18078 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18079 let target_start = target_buffer
18080 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18081 let target_end = target_buffer
18082 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18083 target_buffer.anchor_after(target_start)
18084 ..target_buffer.anchor_before(target_end)
18085 });
18086 Location {
18087 buffer: target_buffer_handle,
18088 range,
18089 }
18090 });
18091 Ok(location)
18092 })
18093 }
18094
18095 fn go_to_next_reference(
18096 &mut self,
18097 _: &GoToNextReference,
18098 window: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) {
18101 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18102 if let Some(task) = task {
18103 task.detach();
18104 };
18105 }
18106
18107 fn go_to_prev_reference(
18108 &mut self,
18109 _: &GoToPreviousReference,
18110 window: &mut Window,
18111 cx: &mut Context<Self>,
18112 ) {
18113 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18114 if let Some(task) = task {
18115 task.detach();
18116 };
18117 }
18118
18119 pub fn go_to_reference_before_or_after_position(
18120 &mut self,
18121 direction: Direction,
18122 count: usize,
18123 window: &mut Window,
18124 cx: &mut Context<Self>,
18125 ) -> Option<Task<Result<()>>> {
18126 let selection = self.selections.newest_anchor();
18127 let head = selection.head();
18128
18129 let multi_buffer = self.buffer.read(cx);
18130
18131 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18132 let workspace = self.workspace()?;
18133 let project = workspace.read(cx).project().clone();
18134 let references =
18135 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18136 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18137 let Some(locations) = references.await? else {
18138 return Ok(());
18139 };
18140
18141 if locations.is_empty() {
18142 // totally normal - the cursor may be on something which is not
18143 // a symbol (e.g. a keyword)
18144 log::info!("no references found under cursor");
18145 return Ok(());
18146 }
18147
18148 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18149
18150 let (locations, current_location_index) =
18151 multi_buffer.update(cx, |multi_buffer, cx| {
18152 let mut locations = locations
18153 .into_iter()
18154 .filter_map(|loc| {
18155 let start = multi_buffer.buffer_anchor_to_anchor(
18156 &loc.buffer,
18157 loc.range.start,
18158 cx,
18159 )?;
18160 let end = multi_buffer.buffer_anchor_to_anchor(
18161 &loc.buffer,
18162 loc.range.end,
18163 cx,
18164 )?;
18165 Some(start..end)
18166 })
18167 .collect::<Vec<_>>();
18168
18169 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18170 // There is an O(n) implementation, but given this list will be
18171 // small (usually <100 items), the extra O(log(n)) factor isn't
18172 // worth the (surprisingly large amount of) extra complexity.
18173 locations
18174 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18175
18176 let head_offset = head.to_offset(&multi_buffer_snapshot);
18177
18178 let current_location_index = locations.iter().position(|loc| {
18179 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18180 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18181 });
18182
18183 (locations, current_location_index)
18184 });
18185
18186 let Some(current_location_index) = current_location_index else {
18187 // This indicates something has gone wrong, because we already
18188 // handle the "no references" case above
18189 log::error!(
18190 "failed to find current reference under cursor. Total references: {}",
18191 locations.len()
18192 );
18193 return Ok(());
18194 };
18195
18196 let destination_location_index = match direction {
18197 Direction::Next => (current_location_index + count) % locations.len(),
18198 Direction::Prev => {
18199 (current_location_index + locations.len() - count % locations.len())
18200 % locations.len()
18201 }
18202 };
18203
18204 // TODO(cameron): is this needed?
18205 // the thinking is to avoid "jumping to the current location" (avoid
18206 // polluting "jumplist" in vim terms)
18207 if current_location_index == destination_location_index {
18208 return Ok(());
18209 }
18210
18211 let Range { start, end } = locations[destination_location_index];
18212
18213 editor.update_in(cx, |editor, window, cx| {
18214 let effects = SelectionEffects::default();
18215
18216 editor.unfold_ranges(&[start..end], false, false, cx);
18217 editor.change_selections(effects, window, cx, |s| {
18218 s.select_ranges([start..start]);
18219 });
18220 })?;
18221
18222 Ok(())
18223 }))
18224 }
18225
18226 pub fn find_all_references(
18227 &mut self,
18228 action: &FindAllReferences,
18229 window: &mut Window,
18230 cx: &mut Context<Self>,
18231 ) -> Option<Task<Result<Navigated>>> {
18232 let always_open_multibuffer = action.always_open_multibuffer;
18233 let selection = self.selections.newest_anchor();
18234 let multi_buffer = self.buffer.read(cx);
18235 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18236 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18237 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18238 let head = selection_offset.head();
18239
18240 let head_anchor = multi_buffer_snapshot.anchor_at(
18241 head,
18242 if head < selection_offset.tail() {
18243 Bias::Right
18244 } else {
18245 Bias::Left
18246 },
18247 );
18248
18249 match self
18250 .find_all_references_task_sources
18251 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18252 {
18253 Ok(_) => {
18254 log::info!(
18255 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18256 );
18257 return None;
18258 }
18259 Err(i) => {
18260 self.find_all_references_task_sources.insert(i, head_anchor);
18261 }
18262 }
18263
18264 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18265 let workspace = self.workspace()?;
18266 let project = workspace.read(cx).project().clone();
18267 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18268 Some(cx.spawn_in(window, async move |editor, cx| {
18269 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18270 if let Ok(i) = editor
18271 .find_all_references_task_sources
18272 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18273 {
18274 editor.find_all_references_task_sources.remove(i);
18275 }
18276 });
18277
18278 let Some(locations) = references.await? else {
18279 return anyhow::Ok(Navigated::No);
18280 };
18281 let mut locations = cx.update(|_, cx| {
18282 locations
18283 .into_iter()
18284 .map(|location| {
18285 let buffer = location.buffer.read(cx);
18286 (location.buffer, location.range.to_point(buffer))
18287 })
18288 // if special-casing the single-match case, remove ranges
18289 // that intersect current selection
18290 .filter(|(location_buffer, location)| {
18291 if always_open_multibuffer || &buffer != location_buffer {
18292 return true;
18293 }
18294
18295 !location.contains_inclusive(&selection_point.range())
18296 })
18297 .into_group_map()
18298 })?;
18299 if locations.is_empty() {
18300 return anyhow::Ok(Navigated::No);
18301 }
18302 for ranges in locations.values_mut() {
18303 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18304 ranges.dedup();
18305 }
18306 let mut num_locations = 0;
18307 for ranges in locations.values_mut() {
18308 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18309 ranges.dedup();
18310 num_locations += ranges.len();
18311 }
18312
18313 if num_locations == 1 && !always_open_multibuffer {
18314 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18315 let target_range = target_ranges.first().unwrap().clone();
18316
18317 return editor.update_in(cx, |editor, window, cx| {
18318 let range = target_range.to_point(target_buffer.read(cx));
18319 let range = editor.range_for_match(&range);
18320 let range = range.start..range.start;
18321
18322 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18323 editor.go_to_singleton_buffer_range(range, window, cx);
18324 } else {
18325 let pane = workspace.read(cx).active_pane().clone();
18326 window.defer(cx, move |window, cx| {
18327 let target_editor: Entity<Self> =
18328 workspace.update(cx, |workspace, cx| {
18329 let pane = workspace.active_pane().clone();
18330
18331 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18332 let keep_old_preview = preview_tabs_settings
18333 .enable_keep_preview_on_code_navigation;
18334 let allow_new_preview = preview_tabs_settings
18335 .enable_preview_file_from_code_navigation;
18336
18337 workspace.open_project_item(
18338 pane,
18339 target_buffer.clone(),
18340 true,
18341 true,
18342 keep_old_preview,
18343 allow_new_preview,
18344 window,
18345 cx,
18346 )
18347 });
18348 target_editor.update(cx, |target_editor, cx| {
18349 // When selecting a definition in a different buffer, disable the nav history
18350 // to avoid creating a history entry at the previous cursor location.
18351 pane.update(cx, |pane, _| pane.disable_history());
18352 target_editor.go_to_singleton_buffer_range(range, window, cx);
18353 pane.update(cx, |pane, _| pane.enable_history());
18354 });
18355 });
18356 }
18357 Navigated::No
18358 });
18359 }
18360
18361 workspace.update_in(cx, |workspace, window, cx| {
18362 let target = locations
18363 .iter()
18364 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18365 .map(|(buffer, location)| {
18366 buffer
18367 .read(cx)
18368 .text_for_range(location.clone())
18369 .collect::<String>()
18370 })
18371 .filter(|text| !text.contains('\n'))
18372 .unique()
18373 .take(3)
18374 .join(", ");
18375 let title = if target.is_empty() {
18376 "References".to_owned()
18377 } else {
18378 format!("References to {target}")
18379 };
18380 let allow_preview = PreviewTabsSettings::get_global(cx)
18381 .enable_preview_multibuffer_from_code_navigation;
18382 Self::open_locations_in_multibuffer(
18383 workspace,
18384 locations,
18385 title,
18386 false,
18387 allow_preview,
18388 MultibufferSelectionMode::First,
18389 window,
18390 cx,
18391 );
18392 Navigated::Yes
18393 })
18394 }))
18395 }
18396
18397 /// Opens a multibuffer with the given project locations in it.
18398 pub fn open_locations_in_multibuffer(
18399 workspace: &mut Workspace,
18400 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18401 title: String,
18402 split: bool,
18403 allow_preview: bool,
18404 multibuffer_selection_mode: MultibufferSelectionMode,
18405 window: &mut Window,
18406 cx: &mut Context<Workspace>,
18407 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18408 if locations.is_empty() {
18409 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18410 return None;
18411 }
18412
18413 let capability = workspace.project().read(cx).capability();
18414 let mut ranges = <Vec<Range<Anchor>>>::new();
18415
18416 // a key to find existing multibuffer editors with the same set of locations
18417 // to prevent us from opening more and more multibuffer tabs for searches and the like
18418 let mut key = (title.clone(), vec![]);
18419 let excerpt_buffer = cx.new(|cx| {
18420 let key = &mut key.1;
18421 let mut multibuffer = MultiBuffer::new(capability);
18422 for (buffer, mut ranges_for_buffer) in locations {
18423 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18424 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18425 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18426 PathKey::for_buffer(&buffer, cx),
18427 buffer.clone(),
18428 ranges_for_buffer,
18429 multibuffer_context_lines(cx),
18430 cx,
18431 );
18432 ranges.extend(new_ranges)
18433 }
18434
18435 multibuffer.with_title(title)
18436 });
18437 let existing = workspace.active_pane().update(cx, |pane, cx| {
18438 pane.items()
18439 .filter_map(|item| item.downcast::<Editor>())
18440 .find(|editor| {
18441 editor
18442 .read(cx)
18443 .lookup_key
18444 .as_ref()
18445 .and_then(|it| {
18446 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18447 })
18448 .is_some_and(|it| *it == key)
18449 })
18450 });
18451 let was_existing = existing.is_some();
18452 let editor = existing.unwrap_or_else(|| {
18453 cx.new(|cx| {
18454 let mut editor = Editor::for_multibuffer(
18455 excerpt_buffer,
18456 Some(workspace.project().clone()),
18457 window,
18458 cx,
18459 );
18460 editor.lookup_key = Some(Box::new(key));
18461 editor
18462 })
18463 });
18464 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18465 MultibufferSelectionMode::First => {
18466 if let Some(first_range) = ranges.first() {
18467 editor.change_selections(
18468 SelectionEffects::no_scroll(),
18469 window,
18470 cx,
18471 |selections| {
18472 selections.clear_disjoint();
18473 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18474 },
18475 );
18476 }
18477 editor.highlight_background(
18478 HighlightKey::Editor,
18479 &ranges,
18480 |_, theme| theme.colors().editor_highlighted_line_background,
18481 cx,
18482 );
18483 }
18484 MultibufferSelectionMode::All => {
18485 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18486 selections.clear_disjoint();
18487 selections.select_anchor_ranges(ranges);
18488 });
18489 }
18490 });
18491
18492 let item = Box::new(editor.clone());
18493
18494 let pane = if split {
18495 workspace.adjacent_pane(window, cx)
18496 } else {
18497 workspace.active_pane().clone()
18498 };
18499 let activate_pane = split;
18500
18501 let mut destination_index = None;
18502 pane.update(cx, |pane, cx| {
18503 if allow_preview && !was_existing {
18504 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18505 }
18506 if was_existing && !allow_preview {
18507 pane.unpreview_item_if_preview(item.item_id());
18508 }
18509 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18510 });
18511
18512 Some((editor, pane))
18513 }
18514
18515 pub fn rename(
18516 &mut self,
18517 _: &Rename,
18518 window: &mut Window,
18519 cx: &mut Context<Self>,
18520 ) -> Option<Task<Result<()>>> {
18521 use language::ToOffset as _;
18522
18523 let provider = self.semantics_provider.clone()?;
18524 let selection = self.selections.newest_anchor().clone();
18525 let (cursor_buffer, cursor_buffer_position) = self
18526 .buffer
18527 .read(cx)
18528 .text_anchor_for_position(selection.head(), cx)?;
18529 let (tail_buffer, cursor_buffer_position_end) = self
18530 .buffer
18531 .read(cx)
18532 .text_anchor_for_position(selection.tail(), cx)?;
18533 if tail_buffer != cursor_buffer {
18534 return None;
18535 }
18536
18537 let snapshot = cursor_buffer.read(cx).snapshot();
18538 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18539 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18540 let prepare_rename = provider
18541 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18542 .unwrap_or_else(|| Task::ready(Ok(None)));
18543 drop(snapshot);
18544
18545 Some(cx.spawn_in(window, async move |this, cx| {
18546 let rename_range = if let Some(range) = prepare_rename.await? {
18547 Some(range)
18548 } else {
18549 this.update(cx, |this, cx| {
18550 let buffer = this.buffer.read(cx).snapshot(cx);
18551 let mut buffer_highlights = this
18552 .document_highlights_for_position(selection.head(), &buffer)
18553 .filter(|highlight| {
18554 highlight.start.excerpt_id == selection.head().excerpt_id
18555 && highlight.end.excerpt_id == selection.head().excerpt_id
18556 });
18557 buffer_highlights
18558 .next()
18559 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18560 })?
18561 };
18562 if let Some(rename_range) = rename_range {
18563 this.update_in(cx, |this, window, cx| {
18564 let snapshot = cursor_buffer.read(cx).snapshot();
18565 let rename_buffer_range = rename_range.to_offset(&snapshot);
18566 let cursor_offset_in_rename_range =
18567 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18568 let cursor_offset_in_rename_range_end =
18569 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18570
18571 this.take_rename(false, window, cx);
18572 let buffer = this.buffer.read(cx).read(cx);
18573 let cursor_offset = selection.head().to_offset(&buffer);
18574 let rename_start =
18575 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18576 let rename_end = rename_start + rename_buffer_range.len();
18577 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18578 let mut old_highlight_id = None;
18579 let old_name: Arc<str> = buffer
18580 .chunks(rename_start..rename_end, true)
18581 .map(|chunk| {
18582 if old_highlight_id.is_none() {
18583 old_highlight_id = chunk.syntax_highlight_id;
18584 }
18585 chunk.text
18586 })
18587 .collect::<String>()
18588 .into();
18589
18590 drop(buffer);
18591
18592 // Position the selection in the rename editor so that it matches the current selection.
18593 this.show_local_selections = false;
18594 let rename_editor = cx.new(|cx| {
18595 let mut editor = Editor::single_line(window, cx);
18596 editor.buffer.update(cx, |buffer, cx| {
18597 buffer.edit(
18598 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18599 None,
18600 cx,
18601 )
18602 });
18603 let cursor_offset_in_rename_range =
18604 MultiBufferOffset(cursor_offset_in_rename_range);
18605 let cursor_offset_in_rename_range_end =
18606 MultiBufferOffset(cursor_offset_in_rename_range_end);
18607 let rename_selection_range = match cursor_offset_in_rename_range
18608 .cmp(&cursor_offset_in_rename_range_end)
18609 {
18610 Ordering::Equal => {
18611 editor.select_all(&SelectAll, window, cx);
18612 return editor;
18613 }
18614 Ordering::Less => {
18615 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18616 }
18617 Ordering::Greater => {
18618 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18619 }
18620 };
18621 if rename_selection_range.end.0 > old_name.len() {
18622 editor.select_all(&SelectAll, window, cx);
18623 } else {
18624 editor.change_selections(Default::default(), window, cx, |s| {
18625 s.select_ranges([rename_selection_range]);
18626 });
18627 }
18628 editor
18629 });
18630 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18631 if e == &EditorEvent::Focused {
18632 cx.emit(EditorEvent::FocusedIn)
18633 }
18634 })
18635 .detach();
18636
18637 let write_highlights =
18638 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
18639 let read_highlights =
18640 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
18641 let ranges = write_highlights
18642 .iter()
18643 .flat_map(|(_, ranges)| ranges.iter())
18644 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18645 .cloned()
18646 .collect();
18647
18648 this.highlight_text(
18649 HighlightKey::Rename,
18650 ranges,
18651 HighlightStyle {
18652 fade_out: Some(0.6),
18653 ..Default::default()
18654 },
18655 cx,
18656 );
18657 let rename_focus_handle = rename_editor.focus_handle(cx);
18658 window.focus(&rename_focus_handle, cx);
18659 let block_id = this.insert_blocks(
18660 [BlockProperties {
18661 style: BlockStyle::Flex,
18662 placement: BlockPlacement::Below(range.start),
18663 height: Some(1),
18664 render: Arc::new({
18665 let rename_editor = rename_editor.clone();
18666 move |cx: &mut BlockContext| {
18667 let mut text_style = cx.editor_style.text.clone();
18668 if let Some(highlight_style) = old_highlight_id
18669 .and_then(|h| h.style(&cx.editor_style.syntax))
18670 {
18671 text_style = text_style.highlight(highlight_style);
18672 }
18673 div()
18674 .block_mouse_except_scroll()
18675 .pl(cx.anchor_x)
18676 .child(EditorElement::new(
18677 &rename_editor,
18678 EditorStyle {
18679 background: cx.theme().system().transparent,
18680 local_player: cx.editor_style.local_player,
18681 text: text_style,
18682 scrollbar_width: cx.editor_style.scrollbar_width,
18683 syntax: cx.editor_style.syntax.clone(),
18684 status: cx.editor_style.status.clone(),
18685 inlay_hints_style: HighlightStyle {
18686 font_weight: Some(FontWeight::BOLD),
18687 ..make_inlay_hints_style(cx.app)
18688 },
18689 edit_prediction_styles: make_suggestion_styles(
18690 cx.app,
18691 ),
18692 ..EditorStyle::default()
18693 },
18694 ))
18695 .into_any_element()
18696 }
18697 }),
18698 priority: 0,
18699 }],
18700 Some(Autoscroll::fit()),
18701 cx,
18702 )[0];
18703 this.pending_rename = Some(RenameState {
18704 range,
18705 old_name,
18706 editor: rename_editor,
18707 block_id,
18708 });
18709 })?;
18710 }
18711
18712 Ok(())
18713 }))
18714 }
18715
18716 pub fn confirm_rename(
18717 &mut self,
18718 _: &ConfirmRename,
18719 window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) -> Option<Task<Result<()>>> {
18722 let rename = self.take_rename(false, window, cx)?;
18723 let workspace = self.workspace()?.downgrade();
18724 let (buffer, start) = self
18725 .buffer
18726 .read(cx)
18727 .text_anchor_for_position(rename.range.start, cx)?;
18728 let (end_buffer, _) = self
18729 .buffer
18730 .read(cx)
18731 .text_anchor_for_position(rename.range.end, cx)?;
18732 if buffer != end_buffer {
18733 return None;
18734 }
18735
18736 let old_name = rename.old_name;
18737 let new_name = rename.editor.read(cx).text(cx);
18738
18739 let rename = self.semantics_provider.as_ref()?.perform_rename(
18740 &buffer,
18741 start,
18742 new_name.clone(),
18743 cx,
18744 )?;
18745
18746 Some(cx.spawn_in(window, async move |editor, cx| {
18747 let project_transaction = rename.await?;
18748 Self::open_project_transaction(
18749 &editor,
18750 workspace,
18751 project_transaction,
18752 format!("Rename: {} → {}", old_name, new_name),
18753 cx,
18754 )
18755 .await?;
18756
18757 editor.update(cx, |editor, cx| {
18758 editor.refresh_document_highlights(cx);
18759 })?;
18760 Ok(())
18761 }))
18762 }
18763
18764 fn take_rename(
18765 &mut self,
18766 moving_cursor: bool,
18767 window: &mut Window,
18768 cx: &mut Context<Self>,
18769 ) -> Option<RenameState> {
18770 let rename = self.pending_rename.take()?;
18771 if rename.editor.focus_handle(cx).is_focused(window) {
18772 window.focus(&self.focus_handle, cx);
18773 }
18774
18775 self.remove_blocks(
18776 [rename.block_id].into_iter().collect(),
18777 Some(Autoscroll::fit()),
18778 cx,
18779 );
18780 self.clear_highlights(HighlightKey::Rename, cx);
18781 self.show_local_selections = true;
18782
18783 if moving_cursor {
18784 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18785 editor
18786 .selections
18787 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18788 .head()
18789 });
18790
18791 // Update the selection to match the position of the selection inside
18792 // the rename editor.
18793 let snapshot = self.buffer.read(cx).read(cx);
18794 let rename_range = rename.range.to_offset(&snapshot);
18795 let cursor_in_editor = snapshot
18796 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18797 .min(rename_range.end);
18798 drop(snapshot);
18799
18800 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18801 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18802 });
18803 } else {
18804 self.refresh_document_highlights(cx);
18805 }
18806
18807 Some(rename)
18808 }
18809
18810 pub fn pending_rename(&self) -> Option<&RenameState> {
18811 self.pending_rename.as_ref()
18812 }
18813
18814 fn format(
18815 &mut self,
18816 _: &Format,
18817 window: &mut Window,
18818 cx: &mut Context<Self>,
18819 ) -> Option<Task<Result<()>>> {
18820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18821
18822 let project = match &self.project {
18823 Some(project) => project.clone(),
18824 None => return None,
18825 };
18826
18827 Some(self.perform_format(
18828 project,
18829 FormatTrigger::Manual,
18830 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18831 window,
18832 cx,
18833 ))
18834 }
18835
18836 fn format_selections(
18837 &mut self,
18838 _: &FormatSelections,
18839 window: &mut Window,
18840 cx: &mut Context<Self>,
18841 ) -> Option<Task<Result<()>>> {
18842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18843
18844 let project = match &self.project {
18845 Some(project) => project.clone(),
18846 None => return None,
18847 };
18848
18849 let ranges = self
18850 .selections
18851 .all_adjusted(&self.display_snapshot(cx))
18852 .into_iter()
18853 .map(|selection| selection.range())
18854 .collect_vec();
18855
18856 Some(self.perform_format(
18857 project,
18858 FormatTrigger::Manual,
18859 FormatTarget::Ranges(ranges),
18860 window,
18861 cx,
18862 ))
18863 }
18864
18865 fn perform_format(
18866 &mut self,
18867 project: Entity<Project>,
18868 trigger: FormatTrigger,
18869 target: FormatTarget,
18870 window: &mut Window,
18871 cx: &mut Context<Self>,
18872 ) -> Task<Result<()>> {
18873 let buffer = self.buffer.clone();
18874 let (buffers, target) = match target {
18875 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18876 FormatTarget::Ranges(selection_ranges) => {
18877 let multi_buffer = buffer.read(cx);
18878 let snapshot = multi_buffer.read(cx);
18879 let mut buffers = HashSet::default();
18880 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18881 BTreeMap::new();
18882 for selection_range in selection_ranges {
18883 for (buffer, buffer_range, _) in
18884 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
18885 {
18886 let buffer_id = buffer.remote_id();
18887 let start = buffer.anchor_before(buffer_range.start);
18888 let end = buffer.anchor_after(buffer_range.end);
18889 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18890 buffer_id_to_ranges
18891 .entry(buffer_id)
18892 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18893 .or_insert_with(|| vec![start..end]);
18894 }
18895 }
18896 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18897 }
18898 };
18899
18900 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18901 let selections_prev = transaction_id_prev
18902 .and_then(|transaction_id_prev| {
18903 // default to selections as they were after the last edit, if we have them,
18904 // instead of how they are now.
18905 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18906 // will take you back to where you made the last edit, instead of staying where you scrolled
18907 self.selection_history
18908 .transaction(transaction_id_prev)
18909 .map(|t| t.0.clone())
18910 })
18911 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18912
18913 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18914 let format = project.update(cx, |project, cx| {
18915 project.format(buffers, target, true, trigger, cx)
18916 });
18917
18918 cx.spawn_in(window, async move |editor, cx| {
18919 let transaction = futures::select_biased! {
18920 transaction = format.log_err().fuse() => transaction,
18921 () = timeout => {
18922 log::warn!("timed out waiting for formatting");
18923 None
18924 }
18925 };
18926
18927 buffer.update(cx, |buffer, cx| {
18928 if let Some(transaction) = transaction
18929 && !buffer.is_singleton()
18930 {
18931 buffer.push_transaction(&transaction.0, cx);
18932 }
18933 cx.notify();
18934 });
18935
18936 if let Some(transaction_id_now) =
18937 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18938 {
18939 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18940 if has_new_transaction {
18941 editor
18942 .update(cx, |editor, _| {
18943 editor
18944 .selection_history
18945 .insert_transaction(transaction_id_now, selections_prev);
18946 })
18947 .ok();
18948 }
18949 }
18950
18951 Ok(())
18952 })
18953 }
18954
18955 fn organize_imports(
18956 &mut self,
18957 _: &OrganizeImports,
18958 window: &mut Window,
18959 cx: &mut Context<Self>,
18960 ) -> Option<Task<Result<()>>> {
18961 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18962 let project = match &self.project {
18963 Some(project) => project.clone(),
18964 None => return None,
18965 };
18966 Some(self.perform_code_action_kind(
18967 project,
18968 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18969 window,
18970 cx,
18971 ))
18972 }
18973
18974 fn perform_code_action_kind(
18975 &mut self,
18976 project: Entity<Project>,
18977 kind: CodeActionKind,
18978 window: &mut Window,
18979 cx: &mut Context<Self>,
18980 ) -> Task<Result<()>> {
18981 let buffer = self.buffer.clone();
18982 let buffers = buffer.read(cx).all_buffers();
18983 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18984 let apply_action = project.update(cx, |project, cx| {
18985 project.apply_code_action_kind(buffers, kind, true, cx)
18986 });
18987 cx.spawn_in(window, async move |_, cx| {
18988 let transaction = futures::select_biased! {
18989 () = timeout => {
18990 log::warn!("timed out waiting for executing code action");
18991 None
18992 }
18993 transaction = apply_action.log_err().fuse() => transaction,
18994 };
18995 buffer.update(cx, |buffer, cx| {
18996 // check if we need this
18997 if let Some(transaction) = transaction
18998 && !buffer.is_singleton()
18999 {
19000 buffer.push_transaction(&transaction.0, cx);
19001 }
19002 cx.notify();
19003 });
19004 Ok(())
19005 })
19006 }
19007
19008 pub fn restart_language_server(
19009 &mut self,
19010 _: &RestartLanguageServer,
19011 _: &mut Window,
19012 cx: &mut Context<Self>,
19013 ) {
19014 if let Some(project) = self.project.clone() {
19015 self.buffer.update(cx, |multi_buffer, cx| {
19016 project.update(cx, |project, cx| {
19017 project.restart_language_servers_for_buffers(
19018 multi_buffer.all_buffers().into_iter().collect(),
19019 HashSet::default(),
19020 cx,
19021 );
19022 });
19023 })
19024 }
19025 }
19026
19027 pub fn stop_language_server(
19028 &mut self,
19029 _: &StopLanguageServer,
19030 _: &mut Window,
19031 cx: &mut Context<Self>,
19032 ) {
19033 if let Some(project) = self.project.clone() {
19034 self.buffer.update(cx, |multi_buffer, cx| {
19035 project.update(cx, |project, cx| {
19036 project.stop_language_servers_for_buffers(
19037 multi_buffer.all_buffers().into_iter().collect(),
19038 HashSet::default(),
19039 cx,
19040 );
19041 });
19042 });
19043 }
19044 }
19045
19046 fn cancel_language_server_work(
19047 workspace: &mut Workspace,
19048 _: &actions::CancelLanguageServerWork,
19049 _: &mut Window,
19050 cx: &mut Context<Workspace>,
19051 ) {
19052 let project = workspace.project();
19053 let buffers = workspace
19054 .active_item(cx)
19055 .and_then(|item| item.act_as::<Editor>(cx))
19056 .map_or(HashSet::default(), |editor| {
19057 editor.read(cx).buffer.read(cx).all_buffers()
19058 });
19059 project.update(cx, |project, cx| {
19060 project.cancel_language_server_work_for_buffers(buffers, cx);
19061 });
19062 }
19063
19064 fn show_character_palette(
19065 &mut self,
19066 _: &ShowCharacterPalette,
19067 window: &mut Window,
19068 _: &mut Context<Self>,
19069 ) {
19070 window.show_character_palette();
19071 }
19072
19073 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19074 if !self.diagnostics_enabled() {
19075 return;
19076 }
19077
19078 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19079 let buffer = self.buffer.read(cx).snapshot(cx);
19080 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19081 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19082 let is_valid = buffer
19083 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19084 .any(|entry| {
19085 entry.diagnostic.is_primary
19086 && !entry.range.is_empty()
19087 && entry.range.start == primary_range_start
19088 && entry.diagnostic.message == active_diagnostics.active_message
19089 });
19090
19091 if !is_valid {
19092 self.dismiss_diagnostics(cx);
19093 }
19094 }
19095 }
19096
19097 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19098 match &self.active_diagnostics {
19099 ActiveDiagnostic::Group(group) => Some(group),
19100 _ => None,
19101 }
19102 }
19103
19104 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19105 if !self.diagnostics_enabled() {
19106 return;
19107 }
19108 self.dismiss_diagnostics(cx);
19109 self.active_diagnostics = ActiveDiagnostic::All;
19110 }
19111
19112 fn activate_diagnostics(
19113 &mut self,
19114 buffer_id: BufferId,
19115 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19116 window: &mut Window,
19117 cx: &mut Context<Self>,
19118 ) {
19119 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19120 return;
19121 }
19122 self.dismiss_diagnostics(cx);
19123 let snapshot = self.snapshot(window, cx);
19124 let buffer = self.buffer.read(cx).snapshot(cx);
19125 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19126 return;
19127 };
19128
19129 let diagnostic_group = buffer
19130 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19131 .collect::<Vec<_>>();
19132
19133 let language_registry = self
19134 .project()
19135 .map(|project| project.read(cx).languages().clone());
19136
19137 let blocks = renderer.render_group(
19138 diagnostic_group,
19139 buffer_id,
19140 snapshot,
19141 cx.weak_entity(),
19142 language_registry,
19143 cx,
19144 );
19145
19146 let blocks = self.display_map.update(cx, |display_map, cx| {
19147 display_map.insert_blocks(blocks, cx).into_iter().collect()
19148 });
19149 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19150 active_range: buffer.anchor_before(diagnostic.range.start)
19151 ..buffer.anchor_after(diagnostic.range.end),
19152 active_message: diagnostic.diagnostic.message.clone(),
19153 group_id: diagnostic.diagnostic.group_id,
19154 blocks,
19155 });
19156 cx.notify();
19157 }
19158
19159 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19160 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19161 return;
19162 };
19163
19164 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19165 if let ActiveDiagnostic::Group(group) = prev {
19166 self.display_map.update(cx, |display_map, cx| {
19167 display_map.remove_blocks(group.blocks, cx);
19168 });
19169 cx.notify();
19170 }
19171 }
19172
19173 /// Disable inline diagnostics rendering for this editor.
19174 pub fn disable_inline_diagnostics(&mut self) {
19175 self.inline_diagnostics_enabled = false;
19176 self.inline_diagnostics_update = Task::ready(());
19177 self.inline_diagnostics.clear();
19178 }
19179
19180 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19181 self.diagnostics_enabled = false;
19182 self.dismiss_diagnostics(cx);
19183 self.inline_diagnostics_update = Task::ready(());
19184 self.inline_diagnostics.clear();
19185 }
19186
19187 pub fn disable_word_completions(&mut self) {
19188 self.word_completions_enabled = false;
19189 }
19190
19191 pub fn diagnostics_enabled(&self) -> bool {
19192 self.diagnostics_enabled && self.lsp_data_enabled()
19193 }
19194
19195 pub fn inline_diagnostics_enabled(&self) -> bool {
19196 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19197 }
19198
19199 pub fn show_inline_diagnostics(&self) -> bool {
19200 self.show_inline_diagnostics
19201 }
19202
19203 pub fn toggle_inline_diagnostics(
19204 &mut self,
19205 _: &ToggleInlineDiagnostics,
19206 window: &mut Window,
19207 cx: &mut Context<Editor>,
19208 ) {
19209 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19210 self.refresh_inline_diagnostics(false, window, cx);
19211 }
19212
19213 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19214 self.diagnostics_max_severity = severity;
19215 self.display_map.update(cx, |display_map, _| {
19216 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19217 });
19218 }
19219
19220 pub fn toggle_diagnostics(
19221 &mut self,
19222 _: &ToggleDiagnostics,
19223 window: &mut Window,
19224 cx: &mut Context<Editor>,
19225 ) {
19226 if !self.diagnostics_enabled() {
19227 return;
19228 }
19229
19230 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19231 EditorSettings::get_global(cx)
19232 .diagnostics_max_severity
19233 .filter(|severity| severity != &DiagnosticSeverity::Off)
19234 .unwrap_or(DiagnosticSeverity::Hint)
19235 } else {
19236 DiagnosticSeverity::Off
19237 };
19238 self.set_max_diagnostics_severity(new_severity, cx);
19239 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19240 self.active_diagnostics = ActiveDiagnostic::None;
19241 self.inline_diagnostics_update = Task::ready(());
19242 self.inline_diagnostics.clear();
19243 } else {
19244 self.refresh_inline_diagnostics(false, window, cx);
19245 }
19246
19247 cx.notify();
19248 }
19249
19250 pub fn toggle_minimap(
19251 &mut self,
19252 _: &ToggleMinimap,
19253 window: &mut Window,
19254 cx: &mut Context<Editor>,
19255 ) {
19256 if self.supports_minimap(cx) {
19257 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19258 }
19259 }
19260
19261 fn refresh_inline_diagnostics(
19262 &mut self,
19263 debounce: bool,
19264 window: &mut Window,
19265 cx: &mut Context<Self>,
19266 ) {
19267 let max_severity = ProjectSettings::get_global(cx)
19268 .diagnostics
19269 .inline
19270 .max_severity
19271 .unwrap_or(self.diagnostics_max_severity);
19272
19273 if !self.inline_diagnostics_enabled()
19274 || !self.diagnostics_enabled()
19275 || !self.show_inline_diagnostics
19276 || max_severity == DiagnosticSeverity::Off
19277 {
19278 self.inline_diagnostics_update = Task::ready(());
19279 self.inline_diagnostics.clear();
19280 return;
19281 }
19282
19283 let debounce_ms = ProjectSettings::get_global(cx)
19284 .diagnostics
19285 .inline
19286 .update_debounce_ms;
19287 let debounce = if debounce && debounce_ms > 0 {
19288 Some(Duration::from_millis(debounce_ms))
19289 } else {
19290 None
19291 };
19292 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19293 if let Some(debounce) = debounce {
19294 cx.background_executor().timer(debounce).await;
19295 }
19296 let Some(snapshot) = editor.upgrade().map(|editor| {
19297 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19298 }) else {
19299 return;
19300 };
19301
19302 let new_inline_diagnostics = cx
19303 .background_spawn(async move {
19304 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19305 for diagnostic_entry in
19306 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19307 {
19308 let message = diagnostic_entry
19309 .diagnostic
19310 .message
19311 .split_once('\n')
19312 .map(|(line, _)| line)
19313 .map(SharedString::new)
19314 .unwrap_or_else(|| {
19315 SharedString::new(&*diagnostic_entry.diagnostic.message)
19316 });
19317 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19318 let (Ok(i) | Err(i)) = inline_diagnostics
19319 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19320 inline_diagnostics.insert(
19321 i,
19322 (
19323 start_anchor,
19324 InlineDiagnostic {
19325 message,
19326 group_id: diagnostic_entry.diagnostic.group_id,
19327 start: diagnostic_entry.range.start.to_point(&snapshot),
19328 is_primary: diagnostic_entry.diagnostic.is_primary,
19329 severity: diagnostic_entry.diagnostic.severity,
19330 },
19331 ),
19332 );
19333 }
19334 inline_diagnostics
19335 })
19336 .await;
19337
19338 editor
19339 .update(cx, |editor, cx| {
19340 editor.inline_diagnostics = new_inline_diagnostics;
19341 cx.notify();
19342 })
19343 .ok();
19344 });
19345 }
19346
19347 fn pull_diagnostics(
19348 &mut self,
19349 buffer_id: BufferId,
19350 _window: &Window,
19351 cx: &mut Context<Self>,
19352 ) -> Option<()> {
19353 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19354 // skip any LSP updates for it.
19355
19356 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19357 return None;
19358 }
19359 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19360 .diagnostics
19361 .lsp_pull_diagnostics;
19362 if !pull_diagnostics_settings.enabled {
19363 return None;
19364 }
19365 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19366 let project = self.project()?.downgrade();
19367 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19368
19369 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19370 cx.background_executor().timer(debounce).await;
19371 if let Ok(task) = project.update(cx, |project, cx| {
19372 project.lsp_store().update(cx, |lsp_store, cx| {
19373 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19374 })
19375 }) {
19376 task.await.log_err();
19377 }
19378 project
19379 .update(cx, |project, cx| {
19380 project.lsp_store().update(cx, |lsp_store, cx| {
19381 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19382 })
19383 })
19384 .log_err();
19385 });
19386
19387 Some(())
19388 }
19389
19390 pub fn set_selections_from_remote(
19391 &mut self,
19392 selections: Vec<Selection<Anchor>>,
19393 pending_selection: Option<Selection<Anchor>>,
19394 window: &mut Window,
19395 cx: &mut Context<Self>,
19396 ) {
19397 let old_cursor_position = self.selections.newest_anchor().head();
19398 self.selections
19399 .change_with(&self.display_snapshot(cx), |s| {
19400 s.select_anchors(selections);
19401 if let Some(pending_selection) = pending_selection {
19402 s.set_pending(pending_selection, SelectMode::Character);
19403 } else {
19404 s.clear_pending();
19405 }
19406 });
19407 self.selections_did_change(
19408 false,
19409 &old_cursor_position,
19410 SelectionEffects::default(),
19411 window,
19412 cx,
19413 );
19414 }
19415
19416 pub fn transact(
19417 &mut self,
19418 window: &mut Window,
19419 cx: &mut Context<Self>,
19420 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19421 ) -> Option<TransactionId> {
19422 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19423 this.start_transaction_at(Instant::now(), window, cx);
19424 update(this, window, cx);
19425 this.end_transaction_at(Instant::now(), cx)
19426 })
19427 }
19428
19429 pub fn start_transaction_at(
19430 &mut self,
19431 now: Instant,
19432 window: &mut Window,
19433 cx: &mut Context<Self>,
19434 ) -> Option<TransactionId> {
19435 self.end_selection(window, cx);
19436 if let Some(tx_id) = self
19437 .buffer
19438 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19439 {
19440 self.selection_history
19441 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19442 cx.emit(EditorEvent::TransactionBegun {
19443 transaction_id: tx_id,
19444 });
19445 Some(tx_id)
19446 } else {
19447 None
19448 }
19449 }
19450
19451 pub fn end_transaction_at(
19452 &mut self,
19453 now: Instant,
19454 cx: &mut Context<Self>,
19455 ) -> Option<TransactionId> {
19456 if let Some(transaction_id) = self
19457 .buffer
19458 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19459 {
19460 if let Some((_, end_selections)) =
19461 self.selection_history.transaction_mut(transaction_id)
19462 {
19463 *end_selections = Some(self.selections.disjoint_anchors_arc());
19464 } else {
19465 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19466 }
19467
19468 cx.emit(EditorEvent::Edited { transaction_id });
19469 Some(transaction_id)
19470 } else {
19471 None
19472 }
19473 }
19474
19475 pub fn modify_transaction_selection_history(
19476 &mut self,
19477 transaction_id: TransactionId,
19478 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19479 ) -> bool {
19480 self.selection_history
19481 .transaction_mut(transaction_id)
19482 .map(modify)
19483 .is_some()
19484 }
19485
19486 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19487 if self.selection_mark_mode {
19488 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19489 s.move_with(&mut |_, sel| {
19490 sel.collapse_to(sel.head(), SelectionGoal::None);
19491 });
19492 })
19493 }
19494 self.selection_mark_mode = true;
19495 cx.notify();
19496 }
19497
19498 pub fn swap_selection_ends(
19499 &mut self,
19500 _: &actions::SwapSelectionEnds,
19501 window: &mut Window,
19502 cx: &mut Context<Self>,
19503 ) {
19504 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19505 s.move_with(&mut |_, sel| {
19506 if sel.start != sel.end {
19507 sel.reversed = !sel.reversed
19508 }
19509 });
19510 });
19511 self.request_autoscroll(Autoscroll::newest(), cx);
19512 cx.notify();
19513 }
19514
19515 pub fn toggle_focus(
19516 workspace: &mut Workspace,
19517 _: &actions::ToggleFocus,
19518 window: &mut Window,
19519 cx: &mut Context<Workspace>,
19520 ) {
19521 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19522 return;
19523 };
19524 workspace.activate_item(&item, true, true, window, cx);
19525 }
19526
19527 pub fn toggle_fold(
19528 &mut self,
19529 _: &actions::ToggleFold,
19530 window: &mut Window,
19531 cx: &mut Context<Self>,
19532 ) {
19533 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19535 let selection = self.selections.newest::<Point>(&display_map);
19536
19537 let range = if selection.is_empty() {
19538 let point = selection.head().to_display_point(&display_map);
19539 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19540 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19541 .to_point(&display_map);
19542 start..end
19543 } else {
19544 selection.range()
19545 };
19546 if display_map.folds_in_range(range).next().is_some() {
19547 self.unfold_lines(&Default::default(), window, cx)
19548 } else {
19549 self.fold(&Default::default(), window, cx)
19550 }
19551 } else {
19552 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19553 let buffer_ids: HashSet<_> = self
19554 .selections
19555 .disjoint_anchor_ranges()
19556 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19557 .collect();
19558
19559 let should_unfold = buffer_ids
19560 .iter()
19561 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19562
19563 for buffer_id in buffer_ids {
19564 if should_unfold {
19565 self.unfold_buffer(buffer_id, cx);
19566 } else {
19567 self.fold_buffer(buffer_id, cx);
19568 }
19569 }
19570 }
19571 }
19572
19573 pub fn toggle_fold_recursive(
19574 &mut self,
19575 _: &actions::ToggleFoldRecursive,
19576 window: &mut Window,
19577 cx: &mut Context<Self>,
19578 ) {
19579 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19580
19581 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19582 let range = if selection.is_empty() {
19583 let point = selection.head().to_display_point(&display_map);
19584 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19585 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19586 .to_point(&display_map);
19587 start..end
19588 } else {
19589 selection.range()
19590 };
19591 if display_map.folds_in_range(range).next().is_some() {
19592 self.unfold_recursive(&Default::default(), window, cx)
19593 } else {
19594 self.fold_recursive(&Default::default(), window, cx)
19595 }
19596 }
19597
19598 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19599 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19600 let mut to_fold = Vec::new();
19601 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19602 let selections = self.selections.all_adjusted(&display_map);
19603
19604 for selection in selections {
19605 let range = selection.range().sorted();
19606 let buffer_start_row = range.start.row;
19607
19608 if range.start.row != range.end.row {
19609 let mut found = false;
19610 let mut row = range.start.row;
19611 while row <= range.end.row {
19612 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19613 {
19614 found = true;
19615 row = crease.range().end.row + 1;
19616 to_fold.push(crease);
19617 } else {
19618 row += 1
19619 }
19620 }
19621 if found {
19622 continue;
19623 }
19624 }
19625
19626 for row in (0..=range.start.row).rev() {
19627 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19628 && crease.range().end.row >= buffer_start_row
19629 {
19630 to_fold.push(crease);
19631 if row <= range.start.row {
19632 break;
19633 }
19634 }
19635 }
19636 }
19637
19638 self.fold_creases(to_fold, true, window, cx);
19639 } else {
19640 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19641 let buffer_ids = self
19642 .selections
19643 .disjoint_anchor_ranges()
19644 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19645 .collect::<HashSet<_>>();
19646 for buffer_id in buffer_ids {
19647 self.fold_buffer(buffer_id, cx);
19648 }
19649 }
19650 }
19651
19652 pub fn toggle_fold_all(
19653 &mut self,
19654 _: &actions::ToggleFoldAll,
19655 window: &mut Window,
19656 cx: &mut Context<Self>,
19657 ) {
19658 let has_folds = if self.buffer.read(cx).is_singleton() {
19659 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19660 let has_folds = display_map
19661 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19662 .next()
19663 .is_some();
19664 has_folds
19665 } else {
19666 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19667 let has_folds = buffer_ids
19668 .iter()
19669 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19670 has_folds
19671 };
19672
19673 if has_folds {
19674 self.unfold_all(&actions::UnfoldAll, window, cx);
19675 } else {
19676 self.fold_all(&actions::FoldAll, window, cx);
19677 }
19678 }
19679
19680 fn fold_at_level(
19681 &mut self,
19682 fold_at: &FoldAtLevel,
19683 window: &mut Window,
19684 cx: &mut Context<Self>,
19685 ) {
19686 if !self.buffer.read(cx).is_singleton() {
19687 return;
19688 }
19689
19690 let fold_at_level = fold_at.0;
19691 let snapshot = self.buffer.read(cx).snapshot(cx);
19692 let mut to_fold = Vec::new();
19693 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19694
19695 let row_ranges_to_keep: Vec<Range<u32>> = self
19696 .selections
19697 .all::<Point>(&self.display_snapshot(cx))
19698 .into_iter()
19699 .map(|sel| sel.start.row..sel.end.row)
19700 .collect();
19701
19702 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19703 while start_row < end_row {
19704 match self
19705 .snapshot(window, cx)
19706 .crease_for_buffer_row(MultiBufferRow(start_row))
19707 {
19708 Some(crease) => {
19709 let nested_start_row = crease.range().start.row + 1;
19710 let nested_end_row = crease.range().end.row;
19711
19712 if current_level < fold_at_level {
19713 stack.push((nested_start_row, nested_end_row, current_level + 1));
19714 } else if current_level == fold_at_level {
19715 // Fold iff there is no selection completely contained within the fold region
19716 if !row_ranges_to_keep.iter().any(|selection| {
19717 selection.end >= nested_start_row
19718 && selection.start <= nested_end_row
19719 }) {
19720 to_fold.push(crease);
19721 }
19722 }
19723
19724 start_row = nested_end_row + 1;
19725 }
19726 None => start_row += 1,
19727 }
19728 }
19729 }
19730
19731 self.fold_creases(to_fold, true, window, cx);
19732 }
19733
19734 pub fn fold_at_level_1(
19735 &mut self,
19736 _: &actions::FoldAtLevel1,
19737 window: &mut Window,
19738 cx: &mut Context<Self>,
19739 ) {
19740 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19741 }
19742
19743 pub fn fold_at_level_2(
19744 &mut self,
19745 _: &actions::FoldAtLevel2,
19746 window: &mut Window,
19747 cx: &mut Context<Self>,
19748 ) {
19749 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19750 }
19751
19752 pub fn fold_at_level_3(
19753 &mut self,
19754 _: &actions::FoldAtLevel3,
19755 window: &mut Window,
19756 cx: &mut Context<Self>,
19757 ) {
19758 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19759 }
19760
19761 pub fn fold_at_level_4(
19762 &mut self,
19763 _: &actions::FoldAtLevel4,
19764 window: &mut Window,
19765 cx: &mut Context<Self>,
19766 ) {
19767 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19768 }
19769
19770 pub fn fold_at_level_5(
19771 &mut self,
19772 _: &actions::FoldAtLevel5,
19773 window: &mut Window,
19774 cx: &mut Context<Self>,
19775 ) {
19776 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19777 }
19778
19779 pub fn fold_at_level_6(
19780 &mut self,
19781 _: &actions::FoldAtLevel6,
19782 window: &mut Window,
19783 cx: &mut Context<Self>,
19784 ) {
19785 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19786 }
19787
19788 pub fn fold_at_level_7(
19789 &mut self,
19790 _: &actions::FoldAtLevel7,
19791 window: &mut Window,
19792 cx: &mut Context<Self>,
19793 ) {
19794 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19795 }
19796
19797 pub fn fold_at_level_8(
19798 &mut self,
19799 _: &actions::FoldAtLevel8,
19800 window: &mut Window,
19801 cx: &mut Context<Self>,
19802 ) {
19803 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19804 }
19805
19806 pub fn fold_at_level_9(
19807 &mut self,
19808 _: &actions::FoldAtLevel9,
19809 window: &mut Window,
19810 cx: &mut Context<Self>,
19811 ) {
19812 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19813 }
19814
19815 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19816 if self.buffer.read(cx).is_singleton() {
19817 let mut fold_ranges = Vec::new();
19818 let snapshot = self.buffer.read(cx).snapshot(cx);
19819
19820 for row in 0..snapshot.max_row().0 {
19821 if let Some(foldable_range) = self
19822 .snapshot(window, cx)
19823 .crease_for_buffer_row(MultiBufferRow(row))
19824 {
19825 fold_ranges.push(foldable_range);
19826 }
19827 }
19828
19829 self.fold_creases(fold_ranges, true, window, cx);
19830 } else {
19831 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19832 editor
19833 .update_in(cx, |editor, _, cx| {
19834 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19835 editor.fold_buffer(buffer_id, cx);
19836 }
19837 })
19838 .ok();
19839 });
19840 }
19841 }
19842
19843 pub fn fold_function_bodies(
19844 &mut self,
19845 _: &actions::FoldFunctionBodies,
19846 window: &mut Window,
19847 cx: &mut Context<Self>,
19848 ) {
19849 let snapshot = self.buffer.read(cx).snapshot(cx);
19850
19851 let ranges = snapshot
19852 .text_object_ranges(
19853 MultiBufferOffset(0)..snapshot.len(),
19854 TreeSitterOptions::default(),
19855 )
19856 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19857 .collect::<Vec<_>>();
19858
19859 let creases = ranges
19860 .into_iter()
19861 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19862 .collect();
19863
19864 self.fold_creases(creases, true, window, cx);
19865 }
19866
19867 pub fn fold_recursive(
19868 &mut self,
19869 _: &actions::FoldRecursive,
19870 window: &mut Window,
19871 cx: &mut Context<Self>,
19872 ) {
19873 let mut to_fold = Vec::new();
19874 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19875 let selections = self.selections.all_adjusted(&display_map);
19876
19877 for selection in selections {
19878 let range = selection.range().sorted();
19879 let buffer_start_row = range.start.row;
19880
19881 if range.start.row != range.end.row {
19882 let mut found = false;
19883 for row in range.start.row..=range.end.row {
19884 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19885 found = true;
19886 to_fold.push(crease);
19887 }
19888 }
19889 if found {
19890 continue;
19891 }
19892 }
19893
19894 for row in (0..=range.start.row).rev() {
19895 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19896 if crease.range().end.row >= buffer_start_row {
19897 to_fold.push(crease);
19898 } else {
19899 break;
19900 }
19901 }
19902 }
19903 }
19904
19905 self.fold_creases(to_fold, true, window, cx);
19906 }
19907
19908 pub fn fold_at(
19909 &mut self,
19910 buffer_row: MultiBufferRow,
19911 window: &mut Window,
19912 cx: &mut Context<Self>,
19913 ) {
19914 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19915
19916 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19917 let autoscroll = self
19918 .selections
19919 .all::<Point>(&display_map)
19920 .iter()
19921 .any(|selection| crease.range().overlaps(&selection.range()));
19922
19923 self.fold_creases(vec![crease], autoscroll, window, cx);
19924 }
19925 }
19926
19927 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19928 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19929 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19930 let buffer = display_map.buffer_snapshot();
19931 let selections = self.selections.all::<Point>(&display_map);
19932 let ranges = selections
19933 .iter()
19934 .map(|s| {
19935 let range = s.display_range(&display_map).sorted();
19936 let mut start = range.start.to_point(&display_map);
19937 let mut end = range.end.to_point(&display_map);
19938 start.column = 0;
19939 end.column = buffer.line_len(MultiBufferRow(end.row));
19940 start..end
19941 })
19942 .collect::<Vec<_>>();
19943
19944 self.unfold_ranges(&ranges, true, true, cx);
19945 } else {
19946 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19947 let buffer_ids = self
19948 .selections
19949 .disjoint_anchor_ranges()
19950 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19951 .collect::<HashSet<_>>();
19952 for buffer_id in buffer_ids {
19953 self.unfold_buffer(buffer_id, cx);
19954 }
19955 }
19956 }
19957
19958 pub fn unfold_recursive(
19959 &mut self,
19960 _: &UnfoldRecursive,
19961 _window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19965 let selections = self.selections.all::<Point>(&display_map);
19966 let ranges = selections
19967 .iter()
19968 .map(|s| {
19969 let mut range = s.display_range(&display_map).sorted();
19970 *range.start.column_mut() = 0;
19971 *range.end.column_mut() = display_map.line_len(range.end.row());
19972 let start = range.start.to_point(&display_map);
19973 let end = range.end.to_point(&display_map);
19974 start..end
19975 })
19976 .collect::<Vec<_>>();
19977
19978 self.unfold_ranges(&ranges, true, true, cx);
19979 }
19980
19981 pub fn unfold_at(
19982 &mut self,
19983 buffer_row: MultiBufferRow,
19984 _window: &mut Window,
19985 cx: &mut Context<Self>,
19986 ) {
19987 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19988
19989 let intersection_range = Point::new(buffer_row.0, 0)
19990 ..Point::new(
19991 buffer_row.0,
19992 display_map.buffer_snapshot().line_len(buffer_row),
19993 );
19994
19995 let autoscroll = self
19996 .selections
19997 .all::<Point>(&display_map)
19998 .iter()
19999 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20000
20001 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20002 }
20003
20004 pub fn unfold_all(
20005 &mut self,
20006 _: &actions::UnfoldAll,
20007 _window: &mut Window,
20008 cx: &mut Context<Self>,
20009 ) {
20010 if self.buffer.read(cx).is_singleton() {
20011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20012 self.unfold_ranges(
20013 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20014 true,
20015 true,
20016 cx,
20017 );
20018 } else {
20019 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20020 editor
20021 .update(cx, |editor, cx| {
20022 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20023 editor.unfold_buffer(buffer_id, cx);
20024 }
20025 })
20026 .ok();
20027 });
20028 }
20029 }
20030
20031 pub fn fold_selected_ranges(
20032 &mut self,
20033 _: &FoldSelectedRanges,
20034 window: &mut Window,
20035 cx: &mut Context<Self>,
20036 ) {
20037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20038 let selections = self.selections.all_adjusted(&display_map);
20039 let ranges = selections
20040 .into_iter()
20041 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20042 .collect::<Vec<_>>();
20043 self.fold_creases(ranges, true, window, cx);
20044 }
20045
20046 pub fn fold_ranges<T: ToOffset + Clone>(
20047 &mut self,
20048 ranges: Vec<Range<T>>,
20049 auto_scroll: bool,
20050 window: &mut Window,
20051 cx: &mut Context<Self>,
20052 ) {
20053 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20054 let ranges = ranges
20055 .into_iter()
20056 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20057 .collect::<Vec<_>>();
20058 self.fold_creases(ranges, auto_scroll, window, cx);
20059 }
20060
20061 pub fn fold_creases<T: ToOffset + Clone>(
20062 &mut self,
20063 creases: Vec<Crease<T>>,
20064 auto_scroll: bool,
20065 window: &mut Window,
20066 cx: &mut Context<Self>,
20067 ) {
20068 if creases.is_empty() {
20069 return;
20070 }
20071
20072 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20073
20074 if auto_scroll {
20075 self.request_autoscroll(Autoscroll::fit(), cx);
20076 }
20077
20078 cx.notify();
20079
20080 self.scrollbar_marker_state.dirty = true;
20081 self.update_data_on_scroll(window, cx);
20082 self.folds_did_change(cx);
20083 }
20084
20085 /// Removes any folds whose ranges intersect any of the given ranges.
20086 pub fn unfold_ranges<T: ToOffset + Clone>(
20087 &mut self,
20088 ranges: &[Range<T>],
20089 inclusive: bool,
20090 auto_scroll: bool,
20091 cx: &mut Context<Self>,
20092 ) {
20093 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20094 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20095 });
20096 self.folds_did_change(cx);
20097 }
20098
20099 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20100 self.fold_buffers([buffer_id], cx);
20101 }
20102
20103 pub fn fold_buffers(
20104 &mut self,
20105 buffer_ids: impl IntoIterator<Item = BufferId>,
20106 cx: &mut Context<Self>,
20107 ) {
20108 if self.buffer().read(cx).is_singleton() {
20109 return;
20110 }
20111
20112 let ids_to_fold: Vec<BufferId> = buffer_ids
20113 .into_iter()
20114 .filter(|id| !self.is_buffer_folded(*id, cx))
20115 .collect();
20116
20117 if ids_to_fold.is_empty() {
20118 return;
20119 }
20120
20121 let mut all_folded_excerpt_ids = Vec::new();
20122 for buffer_id in &ids_to_fold {
20123 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20124 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20125 }
20126
20127 self.display_map.update(cx, |display_map, cx| {
20128 display_map.fold_buffers(ids_to_fold.clone(), cx)
20129 });
20130
20131 let snapshot = self.display_snapshot(cx);
20132 self.selections.change_with(&snapshot, |selections| {
20133 for buffer_id in ids_to_fold {
20134 selections.remove_selections_from_buffer(buffer_id);
20135 }
20136 });
20137
20138 cx.emit(EditorEvent::BufferFoldToggled {
20139 ids: all_folded_excerpt_ids,
20140 folded: true,
20141 });
20142 cx.notify();
20143 }
20144
20145 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20146 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20147 return;
20148 }
20149 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20150 self.display_map.update(cx, |display_map, cx| {
20151 display_map.unfold_buffers([buffer_id], cx);
20152 });
20153 cx.emit(EditorEvent::BufferFoldToggled {
20154 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20155 folded: false,
20156 });
20157 cx.notify();
20158 }
20159
20160 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20161 self.display_map.read(cx).is_buffer_folded(buffer)
20162 }
20163
20164 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20165 if self.buffer().read(cx).is_singleton() {
20166 return false;
20167 }
20168 !self.folded_buffers(cx).is_empty()
20169 }
20170
20171 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20172 self.display_map.read(cx).folded_buffers()
20173 }
20174
20175 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20176 self.display_map.update(cx, |display_map, cx| {
20177 display_map.disable_header_for_buffer(buffer_id, cx);
20178 });
20179 cx.notify();
20180 }
20181
20182 /// Removes any folds with the given ranges.
20183 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20184 &mut self,
20185 ranges: &[Range<T>],
20186 type_id: TypeId,
20187 auto_scroll: bool,
20188 cx: &mut Context<Self>,
20189 ) {
20190 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20191 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20192 });
20193 self.folds_did_change(cx);
20194 }
20195
20196 fn remove_folds_with<T: ToOffset + Clone>(
20197 &mut self,
20198 ranges: &[Range<T>],
20199 auto_scroll: bool,
20200 cx: &mut Context<Self>,
20201 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20202 ) {
20203 if ranges.is_empty() {
20204 return;
20205 }
20206
20207 let mut buffers_affected = HashSet::default();
20208 let multi_buffer = self.buffer().read(cx);
20209 for range in ranges {
20210 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20211 buffers_affected.insert(buffer.read(cx).remote_id());
20212 };
20213 }
20214
20215 self.display_map.update(cx, update);
20216
20217 if auto_scroll {
20218 self.request_autoscroll(Autoscroll::fit(), cx);
20219 }
20220
20221 cx.notify();
20222 self.scrollbar_marker_state.dirty = true;
20223 self.active_indent_guides_state.dirty = true;
20224 }
20225
20226 pub fn update_renderer_widths(
20227 &mut self,
20228 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20229 cx: &mut Context<Self>,
20230 ) -> bool {
20231 self.display_map
20232 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20233 }
20234
20235 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20236 self.display_map.read(cx).fold_placeholder.clone()
20237 }
20238
20239 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20240 self.buffer.update(cx, |buffer, cx| {
20241 buffer.set_all_diff_hunks_expanded(cx);
20242 });
20243 }
20244
20245 pub fn expand_all_diff_hunks(
20246 &mut self,
20247 _: &ExpandAllDiffHunks,
20248 _window: &mut Window,
20249 cx: &mut Context<Self>,
20250 ) {
20251 self.buffer.update(cx, |buffer, cx| {
20252 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20253 });
20254 }
20255
20256 pub fn collapse_all_diff_hunks(
20257 &mut self,
20258 _: &CollapseAllDiffHunks,
20259 _window: &mut Window,
20260 cx: &mut Context<Self>,
20261 ) {
20262 self.buffer.update(cx, |buffer, cx| {
20263 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20264 });
20265 }
20266
20267 pub fn toggle_selected_diff_hunks(
20268 &mut self,
20269 _: &ToggleSelectedDiffHunks,
20270 _window: &mut Window,
20271 cx: &mut Context<Self>,
20272 ) {
20273 let ranges: Vec<_> = self
20274 .selections
20275 .disjoint_anchors()
20276 .iter()
20277 .map(|s| s.range())
20278 .collect();
20279 self.toggle_diff_hunks_in_ranges(ranges, cx);
20280 }
20281
20282 pub fn diff_hunks_in_ranges<'a>(
20283 &'a self,
20284 ranges: &'a [Range<Anchor>],
20285 buffer: &'a MultiBufferSnapshot,
20286 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20287 ranges.iter().flat_map(move |range| {
20288 let end_excerpt_id = range.end.excerpt_id;
20289 let range = range.to_point(buffer);
20290 let mut peek_end = range.end;
20291 if range.end.row < buffer.max_row().0 {
20292 peek_end = Point::new(range.end.row + 1, 0);
20293 }
20294 buffer
20295 .diff_hunks_in_range(range.start..peek_end)
20296 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20297 })
20298 }
20299
20300 pub fn has_stageable_diff_hunks_in_ranges(
20301 &self,
20302 ranges: &[Range<Anchor>],
20303 snapshot: &MultiBufferSnapshot,
20304 ) -> bool {
20305 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20306 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20307 }
20308
20309 pub fn toggle_staged_selected_diff_hunks(
20310 &mut self,
20311 _: &::git::ToggleStaged,
20312 _: &mut Window,
20313 cx: &mut Context<Self>,
20314 ) {
20315 let snapshot = self.buffer.read(cx).snapshot(cx);
20316 let ranges: Vec<_> = self
20317 .selections
20318 .disjoint_anchors()
20319 .iter()
20320 .map(|s| s.range())
20321 .collect();
20322 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20323 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20324 }
20325
20326 pub fn set_render_diff_hunk_controls(
20327 &mut self,
20328 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20329 cx: &mut Context<Self>,
20330 ) {
20331 self.render_diff_hunk_controls = render_diff_hunk_controls;
20332 cx.notify();
20333 }
20334
20335 pub fn stage_and_next(
20336 &mut self,
20337 _: &::git::StageAndNext,
20338 window: &mut Window,
20339 cx: &mut Context<Self>,
20340 ) {
20341 self.do_stage_or_unstage_and_next(true, window, cx);
20342 }
20343
20344 pub fn unstage_and_next(
20345 &mut self,
20346 _: &::git::UnstageAndNext,
20347 window: &mut Window,
20348 cx: &mut Context<Self>,
20349 ) {
20350 self.do_stage_or_unstage_and_next(false, window, cx);
20351 }
20352
20353 pub fn stage_or_unstage_diff_hunks(
20354 &mut self,
20355 stage: bool,
20356 ranges: Vec<Range<Anchor>>,
20357 cx: &mut Context<Self>,
20358 ) {
20359 if self.delegate_stage_and_restore {
20360 let snapshot = self.buffer.read(cx).snapshot(cx);
20361 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20362 if !hunks.is_empty() {
20363 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20364 }
20365 return;
20366 }
20367 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20368 cx.spawn(async move |this, cx| {
20369 task.await?;
20370 this.update(cx, |this, cx| {
20371 let snapshot = this.buffer.read(cx).snapshot(cx);
20372 let chunk_by = this
20373 .diff_hunks_in_ranges(&ranges, &snapshot)
20374 .chunk_by(|hunk| hunk.buffer_id);
20375 for (buffer_id, hunks) in &chunk_by {
20376 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20377 }
20378 })
20379 })
20380 .detach_and_log_err(cx);
20381 }
20382
20383 fn save_buffers_for_ranges_if_needed(
20384 &mut self,
20385 ranges: &[Range<Anchor>],
20386 cx: &mut Context<Editor>,
20387 ) -> Task<Result<()>> {
20388 let multibuffer = self.buffer.read(cx);
20389 let snapshot = multibuffer.read(cx);
20390 let buffer_ids: HashSet<_> = ranges
20391 .iter()
20392 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20393 .collect();
20394 drop(snapshot);
20395
20396 let mut buffers = HashSet::default();
20397 for buffer_id in buffer_ids {
20398 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20399 let buffer = buffer_entity.read(cx);
20400 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20401 {
20402 buffers.insert(buffer_entity);
20403 }
20404 }
20405 }
20406
20407 if let Some(project) = &self.project {
20408 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20409 } else {
20410 Task::ready(Ok(()))
20411 }
20412 }
20413
20414 fn do_stage_or_unstage_and_next(
20415 &mut self,
20416 stage: bool,
20417 window: &mut Window,
20418 cx: &mut Context<Self>,
20419 ) {
20420 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20421
20422 if ranges.iter().any(|range| range.start != range.end) {
20423 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20424 return;
20425 }
20426
20427 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20428
20429 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20430 let wrap_around = !all_diff_hunks_expanded;
20431 let snapshot = self.snapshot(window, cx);
20432 let position = self
20433 .selections
20434 .newest::<Point>(&snapshot.display_snapshot)
20435 .head();
20436
20437 self.go_to_hunk_before_or_after_position(
20438 &snapshot,
20439 position,
20440 Direction::Next,
20441 wrap_around,
20442 window,
20443 cx,
20444 );
20445 }
20446
20447 pub(crate) fn do_stage_or_unstage(
20448 &self,
20449 stage: bool,
20450 buffer_id: BufferId,
20451 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20452 cx: &mut App,
20453 ) -> Option<()> {
20454 let project = self.project()?;
20455 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20456 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20457 let buffer_snapshot = buffer.read(cx).snapshot();
20458 let file_exists = buffer_snapshot
20459 .file()
20460 .is_some_and(|file| file.disk_state().exists());
20461 diff.update(cx, |diff, cx| {
20462 diff.stage_or_unstage_hunks(
20463 stage,
20464 &hunks
20465 .map(|hunk| buffer_diff::DiffHunk {
20466 buffer_range: hunk.buffer_range,
20467 // We don't need to pass in word diffs here because they're only used for rendering and
20468 // this function changes internal state
20469 base_word_diffs: Vec::default(),
20470 buffer_word_diffs: Vec::default(),
20471 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20472 ..hunk.diff_base_byte_range.end.0,
20473 secondary_status: hunk.status.secondary,
20474 range: Point::zero()..Point::zero(), // unused
20475 })
20476 .collect::<Vec<_>>(),
20477 &buffer_snapshot,
20478 file_exists,
20479 cx,
20480 )
20481 });
20482 None
20483 }
20484
20485 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20486 let ranges: Vec<_> = self
20487 .selections
20488 .disjoint_anchors()
20489 .iter()
20490 .map(|s| s.range())
20491 .collect();
20492 self.buffer
20493 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20494 }
20495
20496 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20497 self.buffer.update(cx, |buffer, cx| {
20498 let ranges = vec![Anchor::min()..Anchor::max()];
20499 if !buffer.all_diff_hunks_expanded()
20500 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20501 {
20502 buffer.collapse_diff_hunks(ranges, cx);
20503 true
20504 } else {
20505 false
20506 }
20507 })
20508 }
20509
20510 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20511 if self.buffer.read(cx).all_diff_hunks_expanded() {
20512 return true;
20513 }
20514 let ranges = vec![Anchor::min()..Anchor::max()];
20515 self.buffer
20516 .read(cx)
20517 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20518 }
20519
20520 fn toggle_diff_hunks_in_ranges(
20521 &mut self,
20522 ranges: Vec<Range<Anchor>>,
20523 cx: &mut Context<Editor>,
20524 ) {
20525 self.buffer.update(cx, |buffer, cx| {
20526 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20527 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20528 })
20529 }
20530
20531 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20532 self.buffer.update(cx, |buffer, cx| {
20533 buffer.toggle_single_diff_hunk(range, cx);
20534 })
20535 }
20536
20537 pub(crate) fn apply_all_diff_hunks(
20538 &mut self,
20539 _: &ApplyAllDiffHunks,
20540 window: &mut Window,
20541 cx: &mut Context<Self>,
20542 ) {
20543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20544
20545 let buffers = self.buffer.read(cx).all_buffers();
20546 for branch_buffer in buffers {
20547 branch_buffer.update(cx, |branch_buffer, cx| {
20548 branch_buffer.merge_into_base(Vec::new(), cx);
20549 });
20550 }
20551
20552 if let Some(project) = self.project.clone() {
20553 self.save(
20554 SaveOptions {
20555 format: true,
20556 autosave: false,
20557 },
20558 project,
20559 window,
20560 cx,
20561 )
20562 .detach_and_log_err(cx);
20563 }
20564 }
20565
20566 pub(crate) fn apply_selected_diff_hunks(
20567 &mut self,
20568 _: &ApplyDiffHunk,
20569 window: &mut Window,
20570 cx: &mut Context<Self>,
20571 ) {
20572 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20573 let snapshot = self.snapshot(window, cx);
20574 let hunks = snapshot.hunks_for_ranges(
20575 self.selections
20576 .all(&snapshot.display_snapshot)
20577 .into_iter()
20578 .map(|selection| selection.range()),
20579 );
20580 let mut ranges_by_buffer = HashMap::default();
20581 self.transact(window, cx, |editor, _window, cx| {
20582 for hunk in hunks {
20583 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20584 ranges_by_buffer
20585 .entry(buffer.clone())
20586 .or_insert_with(Vec::new)
20587 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20588 }
20589 }
20590
20591 for (buffer, ranges) in ranges_by_buffer {
20592 buffer.update(cx, |buffer, cx| {
20593 buffer.merge_into_base(ranges, cx);
20594 });
20595 }
20596 });
20597
20598 if let Some(project) = self.project.clone() {
20599 self.save(
20600 SaveOptions {
20601 format: true,
20602 autosave: false,
20603 },
20604 project,
20605 window,
20606 cx,
20607 )
20608 .detach_and_log_err(cx);
20609 }
20610 }
20611
20612 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20613 if hovered != self.gutter_hovered {
20614 self.gutter_hovered = hovered;
20615 cx.notify();
20616 }
20617 }
20618
20619 pub fn insert_blocks(
20620 &mut self,
20621 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20622 autoscroll: Option<Autoscroll>,
20623 cx: &mut Context<Self>,
20624 ) -> Vec<CustomBlockId> {
20625 let blocks = self
20626 .display_map
20627 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20628 if let Some(autoscroll) = autoscroll {
20629 self.request_autoscroll(autoscroll, cx);
20630 }
20631 cx.notify();
20632 blocks
20633 }
20634
20635 pub fn resize_blocks(
20636 &mut self,
20637 heights: HashMap<CustomBlockId, u32>,
20638 autoscroll: Option<Autoscroll>,
20639 cx: &mut Context<Self>,
20640 ) {
20641 self.display_map
20642 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20643 if let Some(autoscroll) = autoscroll {
20644 self.request_autoscroll(autoscroll, cx);
20645 }
20646 cx.notify();
20647 }
20648
20649 pub fn replace_blocks(
20650 &mut self,
20651 renderers: HashMap<CustomBlockId, RenderBlock>,
20652 autoscroll: Option<Autoscroll>,
20653 cx: &mut Context<Self>,
20654 ) {
20655 self.display_map
20656 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20657 if let Some(autoscroll) = autoscroll {
20658 self.request_autoscroll(autoscroll, cx);
20659 }
20660 cx.notify();
20661 }
20662
20663 pub fn remove_blocks(
20664 &mut self,
20665 block_ids: HashSet<CustomBlockId>,
20666 autoscroll: Option<Autoscroll>,
20667 cx: &mut Context<Self>,
20668 ) {
20669 self.display_map.update(cx, |display_map, cx| {
20670 display_map.remove_blocks(block_ids, cx)
20671 });
20672 if let Some(autoscroll) = autoscroll {
20673 self.request_autoscroll(autoscroll, cx);
20674 }
20675 cx.notify();
20676 }
20677
20678 pub fn row_for_block(
20679 &self,
20680 block_id: CustomBlockId,
20681 cx: &mut Context<Self>,
20682 ) -> Option<DisplayRow> {
20683 self.display_map
20684 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20685 }
20686
20687 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20688 self.focused_block = Some(focused_block);
20689 }
20690
20691 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20692 self.focused_block.take()
20693 }
20694
20695 pub fn insert_creases(
20696 &mut self,
20697 creases: impl IntoIterator<Item = Crease<Anchor>>,
20698 cx: &mut Context<Self>,
20699 ) -> Vec<CreaseId> {
20700 self.display_map
20701 .update(cx, |map, cx| map.insert_creases(creases, cx))
20702 }
20703
20704 pub fn remove_creases(
20705 &mut self,
20706 ids: impl IntoIterator<Item = CreaseId>,
20707 cx: &mut Context<Self>,
20708 ) -> Vec<(CreaseId, Range<Anchor>)> {
20709 self.display_map
20710 .update(cx, |map, cx| map.remove_creases(ids, cx))
20711 }
20712
20713 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20714 self.display_map
20715 .update(cx, |map, cx| map.snapshot(cx))
20716 .longest_row()
20717 }
20718
20719 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20720 self.display_map
20721 .update(cx, |map, cx| map.snapshot(cx))
20722 .max_point()
20723 }
20724
20725 pub fn text(&self, cx: &App) -> String {
20726 self.buffer.read(cx).read(cx).text()
20727 }
20728
20729 pub fn is_empty(&self, cx: &App) -> bool {
20730 self.buffer.read(cx).read(cx).is_empty()
20731 }
20732
20733 pub fn text_option(&self, cx: &App) -> Option<String> {
20734 let text = self.text(cx);
20735 let text = text.trim();
20736
20737 if text.is_empty() {
20738 return None;
20739 }
20740
20741 Some(text.to_string())
20742 }
20743
20744 pub fn set_text(
20745 &mut self,
20746 text: impl Into<Arc<str>>,
20747 window: &mut Window,
20748 cx: &mut Context<Self>,
20749 ) {
20750 self.transact(window, cx, |this, _, cx| {
20751 this.buffer
20752 .read(cx)
20753 .as_singleton()
20754 .expect("you can only call set_text on editors for singleton buffers")
20755 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20756 });
20757 }
20758
20759 pub fn display_text(&self, cx: &mut App) -> String {
20760 self.display_map
20761 .update(cx, |map, cx| map.snapshot(cx))
20762 .text()
20763 }
20764
20765 fn create_minimap(
20766 &self,
20767 minimap_settings: MinimapSettings,
20768 window: &mut Window,
20769 cx: &mut Context<Self>,
20770 ) -> Option<Entity<Self>> {
20771 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20772 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20773 }
20774
20775 fn initialize_new_minimap(
20776 &self,
20777 minimap_settings: MinimapSettings,
20778 window: &mut Window,
20779 cx: &mut Context<Self>,
20780 ) -> Entity<Self> {
20781 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20782 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20783
20784 let mut minimap = Editor::new_internal(
20785 EditorMode::Minimap {
20786 parent: cx.weak_entity(),
20787 },
20788 self.buffer.clone(),
20789 None,
20790 Some(self.display_map.clone()),
20791 window,
20792 cx,
20793 );
20794 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20795 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
20796 minimap.scroll_manager.clone_state(
20797 &self.scroll_manager,
20798 &my_snapshot,
20799 &minimap_snapshot,
20800 cx,
20801 );
20802 minimap.set_text_style_refinement(TextStyleRefinement {
20803 font_size: Some(MINIMAP_FONT_SIZE),
20804 font_weight: Some(MINIMAP_FONT_WEIGHT),
20805 font_family: Some(MINIMAP_FONT_FAMILY),
20806 ..Default::default()
20807 });
20808 minimap.update_minimap_configuration(minimap_settings, cx);
20809 cx.new(|_| minimap)
20810 }
20811
20812 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20813 let current_line_highlight = minimap_settings
20814 .current_line_highlight
20815 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20816 self.set_current_line_highlight(Some(current_line_highlight));
20817 }
20818
20819 pub fn minimap(&self) -> Option<&Entity<Self>> {
20820 self.minimap
20821 .as_ref()
20822 .filter(|_| self.minimap_visibility.visible())
20823 }
20824
20825 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20826 let mut wrap_guides = smallvec![];
20827
20828 if self.show_wrap_guides == Some(false) {
20829 return wrap_guides;
20830 }
20831
20832 let settings = self.buffer.read(cx).language_settings(cx);
20833 if settings.show_wrap_guides {
20834 match self.soft_wrap_mode(cx) {
20835 SoftWrap::Column(soft_wrap) => {
20836 wrap_guides.push((soft_wrap as usize, true));
20837 }
20838 SoftWrap::Bounded(soft_wrap) => {
20839 wrap_guides.push((soft_wrap as usize, true));
20840 }
20841 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20842 }
20843 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20844 }
20845
20846 wrap_guides
20847 }
20848
20849 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20850 let settings = self.buffer.read(cx).language_settings(cx);
20851 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20852 match mode {
20853 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20854 SoftWrap::None
20855 }
20856 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20857 language_settings::SoftWrap::PreferredLineLength => {
20858 SoftWrap::Column(settings.preferred_line_length)
20859 }
20860 language_settings::SoftWrap::Bounded => {
20861 SoftWrap::Bounded(settings.preferred_line_length)
20862 }
20863 }
20864 }
20865
20866 pub fn set_soft_wrap_mode(
20867 &mut self,
20868 mode: language_settings::SoftWrap,
20869 cx: &mut Context<Self>,
20870 ) {
20871 self.soft_wrap_mode_override = Some(mode);
20872 cx.notify();
20873 }
20874
20875 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20876 self.hard_wrap = hard_wrap;
20877 cx.notify();
20878 }
20879
20880 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20881 self.text_style_refinement = Some(style);
20882 }
20883
20884 /// called by the Element so we know what style we were most recently rendered with.
20885 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20886 // We intentionally do not inform the display map about the minimap style
20887 // so that wrapping is not recalculated and stays consistent for the editor
20888 // and its linked minimap.
20889 if !self.mode.is_minimap() {
20890 let font = style.text.font();
20891 let font_size = style.text.font_size.to_pixels(window.rem_size());
20892 let display_map = self
20893 .placeholder_display_map
20894 .as_ref()
20895 .filter(|_| self.is_empty(cx))
20896 .unwrap_or(&self.display_map);
20897
20898 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20899 }
20900 self.style = Some(style);
20901 }
20902
20903 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20904 if self.style.is_none() {
20905 self.style = Some(self.create_style(cx));
20906 }
20907 self.style.as_ref().unwrap()
20908 }
20909
20910 // Called by the element. This method is not designed to be called outside of the editor
20911 // element's layout code because it does not notify when rewrapping is computed synchronously.
20912 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20913 if self.is_empty(cx) {
20914 self.placeholder_display_map
20915 .as_ref()
20916 .map_or(false, |display_map| {
20917 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20918 })
20919 } else {
20920 self.display_map
20921 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20922 }
20923 }
20924
20925 pub fn set_soft_wrap(&mut self) {
20926 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20927 }
20928
20929 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20930 if self.soft_wrap_mode_override.is_some() {
20931 self.soft_wrap_mode_override.take();
20932 } else {
20933 let soft_wrap = match self.soft_wrap_mode(cx) {
20934 SoftWrap::GitDiff => return,
20935 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20936 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20937 language_settings::SoftWrap::None
20938 }
20939 };
20940 self.soft_wrap_mode_override = Some(soft_wrap);
20941 }
20942 cx.notify();
20943 }
20944
20945 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20946 let Some(workspace) = self.workspace() else {
20947 return;
20948 };
20949 let fs = workspace.read(cx).app_state().fs.clone();
20950 let current_show = TabBarSettings::get_global(cx).show;
20951 update_settings_file(fs, cx, move |setting, _| {
20952 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20953 });
20954 }
20955
20956 pub fn toggle_indent_guides(
20957 &mut self,
20958 _: &ToggleIndentGuides,
20959 _: &mut Window,
20960 cx: &mut Context<Self>,
20961 ) {
20962 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20963 self.buffer
20964 .read(cx)
20965 .language_settings(cx)
20966 .indent_guides
20967 .enabled
20968 });
20969 self.show_indent_guides = Some(!currently_enabled);
20970 cx.notify();
20971 }
20972
20973 fn should_show_indent_guides(&self) -> Option<bool> {
20974 self.show_indent_guides
20975 }
20976
20977 pub fn disable_indent_guides_for_buffer(
20978 &mut self,
20979 buffer_id: BufferId,
20980 cx: &mut Context<Self>,
20981 ) {
20982 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20983 cx.notify();
20984 }
20985
20986 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20987 self.buffers_with_disabled_indent_guides
20988 .contains(&buffer_id)
20989 }
20990
20991 pub fn toggle_line_numbers(
20992 &mut self,
20993 _: &ToggleLineNumbers,
20994 _: &mut Window,
20995 cx: &mut Context<Self>,
20996 ) {
20997 let mut editor_settings = EditorSettings::get_global(cx).clone();
20998 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20999 EditorSettings::override_global(editor_settings, cx);
21000 }
21001
21002 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21003 if let Some(show_line_numbers) = self.show_line_numbers {
21004 return show_line_numbers;
21005 }
21006 EditorSettings::get_global(cx).gutter.line_numbers
21007 }
21008
21009 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21010 match (
21011 self.use_relative_line_numbers,
21012 EditorSettings::get_global(cx).relative_line_numbers,
21013 ) {
21014 (None, setting) => setting,
21015 (Some(false), _) => RelativeLineNumbers::Disabled,
21016 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21017 (Some(true), _) => RelativeLineNumbers::Enabled,
21018 }
21019 }
21020
21021 pub fn toggle_relative_line_numbers(
21022 &mut self,
21023 _: &ToggleRelativeLineNumbers,
21024 _: &mut Window,
21025 cx: &mut Context<Self>,
21026 ) {
21027 let is_relative = self.relative_line_numbers(cx);
21028 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21029 }
21030
21031 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21032 self.use_relative_line_numbers = is_relative;
21033 cx.notify();
21034 }
21035
21036 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21037 self.show_gutter = show_gutter;
21038 cx.notify();
21039 }
21040
21041 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21042 self.show_scrollbars = ScrollbarAxes {
21043 horizontal: show,
21044 vertical: show,
21045 };
21046 cx.notify();
21047 }
21048
21049 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21050 self.show_scrollbars.vertical = show;
21051 cx.notify();
21052 }
21053
21054 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21055 self.show_scrollbars.horizontal = show;
21056 cx.notify();
21057 }
21058
21059 pub fn set_minimap_visibility(
21060 &mut self,
21061 minimap_visibility: MinimapVisibility,
21062 window: &mut Window,
21063 cx: &mut Context<Self>,
21064 ) {
21065 if self.minimap_visibility != minimap_visibility {
21066 if minimap_visibility.visible() && self.minimap.is_none() {
21067 let minimap_settings = EditorSettings::get_global(cx).minimap;
21068 self.minimap =
21069 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21070 }
21071 self.minimap_visibility = minimap_visibility;
21072 cx.notify();
21073 }
21074 }
21075
21076 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21077 self.set_show_scrollbars(false, cx);
21078 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21079 }
21080
21081 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21082 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21083 }
21084
21085 /// Normally the text in full mode and auto height editors is padded on the
21086 /// left side by roughly half a character width for improved hit testing.
21087 ///
21088 /// Use this method to disable this for cases where this is not wanted (e.g.
21089 /// if you want to align the editor text with some other text above or below)
21090 /// or if you want to add this padding to single-line editors.
21091 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21092 self.offset_content = offset_content;
21093 cx.notify();
21094 }
21095
21096 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21097 self.show_line_numbers = Some(show_line_numbers);
21098 cx.notify();
21099 }
21100
21101 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21102 self.disable_expand_excerpt_buttons = true;
21103 cx.notify();
21104 }
21105
21106 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21107 self.number_deleted_lines = number;
21108 cx.notify();
21109 }
21110
21111 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21112 self.delegate_expand_excerpts = delegate;
21113 }
21114
21115 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21116 self.delegate_stage_and_restore = delegate;
21117 }
21118
21119 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21120 self.delegate_open_excerpts = delegate;
21121 }
21122
21123 pub fn set_on_local_selections_changed(
21124 &mut self,
21125 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21126 ) {
21127 self.on_local_selections_changed = callback;
21128 }
21129
21130 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21131 self.suppress_selection_callback = suppress;
21132 }
21133
21134 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21135 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21136 cx.notify();
21137 }
21138
21139 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21140 self.show_code_actions = Some(show_code_actions);
21141 cx.notify();
21142 }
21143
21144 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21145 self.show_runnables = Some(show_runnables);
21146 cx.notify();
21147 }
21148
21149 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21150 self.show_breakpoints = Some(show_breakpoints);
21151 cx.notify();
21152 }
21153
21154 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21155 self.show_diff_review_button = show;
21156 cx.notify();
21157 }
21158
21159 pub fn show_diff_review_button(&self) -> bool {
21160 self.show_diff_review_button
21161 }
21162
21163 pub fn render_diff_review_button(
21164 &self,
21165 display_row: DisplayRow,
21166 width: Pixels,
21167 cx: &mut Context<Self>,
21168 ) -> impl IntoElement {
21169 let text_color = cx.theme().colors().text;
21170 let icon_color = cx.theme().colors().icon_accent;
21171
21172 h_flex()
21173 .id("diff_review_button")
21174 .cursor_pointer()
21175 .w(width - px(1.))
21176 .h(relative(0.9))
21177 .justify_center()
21178 .rounded_sm()
21179 .border_1()
21180 .border_color(text_color.opacity(0.1))
21181 .bg(text_color.opacity(0.15))
21182 .hover(|s| {
21183 s.bg(icon_color.opacity(0.4))
21184 .border_color(icon_color.opacity(0.5))
21185 })
21186 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21187 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21188 .on_mouse_down(
21189 gpui::MouseButton::Left,
21190 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21191 editor.start_diff_review_drag(display_row, window, cx);
21192 }),
21193 )
21194 }
21195
21196 pub fn start_diff_review_drag(
21197 &mut self,
21198 display_row: DisplayRow,
21199 window: &mut Window,
21200 cx: &mut Context<Self>,
21201 ) {
21202 let snapshot = self.snapshot(window, cx);
21203 let point = snapshot
21204 .display_snapshot
21205 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21206 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21207 self.diff_review_drag_state = Some(DiffReviewDragState {
21208 start_anchor: anchor,
21209 current_anchor: anchor,
21210 });
21211 cx.notify();
21212 }
21213
21214 pub fn update_diff_review_drag(
21215 &mut self,
21216 display_row: DisplayRow,
21217 window: &mut Window,
21218 cx: &mut Context<Self>,
21219 ) {
21220 if self.diff_review_drag_state.is_none() {
21221 return;
21222 }
21223 let snapshot = self.snapshot(window, cx);
21224 let point = snapshot
21225 .display_snapshot
21226 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21227 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21228 if let Some(drag_state) = &mut self.diff_review_drag_state {
21229 drag_state.current_anchor = anchor;
21230 cx.notify();
21231 }
21232 }
21233
21234 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21235 if let Some(drag_state) = self.diff_review_drag_state.take() {
21236 let snapshot = self.snapshot(window, cx);
21237 let range = drag_state.row_range(&snapshot.display_snapshot);
21238 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21239 }
21240 cx.notify();
21241 }
21242
21243 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21244 self.diff_review_drag_state = None;
21245 cx.notify();
21246 }
21247
21248 /// Calculates the appropriate block height for the diff review overlay.
21249 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21250 /// and 2 lines per comment when expanded.
21251 fn calculate_overlay_height(
21252 &self,
21253 hunk_key: &DiffHunkKey,
21254 comments_expanded: bool,
21255 snapshot: &MultiBufferSnapshot,
21256 ) -> u32 {
21257 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21258 let base_height: u32 = 2; // Input row with avatar and buttons
21259
21260 if comment_count == 0 {
21261 base_height
21262 } else if comments_expanded {
21263 // Header (1 line) + 2 lines per comment
21264 base_height + 1 + (comment_count as u32 * 2)
21265 } else {
21266 // Just header when collapsed
21267 base_height + 1
21268 }
21269 }
21270
21271 pub fn show_diff_review_overlay(
21272 &mut self,
21273 display_range: Range<DisplayRow>,
21274 window: &mut Window,
21275 cx: &mut Context<Self>,
21276 ) {
21277 let Range { start, end } = display_range.sorted();
21278
21279 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21280 let editor_snapshot = self.snapshot(window, cx);
21281
21282 // Convert display rows to multibuffer points
21283 let start_point = editor_snapshot
21284 .display_snapshot
21285 .display_point_to_point(start.as_display_point(), Bias::Left);
21286 let end_point = editor_snapshot
21287 .display_snapshot
21288 .display_point_to_point(end.as_display_point(), Bias::Left);
21289 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21290
21291 // Create anchor range for the selected lines (start of first line to end of last line)
21292 let line_end = Point::new(
21293 end_point.row,
21294 buffer_snapshot.line_len(end_multi_buffer_row),
21295 );
21296 let anchor_range =
21297 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21298
21299 // Compute the hunk key for this display row
21300 let file_path = buffer_snapshot
21301 .file_at(start_point)
21302 .map(|file: &Arc<dyn language::File>| file.path().clone())
21303 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21304 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21305 let new_hunk_key = DiffHunkKey {
21306 file_path,
21307 hunk_start_anchor,
21308 };
21309
21310 // Check if we already have an overlay for this hunk
21311 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21312 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21313 }) {
21314 // Just focus the existing overlay's prompt editor
21315 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21316 window.focus(&focus_handle, cx);
21317 return;
21318 }
21319
21320 // Dismiss overlays that have no comments for their hunks
21321 self.dismiss_overlays_without_comments(cx);
21322
21323 // Get the current user's avatar URI from the project's user_store
21324 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21325 let user_store = project.read(cx).user_store();
21326 user_store
21327 .read(cx)
21328 .current_user()
21329 .map(|user| user.avatar_uri.clone())
21330 });
21331
21332 // Create anchor at the end of the last row so the block appears immediately below it
21333 // Use multibuffer coordinates for anchor creation
21334 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21335 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21336
21337 // Use the hunk key we already computed
21338 let hunk_key = new_hunk_key;
21339
21340 // Create the prompt editor for the review input
21341 let prompt_editor = cx.new(|cx| {
21342 let mut editor = Editor::single_line(window, cx);
21343 editor.set_placeholder_text("Add a review comment...", window, cx);
21344 editor
21345 });
21346
21347 // Register the Newline action on the prompt editor to submit the review
21348 let parent_editor = cx.entity().downgrade();
21349 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21350 prompt_editor.register_action({
21351 let parent_editor = parent_editor.clone();
21352 move |_: &crate::actions::Newline, window, cx| {
21353 if let Some(editor) = parent_editor.upgrade() {
21354 editor.update(cx, |editor, cx| {
21355 editor.submit_diff_review_comment(window, cx);
21356 });
21357 }
21358 }
21359 })
21360 });
21361
21362 // Calculate initial height based on existing comments for this hunk
21363 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21364
21365 // Create the overlay block
21366 let prompt_editor_for_render = prompt_editor.clone();
21367 let hunk_key_for_render = hunk_key.clone();
21368 let editor_handle = cx.entity().downgrade();
21369 let block = BlockProperties {
21370 style: BlockStyle::Sticky,
21371 placement: BlockPlacement::Below(anchor),
21372 height: Some(initial_height),
21373 render: Arc::new(move |cx| {
21374 Self::render_diff_review_overlay(
21375 &prompt_editor_for_render,
21376 &hunk_key_for_render,
21377 &editor_handle,
21378 cx,
21379 )
21380 }),
21381 priority: 0,
21382 };
21383
21384 let block_ids = self.insert_blocks([block], None, cx);
21385 let Some(block_id) = block_ids.into_iter().next() else {
21386 log::error!("Failed to insert diff review overlay block");
21387 return;
21388 };
21389
21390 self.diff_review_overlays.push(DiffReviewOverlay {
21391 anchor_range,
21392 block_id,
21393 prompt_editor: prompt_editor.clone(),
21394 hunk_key,
21395 comments_expanded: true,
21396 inline_edit_editors: HashMap::default(),
21397 inline_edit_subscriptions: HashMap::default(),
21398 user_avatar_uri,
21399 _subscription: subscription,
21400 });
21401
21402 // Focus the prompt editor
21403 let focus_handle = prompt_editor.focus_handle(cx);
21404 window.focus(&focus_handle, cx);
21405
21406 cx.notify();
21407 }
21408
21409 /// Dismisses all diff review overlays.
21410 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21411 if self.diff_review_overlays.is_empty() {
21412 return;
21413 }
21414 let block_ids: HashSet<_> = self
21415 .diff_review_overlays
21416 .drain(..)
21417 .map(|overlay| overlay.block_id)
21418 .collect();
21419 self.remove_blocks(block_ids, None, cx);
21420 cx.notify();
21421 }
21422
21423 /// Dismisses overlays that have no comments stored for their hunks.
21424 /// Keeps overlays that have at least one comment.
21425 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21426 let snapshot = self.buffer.read(cx).snapshot(cx);
21427
21428 // First, compute which overlays have comments (to avoid borrow issues with retain)
21429 let overlays_with_comments: Vec<bool> = self
21430 .diff_review_overlays
21431 .iter()
21432 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21433 .collect();
21434
21435 // Now collect block IDs to remove and retain overlays
21436 let mut block_ids_to_remove = HashSet::default();
21437 let mut index = 0;
21438 self.diff_review_overlays.retain(|overlay| {
21439 let has_comments = overlays_with_comments[index];
21440 index += 1;
21441 if !has_comments {
21442 block_ids_to_remove.insert(overlay.block_id);
21443 }
21444 has_comments
21445 });
21446
21447 if !block_ids_to_remove.is_empty() {
21448 self.remove_blocks(block_ids_to_remove, None, cx);
21449 cx.notify();
21450 }
21451 }
21452
21453 /// Refreshes the diff review overlay block to update its height and render function.
21454 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21455 fn refresh_diff_review_overlay_height(
21456 &mut self,
21457 hunk_key: &DiffHunkKey,
21458 _window: &mut Window,
21459 cx: &mut Context<Self>,
21460 ) {
21461 // Extract all needed data from overlay first to avoid borrow conflicts
21462 let snapshot = self.buffer.read(cx).snapshot(cx);
21463 let (comments_expanded, block_id, prompt_editor) = {
21464 let Some(overlay) = self
21465 .diff_review_overlays
21466 .iter()
21467 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21468 else {
21469 return;
21470 };
21471
21472 (
21473 overlay.comments_expanded,
21474 overlay.block_id,
21475 overlay.prompt_editor.clone(),
21476 )
21477 };
21478
21479 // Calculate new height
21480 let snapshot = self.buffer.read(cx).snapshot(cx);
21481 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21482
21483 // Update the block height using resize_blocks (avoids flicker)
21484 let mut heights = HashMap::default();
21485 heights.insert(block_id, new_height);
21486 self.resize_blocks(heights, None, cx);
21487
21488 // Update the render function using replace_blocks (avoids flicker)
21489 let hunk_key_for_render = hunk_key.clone();
21490 let editor_handle = cx.entity().downgrade();
21491 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21492 Arc::new(move |cx| {
21493 Self::render_diff_review_overlay(
21494 &prompt_editor,
21495 &hunk_key_for_render,
21496 &editor_handle,
21497 cx,
21498 )
21499 });
21500
21501 let mut renderers = HashMap::default();
21502 renderers.insert(block_id, render);
21503 self.replace_blocks(renderers, None, cx);
21504 }
21505
21506 /// Action handler for SubmitDiffReviewComment.
21507 pub fn submit_diff_review_comment_action(
21508 &mut self,
21509 _: &SubmitDiffReviewComment,
21510 window: &mut Window,
21511 cx: &mut Context<Self>,
21512 ) {
21513 self.submit_diff_review_comment(window, cx);
21514 }
21515
21516 /// Stores the diff review comment locally.
21517 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21518 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21519 // Find the overlay that currently has focus
21520 let overlay_index = self
21521 .diff_review_overlays
21522 .iter()
21523 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21524 let Some(overlay_index) = overlay_index else {
21525 return;
21526 };
21527 let overlay = &self.diff_review_overlays[overlay_index];
21528
21529 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21530 if comment_text.is_empty() {
21531 return;
21532 }
21533
21534 let anchor_range = overlay.anchor_range.clone();
21535 let hunk_key = overlay.hunk_key.clone();
21536
21537 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21538
21539 // Clear the prompt editor but keep the overlay open
21540 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21541 overlay.prompt_editor.update(cx, |editor, cx| {
21542 editor.clear(window, cx);
21543 });
21544 }
21545
21546 // Refresh the overlay to update the block height for the new comment
21547 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21548
21549 cx.notify();
21550 }
21551
21552 /// Returns the prompt editor for the diff review overlay, if one is active.
21553 /// This is primarily used for testing.
21554 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21555 self.diff_review_overlays
21556 .first()
21557 .map(|overlay| &overlay.prompt_editor)
21558 }
21559
21560 /// Returns the line range for the first diff review overlay, if one is active.
21561 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21562 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21563 let overlay = self.diff_review_overlays.first()?;
21564 let snapshot = self.buffer.read(cx).snapshot(cx);
21565 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21566 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21567 let start_row = snapshot
21568 .point_to_buffer_point(start_point)
21569 .map(|(_, p, _)| p.row)
21570 .unwrap_or(start_point.row);
21571 let end_row = snapshot
21572 .point_to_buffer_point(end_point)
21573 .map(|(_, p, _)| p.row)
21574 .unwrap_or(end_point.row);
21575 Some((start_row, end_row))
21576 }
21577
21578 /// Sets whether the comments section is expanded in the diff review overlay.
21579 /// This is primarily used for testing.
21580 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21581 for overlay in &mut self.diff_review_overlays {
21582 overlay.comments_expanded = expanded;
21583 }
21584 cx.notify();
21585 }
21586
21587 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21588 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21589 a.file_path == b.file_path
21590 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21591 }
21592
21593 /// Returns comments for a specific hunk, ordered by creation time.
21594 pub fn comments_for_hunk<'a>(
21595 &'a self,
21596 key: &DiffHunkKey,
21597 snapshot: &MultiBufferSnapshot,
21598 ) -> &'a [StoredReviewComment] {
21599 let key_point = key.hunk_start_anchor.to_point(snapshot);
21600 self.stored_review_comments
21601 .iter()
21602 .find(|(k, _)| {
21603 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21604 })
21605 .map(|(_, comments)| comments.as_slice())
21606 .unwrap_or(&[])
21607 }
21608
21609 /// Returns the total count of stored review comments across all hunks.
21610 pub fn total_review_comment_count(&self) -> usize {
21611 self.stored_review_comments
21612 .iter()
21613 .map(|(_, v)| v.len())
21614 .sum()
21615 }
21616
21617 /// Returns the count of comments for a specific hunk.
21618 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21619 let key_point = key.hunk_start_anchor.to_point(snapshot);
21620 self.stored_review_comments
21621 .iter()
21622 .find(|(k, _)| {
21623 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21624 })
21625 .map(|(_, v)| v.len())
21626 .unwrap_or(0)
21627 }
21628
21629 /// Adds a new review comment to a specific hunk.
21630 pub fn add_review_comment(
21631 &mut self,
21632 hunk_key: DiffHunkKey,
21633 comment: String,
21634 anchor_range: Range<Anchor>,
21635 cx: &mut Context<Self>,
21636 ) -> usize {
21637 let id = self.next_review_comment_id;
21638 self.next_review_comment_id += 1;
21639
21640 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21641
21642 let snapshot = self.buffer.read(cx).snapshot(cx);
21643 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21644
21645 // Find existing entry for this hunk or add a new one
21646 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21647 k.file_path == hunk_key.file_path
21648 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21649 }) {
21650 comments.push(stored_comment);
21651 } else {
21652 self.stored_review_comments
21653 .push((hunk_key, vec![stored_comment]));
21654 }
21655
21656 cx.emit(EditorEvent::ReviewCommentsChanged {
21657 total_count: self.total_review_comment_count(),
21658 });
21659 cx.notify();
21660 id
21661 }
21662
21663 /// Removes a review comment by ID from any hunk.
21664 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21665 for (_, comments) in self.stored_review_comments.iter_mut() {
21666 if let Some(index) = comments.iter().position(|c| c.id == id) {
21667 comments.remove(index);
21668 cx.emit(EditorEvent::ReviewCommentsChanged {
21669 total_count: self.total_review_comment_count(),
21670 });
21671 cx.notify();
21672 return true;
21673 }
21674 }
21675 false
21676 }
21677
21678 /// Updates a review comment's text by ID.
21679 pub fn update_review_comment(
21680 &mut self,
21681 id: usize,
21682 new_comment: String,
21683 cx: &mut Context<Self>,
21684 ) -> bool {
21685 for (_, comments) in self.stored_review_comments.iter_mut() {
21686 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21687 comment.comment = new_comment;
21688 comment.is_editing = false;
21689 cx.emit(EditorEvent::ReviewCommentsChanged {
21690 total_count: self.total_review_comment_count(),
21691 });
21692 cx.notify();
21693 return true;
21694 }
21695 }
21696 false
21697 }
21698
21699 /// Sets a comment's editing state.
21700 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21701 for (_, comments) in self.stored_review_comments.iter_mut() {
21702 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21703 comment.is_editing = is_editing;
21704 cx.notify();
21705 return;
21706 }
21707 }
21708 }
21709
21710 /// Takes all stored comments from all hunks, clearing the storage.
21711 /// Returns a Vec of (hunk_key, comments) pairs.
21712 pub fn take_all_review_comments(
21713 &mut self,
21714 cx: &mut Context<Self>,
21715 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21716 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21717 self.dismiss_all_diff_review_overlays(cx);
21718 let comments = std::mem::take(&mut self.stored_review_comments);
21719 // Reset the ID counter since all comments have been taken
21720 self.next_review_comment_id = 0;
21721 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21722 cx.notify();
21723 comments
21724 }
21725
21726 /// Removes review comments whose anchors are no longer valid or whose
21727 /// associated diff hunks no longer exist.
21728 ///
21729 /// This should be called when the buffer changes to prevent orphaned comments
21730 /// from accumulating.
21731 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21732 let snapshot = self.buffer.read(cx).snapshot(cx);
21733 let original_count = self.total_review_comment_count();
21734
21735 // Remove comments with invalid hunk anchors
21736 self.stored_review_comments
21737 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21738
21739 // Also clean up individual comments with invalid anchor ranges
21740 for (_, comments) in &mut self.stored_review_comments {
21741 comments.retain(|comment| {
21742 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
21743 });
21744 }
21745
21746 // Remove empty hunk entries
21747 self.stored_review_comments
21748 .retain(|(_, comments)| !comments.is_empty());
21749
21750 let new_count = self.total_review_comment_count();
21751 if new_count != original_count {
21752 cx.emit(EditorEvent::ReviewCommentsChanged {
21753 total_count: new_count,
21754 });
21755 cx.notify();
21756 }
21757 }
21758
21759 /// Toggles the expanded state of the comments section in the overlay.
21760 pub fn toggle_review_comments_expanded(
21761 &mut self,
21762 _: &ToggleReviewCommentsExpanded,
21763 window: &mut Window,
21764 cx: &mut Context<Self>,
21765 ) {
21766 // Find the overlay that currently has focus, or use the first one
21767 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21768 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21769 overlay.comments_expanded = !overlay.comments_expanded;
21770 Some(overlay.hunk_key.clone())
21771 } else {
21772 None
21773 }
21774 });
21775
21776 // If no focused overlay found, toggle the first one
21777 let hunk_key = overlay_info.or_else(|| {
21778 self.diff_review_overlays.first_mut().map(|overlay| {
21779 overlay.comments_expanded = !overlay.comments_expanded;
21780 overlay.hunk_key.clone()
21781 })
21782 });
21783
21784 if let Some(hunk_key) = hunk_key {
21785 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21786 cx.notify();
21787 }
21788 }
21789
21790 /// Handles the EditReviewComment action - sets a comment into editing mode.
21791 pub fn edit_review_comment(
21792 &mut self,
21793 action: &EditReviewComment,
21794 window: &mut Window,
21795 cx: &mut Context<Self>,
21796 ) {
21797 let comment_id = action.id;
21798
21799 // Set the comment to editing mode
21800 self.set_comment_editing(comment_id, true, cx);
21801
21802 // Find the overlay that contains this comment and create an inline editor if needed
21803 // First, find which hunk this comment belongs to
21804 let hunk_key = self
21805 .stored_review_comments
21806 .iter()
21807 .find_map(|(key, comments)| {
21808 if comments.iter().any(|c| c.id == comment_id) {
21809 Some(key.clone())
21810 } else {
21811 None
21812 }
21813 });
21814
21815 let snapshot = self.buffer.read(cx).snapshot(cx);
21816 if let Some(hunk_key) = hunk_key {
21817 if let Some(overlay) = self
21818 .diff_review_overlays
21819 .iter_mut()
21820 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21821 {
21822 if let std::collections::hash_map::Entry::Vacant(entry) =
21823 overlay.inline_edit_editors.entry(comment_id)
21824 {
21825 // Find the comment text
21826 let comment_text = self
21827 .stored_review_comments
21828 .iter()
21829 .flat_map(|(_, comments)| comments)
21830 .find(|c| c.id == comment_id)
21831 .map(|c| c.comment.clone())
21832 .unwrap_or_default();
21833
21834 // Create inline editor
21835 let parent_editor = cx.entity().downgrade();
21836 let inline_editor = cx.new(|cx| {
21837 let mut editor = Editor::single_line(window, cx);
21838 editor.set_text(&*comment_text, window, cx);
21839 // Select all text for easy replacement
21840 editor.select_all(&crate::actions::SelectAll, window, cx);
21841 editor
21842 });
21843
21844 // Register the Newline action to confirm the edit
21845 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
21846 inline_editor.register_action({
21847 let parent_editor = parent_editor.clone();
21848 move |_: &crate::actions::Newline, window, cx| {
21849 if let Some(editor) = parent_editor.upgrade() {
21850 editor.update(cx, |editor, cx| {
21851 editor.confirm_edit_review_comment(comment_id, window, cx);
21852 });
21853 }
21854 }
21855 })
21856 });
21857
21858 // Store the subscription to keep the action handler alive
21859 overlay
21860 .inline_edit_subscriptions
21861 .insert(comment_id, subscription);
21862
21863 // Focus the inline editor
21864 let focus_handle = inline_editor.focus_handle(cx);
21865 window.focus(&focus_handle, cx);
21866
21867 entry.insert(inline_editor);
21868 }
21869 }
21870 }
21871
21872 cx.notify();
21873 }
21874
21875 /// Confirms an inline edit of a review comment.
21876 pub fn confirm_edit_review_comment(
21877 &mut self,
21878 comment_id: usize,
21879 _window: &mut Window,
21880 cx: &mut Context<Self>,
21881 ) {
21882 // Get the new text from the inline editor
21883 // Find the overlay containing this comment's inline editor
21884 let snapshot = self.buffer.read(cx).snapshot(cx);
21885 let hunk_key = self
21886 .stored_review_comments
21887 .iter()
21888 .find_map(|(key, comments)| {
21889 if comments.iter().any(|c| c.id == comment_id) {
21890 Some(key.clone())
21891 } else {
21892 None
21893 }
21894 });
21895
21896 let new_text = hunk_key
21897 .as_ref()
21898 .and_then(|hunk_key| {
21899 self.diff_review_overlays
21900 .iter()
21901 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21902 })
21903 .as_ref()
21904 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
21905 .map(|editor| editor.read(cx).text(cx).trim().to_string());
21906
21907 if let Some(new_text) = new_text {
21908 if !new_text.is_empty() {
21909 self.update_review_comment(comment_id, new_text, cx);
21910 }
21911 }
21912
21913 // Remove the inline editor and its subscription
21914 if let Some(hunk_key) = hunk_key {
21915 if let Some(overlay) = self
21916 .diff_review_overlays
21917 .iter_mut()
21918 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21919 {
21920 overlay.inline_edit_editors.remove(&comment_id);
21921 overlay.inline_edit_subscriptions.remove(&comment_id);
21922 }
21923 }
21924
21925 // Clear editing state
21926 self.set_comment_editing(comment_id, false, cx);
21927 }
21928
21929 /// Cancels an inline edit of a review comment.
21930 pub fn cancel_edit_review_comment(
21931 &mut self,
21932 comment_id: usize,
21933 _window: &mut Window,
21934 cx: &mut Context<Self>,
21935 ) {
21936 // Find which hunk this comment belongs to
21937 let hunk_key = self
21938 .stored_review_comments
21939 .iter()
21940 .find_map(|(key, comments)| {
21941 if comments.iter().any(|c| c.id == comment_id) {
21942 Some(key.clone())
21943 } else {
21944 None
21945 }
21946 });
21947
21948 // Remove the inline editor and its subscription
21949 if let Some(hunk_key) = hunk_key {
21950 let snapshot = self.buffer.read(cx).snapshot(cx);
21951 if let Some(overlay) = self
21952 .diff_review_overlays
21953 .iter_mut()
21954 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21955 {
21956 overlay.inline_edit_editors.remove(&comment_id);
21957 overlay.inline_edit_subscriptions.remove(&comment_id);
21958 }
21959 }
21960
21961 // Clear editing state
21962 self.set_comment_editing(comment_id, false, cx);
21963 }
21964
21965 /// Action handler for ConfirmEditReviewComment.
21966 pub fn confirm_edit_review_comment_action(
21967 &mut self,
21968 action: &ConfirmEditReviewComment,
21969 window: &mut Window,
21970 cx: &mut Context<Self>,
21971 ) {
21972 self.confirm_edit_review_comment(action.id, window, cx);
21973 }
21974
21975 /// Action handler for CancelEditReviewComment.
21976 pub fn cancel_edit_review_comment_action(
21977 &mut self,
21978 action: &CancelEditReviewComment,
21979 window: &mut Window,
21980 cx: &mut Context<Self>,
21981 ) {
21982 self.cancel_edit_review_comment(action.id, window, cx);
21983 }
21984
21985 /// Handles the DeleteReviewComment action - removes a comment.
21986 pub fn delete_review_comment(
21987 &mut self,
21988 action: &DeleteReviewComment,
21989 window: &mut Window,
21990 cx: &mut Context<Self>,
21991 ) {
21992 // Get the hunk key before removing the comment
21993 // Find the hunk key from the comment itself
21994 let comment_id = action.id;
21995 let hunk_key = self
21996 .stored_review_comments
21997 .iter()
21998 .find_map(|(key, comments)| {
21999 if comments.iter().any(|c| c.id == comment_id) {
22000 Some(key.clone())
22001 } else {
22002 None
22003 }
22004 });
22005
22006 // Also get it from the overlay for refresh purposes
22007 let overlay_hunk_key = self
22008 .diff_review_overlays
22009 .first()
22010 .map(|o| o.hunk_key.clone());
22011
22012 self.remove_review_comment(action.id, cx);
22013
22014 // Refresh the overlay height after removing a comment
22015 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22016 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22017 }
22018 }
22019
22020 fn render_diff_review_overlay(
22021 prompt_editor: &Entity<Editor>,
22022 hunk_key: &DiffHunkKey,
22023 editor_handle: &WeakEntity<Editor>,
22024 cx: &mut BlockContext,
22025 ) -> AnyElement {
22026 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22027 if ranges.is_empty() {
22028 return None;
22029 }
22030 let formatted: Vec<String> = ranges
22031 .iter()
22032 .map(|(start, end)| {
22033 let start_line = start + 1;
22034 let end_line = end + 1;
22035 if start_line == end_line {
22036 format!("Line {start_line}")
22037 } else {
22038 format!("Lines {start_line}-{end_line}")
22039 }
22040 })
22041 .collect();
22042 // Don't show label for single line in single excerpt
22043 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22044 return None;
22045 }
22046 Some(formatted.join(" ⋯ "))
22047 }
22048
22049 let theme = cx.theme();
22050 let colors = theme.colors();
22051
22052 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22053 editor_handle
22054 .upgrade()
22055 .map(|editor| {
22056 let editor = editor.read(cx);
22057 let snapshot = editor.buffer().read(cx).snapshot(cx);
22058 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22059 let (expanded, editors, avatar_uri, line_ranges) = editor
22060 .diff_review_overlays
22061 .iter()
22062 .find(|overlay| {
22063 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22064 })
22065 .map(|o| {
22066 let start_point = o.anchor_range.start.to_point(&snapshot);
22067 let end_point = o.anchor_range.end.to_point(&snapshot);
22068 // Get line ranges per excerpt to detect discontinuities
22069 let buffer_ranges =
22070 snapshot.range_to_buffer_ranges(start_point..end_point);
22071 let ranges: Vec<(u32, u32)> = buffer_ranges
22072 .iter()
22073 .map(|(buffer, range, _)| {
22074 let start = buffer.offset_to_point(range.start.0).row;
22075 let end = buffer.offset_to_point(range.end.0).row;
22076 (start, end)
22077 })
22078 .collect();
22079 (
22080 o.comments_expanded,
22081 o.inline_edit_editors.clone(),
22082 o.user_avatar_uri.clone(),
22083 if ranges.is_empty() {
22084 None
22085 } else {
22086 Some(ranges)
22087 },
22088 )
22089 })
22090 .unwrap_or((true, HashMap::default(), None, None));
22091 (comments, expanded, editors, avatar_uri, line_ranges)
22092 })
22093 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22094
22095 let comment_count = comments.len();
22096 let avatar_size = px(20.);
22097 let action_icon_size = IconSize::XSmall;
22098
22099 v_flex()
22100 .w_full()
22101 .bg(colors.editor_background)
22102 .border_b_1()
22103 .border_color(colors.border)
22104 .px_2()
22105 .pb_2()
22106 .gap_2()
22107 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22108 .when_some(line_ranges, |el, ranges| {
22109 let label = format_line_ranges(&ranges);
22110 if let Some(label) = label {
22111 el.child(
22112 h_flex()
22113 .w_full()
22114 .px_2()
22115 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22116 )
22117 } else {
22118 el
22119 }
22120 })
22121 // Top row: editable input with user's avatar
22122 .child(
22123 h_flex()
22124 .w_full()
22125 .items_center()
22126 .gap_2()
22127 .px_2()
22128 .py_1p5()
22129 .rounded_md()
22130 .bg(colors.surface_background)
22131 .child(
22132 div()
22133 .size(avatar_size)
22134 .flex_shrink_0()
22135 .rounded_full()
22136 .overflow_hidden()
22137 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22138 Avatar::new(avatar_uri.clone())
22139 .size(avatar_size)
22140 .into_any_element()
22141 } else {
22142 Icon::new(IconName::Person)
22143 .size(IconSize::Small)
22144 .color(ui::Color::Muted)
22145 .into_any_element()
22146 }),
22147 )
22148 .child(
22149 div()
22150 .flex_1()
22151 .border_1()
22152 .border_color(colors.border)
22153 .rounded_md()
22154 .bg(colors.editor_background)
22155 .px_2()
22156 .py_1()
22157 .child(prompt_editor.clone()),
22158 )
22159 .child(
22160 h_flex()
22161 .flex_shrink_0()
22162 .gap_1()
22163 .child(
22164 IconButton::new("diff-review-close", IconName::Close)
22165 .icon_color(ui::Color::Muted)
22166 .icon_size(action_icon_size)
22167 .tooltip(Tooltip::text("Close"))
22168 .on_click(|_, window, cx| {
22169 window
22170 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22171 }),
22172 )
22173 .child(
22174 IconButton::new("diff-review-add", IconName::Return)
22175 .icon_color(ui::Color::Muted)
22176 .icon_size(action_icon_size)
22177 .tooltip(Tooltip::text("Add comment"))
22178 .on_click(|_, window, cx| {
22179 window.dispatch_action(
22180 Box::new(crate::actions::SubmitDiffReviewComment),
22181 cx,
22182 );
22183 }),
22184 ),
22185 ),
22186 )
22187 // Expandable comments section (only shown when there are comments)
22188 .when(comment_count > 0, |el| {
22189 el.child(Self::render_comments_section(
22190 comments,
22191 comments_expanded,
22192 inline_editors,
22193 user_avatar_uri,
22194 avatar_size,
22195 action_icon_size,
22196 colors,
22197 ))
22198 })
22199 .into_any_element()
22200 }
22201
22202 fn render_comments_section(
22203 comments: Vec<StoredReviewComment>,
22204 expanded: bool,
22205 inline_editors: HashMap<usize, Entity<Editor>>,
22206 user_avatar_uri: Option<SharedUri>,
22207 avatar_size: Pixels,
22208 action_icon_size: IconSize,
22209 colors: &theme::ThemeColors,
22210 ) -> impl IntoElement {
22211 let comment_count = comments.len();
22212
22213 v_flex()
22214 .w_full()
22215 .gap_1()
22216 // Header with expand/collapse toggle
22217 .child(
22218 h_flex()
22219 .id("review-comments-header")
22220 .w_full()
22221 .items_center()
22222 .gap_1()
22223 .px_2()
22224 .py_1()
22225 .cursor_pointer()
22226 .rounded_md()
22227 .hover(|style| style.bg(colors.ghost_element_hover))
22228 .on_click(|_, window: &mut Window, cx| {
22229 window.dispatch_action(
22230 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22231 cx,
22232 );
22233 })
22234 .child(
22235 Icon::new(if expanded {
22236 IconName::ChevronDown
22237 } else {
22238 IconName::ChevronRight
22239 })
22240 .size(IconSize::Small)
22241 .color(ui::Color::Muted),
22242 )
22243 .child(
22244 Label::new(format!(
22245 "{} Comment{}",
22246 comment_count,
22247 if comment_count == 1 { "" } else { "s" }
22248 ))
22249 .size(LabelSize::Small)
22250 .color(Color::Muted),
22251 ),
22252 )
22253 // Comments list (when expanded)
22254 .when(expanded, |el| {
22255 el.children(comments.into_iter().map(|comment| {
22256 let inline_editor = inline_editors.get(&comment.id).cloned();
22257 Self::render_comment_row(
22258 comment,
22259 inline_editor,
22260 user_avatar_uri.clone(),
22261 avatar_size,
22262 action_icon_size,
22263 colors,
22264 )
22265 }))
22266 })
22267 }
22268
22269 fn render_comment_row(
22270 comment: StoredReviewComment,
22271 inline_editor: Option<Entity<Editor>>,
22272 user_avatar_uri: Option<SharedUri>,
22273 avatar_size: Pixels,
22274 action_icon_size: IconSize,
22275 colors: &theme::ThemeColors,
22276 ) -> impl IntoElement {
22277 let comment_id = comment.id;
22278 let is_editing = inline_editor.is_some();
22279
22280 h_flex()
22281 .w_full()
22282 .items_center()
22283 .gap_2()
22284 .px_2()
22285 .py_1p5()
22286 .rounded_md()
22287 .bg(colors.surface_background)
22288 .child(
22289 div()
22290 .size(avatar_size)
22291 .flex_shrink_0()
22292 .rounded_full()
22293 .overflow_hidden()
22294 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22295 Avatar::new(avatar_uri.clone())
22296 .size(avatar_size)
22297 .into_any_element()
22298 } else {
22299 Icon::new(IconName::Person)
22300 .size(IconSize::Small)
22301 .color(ui::Color::Muted)
22302 .into_any_element()
22303 }),
22304 )
22305 .child(if let Some(editor) = inline_editor {
22306 // Inline edit mode: show an editable text field
22307 div()
22308 .flex_1()
22309 .border_1()
22310 .border_color(colors.border)
22311 .rounded_md()
22312 .bg(colors.editor_background)
22313 .px_2()
22314 .py_1()
22315 .child(editor)
22316 .into_any_element()
22317 } else {
22318 // Display mode: show the comment text
22319 div()
22320 .flex_1()
22321 .text_sm()
22322 .text_color(colors.text)
22323 .child(comment.comment)
22324 .into_any_element()
22325 })
22326 .child(if is_editing {
22327 // Editing mode: show close and confirm buttons
22328 h_flex()
22329 .gap_1()
22330 .child(
22331 IconButton::new(
22332 format!("diff-review-cancel-edit-{comment_id}"),
22333 IconName::Close,
22334 )
22335 .icon_color(ui::Color::Muted)
22336 .icon_size(action_icon_size)
22337 .tooltip(Tooltip::text("Cancel"))
22338 .on_click(move |_, window, cx| {
22339 window.dispatch_action(
22340 Box::new(crate::actions::CancelEditReviewComment {
22341 id: comment_id,
22342 }),
22343 cx,
22344 );
22345 }),
22346 )
22347 .child(
22348 IconButton::new(
22349 format!("diff-review-confirm-edit-{comment_id}"),
22350 IconName::Return,
22351 )
22352 .icon_color(ui::Color::Muted)
22353 .icon_size(action_icon_size)
22354 .tooltip(Tooltip::text("Confirm"))
22355 .on_click(move |_, window, cx| {
22356 window.dispatch_action(
22357 Box::new(crate::actions::ConfirmEditReviewComment {
22358 id: comment_id,
22359 }),
22360 cx,
22361 );
22362 }),
22363 )
22364 .into_any_element()
22365 } else {
22366 // Display mode: no action buttons for now (edit/delete not yet implemented)
22367 gpui::Empty.into_any_element()
22368 })
22369 }
22370
22371 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22372 if self.display_map.read(cx).masked != masked {
22373 self.display_map.update(cx, |map, _| map.masked = masked);
22374 }
22375 cx.notify()
22376 }
22377
22378 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22379 self.show_wrap_guides = Some(show_wrap_guides);
22380 cx.notify();
22381 }
22382
22383 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22384 self.show_indent_guides = Some(show_indent_guides);
22385 cx.notify();
22386 }
22387
22388 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22389 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22390 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22391 && let Some(dir) = file.abs_path(cx).parent()
22392 {
22393 return Some(dir.to_owned());
22394 }
22395 }
22396
22397 None
22398 }
22399
22400 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22401 self.active_excerpt(cx)?
22402 .1
22403 .read(cx)
22404 .file()
22405 .and_then(|f| f.as_local())
22406 }
22407
22408 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22409 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22410 let buffer = buffer.read(cx);
22411 if let Some(project_path) = buffer.project_path(cx) {
22412 let project = self.project()?.read(cx);
22413 project.absolute_path(&project_path, cx)
22414 } else {
22415 buffer
22416 .file()
22417 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22418 }
22419 })
22420 }
22421
22422 pub fn reveal_in_finder(
22423 &mut self,
22424 _: &RevealInFileManager,
22425 _window: &mut Window,
22426 cx: &mut Context<Self>,
22427 ) {
22428 if let Some(path) = self.target_file_abs_path(cx) {
22429 if let Some(project) = self.project() {
22430 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22431 } else {
22432 cx.reveal_path(&path);
22433 }
22434 }
22435 }
22436
22437 pub fn copy_path(
22438 &mut self,
22439 _: &zed_actions::workspace::CopyPath,
22440 _window: &mut Window,
22441 cx: &mut Context<Self>,
22442 ) {
22443 if let Some(path) = self.target_file_abs_path(cx)
22444 && let Some(path) = path.to_str()
22445 {
22446 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22447 } else {
22448 cx.propagate();
22449 }
22450 }
22451
22452 pub fn copy_relative_path(
22453 &mut self,
22454 _: &zed_actions::workspace::CopyRelativePath,
22455 _window: &mut Window,
22456 cx: &mut Context<Self>,
22457 ) {
22458 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22459 let project = self.project()?.read(cx);
22460 let path = buffer.read(cx).file()?.path();
22461 let path = path.display(project.path_style(cx));
22462 Some(path)
22463 }) {
22464 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22465 } else {
22466 cx.propagate();
22467 }
22468 }
22469
22470 /// Returns the project path for the editor's buffer, if any buffer is
22471 /// opened in the editor.
22472 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22473 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22474 buffer.read(cx).project_path(cx)
22475 } else {
22476 None
22477 }
22478 }
22479
22480 // Returns true if the editor handled a go-to-line request
22481 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22482 maybe!({
22483 let breakpoint_store = self.breakpoint_store.as_ref()?;
22484
22485 let (active_stack_frame, debug_line_pane_id) = {
22486 let store = breakpoint_store.read(cx);
22487 let active_stack_frame = store.active_position().cloned();
22488 let debug_line_pane_id = store.active_debug_line_pane_id();
22489 (active_stack_frame, debug_line_pane_id)
22490 };
22491
22492 let Some(active_stack_frame) = active_stack_frame else {
22493 self.clear_row_highlights::<ActiveDebugLine>();
22494 return None;
22495 };
22496
22497 if let Some(debug_line_pane_id) = debug_line_pane_id {
22498 if let Some(workspace) = self
22499 .workspace
22500 .as_ref()
22501 .and_then(|(workspace, _)| workspace.upgrade())
22502 {
22503 let editor_pane_id = workspace
22504 .read(cx)
22505 .pane_for_item_id(cx.entity_id())
22506 .map(|pane| pane.entity_id());
22507
22508 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22509 self.clear_row_highlights::<ActiveDebugLine>();
22510 return None;
22511 }
22512 }
22513 }
22514
22515 let position = active_stack_frame.position;
22516 let buffer_id = position.buffer_id?;
22517 let snapshot = self
22518 .project
22519 .as_ref()?
22520 .read(cx)
22521 .buffer_for_id(buffer_id, cx)?
22522 .read(cx)
22523 .snapshot();
22524
22525 let mut handled = false;
22526 for (id, _, ExcerptRange { context, .. }) in
22527 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22528 {
22529 if context.start.cmp(&position, &snapshot).is_ge()
22530 || context.end.cmp(&position, &snapshot).is_lt()
22531 {
22532 continue;
22533 }
22534 let snapshot = self.buffer.read(cx).snapshot(cx);
22535 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22536
22537 handled = true;
22538 self.clear_row_highlights::<ActiveDebugLine>();
22539
22540 self.go_to_line::<ActiveDebugLine>(
22541 multibuffer_anchor,
22542 Some(cx.theme().colors().editor_debugger_active_line_background),
22543 window,
22544 cx,
22545 );
22546
22547 cx.notify();
22548 }
22549
22550 handled.then_some(())
22551 })
22552 .is_some()
22553 }
22554
22555 pub fn copy_file_name_without_extension(
22556 &mut self,
22557 _: &CopyFileNameWithoutExtension,
22558 _: &mut Window,
22559 cx: &mut Context<Self>,
22560 ) {
22561 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22562 let file = buffer.read(cx).file()?;
22563 file.path().file_stem()
22564 }) {
22565 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22566 }
22567 }
22568
22569 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22570 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22571 let file = buffer.read(cx).file()?;
22572 Some(file.file_name(cx))
22573 }) {
22574 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22575 }
22576 }
22577
22578 pub fn toggle_git_blame(
22579 &mut self,
22580 _: &::git::Blame,
22581 window: &mut Window,
22582 cx: &mut Context<Self>,
22583 ) {
22584 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22585
22586 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22587 self.start_git_blame(true, window, cx);
22588 }
22589
22590 cx.notify();
22591 }
22592
22593 pub fn toggle_git_blame_inline(
22594 &mut self,
22595 _: &ToggleGitBlameInline,
22596 window: &mut Window,
22597 cx: &mut Context<Self>,
22598 ) {
22599 self.toggle_git_blame_inline_internal(true, window, cx);
22600 cx.notify();
22601 }
22602
22603 pub fn open_git_blame_commit(
22604 &mut self,
22605 _: &OpenGitBlameCommit,
22606 window: &mut Window,
22607 cx: &mut Context<Self>,
22608 ) {
22609 self.open_git_blame_commit_internal(window, cx);
22610 }
22611
22612 fn open_git_blame_commit_internal(
22613 &mut self,
22614 window: &mut Window,
22615 cx: &mut Context<Self>,
22616 ) -> Option<()> {
22617 let blame = self.blame.as_ref()?;
22618 let snapshot = self.snapshot(window, cx);
22619 let cursor = self
22620 .selections
22621 .newest::<Point>(&snapshot.display_snapshot)
22622 .head();
22623 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22624 let (_, blame_entry) = blame
22625 .update(cx, |blame, cx| {
22626 blame
22627 .blame_for_rows(
22628 &[RowInfo {
22629 buffer_id: Some(buffer.remote_id()),
22630 buffer_row: Some(point.row),
22631 ..Default::default()
22632 }],
22633 cx,
22634 )
22635 .next()
22636 })
22637 .flatten()?;
22638 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22639 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22640 let workspace = self.workspace()?.downgrade();
22641 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22642 None
22643 }
22644
22645 pub fn git_blame_inline_enabled(&self) -> bool {
22646 self.git_blame_inline_enabled
22647 }
22648
22649 pub fn toggle_selection_menu(
22650 &mut self,
22651 _: &ToggleSelectionMenu,
22652 _: &mut Window,
22653 cx: &mut Context<Self>,
22654 ) {
22655 self.show_selection_menu = self
22656 .show_selection_menu
22657 .map(|show_selections_menu| !show_selections_menu)
22658 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22659
22660 cx.notify();
22661 }
22662
22663 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22664 self.show_selection_menu
22665 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22666 }
22667
22668 fn start_git_blame(
22669 &mut self,
22670 user_triggered: bool,
22671 window: &mut Window,
22672 cx: &mut Context<Self>,
22673 ) {
22674 if let Some(project) = self.project() {
22675 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22676 && buffer.read(cx).file().is_none()
22677 {
22678 return;
22679 }
22680
22681 let focused = self.focus_handle(cx).contains_focused(window, cx);
22682
22683 let project = project.clone();
22684 let blame = cx
22685 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22686 self.blame_subscription =
22687 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22688 self.blame = Some(blame);
22689 }
22690 }
22691
22692 fn toggle_git_blame_inline_internal(
22693 &mut self,
22694 user_triggered: bool,
22695 window: &mut Window,
22696 cx: &mut Context<Self>,
22697 ) {
22698 if self.git_blame_inline_enabled {
22699 self.git_blame_inline_enabled = false;
22700 self.show_git_blame_inline = false;
22701 self.show_git_blame_inline_delay_task.take();
22702 } else {
22703 self.git_blame_inline_enabled = true;
22704 self.start_git_blame_inline(user_triggered, window, cx);
22705 }
22706
22707 cx.notify();
22708 }
22709
22710 fn start_git_blame_inline(
22711 &mut self,
22712 user_triggered: bool,
22713 window: &mut Window,
22714 cx: &mut Context<Self>,
22715 ) {
22716 self.start_git_blame(user_triggered, window, cx);
22717
22718 if ProjectSettings::get_global(cx)
22719 .git
22720 .inline_blame_delay()
22721 .is_some()
22722 {
22723 self.start_inline_blame_timer(window, cx);
22724 } else {
22725 self.show_git_blame_inline = true
22726 }
22727 }
22728
22729 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22730 self.blame.as_ref()
22731 }
22732
22733 pub fn show_git_blame_gutter(&self) -> bool {
22734 self.show_git_blame_gutter
22735 }
22736
22737 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22738 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22739 }
22740
22741 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22742 self.show_git_blame_inline
22743 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22744 && !self.newest_selection_head_on_empty_line(cx)
22745 && self.has_blame_entries(cx)
22746 }
22747
22748 fn has_blame_entries(&self, cx: &App) -> bool {
22749 self.blame()
22750 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22751 }
22752
22753 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22754 let cursor_anchor = self.selections.newest_anchor().head();
22755
22756 let snapshot = self.buffer.read(cx).snapshot(cx);
22757 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22758
22759 snapshot.line_len(buffer_row) == 0
22760 }
22761
22762 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22763 let buffer_and_selection = maybe!({
22764 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22765 let selection_range = selection.range();
22766
22767 let multi_buffer = self.buffer().read(cx);
22768 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22769 let buffer_ranges = multi_buffer_snapshot
22770 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
22771
22772 let (buffer, range, _) = if selection.reversed {
22773 buffer_ranges.first()
22774 } else {
22775 buffer_ranges.last()
22776 }?;
22777
22778 let buffer_range = range.to_point(buffer);
22779
22780 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22781 return Some((
22782 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22783 buffer_range.start.row..buffer_range.end.row,
22784 ));
22785 };
22786
22787 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22788 let start =
22789 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
22790 let end =
22791 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
22792
22793 Some((
22794 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22795 start.row..end.row,
22796 ))
22797 });
22798
22799 let Some((buffer, selection)) = buffer_and_selection else {
22800 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22801 };
22802
22803 let Some(project) = self.project() else {
22804 return Task::ready(Err(anyhow!("editor does not have project")));
22805 };
22806
22807 project.update(cx, |project, cx| {
22808 project.get_permalink_to_line(&buffer, selection, cx)
22809 })
22810 }
22811
22812 pub fn copy_permalink_to_line(
22813 &mut self,
22814 _: &CopyPermalinkToLine,
22815 window: &mut Window,
22816 cx: &mut Context<Self>,
22817 ) {
22818 let permalink_task = self.get_permalink_to_line(cx);
22819 let workspace = self.workspace();
22820
22821 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22822 Ok(permalink) => {
22823 cx.update(|_, cx| {
22824 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22825 })
22826 .ok();
22827 }
22828 Err(err) => {
22829 let message = format!("Failed to copy permalink: {err}");
22830
22831 anyhow::Result::<()>::Err(err).log_err();
22832
22833 if let Some(workspace) = workspace {
22834 workspace
22835 .update_in(cx, |workspace, _, cx| {
22836 struct CopyPermalinkToLine;
22837
22838 workspace.show_toast(
22839 Toast::new(
22840 NotificationId::unique::<CopyPermalinkToLine>(),
22841 message,
22842 ),
22843 cx,
22844 )
22845 })
22846 .ok();
22847 }
22848 }
22849 })
22850 .detach();
22851 }
22852
22853 pub fn copy_file_location(
22854 &mut self,
22855 _: &CopyFileLocation,
22856 _: &mut Window,
22857 cx: &mut Context<Self>,
22858 ) {
22859 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22860
22861 let start_line = selection.start.row + 1;
22862 let end_line = selection.end.row + 1;
22863
22864 let end_line = if selection.end.column == 0 && end_line > start_line {
22865 end_line - 1
22866 } else {
22867 end_line
22868 };
22869
22870 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22871 let project = self.project()?.read(cx);
22872 let file = buffer.read(cx).file()?;
22873 let path = file.path().display(project.path_style(cx));
22874
22875 let location = if start_line == end_line {
22876 format!("{path}:{start_line}")
22877 } else {
22878 format!("{path}:{start_line}-{end_line}")
22879 };
22880 Some(location)
22881 }) {
22882 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
22883 }
22884 }
22885
22886 pub fn open_permalink_to_line(
22887 &mut self,
22888 _: &OpenPermalinkToLine,
22889 window: &mut Window,
22890 cx: &mut Context<Self>,
22891 ) {
22892 let permalink_task = self.get_permalink_to_line(cx);
22893 let workspace = self.workspace();
22894
22895 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22896 Ok(permalink) => {
22897 cx.update(|_, cx| {
22898 cx.open_url(permalink.as_ref());
22899 })
22900 .ok();
22901 }
22902 Err(err) => {
22903 let message = format!("Failed to open permalink: {err}");
22904
22905 anyhow::Result::<()>::Err(err).log_err();
22906
22907 if let Some(workspace) = workspace {
22908 workspace.update(cx, |workspace, cx| {
22909 struct OpenPermalinkToLine;
22910
22911 workspace.show_toast(
22912 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
22913 cx,
22914 )
22915 });
22916 }
22917 }
22918 })
22919 .detach();
22920 }
22921
22922 pub fn insert_uuid_v4(
22923 &mut self,
22924 _: &InsertUuidV4,
22925 window: &mut Window,
22926 cx: &mut Context<Self>,
22927 ) {
22928 self.insert_uuid(UuidVersion::V4, window, cx);
22929 }
22930
22931 pub fn insert_uuid_v7(
22932 &mut self,
22933 _: &InsertUuidV7,
22934 window: &mut Window,
22935 cx: &mut Context<Self>,
22936 ) {
22937 self.insert_uuid(UuidVersion::V7, window, cx);
22938 }
22939
22940 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
22941 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
22942 self.transact(window, cx, |this, window, cx| {
22943 let edits = this
22944 .selections
22945 .all::<Point>(&this.display_snapshot(cx))
22946 .into_iter()
22947 .map(|selection| {
22948 let uuid = match version {
22949 UuidVersion::V4 => uuid::Uuid::new_v4(),
22950 UuidVersion::V7 => uuid::Uuid::now_v7(),
22951 };
22952
22953 (selection.range(), uuid.to_string())
22954 });
22955 this.edit(edits, cx);
22956 this.refresh_edit_prediction(true, false, window, cx);
22957 });
22958 }
22959
22960 pub fn open_selections_in_multibuffer(
22961 &mut self,
22962 _: &OpenSelectionsInMultibuffer,
22963 window: &mut Window,
22964 cx: &mut Context<Self>,
22965 ) {
22966 let multibuffer = self.buffer.read(cx);
22967
22968 let Some(buffer) = multibuffer.as_singleton() else {
22969 return;
22970 };
22971
22972 let Some(workspace) = self.workspace() else {
22973 return;
22974 };
22975
22976 let title = multibuffer.title(cx).to_string();
22977
22978 let locations = self
22979 .selections
22980 .all_anchors(&self.display_snapshot(cx))
22981 .iter()
22982 .map(|selection| {
22983 (
22984 buffer.clone(),
22985 (selection.start.text_anchor..selection.end.text_anchor)
22986 .to_point(buffer.read(cx)),
22987 )
22988 })
22989 .into_group_map();
22990
22991 cx.spawn_in(window, async move |_, cx| {
22992 workspace.update_in(cx, |workspace, window, cx| {
22993 Self::open_locations_in_multibuffer(
22994 workspace,
22995 locations,
22996 format!("Selections for '{title}'"),
22997 false,
22998 false,
22999 MultibufferSelectionMode::All,
23000 window,
23001 cx,
23002 );
23003 })
23004 })
23005 .detach();
23006 }
23007
23008 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23009 /// last highlight added will be used.
23010 ///
23011 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23012 pub fn highlight_rows<T: 'static>(
23013 &mut self,
23014 range: Range<Anchor>,
23015 color: Hsla,
23016 options: RowHighlightOptions,
23017 cx: &mut Context<Self>,
23018 ) {
23019 let snapshot = self.buffer().read(cx).snapshot(cx);
23020 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23021 let ix = row_highlights.binary_search_by(|highlight| {
23022 Ordering::Equal
23023 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23024 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23025 });
23026
23027 if let Err(mut ix) = ix {
23028 let index = post_inc(&mut self.highlight_order);
23029
23030 // If this range intersects with the preceding highlight, then merge it with
23031 // the preceding highlight. Otherwise insert a new highlight.
23032 let mut merged = false;
23033 if ix > 0 {
23034 let prev_highlight = &mut row_highlights[ix - 1];
23035 if prev_highlight
23036 .range
23037 .end
23038 .cmp(&range.start, &snapshot)
23039 .is_ge()
23040 {
23041 ix -= 1;
23042 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23043 prev_highlight.range.end = range.end;
23044 }
23045 merged = true;
23046 prev_highlight.index = index;
23047 prev_highlight.color = color;
23048 prev_highlight.options = options;
23049 }
23050 }
23051
23052 if !merged {
23053 row_highlights.insert(
23054 ix,
23055 RowHighlight {
23056 range,
23057 index,
23058 color,
23059 options,
23060 type_id: TypeId::of::<T>(),
23061 },
23062 );
23063 }
23064
23065 // If any of the following highlights intersect with this one, merge them.
23066 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23067 let highlight = &row_highlights[ix];
23068 if next_highlight
23069 .range
23070 .start
23071 .cmp(&highlight.range.end, &snapshot)
23072 .is_le()
23073 {
23074 if next_highlight
23075 .range
23076 .end
23077 .cmp(&highlight.range.end, &snapshot)
23078 .is_gt()
23079 {
23080 row_highlights[ix].range.end = next_highlight.range.end;
23081 }
23082 row_highlights.remove(ix + 1);
23083 } else {
23084 break;
23085 }
23086 }
23087 }
23088 }
23089
23090 /// Remove any highlighted row ranges of the given type that intersect the
23091 /// given ranges.
23092 pub fn remove_highlighted_rows<T: 'static>(
23093 &mut self,
23094 ranges_to_remove: Vec<Range<Anchor>>,
23095 cx: &mut Context<Self>,
23096 ) {
23097 let snapshot = self.buffer().read(cx).snapshot(cx);
23098 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23099 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23100 row_highlights.retain(|highlight| {
23101 while let Some(range_to_remove) = ranges_to_remove.peek() {
23102 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23103 Ordering::Less | Ordering::Equal => {
23104 ranges_to_remove.next();
23105 }
23106 Ordering::Greater => {
23107 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23108 Ordering::Less | Ordering::Equal => {
23109 return false;
23110 }
23111 Ordering::Greater => break,
23112 }
23113 }
23114 }
23115 }
23116
23117 true
23118 })
23119 }
23120
23121 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23122 pub fn clear_row_highlights<T: 'static>(&mut self) {
23123 self.highlighted_rows.remove(&TypeId::of::<T>());
23124 }
23125
23126 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23127 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23128 self.highlighted_rows
23129 .get(&TypeId::of::<T>())
23130 .map_or(&[] as &[_], |vec| vec.as_slice())
23131 .iter()
23132 .map(|highlight| (highlight.range.clone(), highlight.color))
23133 }
23134
23135 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23136 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23137 /// Allows to ignore certain kinds of highlights.
23138 pub fn highlighted_display_rows(
23139 &self,
23140 window: &mut Window,
23141 cx: &mut App,
23142 ) -> BTreeMap<DisplayRow, LineHighlight> {
23143 let snapshot = self.snapshot(window, cx);
23144 let mut used_highlight_orders = HashMap::default();
23145 self.highlighted_rows
23146 .iter()
23147 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23148 .fold(
23149 BTreeMap::<DisplayRow, LineHighlight>::new(),
23150 |mut unique_rows, highlight| {
23151 let start = highlight.range.start.to_display_point(&snapshot);
23152 let end = highlight.range.end.to_display_point(&snapshot);
23153 let start_row = start.row().0;
23154 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23155 {
23156 end.row().0.saturating_sub(1)
23157 } else {
23158 end.row().0
23159 };
23160 for row in start_row..=end_row {
23161 let used_index =
23162 used_highlight_orders.entry(row).or_insert(highlight.index);
23163 if highlight.index >= *used_index {
23164 *used_index = highlight.index;
23165 unique_rows.insert(
23166 DisplayRow(row),
23167 LineHighlight {
23168 include_gutter: highlight.options.include_gutter,
23169 border: None,
23170 background: highlight.color.into(),
23171 type_id: Some(highlight.type_id),
23172 },
23173 );
23174 }
23175 }
23176 unique_rows
23177 },
23178 )
23179 }
23180
23181 pub fn highlighted_display_row_for_autoscroll(
23182 &self,
23183 snapshot: &DisplaySnapshot,
23184 ) -> Option<DisplayRow> {
23185 self.highlighted_rows
23186 .values()
23187 .flat_map(|highlighted_rows| highlighted_rows.iter())
23188 .filter_map(|highlight| {
23189 if highlight.options.autoscroll {
23190 Some(highlight.range.start.to_display_point(snapshot).row())
23191 } else {
23192 None
23193 }
23194 })
23195 .min()
23196 }
23197
23198 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23199 self.highlight_background(
23200 HighlightKey::SearchWithinRange,
23201 ranges,
23202 |_, colors| colors.colors().editor_document_highlight_read_background,
23203 cx,
23204 )
23205 }
23206
23207 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23208 self.breadcrumb_header = Some(new_header);
23209 }
23210
23211 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23212 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23213 }
23214
23215 pub fn highlight_background(
23216 &mut self,
23217 key: HighlightKey,
23218 ranges: &[Range<Anchor>],
23219 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23220 cx: &mut Context<Self>,
23221 ) {
23222 self.background_highlights
23223 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23224 self.scrollbar_marker_state.dirty = true;
23225 cx.notify();
23226 }
23227
23228 pub fn clear_background_highlights(
23229 &mut self,
23230 key: HighlightKey,
23231 cx: &mut Context<Self>,
23232 ) -> Option<BackgroundHighlight> {
23233 let text_highlights = self.background_highlights.remove(&key)?;
23234 if !text_highlights.1.is_empty() {
23235 self.scrollbar_marker_state.dirty = true;
23236 cx.notify();
23237 }
23238 Some(text_highlights)
23239 }
23240
23241 pub fn highlight_gutter<T: 'static>(
23242 &mut self,
23243 ranges: impl Into<Vec<Range<Anchor>>>,
23244 color_fetcher: fn(&App) -> Hsla,
23245 cx: &mut Context<Self>,
23246 ) {
23247 self.gutter_highlights
23248 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23249 cx.notify();
23250 }
23251
23252 pub fn clear_gutter_highlights<T: 'static>(
23253 &mut self,
23254 cx: &mut Context<Self>,
23255 ) -> Option<GutterHighlight> {
23256 cx.notify();
23257 self.gutter_highlights.remove(&TypeId::of::<T>())
23258 }
23259
23260 pub fn insert_gutter_highlight<T: 'static>(
23261 &mut self,
23262 range: Range<Anchor>,
23263 color_fetcher: fn(&App) -> Hsla,
23264 cx: &mut Context<Self>,
23265 ) {
23266 let snapshot = self.buffer().read(cx).snapshot(cx);
23267 let mut highlights = self
23268 .gutter_highlights
23269 .remove(&TypeId::of::<T>())
23270 .map(|(_, highlights)| highlights)
23271 .unwrap_or_default();
23272 let ix = highlights.binary_search_by(|highlight| {
23273 Ordering::Equal
23274 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23275 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23276 });
23277 if let Err(ix) = ix {
23278 highlights.insert(ix, range);
23279 }
23280 self.gutter_highlights
23281 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23282 }
23283
23284 pub fn remove_gutter_highlights<T: 'static>(
23285 &mut self,
23286 ranges_to_remove: Vec<Range<Anchor>>,
23287 cx: &mut Context<Self>,
23288 ) {
23289 let snapshot = self.buffer().read(cx).snapshot(cx);
23290 let Some((color_fetcher, mut gutter_highlights)) =
23291 self.gutter_highlights.remove(&TypeId::of::<T>())
23292 else {
23293 return;
23294 };
23295 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23296 gutter_highlights.retain(|highlight| {
23297 while let Some(range_to_remove) = ranges_to_remove.peek() {
23298 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23299 Ordering::Less | Ordering::Equal => {
23300 ranges_to_remove.next();
23301 }
23302 Ordering::Greater => {
23303 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23304 Ordering::Less | Ordering::Equal => {
23305 return false;
23306 }
23307 Ordering::Greater => break,
23308 }
23309 }
23310 }
23311 }
23312
23313 true
23314 });
23315 self.gutter_highlights
23316 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23317 }
23318
23319 #[cfg(any(test, feature = "test-support"))]
23320 pub fn all_text_highlights(
23321 &self,
23322 window: &mut Window,
23323 cx: &mut Context<Self>,
23324 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23325 let snapshot = self.snapshot(window, cx);
23326 self.display_map.update(cx, |display_map, _| {
23327 display_map
23328 .all_text_highlights()
23329 .map(|(_, highlight)| {
23330 let (style, ranges) = highlight.as_ref();
23331 (
23332 *style,
23333 ranges
23334 .iter()
23335 .map(|range| range.clone().to_display_points(&snapshot))
23336 .collect(),
23337 )
23338 })
23339 .collect()
23340 })
23341 }
23342
23343 #[cfg(any(test, feature = "test-support"))]
23344 pub fn all_text_background_highlights(
23345 &self,
23346 window: &mut Window,
23347 cx: &mut Context<Self>,
23348 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23349 let snapshot = self.snapshot(window, cx);
23350 let buffer = &snapshot.buffer_snapshot();
23351 let start = buffer.anchor_before(MultiBufferOffset(0));
23352 let end = buffer.anchor_after(buffer.len());
23353 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23354 }
23355
23356 #[cfg(any(test, feature = "test-support"))]
23357 pub fn sorted_background_highlights_in_range(
23358 &self,
23359 search_range: Range<Anchor>,
23360 display_snapshot: &DisplaySnapshot,
23361 theme: &Theme,
23362 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23363 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23364 res.sort_by(|a, b| {
23365 a.0.start
23366 .cmp(&b.0.start)
23367 .then_with(|| a.0.end.cmp(&b.0.end))
23368 .then_with(|| a.1.cmp(&b.1))
23369 });
23370 res
23371 }
23372
23373 #[cfg(any(test, feature = "test-support"))]
23374 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23375 let snapshot = self.buffer().read(cx).snapshot(cx);
23376
23377 let highlights = self
23378 .background_highlights
23379 .get(&HighlightKey::BufferSearchHighlights);
23380
23381 if let Some((_color, ranges)) = highlights {
23382 ranges
23383 .iter()
23384 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23385 .collect_vec()
23386 } else {
23387 vec![]
23388 }
23389 }
23390
23391 fn document_highlights_for_position<'a>(
23392 &'a self,
23393 position: Anchor,
23394 buffer: &'a MultiBufferSnapshot,
23395 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23396 let read_highlights = self
23397 .background_highlights
23398 .get(&HighlightKey::DocumentHighlightRead)
23399 .map(|h| &h.1);
23400 let write_highlights = self
23401 .background_highlights
23402 .get(&HighlightKey::DocumentHighlightWrite)
23403 .map(|h| &h.1);
23404 let left_position = position.bias_left(buffer);
23405 let right_position = position.bias_right(buffer);
23406 read_highlights
23407 .into_iter()
23408 .chain(write_highlights)
23409 .flat_map(move |ranges| {
23410 let start_ix = match ranges.binary_search_by(|probe| {
23411 let cmp = probe.end.cmp(&left_position, buffer);
23412 if cmp.is_ge() {
23413 Ordering::Greater
23414 } else {
23415 Ordering::Less
23416 }
23417 }) {
23418 Ok(i) | Err(i) => i,
23419 };
23420
23421 ranges[start_ix..]
23422 .iter()
23423 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23424 })
23425 }
23426
23427 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23428 self.background_highlights
23429 .get(&key)
23430 .is_some_and(|(_, highlights)| !highlights.is_empty())
23431 }
23432
23433 /// Returns all background highlights for a given range.
23434 ///
23435 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23436 pub fn background_highlights_in_range(
23437 &self,
23438 search_range: Range<Anchor>,
23439 display_snapshot: &DisplaySnapshot,
23440 theme: &Theme,
23441 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23442 let mut results = Vec::new();
23443 for (color_fetcher, ranges) in self.background_highlights.values() {
23444 let start_ix = match ranges.binary_search_by(|probe| {
23445 let cmp = probe
23446 .end
23447 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23448 if cmp.is_gt() {
23449 Ordering::Greater
23450 } else {
23451 Ordering::Less
23452 }
23453 }) {
23454 Ok(i) | Err(i) => i,
23455 };
23456 for (index, range) in ranges[start_ix..].iter().enumerate() {
23457 if range
23458 .start
23459 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23460 .is_ge()
23461 {
23462 break;
23463 }
23464
23465 let color = color_fetcher(&(start_ix + index), theme);
23466 let start = range.start.to_display_point(display_snapshot);
23467 let end = range.end.to_display_point(display_snapshot);
23468 results.push((start..end, color))
23469 }
23470 }
23471 results
23472 }
23473
23474 pub fn gutter_highlights_in_range(
23475 &self,
23476 search_range: Range<Anchor>,
23477 display_snapshot: &DisplaySnapshot,
23478 cx: &App,
23479 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23480 let mut results = Vec::new();
23481 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23482 let color = color_fetcher(cx);
23483 let start_ix = match ranges.binary_search_by(|probe| {
23484 let cmp = probe
23485 .end
23486 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23487 if cmp.is_gt() {
23488 Ordering::Greater
23489 } else {
23490 Ordering::Less
23491 }
23492 }) {
23493 Ok(i) | Err(i) => i,
23494 };
23495 for range in &ranges[start_ix..] {
23496 if range
23497 .start
23498 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23499 .is_ge()
23500 {
23501 break;
23502 }
23503
23504 let start = range.start.to_display_point(display_snapshot);
23505 let end = range.end.to_display_point(display_snapshot);
23506 results.push((start..end, color))
23507 }
23508 }
23509 results
23510 }
23511
23512 /// Get the text ranges corresponding to the redaction query
23513 pub fn redacted_ranges(
23514 &self,
23515 search_range: Range<Anchor>,
23516 display_snapshot: &DisplaySnapshot,
23517 cx: &App,
23518 ) -> Vec<Range<DisplayPoint>> {
23519 display_snapshot
23520 .buffer_snapshot()
23521 .redacted_ranges(search_range, |file| {
23522 if let Some(file) = file {
23523 file.is_private()
23524 && EditorSettings::get(
23525 Some(SettingsLocation {
23526 worktree_id: file.worktree_id(cx),
23527 path: file.path().as_ref(),
23528 }),
23529 cx,
23530 )
23531 .redact_private_values
23532 } else {
23533 false
23534 }
23535 })
23536 .map(|range| {
23537 range.start.to_display_point(display_snapshot)
23538 ..range.end.to_display_point(display_snapshot)
23539 })
23540 .collect()
23541 }
23542
23543 pub fn highlight_text_key(
23544 &mut self,
23545 key: HighlightKey,
23546 ranges: Vec<Range<Anchor>>,
23547 style: HighlightStyle,
23548 merge: bool,
23549 cx: &mut Context<Self>,
23550 ) {
23551 self.display_map.update(cx, |map, cx| {
23552 map.highlight_text(key, ranges, style, merge, cx);
23553 });
23554 cx.notify();
23555 }
23556
23557 pub fn highlight_text(
23558 &mut self,
23559 key: HighlightKey,
23560 ranges: Vec<Range<Anchor>>,
23561 style: HighlightStyle,
23562 cx: &mut Context<Self>,
23563 ) {
23564 self.display_map.update(cx, |map, cx| {
23565 map.highlight_text(key, ranges, style, false, cx)
23566 });
23567 cx.notify();
23568 }
23569
23570 pub fn text_highlights<'a>(
23571 &'a self,
23572 key: HighlightKey,
23573 cx: &'a App,
23574 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23575 self.display_map.read(cx).text_highlights(key)
23576 }
23577
23578 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23579 let cleared = self
23580 .display_map
23581 .update(cx, |map, _| map.clear_highlights(key));
23582 if cleared {
23583 cx.notify();
23584 }
23585 }
23586
23587 pub fn clear_highlights_with(
23588 &mut self,
23589 f: &mut dyn FnMut(&HighlightKey) -> bool,
23590 cx: &mut Context<Self>,
23591 ) {
23592 let cleared = self
23593 .display_map
23594 .update(cx, |map, _| map.clear_highlights_with(f));
23595 if cleared {
23596 cx.notify();
23597 }
23598 }
23599
23600 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23601 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23602 && self.focus_handle.is_focused(window)
23603 }
23604
23605 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23606 self.show_cursor_when_unfocused = is_enabled;
23607 cx.notify();
23608 }
23609
23610 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23611 cx.notify();
23612 }
23613
23614 fn on_debug_session_event(
23615 &mut self,
23616 _session: Entity<Session>,
23617 event: &SessionEvent,
23618 cx: &mut Context<Self>,
23619 ) {
23620 if let SessionEvent::InvalidateInlineValue = event {
23621 self.refresh_inline_values(cx);
23622 }
23623 }
23624
23625 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23626 let Some(semantics) = self.semantics_provider.clone() else {
23627 return;
23628 };
23629
23630 if !self.inline_value_cache.enabled {
23631 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23632 self.splice_inlays(&inlays, Vec::new(), cx);
23633 return;
23634 }
23635
23636 let current_execution_position = self
23637 .highlighted_rows
23638 .get(&TypeId::of::<ActiveDebugLine>())
23639 .and_then(|lines| lines.last().map(|line| line.range.end));
23640
23641 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23642 let inline_values = editor
23643 .update(cx, |editor, cx| {
23644 let Some(current_execution_position) = current_execution_position else {
23645 return Some(Task::ready(Ok(Vec::new())));
23646 };
23647
23648 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23649 let snapshot = buffer.snapshot(cx);
23650
23651 let excerpt = snapshot.excerpt_containing(
23652 current_execution_position..current_execution_position,
23653 )?;
23654
23655 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23656 })?;
23657
23658 let range =
23659 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23660
23661 semantics.inline_values(buffer, range, cx)
23662 })
23663 .ok()
23664 .flatten()?
23665 .await
23666 .context("refreshing debugger inlays")
23667 .log_err()?;
23668
23669 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23670
23671 for (buffer_id, inline_value) in inline_values
23672 .into_iter()
23673 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23674 {
23675 buffer_inline_values
23676 .entry(buffer_id)
23677 .or_default()
23678 .push(inline_value);
23679 }
23680
23681 editor
23682 .update(cx, |editor, cx| {
23683 let snapshot = editor.buffer.read(cx).snapshot(cx);
23684 let mut new_inlays = Vec::default();
23685
23686 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23687 let buffer_id = buffer_snapshot.remote_id();
23688 buffer_inline_values
23689 .get(&buffer_id)
23690 .into_iter()
23691 .flatten()
23692 .for_each(|hint| {
23693 let inlay = Inlay::debugger(
23694 post_inc(&mut editor.next_inlay_id),
23695 Anchor::in_buffer(excerpt_id, hint.position),
23696 hint.text(),
23697 );
23698 if !inlay.text().chars().contains(&'\n') {
23699 new_inlays.push(inlay);
23700 }
23701 });
23702 }
23703
23704 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23705 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23706
23707 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23708 })
23709 .ok()?;
23710 Some(())
23711 });
23712 }
23713
23714 fn on_buffer_event(
23715 &mut self,
23716 multibuffer: &Entity<MultiBuffer>,
23717 event: &multi_buffer::Event,
23718 window: &mut Window,
23719 cx: &mut Context<Self>,
23720 ) {
23721 match event {
23722 multi_buffer::Event::Edited {
23723 edited_buffer,
23724 is_local,
23725 } => {
23726 self.scrollbar_marker_state.dirty = true;
23727 self.active_indent_guides_state.dirty = true;
23728 self.refresh_active_diagnostics(cx);
23729 self.refresh_code_actions(window, cx);
23730 self.refresh_single_line_folds(window, cx);
23731 let snapshot = self.snapshot(window, cx);
23732 self.refresh_matching_bracket_highlights(&snapshot, cx);
23733 self.refresh_outline_symbols_at_cursor(cx);
23734 self.refresh_sticky_headers(&snapshot, cx);
23735 if *is_local && self.has_active_edit_prediction() {
23736 self.update_visible_edit_prediction(window, cx);
23737 }
23738
23739 // Clean up orphaned review comments after edits
23740 self.cleanup_orphaned_review_comments(cx);
23741
23742 if let Some(buffer) = edited_buffer {
23743 if buffer.read(cx).file().is_none() {
23744 cx.emit(EditorEvent::TitleChanged);
23745 }
23746
23747 if self.project.is_some() {
23748 let buffer_id = buffer.read(cx).remote_id();
23749 self.register_buffer(buffer_id, cx);
23750 self.update_lsp_data(Some(buffer_id), window, cx);
23751 self.refresh_inlay_hints(
23752 InlayHintRefreshReason::BufferEdited(buffer_id),
23753 cx,
23754 );
23755 }
23756 }
23757
23758 cx.emit(EditorEvent::BufferEdited);
23759 cx.emit(SearchEvent::MatchesInvalidated);
23760
23761 let Some(project) = &self.project else { return };
23762 let (telemetry, is_via_ssh) = {
23763 let project = project.read(cx);
23764 let telemetry = project.client().telemetry().clone();
23765 let is_via_ssh = project.is_via_remote_server();
23766 (telemetry, is_via_ssh)
23767 };
23768 telemetry.log_edit_event("editor", is_via_ssh);
23769 }
23770 multi_buffer::Event::ExcerptsAdded {
23771 buffer,
23772 predecessor,
23773 excerpts,
23774 } => {
23775 let buffer_id = buffer.read(cx).remote_id();
23776 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23777 && let Some(project) = &self.project
23778 {
23779 update_uncommitted_diff_for_buffer(
23780 cx.entity(),
23781 project,
23782 [buffer.clone()],
23783 self.buffer.clone(),
23784 cx,
23785 )
23786 .detach();
23787 }
23788 self.semantic_token_state
23789 .invalidate_buffer(&buffer.read(cx).remote_id());
23790 self.update_lsp_data(Some(buffer_id), window, cx);
23791 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23792 self.refresh_runnables(window, cx);
23793 self.colorize_brackets(false, cx);
23794 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
23795 cx.emit(EditorEvent::ExcerptsAdded {
23796 buffer: buffer.clone(),
23797 predecessor: *predecessor,
23798 excerpts: excerpts.clone(),
23799 });
23800 }
23801 multi_buffer::Event::ExcerptsRemoved {
23802 ids,
23803 removed_buffer_ids,
23804 } => {
23805 if let Some(inlay_hints) = &mut self.inlay_hints {
23806 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23807 }
23808 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23809 for buffer_id in removed_buffer_ids {
23810 self.registered_buffers.remove(buffer_id);
23811 self.clear_runnables(Some(*buffer_id));
23812 self.semantic_token_state.invalidate_buffer(buffer_id);
23813 self.display_map.update(cx, |display_map, cx| {
23814 display_map.invalidate_semantic_highlights(*buffer_id);
23815 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
23816 });
23817 }
23818
23819 self.display_map.update(cx, |display_map, cx| {
23820 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
23821 });
23822
23823 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23824 cx.emit(EditorEvent::ExcerptsRemoved {
23825 ids: ids.clone(),
23826 removed_buffer_ids: removed_buffer_ids.clone(),
23827 });
23828 }
23829 multi_buffer::Event::ExcerptsEdited {
23830 excerpt_ids,
23831 buffer_ids,
23832 } => {
23833 self.display_map.update(cx, |map, cx| {
23834 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23835 });
23836 cx.emit(EditorEvent::ExcerptsEdited {
23837 ids: excerpt_ids.clone(),
23838 });
23839 }
23840 multi_buffer::Event::ExcerptsExpanded { ids } => {
23841 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23842 self.refresh_document_highlights(cx);
23843 let snapshot = multibuffer.read(cx).snapshot(cx);
23844 for id in ids {
23845 self.bracket_fetched_tree_sitter_chunks.remove(id);
23846 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
23847 self.semantic_token_state
23848 .invalidate_buffer(&buffer.remote_id());
23849 }
23850 }
23851 self.colorize_brackets(false, cx);
23852 self.update_lsp_data(None, window, cx);
23853 self.refresh_runnables(window, cx);
23854 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23855 }
23856 multi_buffer::Event::Reparsed(buffer_id) => {
23857 self.clear_runnables(Some(*buffer_id));
23858 self.refresh_runnables(window, cx);
23859 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
23860 self.colorize_brackets(true, cx);
23861 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23862
23863 cx.emit(EditorEvent::Reparsed(*buffer_id));
23864 }
23865 multi_buffer::Event::DiffHunksToggled => {
23866 self.refresh_runnables(window, cx);
23867 }
23868 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23869 if !is_fresh_language {
23870 self.registered_buffers.remove(&buffer_id);
23871 }
23872 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23873 cx.emit(EditorEvent::Reparsed(*buffer_id));
23874 cx.notify();
23875 }
23876 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
23877 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
23878 multi_buffer::Event::FileHandleChanged
23879 | multi_buffer::Event::Reloaded
23880 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
23881 multi_buffer::Event::DiagnosticsUpdated => {
23882 self.update_diagnostics_state(window, cx);
23883 }
23884 _ => {}
23885 };
23886 }
23887
23888 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
23889 if !self.diagnostics_enabled() {
23890 return;
23891 }
23892 self.refresh_active_diagnostics(cx);
23893 self.refresh_inline_diagnostics(true, window, cx);
23894 self.scrollbar_marker_state.dirty = true;
23895 cx.notify();
23896 }
23897
23898 pub fn start_temporary_diff_override(&mut self) {
23899 self.load_diff_task.take();
23900 self.temporary_diff_override = true;
23901 }
23902
23903 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
23904 self.temporary_diff_override = false;
23905 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
23906 self.buffer.update(cx, |buffer, cx| {
23907 buffer.set_all_diff_hunks_collapsed(cx);
23908 });
23909
23910 if let Some(project) = self.project.clone() {
23911 self.load_diff_task = Some(
23912 update_uncommitted_diff_for_buffer(
23913 cx.entity(),
23914 &project,
23915 self.buffer.read(cx).all_buffers(),
23916 self.buffer.clone(),
23917 cx,
23918 )
23919 .shared(),
23920 );
23921 }
23922 }
23923
23924 fn on_display_map_changed(
23925 &mut self,
23926 _: Entity<DisplayMap>,
23927 _: &mut Window,
23928 cx: &mut Context<Self>,
23929 ) {
23930 cx.notify();
23931 }
23932
23933 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
23934 if !self.mode.is_full() {
23935 return None;
23936 }
23937
23938 let theme_settings = theme::ThemeSettings::get_global(cx);
23939 let theme = cx.theme();
23940 let accent_colors = theme.accents().clone();
23941
23942 let accent_overrides = theme_settings
23943 .theme_overrides
23944 .get(theme.name.as_ref())
23945 .map(|theme_style| &theme_style.accents)
23946 .into_iter()
23947 .flatten()
23948 .chain(
23949 theme_settings
23950 .experimental_theme_overrides
23951 .as_ref()
23952 .map(|overrides| &overrides.accents)
23953 .into_iter()
23954 .flatten(),
23955 )
23956 .flat_map(|accent| accent.0.clone().map(SharedString::from))
23957 .collect();
23958
23959 Some(AccentData {
23960 colors: accent_colors,
23961 overrides: accent_overrides,
23962 })
23963 }
23964
23965 fn fetch_applicable_language_settings(
23966 &self,
23967 cx: &App,
23968 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
23969 if !self.mode.is_full() {
23970 return HashMap::default();
23971 }
23972
23973 self.buffer().read(cx).all_buffers().into_iter().fold(
23974 HashMap::default(),
23975 |mut acc, buffer| {
23976 let buffer = buffer.read(cx);
23977 let language = buffer.language().map(|language| language.name());
23978 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
23979 let file = buffer.file();
23980 v.insert(language_settings(language, file, cx).into_owned());
23981 }
23982 acc
23983 },
23984 )
23985 }
23986
23987 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
23988 let new_language_settings = self.fetch_applicable_language_settings(cx);
23989 let language_settings_changed = new_language_settings != self.applicable_language_settings;
23990 self.applicable_language_settings = new_language_settings;
23991
23992 let new_accents = self.fetch_accent_data(cx);
23993 let accents_changed = new_accents != self.accent_data;
23994 self.accent_data = new_accents;
23995
23996 if self.diagnostics_enabled() {
23997 let new_severity = EditorSettings::get_global(cx)
23998 .diagnostics_max_severity
23999 .unwrap_or(DiagnosticSeverity::Hint);
24000 self.set_max_diagnostics_severity(new_severity, cx);
24001 }
24002 self.refresh_runnables(window, cx);
24003 self.update_edit_prediction_settings(cx);
24004 self.refresh_edit_prediction(true, false, window, cx);
24005 self.refresh_inline_values(cx);
24006
24007 let old_cursor_shape = self.cursor_shape;
24008 let old_show_breadcrumbs = self.show_breadcrumbs;
24009
24010 {
24011 let editor_settings = EditorSettings::get_global(cx);
24012 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24013 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24014 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24015 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24016 }
24017
24018 if old_cursor_shape != self.cursor_shape {
24019 cx.emit(EditorEvent::CursorShapeChanged);
24020 }
24021
24022 if old_show_breadcrumbs != self.show_breadcrumbs {
24023 cx.emit(EditorEvent::BreadcrumbsChanged);
24024 }
24025
24026 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24027 let project_settings = ProjectSettings::get_global(cx);
24028 (
24029 project_settings.session.restore_unsaved_buffers,
24030 project_settings.diagnostics.inline.enabled,
24031 project_settings.git.inline_blame.enabled,
24032 )
24033 };
24034 self.buffer_serialization = self
24035 .should_serialize_buffer()
24036 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24037
24038 if self.mode.is_full() {
24039 if self.show_inline_diagnostics != show_inline_diagnostics {
24040 self.show_inline_diagnostics = show_inline_diagnostics;
24041 self.refresh_inline_diagnostics(false, window, cx);
24042 }
24043
24044 if self.git_blame_inline_enabled != inline_blame_enabled {
24045 self.toggle_git_blame_inline_internal(false, window, cx);
24046 }
24047
24048 let minimap_settings = EditorSettings::get_global(cx).minimap;
24049 if self.minimap_visibility != MinimapVisibility::Disabled {
24050 if self.minimap_visibility.settings_visibility()
24051 != minimap_settings.minimap_enabled()
24052 {
24053 self.set_minimap_visibility(
24054 MinimapVisibility::for_mode(self.mode(), cx),
24055 window,
24056 cx,
24057 );
24058 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24059 minimap_entity.update(cx, |minimap_editor, cx| {
24060 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24061 })
24062 }
24063 }
24064
24065 if language_settings_changed || accents_changed {
24066 self.colorize_brackets(true, cx);
24067 }
24068
24069 if language_settings_changed {
24070 self.clear_disabled_lsp_folding_ranges(window, cx);
24071 self.refresh_document_symbols(None, cx);
24072 }
24073
24074 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24075 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24076 }) {
24077 if !inlay_splice.is_empty() {
24078 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24079 }
24080 self.refresh_document_colors(None, window, cx);
24081 }
24082
24083 self.refresh_inlay_hints(
24084 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24085 self.selections.newest_anchor().head(),
24086 &self.buffer.read(cx).snapshot(cx),
24087 cx,
24088 )),
24089 cx,
24090 );
24091
24092 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24093 .global_lsp_settings
24094 .semantic_token_rules
24095 .clone();
24096 let semantic_token_rules_changed = self
24097 .semantic_token_state
24098 .update_rules(new_semantic_token_rules);
24099 if language_settings_changed || semantic_token_rules_changed {
24100 self.invalidate_semantic_tokens(None);
24101 self.refresh_semantic_tokens(None, None, cx);
24102 }
24103 }
24104
24105 cx.notify();
24106 }
24107
24108 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24109 if !self.mode.is_full() {
24110 return;
24111 }
24112
24113 let new_accents = self.fetch_accent_data(cx);
24114 if new_accents != self.accent_data {
24115 self.accent_data = new_accents;
24116 self.colorize_brackets(true, cx);
24117 }
24118
24119 self.invalidate_semantic_tokens(None);
24120 self.refresh_semantic_tokens(None, None, cx);
24121 }
24122
24123 pub fn set_searchable(&mut self, searchable: bool) {
24124 self.searchable = searchable;
24125 }
24126
24127 pub fn searchable(&self) -> bool {
24128 self.searchable
24129 }
24130
24131 pub fn open_excerpts_in_split(
24132 &mut self,
24133 _: &OpenExcerptsSplit,
24134 window: &mut Window,
24135 cx: &mut Context<Self>,
24136 ) {
24137 self.open_excerpts_common(None, true, window, cx)
24138 }
24139
24140 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24141 self.open_excerpts_common(None, false, window, cx)
24142 }
24143
24144 pub(crate) fn open_excerpts_common(
24145 &mut self,
24146 jump_data: Option<JumpData>,
24147 split: bool,
24148 window: &mut Window,
24149 cx: &mut Context<Self>,
24150 ) {
24151 if self.buffer.read(cx).is_singleton() {
24152 cx.propagate();
24153 return;
24154 }
24155
24156 let mut new_selections_by_buffer = HashMap::default();
24157 match &jump_data {
24158 Some(JumpData::MultiBufferPoint {
24159 excerpt_id,
24160 position,
24161 anchor,
24162 line_offset_from_top,
24163 }) => {
24164 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24165 if let Some(buffer) = multi_buffer_snapshot
24166 .buffer_id_for_excerpt(*excerpt_id)
24167 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24168 {
24169 let buffer_snapshot = buffer.read(cx).snapshot();
24170 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24171 language::ToPoint::to_point(anchor, &buffer_snapshot)
24172 } else {
24173 buffer_snapshot.clip_point(*position, Bias::Left)
24174 };
24175 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24176 new_selections_by_buffer.insert(
24177 buffer,
24178 (
24179 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24180 Some(*line_offset_from_top),
24181 ),
24182 );
24183 }
24184 }
24185 Some(JumpData::MultiBufferRow {
24186 row,
24187 line_offset_from_top,
24188 }) => {
24189 let point = MultiBufferPoint::new(row.0, 0);
24190 if let Some((buffer, buffer_point, _)) =
24191 self.buffer.read(cx).point_to_buffer_point(point, cx)
24192 {
24193 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24194 new_selections_by_buffer
24195 .entry(buffer)
24196 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24197 .0
24198 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24199 }
24200 }
24201 None => {
24202 let selections = self
24203 .selections
24204 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24205 let multi_buffer = self.buffer.read(cx);
24206 for selection in selections {
24207 for (snapshot, range, _, anchor) in multi_buffer
24208 .snapshot(cx)
24209 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24210 {
24211 if let Some(anchor) = anchor {
24212 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24213 else {
24214 continue;
24215 };
24216 let offset = text::ToOffset::to_offset(
24217 &anchor.text_anchor,
24218 &buffer_handle.read(cx).snapshot(),
24219 );
24220 let range = BufferOffset(offset)..BufferOffset(offset);
24221 new_selections_by_buffer
24222 .entry(buffer_handle)
24223 .or_insert((Vec::new(), None))
24224 .0
24225 .push(range)
24226 } else {
24227 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24228 else {
24229 continue;
24230 };
24231 new_selections_by_buffer
24232 .entry(buffer_handle)
24233 .or_insert((Vec::new(), None))
24234 .0
24235 .push(range)
24236 }
24237 }
24238 }
24239 }
24240 }
24241
24242 if self.delegate_open_excerpts {
24243 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24244 .into_iter()
24245 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24246 .collect();
24247 if !selections_by_buffer.is_empty() {
24248 cx.emit(EditorEvent::OpenExcerptsRequested {
24249 selections_by_buffer,
24250 split,
24251 });
24252 }
24253 return;
24254 }
24255
24256 let Some(workspace) = self.workspace() else {
24257 cx.propagate();
24258 return;
24259 };
24260
24261 new_selections_by_buffer
24262 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24263
24264 if new_selections_by_buffer.is_empty() {
24265 return;
24266 }
24267
24268 Self::open_buffers_in_workspace(
24269 workspace.downgrade(),
24270 new_selections_by_buffer,
24271 split,
24272 window,
24273 cx,
24274 );
24275 }
24276
24277 pub(crate) fn open_buffers_in_workspace(
24278 workspace: WeakEntity<Workspace>,
24279 new_selections_by_buffer: HashMap<
24280 Entity<language::Buffer>,
24281 (Vec<Range<BufferOffset>>, Option<u32>),
24282 >,
24283 split: bool,
24284 window: &mut Window,
24285 cx: &mut App,
24286 ) {
24287 // We defer the pane interaction because we ourselves are a workspace item
24288 // and activating a new item causes the pane to call a method on us reentrantly,
24289 // which panics if we're on the stack.
24290 window.defer(cx, move |window, cx| {
24291 workspace
24292 .update(cx, |workspace, cx| {
24293 let pane = if split {
24294 workspace.adjacent_pane(window, cx)
24295 } else {
24296 workspace.active_pane().clone()
24297 };
24298
24299 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24300 let buffer_read = buffer.read(cx);
24301 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24302 (true, project::File::from_dyn(Some(file)).is_some())
24303 } else {
24304 (false, false)
24305 };
24306
24307 // If project file is none workspace.open_project_item will fail to open the excerpt
24308 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24309 // so we check if there's a tab match in that case first
24310 let editor = (!has_file || !is_project_file)
24311 .then(|| {
24312 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24313 // so `workspace.open_project_item` will never find them, always opening a new editor.
24314 // Instead, we try to activate the existing editor in the pane first.
24315 let (editor, pane_item_index, pane_item_id) =
24316 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24317 let editor = item.downcast::<Editor>()?;
24318 let singleton_buffer =
24319 editor.read(cx).buffer().read(cx).as_singleton()?;
24320 if singleton_buffer == buffer {
24321 Some((editor, i, item.item_id()))
24322 } else {
24323 None
24324 }
24325 })?;
24326 pane.update(cx, |pane, cx| {
24327 pane.activate_item(pane_item_index, true, true, window, cx);
24328 if !PreviewTabsSettings::get_global(cx)
24329 .enable_preview_from_multibuffer
24330 {
24331 pane.unpreview_item_if_preview(pane_item_id);
24332 }
24333 });
24334 Some(editor)
24335 })
24336 .flatten()
24337 .unwrap_or_else(|| {
24338 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24339 .enable_keep_preview_on_code_navigation;
24340 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24341 .enable_preview_from_multibuffer;
24342 workspace.open_project_item::<Self>(
24343 pane.clone(),
24344 buffer,
24345 true,
24346 true,
24347 keep_old_preview,
24348 allow_new_preview,
24349 window,
24350 cx,
24351 )
24352 });
24353
24354 editor.update(cx, |editor, cx| {
24355 if has_file && !is_project_file {
24356 editor.set_read_only(true);
24357 }
24358 let autoscroll = match scroll_offset {
24359 Some(scroll_offset) => {
24360 Autoscroll::top_relative(scroll_offset as usize)
24361 }
24362 None => Autoscroll::newest(),
24363 };
24364 let nav_history = editor.nav_history.take();
24365 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24366 let Some((excerpt_id, _, buffer_snapshot)) =
24367 multibuffer_snapshot.as_singleton()
24368 else {
24369 return;
24370 };
24371 editor.change_selections(
24372 SelectionEffects::scroll(autoscroll),
24373 window,
24374 cx,
24375 |s| {
24376 s.select_ranges(ranges.into_iter().map(|range| {
24377 let range = buffer_snapshot.anchor_before(range.start)
24378 ..buffer_snapshot.anchor_after(range.end);
24379 multibuffer_snapshot
24380 .anchor_range_in_excerpt(excerpt_id, range)
24381 .unwrap()
24382 }));
24383 },
24384 );
24385 editor.nav_history = nav_history;
24386 });
24387 }
24388 })
24389 .ok();
24390 });
24391 }
24392
24393 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24394 let snapshot = self.buffer.read(cx).read(cx);
24395 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24396 Some(
24397 ranges
24398 .iter()
24399 .map(move |range| {
24400 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24401 })
24402 .collect(),
24403 )
24404 }
24405
24406 fn selection_replacement_ranges(
24407 &self,
24408 range: Range<MultiBufferOffsetUtf16>,
24409 cx: &mut App,
24410 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24411 let selections = self
24412 .selections
24413 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24414 let newest_selection = selections
24415 .iter()
24416 .max_by_key(|selection| selection.id)
24417 .unwrap();
24418 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24419 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24420 let snapshot = self.buffer.read(cx).read(cx);
24421 selections
24422 .into_iter()
24423 .map(|mut selection| {
24424 selection.start.0.0 =
24425 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24426 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24427 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24428 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24429 })
24430 .collect()
24431 }
24432
24433 fn report_editor_event(
24434 &self,
24435 reported_event: ReportEditorEvent,
24436 file_extension: Option<String>,
24437 cx: &App,
24438 ) {
24439 if cfg!(any(test, feature = "test-support")) {
24440 return;
24441 }
24442
24443 let Some(project) = &self.project else { return };
24444
24445 // If None, we are in a file without an extension
24446 let file = self
24447 .buffer
24448 .read(cx)
24449 .as_singleton()
24450 .and_then(|b| b.read(cx).file());
24451 let file_extension = file_extension.or(file
24452 .as_ref()
24453 .and_then(|file| Path::new(file.file_name(cx)).extension())
24454 .and_then(|e| e.to_str())
24455 .map(|a| a.to_string()));
24456
24457 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24458 .map(|vim_mode| vim_mode.0)
24459 .unwrap_or(false);
24460
24461 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24462 let copilot_enabled = edit_predictions_provider
24463 == language::language_settings::EditPredictionProvider::Copilot;
24464 let copilot_enabled_for_language = self
24465 .buffer
24466 .read(cx)
24467 .language_settings(cx)
24468 .show_edit_predictions;
24469
24470 let project = project.read(cx);
24471 let event_type = reported_event.event_type();
24472
24473 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24474 telemetry::event!(
24475 event_type,
24476 type = if auto_saved {"autosave"} else {"manual"},
24477 file_extension,
24478 vim_mode,
24479 copilot_enabled,
24480 copilot_enabled_for_language,
24481 edit_predictions_provider,
24482 is_via_ssh = project.is_via_remote_server(),
24483 );
24484 } else {
24485 telemetry::event!(
24486 event_type,
24487 file_extension,
24488 vim_mode,
24489 copilot_enabled,
24490 copilot_enabled_for_language,
24491 edit_predictions_provider,
24492 is_via_ssh = project.is_via_remote_server(),
24493 );
24494 };
24495 }
24496
24497 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24498 /// with each line being an array of {text, highlight} objects.
24499 fn copy_highlight_json(
24500 &mut self,
24501 _: &CopyHighlightJson,
24502 window: &mut Window,
24503 cx: &mut Context<Self>,
24504 ) {
24505 #[derive(Serialize)]
24506 struct Chunk<'a> {
24507 text: String,
24508 highlight: Option<&'a str>,
24509 }
24510
24511 let snapshot = self.buffer.read(cx).snapshot(cx);
24512 let range = self
24513 .selected_text_range(false, window, cx)
24514 .and_then(|selection| {
24515 if selection.range.is_empty() {
24516 None
24517 } else {
24518 Some(
24519 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24520 selection.range.start,
24521 )))
24522 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24523 selection.range.end,
24524 ))),
24525 )
24526 }
24527 })
24528 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24529
24530 let chunks = snapshot.chunks(range, true);
24531 let mut lines = Vec::new();
24532 let mut line: VecDeque<Chunk> = VecDeque::new();
24533
24534 let Some(style) = self.style.as_ref() else {
24535 return;
24536 };
24537
24538 for chunk in chunks {
24539 let highlight = chunk
24540 .syntax_highlight_id
24541 .and_then(|id| id.name(&style.syntax));
24542 let mut chunk_lines = chunk.text.split('\n').peekable();
24543 while let Some(text) = chunk_lines.next() {
24544 let mut merged_with_last_token = false;
24545 if let Some(last_token) = line.back_mut()
24546 && last_token.highlight == highlight
24547 {
24548 last_token.text.push_str(text);
24549 merged_with_last_token = true;
24550 }
24551
24552 if !merged_with_last_token {
24553 line.push_back(Chunk {
24554 text: text.into(),
24555 highlight,
24556 });
24557 }
24558
24559 if chunk_lines.peek().is_some() {
24560 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24561 line.pop_front();
24562 }
24563 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24564 line.pop_back();
24565 }
24566
24567 lines.push(mem::take(&mut line));
24568 }
24569 }
24570 }
24571
24572 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24573 return;
24574 };
24575 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24576 }
24577
24578 pub fn open_context_menu(
24579 &mut self,
24580 _: &OpenContextMenu,
24581 window: &mut Window,
24582 cx: &mut Context<Self>,
24583 ) {
24584 self.request_autoscroll(Autoscroll::newest(), cx);
24585 let position = self
24586 .selections
24587 .newest_display(&self.display_snapshot(cx))
24588 .start;
24589 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24590 }
24591
24592 pub fn replay_insert_event(
24593 &mut self,
24594 text: &str,
24595 relative_utf16_range: Option<Range<isize>>,
24596 window: &mut Window,
24597 cx: &mut Context<Self>,
24598 ) {
24599 if !self.input_enabled {
24600 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24601 return;
24602 }
24603 if let Some(relative_utf16_range) = relative_utf16_range {
24604 let selections = self
24605 .selections
24606 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24607 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24608 let new_ranges = selections.into_iter().map(|range| {
24609 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24610 range
24611 .head()
24612 .0
24613 .0
24614 .saturating_add_signed(relative_utf16_range.start),
24615 ));
24616 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24617 range
24618 .head()
24619 .0
24620 .0
24621 .saturating_add_signed(relative_utf16_range.end),
24622 ));
24623 start..end
24624 });
24625 s.select_ranges(new_ranges);
24626 });
24627 }
24628
24629 self.handle_input(text, window, cx);
24630 }
24631
24632 pub fn is_focused(&self, window: &Window) -> bool {
24633 self.focus_handle.is_focused(window)
24634 }
24635
24636 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24637 cx.emit(EditorEvent::Focused);
24638
24639 if let Some(descendant) = self
24640 .last_focused_descendant
24641 .take()
24642 .and_then(|descendant| descendant.upgrade())
24643 {
24644 window.focus(&descendant, cx);
24645 } else {
24646 if let Some(blame) = self.blame.as_ref() {
24647 blame.update(cx, GitBlame::focus)
24648 }
24649
24650 self.blink_manager.update(cx, BlinkManager::enable);
24651 self.show_cursor_names(window, cx);
24652 self.buffer.update(cx, |buffer, cx| {
24653 buffer.finalize_last_transaction(cx);
24654 if self.leader_id.is_none() {
24655 buffer.set_active_selections(
24656 &self.selections.disjoint_anchors_arc(),
24657 self.selections.line_mode(),
24658 self.cursor_shape,
24659 cx,
24660 );
24661 }
24662 });
24663
24664 if let Some(position_map) = self.last_position_map.clone() {
24665 EditorElement::mouse_moved(
24666 self,
24667 &MouseMoveEvent {
24668 position: window.mouse_position(),
24669 pressed_button: None,
24670 modifiers: window.modifiers(),
24671 },
24672 &position_map,
24673 None,
24674 window,
24675 cx,
24676 );
24677 }
24678 }
24679 }
24680
24681 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24682 cx.emit(EditorEvent::FocusedIn)
24683 }
24684
24685 fn handle_focus_out(
24686 &mut self,
24687 event: FocusOutEvent,
24688 _window: &mut Window,
24689 cx: &mut Context<Self>,
24690 ) {
24691 if event.blurred != self.focus_handle {
24692 self.last_focused_descendant = Some(event.blurred);
24693 }
24694 self.selection_drag_state = SelectionDragState::None;
24695 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24696 }
24697
24698 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24699 self.blink_manager.update(cx, BlinkManager::disable);
24700 self.buffer
24701 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24702
24703 if let Some(blame) = self.blame.as_ref() {
24704 blame.update(cx, GitBlame::blur)
24705 }
24706 if !self.hover_state.focused(window, cx) {
24707 hide_hover(self, cx);
24708 }
24709 if !self
24710 .context_menu
24711 .borrow()
24712 .as_ref()
24713 .is_some_and(|context_menu| context_menu.focused(window, cx))
24714 {
24715 self.hide_context_menu(window, cx);
24716 }
24717 self.take_active_edit_prediction(cx);
24718 cx.emit(EditorEvent::Blurred);
24719 cx.notify();
24720 }
24721
24722 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24723 let mut pending: String = window
24724 .pending_input_keystrokes()
24725 .into_iter()
24726 .flatten()
24727 .filter_map(|keystroke| keystroke.key_char.clone())
24728 .collect();
24729
24730 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24731 pending = "".to_string();
24732 }
24733
24734 let existing_pending = self
24735 .text_highlights(HighlightKey::PendingInput, cx)
24736 .map(|(_, ranges)| ranges.to_vec());
24737 if existing_pending.is_none() && pending.is_empty() {
24738 return;
24739 }
24740 let transaction =
24741 self.transact(window, cx, |this, window, cx| {
24742 let selections = this
24743 .selections
24744 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24745 let edits = selections
24746 .iter()
24747 .map(|selection| (selection.end..selection.end, pending.clone()));
24748 this.edit(edits, cx);
24749 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24750 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24751 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24752 }));
24753 });
24754 if let Some(existing_ranges) = existing_pending {
24755 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24756 this.edit(edits, cx);
24757 }
24758 });
24759
24760 let snapshot = self.snapshot(window, cx);
24761 let ranges = self
24762 .selections
24763 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24764 .into_iter()
24765 .map(|selection| {
24766 snapshot.buffer_snapshot().anchor_after(selection.end)
24767 ..snapshot
24768 .buffer_snapshot()
24769 .anchor_before(selection.end + pending.len())
24770 })
24771 .collect();
24772
24773 if pending.is_empty() {
24774 self.clear_highlights(HighlightKey::PendingInput, cx);
24775 } else {
24776 self.highlight_text(
24777 HighlightKey::PendingInput,
24778 ranges,
24779 HighlightStyle {
24780 underline: Some(UnderlineStyle {
24781 thickness: px(1.),
24782 color: None,
24783 wavy: false,
24784 }),
24785 ..Default::default()
24786 },
24787 cx,
24788 );
24789 }
24790
24791 self.ime_transaction = self.ime_transaction.or(transaction);
24792 if let Some(transaction) = self.ime_transaction {
24793 self.buffer.update(cx, |buffer, cx| {
24794 buffer.group_until_transaction(transaction, cx);
24795 });
24796 }
24797
24798 if self
24799 .text_highlights(HighlightKey::PendingInput, cx)
24800 .is_none()
24801 {
24802 self.ime_transaction.take();
24803 }
24804 }
24805
24806 pub fn register_action_renderer(
24807 &mut self,
24808 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24809 ) -> Subscription {
24810 let id = self.next_editor_action_id.post_inc();
24811 self.editor_actions
24812 .borrow_mut()
24813 .insert(id, Box::new(listener));
24814
24815 let editor_actions = self.editor_actions.clone();
24816 Subscription::new(move || {
24817 editor_actions.borrow_mut().remove(&id);
24818 })
24819 }
24820
24821 pub fn register_action<A: Action>(
24822 &mut self,
24823 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24824 ) -> Subscription {
24825 let id = self.next_editor_action_id.post_inc();
24826 let listener = Arc::new(listener);
24827 self.editor_actions.borrow_mut().insert(
24828 id,
24829 Box::new(move |_, window, _| {
24830 let listener = listener.clone();
24831 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24832 let action = action.downcast_ref().unwrap();
24833 if phase == DispatchPhase::Bubble {
24834 listener(action, window, cx)
24835 }
24836 })
24837 }),
24838 );
24839
24840 let editor_actions = self.editor_actions.clone();
24841 Subscription::new(move || {
24842 editor_actions.borrow_mut().remove(&id);
24843 })
24844 }
24845
24846 pub fn file_header_size(&self) -> u32 {
24847 FILE_HEADER_HEIGHT
24848 }
24849
24850 pub fn restore(
24851 &mut self,
24852 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24853 window: &mut Window,
24854 cx: &mut Context<Self>,
24855 ) {
24856 self.buffer().update(cx, |multi_buffer, cx| {
24857 for (buffer_id, changes) in revert_changes {
24858 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24859 buffer.update(cx, |buffer, cx| {
24860 buffer.edit(
24861 changes
24862 .into_iter()
24863 .map(|(range, text)| (range, text.to_string())),
24864 None,
24865 cx,
24866 );
24867 });
24868 }
24869 }
24870 });
24871 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24872 selections.refresh()
24873 });
24874 }
24875
24876 pub fn to_pixel_point(
24877 &mut self,
24878 source: Anchor,
24879 editor_snapshot: &EditorSnapshot,
24880 window: &mut Window,
24881 cx: &mut App,
24882 ) -> Option<gpui::Point<Pixels>> {
24883 let source_point = source.to_display_point(editor_snapshot);
24884 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
24885 }
24886
24887 pub fn display_to_pixel_point(
24888 &mut self,
24889 source: DisplayPoint,
24890 editor_snapshot: &EditorSnapshot,
24891 window: &mut Window,
24892 cx: &mut App,
24893 ) -> Option<gpui::Point<Pixels>> {
24894 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
24895 let text_layout_details = self.text_layout_details(window, cx);
24896 let scroll_top = text_layout_details
24897 .scroll_anchor
24898 .scroll_position(editor_snapshot)
24899 .y;
24900
24901 if source.row().as_f64() < scroll_top.floor() {
24902 return None;
24903 }
24904 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
24905 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
24906 Some(gpui::Point::new(source_x, source_y))
24907 }
24908
24909 pub fn has_visible_completions_menu(&self) -> bool {
24910 !self.edit_prediction_preview_is_active()
24911 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
24912 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
24913 })
24914 }
24915
24916 pub fn register_addon<T: Addon>(&mut self, instance: T) {
24917 if self.mode.is_minimap() {
24918 return;
24919 }
24920 self.addons
24921 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
24922 }
24923
24924 pub fn unregister_addon<T: Addon>(&mut self) {
24925 self.addons.remove(&std::any::TypeId::of::<T>());
24926 }
24927
24928 pub fn addon<T: Addon>(&self) -> Option<&T> {
24929 let type_id = std::any::TypeId::of::<T>();
24930 self.addons
24931 .get(&type_id)
24932 .and_then(|item| item.to_any().downcast_ref::<T>())
24933 }
24934
24935 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
24936 let type_id = std::any::TypeId::of::<T>();
24937 self.addons
24938 .get_mut(&type_id)
24939 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
24940 }
24941
24942 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
24943 let text_layout_details = self.text_layout_details(window, cx);
24944 let style = &text_layout_details.editor_style;
24945 let font_id = window.text_system().resolve_font(&style.text.font());
24946 let font_size = style.text.font_size.to_pixels(window.rem_size());
24947 let line_height = style.text.line_height_in_pixels(window.rem_size());
24948 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
24949 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
24950
24951 CharacterDimensions {
24952 em_width,
24953 em_advance,
24954 line_height,
24955 }
24956 }
24957
24958 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
24959 self.load_diff_task.clone()
24960 }
24961
24962 fn read_metadata_from_db(
24963 &mut self,
24964 item_id: u64,
24965 workspace_id: WorkspaceId,
24966 window: &mut Window,
24967 cx: &mut Context<Editor>,
24968 ) {
24969 if self.buffer_kind(cx) == ItemBufferKind::Singleton
24970 && !self.mode.is_minimap()
24971 && WorkspaceSettings::get(None, cx).restore_on_startup
24972 != RestoreOnStartupBehavior::EmptyTab
24973 {
24974 let buffer_snapshot = OnceCell::new();
24975
24976 // Get file path for path-based fold lookup
24977 let file_path: Option<Arc<Path>> =
24978 self.buffer().read(cx).as_singleton().and_then(|buffer| {
24979 project::File::from_dyn(buffer.read(cx).file())
24980 .map(|file| Arc::from(file.abs_path(cx)))
24981 });
24982
24983 // Try file_folds (path-based) first, fallback to editor_folds (migration)
24984 let (folds, needs_migration) = if let Some(ref path) = file_path {
24985 if let Some(folds) = DB.get_file_folds(workspace_id, path).log_err()
24986 && !folds.is_empty()
24987 {
24988 (Some(folds), false)
24989 } else if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
24990 && !folds.is_empty()
24991 {
24992 // Found old editor_folds data, will migrate to file_folds
24993 (Some(folds), true)
24994 } else {
24995 (None, false)
24996 }
24997 } else {
24998 // No file path, try editor_folds as fallback
24999 let folds = DB.get_editor_folds(item_id, workspace_id).log_err();
25000 (folds.filter(|f| !f.is_empty()), false)
25001 };
25002
25003 if let Some(folds) = folds {
25004 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25005 let snapshot_len = snapshot.len().0;
25006
25007 // Helper: search for fingerprint in buffer, return offset if found
25008 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25009 // Ensure we start at a character boundary (defensive)
25010 let search_start = snapshot
25011 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25012 .0;
25013 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25014
25015 let mut byte_offset = search_start;
25016 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25017 if byte_offset > search_end {
25018 break;
25019 }
25020 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25021 return Some(byte_offset);
25022 }
25023 byte_offset += ch.len_utf8();
25024 }
25025 None
25026 };
25027
25028 // Track search position to handle duplicate fingerprints correctly.
25029 // Folds are stored in document order, so we advance after each match.
25030 let mut search_start = 0usize;
25031
25032 // Collect db_folds for migration (only folds with valid fingerprints)
25033 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25034
25035 let valid_folds: Vec<_> = folds
25036 .into_iter()
25037 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25038 // Skip folds without fingerprints (old data before migration)
25039 let sfp = start_fp?;
25040 let efp = end_fp?;
25041 let efp_len = efp.len();
25042
25043 // Fast path: check if fingerprints match at stored offsets
25044 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25045 let start_matches = stored_start < snapshot_len
25046 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25047 let efp_check_pos = stored_end.saturating_sub(efp_len);
25048 let end_matches = efp_check_pos >= stored_start
25049 && stored_end <= snapshot_len
25050 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25051
25052 let (new_start, new_end) = if start_matches && end_matches {
25053 // Offsets unchanged, use stored values
25054 (stored_start, stored_end)
25055 } else if sfp == efp {
25056 // Short fold: identical fingerprints can only match once per search
25057 // Use stored fold length to compute new_end
25058 let new_start = find_fingerprint(&sfp, search_start)?;
25059 let fold_len = stored_end - stored_start;
25060 let new_end = new_start + fold_len;
25061 (new_start, new_end)
25062 } else {
25063 // Slow path: search for fingerprints in buffer
25064 let new_start = find_fingerprint(&sfp, search_start)?;
25065 // Search for end_fp after start, then add efp_len to get actual fold end
25066 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25067 let new_end = efp_pos + efp_len;
25068 (new_start, new_end)
25069 };
25070
25071 // Advance search position for next fold
25072 search_start = new_end;
25073
25074 // Validate fold makes sense (end must be after start)
25075 if new_end <= new_start {
25076 return None;
25077 }
25078
25079 // Collect for migration if needed
25080 if needs_migration {
25081 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25082 }
25083
25084 Some(
25085 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25086 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25087 )
25088 })
25089 .collect();
25090
25091 if !valid_folds.is_empty() {
25092 self.fold_ranges(valid_folds, false, window, cx);
25093
25094 // Migrate from editor_folds to file_folds if we loaded from old table
25095 if needs_migration {
25096 if let Some(ref path) = file_path {
25097 let path = path.clone();
25098 cx.spawn(async move |_, _| {
25099 DB.save_file_folds(workspace_id, path, db_folds_for_migration)
25100 .await
25101 .log_err();
25102 })
25103 .detach();
25104 }
25105 }
25106 }
25107 }
25108
25109 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25110 && !selections.is_empty()
25111 {
25112 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25113 // skip adding the initial selection to selection history
25114 self.selection_history.mode = SelectionHistoryMode::Skipping;
25115 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25116 s.select_ranges(selections.into_iter().map(|(start, end)| {
25117 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25118 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25119 }));
25120 });
25121 self.selection_history.mode = SelectionHistoryMode::Normal;
25122 };
25123 }
25124
25125 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25126 }
25127
25128 /// Load folds from the file_folds database table by file path.
25129 /// Used when manually opening a file that was previously closed.
25130 fn load_folds_from_db(
25131 &mut self,
25132 workspace_id: WorkspaceId,
25133 file_path: PathBuf,
25134 window: &mut Window,
25135 cx: &mut Context<Editor>,
25136 ) {
25137 if self.mode.is_minimap()
25138 || WorkspaceSettings::get(None, cx).restore_on_startup
25139 == RestoreOnStartupBehavior::EmptyTab
25140 {
25141 return;
25142 }
25143
25144 let Some(folds) = DB.get_file_folds(workspace_id, &file_path).log_err() else {
25145 return;
25146 };
25147 if folds.is_empty() {
25148 return;
25149 }
25150
25151 let snapshot = self.buffer.read(cx).snapshot(cx);
25152 let snapshot_len = snapshot.len().0;
25153
25154 // Helper: search for fingerprint in buffer, return offset if found
25155 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25156 let search_start = snapshot
25157 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25158 .0;
25159 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25160
25161 let mut byte_offset = search_start;
25162 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25163 if byte_offset > search_end {
25164 break;
25165 }
25166 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25167 return Some(byte_offset);
25168 }
25169 byte_offset += ch.len_utf8();
25170 }
25171 None
25172 };
25173
25174 let mut search_start = 0usize;
25175
25176 let valid_folds: Vec<_> = folds
25177 .into_iter()
25178 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25179 let sfp = start_fp?;
25180 let efp = end_fp?;
25181 let efp_len = efp.len();
25182
25183 let start_matches = stored_start < snapshot_len
25184 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25185 let efp_check_pos = stored_end.saturating_sub(efp_len);
25186 let end_matches = efp_check_pos >= stored_start
25187 && stored_end <= snapshot_len
25188 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25189
25190 let (new_start, new_end) = if start_matches && end_matches {
25191 (stored_start, stored_end)
25192 } else if sfp == efp {
25193 let new_start = find_fingerprint(&sfp, search_start)?;
25194 let fold_len = stored_end - stored_start;
25195 let new_end = new_start + fold_len;
25196 (new_start, new_end)
25197 } else {
25198 let new_start = find_fingerprint(&sfp, search_start)?;
25199 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25200 let new_end = efp_pos + efp_len;
25201 (new_start, new_end)
25202 };
25203
25204 search_start = new_end;
25205
25206 if new_end <= new_start {
25207 return None;
25208 }
25209
25210 Some(
25211 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25212 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25213 )
25214 })
25215 .collect();
25216
25217 if !valid_folds.is_empty() {
25218 self.fold_ranges(valid_folds, false, window, cx);
25219 }
25220 }
25221
25222 fn lsp_data_enabled(&self) -> bool {
25223 self.enable_lsp_data && self.mode().is_full()
25224 }
25225
25226 fn update_lsp_data(
25227 &mut self,
25228 for_buffer: Option<BufferId>,
25229 window: &mut Window,
25230 cx: &mut Context<'_, Self>,
25231 ) {
25232 if !self.lsp_data_enabled() {
25233 return;
25234 }
25235
25236 if let Some(buffer_id) = for_buffer {
25237 self.pull_diagnostics(buffer_id, window, cx);
25238 }
25239 self.refresh_semantic_tokens(for_buffer, None, cx);
25240 self.refresh_document_colors(for_buffer, window, cx);
25241 self.refresh_folding_ranges(for_buffer, window, cx);
25242 self.refresh_document_symbols(for_buffer, cx);
25243 }
25244
25245 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25246 if !self.lsp_data_enabled() {
25247 return;
25248 }
25249 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25250 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25251 }
25252 }
25253
25254 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25255 if !self.lsp_data_enabled() {
25256 return;
25257 }
25258
25259 if !self.registered_buffers.contains_key(&buffer_id)
25260 && let Some(project) = self.project.as_ref()
25261 {
25262 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25263 project.update(cx, |project, cx| {
25264 self.registered_buffers.insert(
25265 buffer_id,
25266 project.register_buffer_with_language_servers(&buffer, cx),
25267 );
25268 });
25269 } else {
25270 self.registered_buffers.remove(&buffer_id);
25271 }
25272 }
25273 }
25274
25275 fn create_style(&self, cx: &App) -> EditorStyle {
25276 let settings = ThemeSettings::get_global(cx);
25277
25278 let mut text_style = match self.mode {
25279 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25280 color: cx.theme().colors().editor_foreground,
25281 font_family: settings.ui_font.family.clone(),
25282 font_features: settings.ui_font.features.clone(),
25283 font_fallbacks: settings.ui_font.fallbacks.clone(),
25284 font_size: rems(0.875).into(),
25285 font_weight: settings.ui_font.weight,
25286 line_height: relative(settings.buffer_line_height.value()),
25287 ..Default::default()
25288 },
25289 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25290 color: cx.theme().colors().editor_foreground,
25291 font_family: settings.buffer_font.family.clone(),
25292 font_features: settings.buffer_font.features.clone(),
25293 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25294 font_size: settings.buffer_font_size(cx).into(),
25295 font_weight: settings.buffer_font.weight,
25296 line_height: relative(settings.buffer_line_height.value()),
25297 ..Default::default()
25298 },
25299 };
25300 if let Some(text_style_refinement) = &self.text_style_refinement {
25301 text_style.refine(text_style_refinement)
25302 }
25303
25304 let background = match self.mode {
25305 EditorMode::SingleLine => cx.theme().system().transparent,
25306 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25307 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25308 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25309 };
25310
25311 EditorStyle {
25312 background,
25313 border: cx.theme().colors().border,
25314 local_player: cx.theme().players().local(),
25315 text: text_style,
25316 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25317 syntax: cx.theme().syntax().clone(),
25318 status: cx.theme().status().clone(),
25319 inlay_hints_style: make_inlay_hints_style(cx),
25320 edit_prediction_styles: make_suggestion_styles(cx),
25321 unnecessary_code_fade: settings.unnecessary_code_fade,
25322 show_underlines: self.diagnostics_enabled(),
25323 }
25324 }
25325
25326 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25327 let multibuffer = self.buffer().read(cx);
25328 let is_singleton = multibuffer.is_singleton();
25329 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25330 let buffer = multibuffer.buffer(*buffer_id)?;
25331
25332 let buffer = buffer.read(cx);
25333 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25334 let mut breadcrumbs = if is_singleton {
25335 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25336 buffer
25337 .snapshot()
25338 .resolve_file_path(
25339 self.project
25340 .as_ref()
25341 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25342 .unwrap_or_default(),
25343 cx,
25344 )
25345 .unwrap_or_else(|| {
25346 if multibuffer.is_singleton() {
25347 multibuffer.title(cx).to_string()
25348 } else {
25349 "untitled".to_string()
25350 }
25351 })
25352 });
25353 vec![HighlightedText {
25354 text: text.into(),
25355 highlights: vec![],
25356 }]
25357 } else {
25358 vec![]
25359 };
25360
25361 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25362 text: symbol.text.clone().into(),
25363 highlights: symbol.highlight_ranges.clone(),
25364 }));
25365 Some(breadcrumbs)
25366 }
25367
25368 fn disable_lsp_data(&mut self) {
25369 self.enable_lsp_data = false;
25370 }
25371
25372 fn disable_runnables(&mut self) {
25373 self.enable_runnables = false;
25374 }
25375
25376 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25377 self.register_visible_buffers(cx);
25378 self.colorize_brackets(false, cx);
25379 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25380 if !self.buffer().read(cx).is_singleton() {
25381 self.update_lsp_data(None, window, cx);
25382 self.refresh_runnables(window, cx);
25383 }
25384 }
25385}
25386
25387fn edit_for_markdown_paste<'a>(
25388 buffer: &MultiBufferSnapshot,
25389 range: Range<MultiBufferOffset>,
25390 to_insert: &'a str,
25391 url: Option<url::Url>,
25392) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25393 if url.is_none() {
25394 return (range, Cow::Borrowed(to_insert));
25395 };
25396
25397 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25398
25399 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25400 Cow::Borrowed(to_insert)
25401 } else {
25402 Cow::Owned(format!("[{old_text}]({to_insert})"))
25403 };
25404 (range, new_text)
25405}
25406
25407fn process_completion_for_edit(
25408 completion: &Completion,
25409 intent: CompletionIntent,
25410 buffer: &Entity<Buffer>,
25411 cursor_position: &text::Anchor,
25412 cx: &mut Context<Editor>,
25413) -> CompletionEdit {
25414 let buffer = buffer.read(cx);
25415 let buffer_snapshot = buffer.snapshot();
25416 let (snippet, new_text) = if completion.is_snippet() {
25417 let mut snippet_source = completion.new_text.clone();
25418 // Workaround for typescript language server issues so that methods don't expand within
25419 // strings and functions with type expressions. The previous point is used because the query
25420 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25421 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25422 let previous_point = if previous_point.column > 0 {
25423 cursor_position.to_previous_offset(&buffer_snapshot)
25424 } else {
25425 cursor_position.to_offset(&buffer_snapshot)
25426 };
25427 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25428 && scope.prefers_label_for_snippet_in_completion()
25429 && let Some(label) = completion.label()
25430 && matches!(
25431 completion.kind(),
25432 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25433 )
25434 {
25435 snippet_source = label;
25436 }
25437 match Snippet::parse(&snippet_source).log_err() {
25438 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25439 None => (None, completion.new_text.clone()),
25440 }
25441 } else {
25442 (None, completion.new_text.clone())
25443 };
25444
25445 let mut range_to_replace = {
25446 let replace_range = &completion.replace_range;
25447 if let CompletionSource::Lsp {
25448 insert_range: Some(insert_range),
25449 ..
25450 } = &completion.source
25451 {
25452 debug_assert_eq!(
25453 insert_range.start, replace_range.start,
25454 "insert_range and replace_range should start at the same position"
25455 );
25456 debug_assert!(
25457 insert_range
25458 .start
25459 .cmp(cursor_position, &buffer_snapshot)
25460 .is_le(),
25461 "insert_range should start before or at cursor position"
25462 );
25463 debug_assert!(
25464 replace_range
25465 .start
25466 .cmp(cursor_position, &buffer_snapshot)
25467 .is_le(),
25468 "replace_range should start before or at cursor position"
25469 );
25470
25471 let should_replace = match intent {
25472 CompletionIntent::CompleteWithInsert => false,
25473 CompletionIntent::CompleteWithReplace => true,
25474 CompletionIntent::Complete | CompletionIntent::Compose => {
25475 let insert_mode =
25476 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25477 .completions
25478 .lsp_insert_mode;
25479 match insert_mode {
25480 LspInsertMode::Insert => false,
25481 LspInsertMode::Replace => true,
25482 LspInsertMode::ReplaceSubsequence => {
25483 let mut text_to_replace = buffer.chars_for_range(
25484 buffer.anchor_before(replace_range.start)
25485 ..buffer.anchor_after(replace_range.end),
25486 );
25487 let mut current_needle = text_to_replace.next();
25488 for haystack_ch in completion.label.text.chars() {
25489 if let Some(needle_ch) = current_needle
25490 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25491 {
25492 current_needle = text_to_replace.next();
25493 }
25494 }
25495 current_needle.is_none()
25496 }
25497 LspInsertMode::ReplaceSuffix => {
25498 if replace_range
25499 .end
25500 .cmp(cursor_position, &buffer_snapshot)
25501 .is_gt()
25502 {
25503 let range_after_cursor = *cursor_position..replace_range.end;
25504 let text_after_cursor = buffer
25505 .text_for_range(
25506 buffer.anchor_before(range_after_cursor.start)
25507 ..buffer.anchor_after(range_after_cursor.end),
25508 )
25509 .collect::<String>()
25510 .to_ascii_lowercase();
25511 completion
25512 .label
25513 .text
25514 .to_ascii_lowercase()
25515 .ends_with(&text_after_cursor)
25516 } else {
25517 true
25518 }
25519 }
25520 }
25521 }
25522 };
25523
25524 if should_replace {
25525 replace_range.clone()
25526 } else {
25527 insert_range.clone()
25528 }
25529 } else {
25530 replace_range.clone()
25531 }
25532 };
25533
25534 if range_to_replace
25535 .end
25536 .cmp(cursor_position, &buffer_snapshot)
25537 .is_lt()
25538 {
25539 range_to_replace.end = *cursor_position;
25540 }
25541
25542 let replace_range = range_to_replace.to_offset(buffer);
25543 CompletionEdit {
25544 new_text,
25545 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25546 snippet,
25547 }
25548}
25549
25550struct CompletionEdit {
25551 new_text: String,
25552 replace_range: Range<BufferOffset>,
25553 snippet: Option<Snippet>,
25554}
25555
25556fn comment_delimiter_for_newline(
25557 start_point: &Point,
25558 buffer: &MultiBufferSnapshot,
25559 language: &LanguageScope,
25560) -> Option<Arc<str>> {
25561 let delimiters = language.line_comment_prefixes();
25562 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25563 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25564
25565 let num_of_whitespaces = snapshot
25566 .chars_for_range(range.clone())
25567 .take_while(|c| c.is_whitespace())
25568 .count();
25569 let comment_candidate = snapshot
25570 .chars_for_range(range.clone())
25571 .skip(num_of_whitespaces)
25572 .take(max_len_of_delimiter + 2)
25573 .collect::<String>();
25574 let (delimiter, trimmed_len, is_repl) = delimiters
25575 .iter()
25576 .filter_map(|delimiter| {
25577 let prefix = delimiter.trim_end();
25578 if comment_candidate.starts_with(prefix) {
25579 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
25580 {
25581 stripped_comment.starts_with(" %%")
25582 } else {
25583 false
25584 };
25585 Some((delimiter, prefix.len(), is_repl))
25586 } else {
25587 None
25588 }
25589 })
25590 .max_by_key(|(_, len, _)| *len)?;
25591
25592 if let Some(BlockCommentConfig {
25593 start: block_start, ..
25594 }) = language.block_comment()
25595 {
25596 let block_start_trimmed = block_start.trim_end();
25597 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25598 let line_content = snapshot
25599 .chars_for_range(range.clone())
25600 .skip(num_of_whitespaces)
25601 .take(block_start_trimmed.len())
25602 .collect::<String>();
25603
25604 if line_content.starts_with(block_start_trimmed) {
25605 return None;
25606 }
25607 }
25608 }
25609
25610 let cursor_is_placed_after_comment_marker =
25611 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25612 if cursor_is_placed_after_comment_marker {
25613 if !is_repl {
25614 return Some(delimiter.clone());
25615 }
25616
25617 let line_content_after_cursor: String = snapshot
25618 .chars_for_range(range)
25619 .skip(start_point.column as usize)
25620 .collect();
25621
25622 if line_content_after_cursor.trim().is_empty() {
25623 return None;
25624 } else {
25625 return Some(delimiter.clone());
25626 }
25627 } else {
25628 None
25629 }
25630}
25631
25632fn documentation_delimiter_for_newline(
25633 start_point: &Point,
25634 buffer: &MultiBufferSnapshot,
25635 language: &LanguageScope,
25636 newline_config: &mut NewlineConfig,
25637) -> Option<Arc<str>> {
25638 let BlockCommentConfig {
25639 start: start_tag,
25640 end: end_tag,
25641 prefix: delimiter,
25642 tab_size: len,
25643 } = language.documentation_comment()?;
25644 let is_within_block_comment = buffer
25645 .language_scope_at(*start_point)
25646 .is_some_and(|scope| scope.override_name() == Some("comment"));
25647 if !is_within_block_comment {
25648 return None;
25649 }
25650
25651 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25652
25653 let num_of_whitespaces = snapshot
25654 .chars_for_range(range.clone())
25655 .take_while(|c| c.is_whitespace())
25656 .count();
25657
25658 // 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.
25659 let column = start_point.column;
25660 let cursor_is_after_start_tag = {
25661 let start_tag_len = start_tag.len();
25662 let start_tag_line = snapshot
25663 .chars_for_range(range.clone())
25664 .skip(num_of_whitespaces)
25665 .take(start_tag_len)
25666 .collect::<String>();
25667 if start_tag_line.starts_with(start_tag.as_ref()) {
25668 num_of_whitespaces + start_tag_len <= column as usize
25669 } else {
25670 false
25671 }
25672 };
25673
25674 let cursor_is_after_delimiter = {
25675 let delimiter_trim = delimiter.trim_end();
25676 let delimiter_line = snapshot
25677 .chars_for_range(range.clone())
25678 .skip(num_of_whitespaces)
25679 .take(delimiter_trim.len())
25680 .collect::<String>();
25681 if delimiter_line.starts_with(delimiter_trim) {
25682 num_of_whitespaces + delimiter_trim.len() <= column as usize
25683 } else {
25684 false
25685 }
25686 };
25687
25688 let mut needs_extra_line = false;
25689 let mut extra_line_additional_indent = IndentSize::spaces(0);
25690
25691 let cursor_is_before_end_tag_if_exists = {
25692 let mut char_position = 0u32;
25693 let mut end_tag_offset = None;
25694
25695 'outer: for chunk in snapshot.text_for_range(range) {
25696 if let Some(byte_pos) = chunk.find(&**end_tag) {
25697 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25698 end_tag_offset = Some(char_position + chars_before_match);
25699 break 'outer;
25700 }
25701 char_position += chunk.chars().count() as u32;
25702 }
25703
25704 if let Some(end_tag_offset) = end_tag_offset {
25705 let cursor_is_before_end_tag = column <= end_tag_offset;
25706 if cursor_is_after_start_tag {
25707 if cursor_is_before_end_tag {
25708 needs_extra_line = true;
25709 }
25710 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25711 if cursor_is_at_start_of_end_tag {
25712 extra_line_additional_indent.len = *len;
25713 }
25714 }
25715 cursor_is_before_end_tag
25716 } else {
25717 true
25718 }
25719 };
25720
25721 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25722 && cursor_is_before_end_tag_if_exists
25723 {
25724 let additional_indent = if cursor_is_after_start_tag {
25725 IndentSize::spaces(*len)
25726 } else {
25727 IndentSize::spaces(0)
25728 };
25729
25730 *newline_config = NewlineConfig::Newline {
25731 additional_indent,
25732 extra_line_additional_indent: if needs_extra_line {
25733 Some(extra_line_additional_indent)
25734 } else {
25735 None
25736 },
25737 prevent_auto_indent: true,
25738 };
25739 Some(delimiter.clone())
25740 } else {
25741 None
25742 }
25743}
25744
25745const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25746
25747fn list_delimiter_for_newline(
25748 start_point: &Point,
25749 buffer: &MultiBufferSnapshot,
25750 language: &LanguageScope,
25751 newline_config: &mut NewlineConfig,
25752) -> Option<Arc<str>> {
25753 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25754
25755 let num_of_whitespaces = snapshot
25756 .chars_for_range(range.clone())
25757 .take_while(|c| c.is_whitespace())
25758 .count();
25759
25760 let task_list_entries: Vec<_> = language
25761 .task_list()
25762 .into_iter()
25763 .flat_map(|config| {
25764 config
25765 .prefixes
25766 .iter()
25767 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25768 })
25769 .collect();
25770 let unordered_list_entries: Vec<_> = language
25771 .unordered_list()
25772 .iter()
25773 .map(|marker| (marker.as_ref(), marker.as_ref()))
25774 .collect();
25775
25776 let all_entries: Vec<_> = task_list_entries
25777 .into_iter()
25778 .chain(unordered_list_entries)
25779 .collect();
25780
25781 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25782 let candidate: String = snapshot
25783 .chars_for_range(range.clone())
25784 .skip(num_of_whitespaces)
25785 .take(max_prefix_len)
25786 .collect();
25787
25788 if let Some((prefix, continuation)) = all_entries
25789 .iter()
25790 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25791 .max_by_key(|(prefix, _)| prefix.len())
25792 {
25793 let end_of_prefix = num_of_whitespaces + prefix.len();
25794 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25795 let has_content_after_marker = snapshot
25796 .chars_for_range(range)
25797 .skip(end_of_prefix)
25798 .any(|c| !c.is_whitespace());
25799
25800 if has_content_after_marker && cursor_is_after_prefix {
25801 return Some((*continuation).into());
25802 }
25803
25804 if start_point.column as usize == end_of_prefix {
25805 if num_of_whitespaces == 0 {
25806 *newline_config = NewlineConfig::ClearCurrentLine;
25807 } else {
25808 *newline_config = NewlineConfig::UnindentCurrentLine {
25809 continuation: (*continuation).into(),
25810 };
25811 }
25812 }
25813
25814 return None;
25815 }
25816 }
25817
25818 let candidate: String = snapshot
25819 .chars_for_range(range.clone())
25820 .skip(num_of_whitespaces)
25821 .take(ORDERED_LIST_MAX_MARKER_LEN)
25822 .collect();
25823
25824 for ordered_config in language.ordered_list() {
25825 let regex = match Regex::new(&ordered_config.pattern) {
25826 Ok(r) => r,
25827 Err(_) => continue,
25828 };
25829
25830 if let Some(captures) = regex.captures(&candidate) {
25831 let full_match = captures.get(0)?;
25832 let marker_len = full_match.len();
25833 let end_of_prefix = num_of_whitespaces + marker_len;
25834 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25835
25836 let has_content_after_marker = snapshot
25837 .chars_for_range(range)
25838 .skip(end_of_prefix)
25839 .any(|c| !c.is_whitespace());
25840
25841 if has_content_after_marker && cursor_is_after_prefix {
25842 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25843 let continuation = ordered_config
25844 .format
25845 .replace("{1}", &(number + 1).to_string());
25846 return Some(continuation.into());
25847 }
25848
25849 if start_point.column as usize == end_of_prefix {
25850 let continuation = ordered_config.format.replace("{1}", "1");
25851 if num_of_whitespaces == 0 {
25852 *newline_config = NewlineConfig::ClearCurrentLine;
25853 } else {
25854 *newline_config = NewlineConfig::UnindentCurrentLine {
25855 continuation: continuation.into(),
25856 };
25857 }
25858 }
25859
25860 return None;
25861 }
25862 }
25863
25864 None
25865}
25866
25867fn is_list_prefix_row(
25868 row: MultiBufferRow,
25869 buffer: &MultiBufferSnapshot,
25870 language: &LanguageScope,
25871) -> bool {
25872 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25873 return false;
25874 };
25875
25876 let num_of_whitespaces = snapshot
25877 .chars_for_range(range.clone())
25878 .take_while(|c| c.is_whitespace())
25879 .count();
25880
25881 let task_list_prefixes: Vec<_> = language
25882 .task_list()
25883 .into_iter()
25884 .flat_map(|config| {
25885 config
25886 .prefixes
25887 .iter()
25888 .map(|p| p.as_ref())
25889 .collect::<Vec<_>>()
25890 })
25891 .collect();
25892 let unordered_list_markers: Vec<_> = language
25893 .unordered_list()
25894 .iter()
25895 .map(|marker| marker.as_ref())
25896 .collect();
25897 let all_prefixes: Vec<_> = task_list_prefixes
25898 .into_iter()
25899 .chain(unordered_list_markers)
25900 .collect();
25901 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25902 let candidate: String = snapshot
25903 .chars_for_range(range.clone())
25904 .skip(num_of_whitespaces)
25905 .take(max_prefix_len)
25906 .collect();
25907 if all_prefixes
25908 .iter()
25909 .any(|prefix| candidate.starts_with(*prefix))
25910 {
25911 return true;
25912 }
25913 }
25914
25915 let ordered_list_candidate: String = snapshot
25916 .chars_for_range(range)
25917 .skip(num_of_whitespaces)
25918 .take(ORDERED_LIST_MAX_MARKER_LEN)
25919 .collect();
25920 for ordered_config in language.ordered_list() {
25921 let regex = match Regex::new(&ordered_config.pattern) {
25922 Ok(r) => r,
25923 Err(_) => continue,
25924 };
25925 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25926 return captures.get(0).is_some();
25927 }
25928 }
25929
25930 false
25931}
25932
25933#[derive(Debug)]
25934enum NewlineConfig {
25935 /// Insert newline with optional additional indent and optional extra blank line
25936 Newline {
25937 additional_indent: IndentSize,
25938 extra_line_additional_indent: Option<IndentSize>,
25939 prevent_auto_indent: bool,
25940 },
25941 /// Clear the current line
25942 ClearCurrentLine,
25943 /// Unindent the current line and add continuation
25944 UnindentCurrentLine { continuation: Arc<str> },
25945}
25946
25947impl NewlineConfig {
25948 fn has_extra_line(&self) -> bool {
25949 matches!(
25950 self,
25951 Self::Newline {
25952 extra_line_additional_indent: Some(_),
25953 ..
25954 }
25955 )
25956 }
25957
25958 fn insert_extra_newline_brackets(
25959 buffer: &MultiBufferSnapshot,
25960 range: Range<MultiBufferOffset>,
25961 language: &language::LanguageScope,
25962 ) -> bool {
25963 let leading_whitespace_len = buffer
25964 .reversed_chars_at(range.start)
25965 .take_while(|c| c.is_whitespace() && *c != '\n')
25966 .map(|c| c.len_utf8())
25967 .sum::<usize>();
25968 let trailing_whitespace_len = buffer
25969 .chars_at(range.end)
25970 .take_while(|c| c.is_whitespace() && *c != '\n')
25971 .map(|c| c.len_utf8())
25972 .sum::<usize>();
25973 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25974
25975 language.brackets().any(|(pair, enabled)| {
25976 let pair_start = pair.start.trim_end();
25977 let pair_end = pair.end.trim_start();
25978
25979 enabled
25980 && pair.newline
25981 && buffer.contains_str_at(range.end, pair_end)
25982 && buffer.contains_str_at(
25983 range.start.saturating_sub_usize(pair_start.len()),
25984 pair_start,
25985 )
25986 })
25987 }
25988
25989 fn insert_extra_newline_tree_sitter(
25990 buffer: &MultiBufferSnapshot,
25991 range: Range<MultiBufferOffset>,
25992 ) -> bool {
25993 let (buffer, range) = match buffer
25994 .range_to_buffer_ranges(range.start..=range.end)
25995 .as_slice()
25996 {
25997 [(buffer, range, _)] => (*buffer, range.clone()),
25998 _ => return false,
25999 };
26000 let pair = {
26001 let mut result: Option<BracketMatch<usize>> = None;
26002
26003 for pair in buffer
26004 .all_bracket_ranges(range.start.0..range.end.0)
26005 .filter(move |pair| {
26006 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26007 })
26008 {
26009 let len = pair.close_range.end - pair.open_range.start;
26010
26011 if let Some(existing) = &result {
26012 let existing_len = existing.close_range.end - existing.open_range.start;
26013 if len > existing_len {
26014 continue;
26015 }
26016 }
26017
26018 result = Some(pair);
26019 }
26020
26021 result
26022 };
26023 let Some(pair) = pair else {
26024 return false;
26025 };
26026 pair.newline_only
26027 && buffer
26028 .chars_for_range(pair.open_range.end..range.start.0)
26029 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26030 .all(|c| c.is_whitespace() && c != '\n')
26031 }
26032}
26033
26034fn update_uncommitted_diff_for_buffer(
26035 editor: Entity<Editor>,
26036 project: &Entity<Project>,
26037 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26038 buffer: Entity<MultiBuffer>,
26039 cx: &mut App,
26040) -> Task<()> {
26041 let mut tasks = Vec::new();
26042 project.update(cx, |project, cx| {
26043 for buffer in buffers {
26044 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26045 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26046 }
26047 }
26048 });
26049 cx.spawn(async move |cx| {
26050 let diffs = future::join_all(tasks).await;
26051 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26052 return;
26053 }
26054
26055 buffer.update(cx, |buffer, cx| {
26056 for diff in diffs.into_iter().flatten() {
26057 buffer.add_diff(diff, cx);
26058 }
26059 });
26060 })
26061}
26062
26063fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26064 let tab_size = tab_size.get() as usize;
26065 let mut width = offset;
26066
26067 for ch in text.chars() {
26068 width += if ch == '\t' {
26069 tab_size - (width % tab_size)
26070 } else {
26071 1
26072 };
26073 }
26074
26075 width - offset
26076}
26077
26078#[cfg(test)]
26079mod tests {
26080 use super::*;
26081
26082 #[test]
26083 fn test_string_size_with_expanded_tabs() {
26084 let nz = |val| NonZeroU32::new(val).unwrap();
26085 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26086 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26087 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26088 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26089 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26090 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26091 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26092 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26093 }
26094}
26095
26096/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26097struct WordBreakingTokenizer<'a> {
26098 input: &'a str,
26099}
26100
26101impl<'a> WordBreakingTokenizer<'a> {
26102 fn new(input: &'a str) -> Self {
26103 Self { input }
26104 }
26105}
26106
26107fn is_char_ideographic(ch: char) -> bool {
26108 use unicode_script::Script::*;
26109 use unicode_script::UnicodeScript;
26110 matches!(ch.script(), Han | Tangut | Yi)
26111}
26112
26113fn is_grapheme_ideographic(text: &str) -> bool {
26114 text.chars().any(is_char_ideographic)
26115}
26116
26117fn is_grapheme_whitespace(text: &str) -> bool {
26118 text.chars().any(|x| x.is_whitespace())
26119}
26120
26121fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26122 text.chars()
26123 .next()
26124 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26125}
26126
26127#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26128enum WordBreakToken<'a> {
26129 Word { token: &'a str, grapheme_len: usize },
26130 InlineWhitespace { token: &'a str, grapheme_len: usize },
26131 Newline,
26132}
26133
26134impl<'a> Iterator for WordBreakingTokenizer<'a> {
26135 /// Yields a span, the count of graphemes in the token, and whether it was
26136 /// whitespace. Note that it also breaks at word boundaries.
26137 type Item = WordBreakToken<'a>;
26138
26139 fn next(&mut self) -> Option<Self::Item> {
26140 use unicode_segmentation::UnicodeSegmentation;
26141 if self.input.is_empty() {
26142 return None;
26143 }
26144
26145 let mut iter = self.input.graphemes(true).peekable();
26146 let mut offset = 0;
26147 let mut grapheme_len = 0;
26148 if let Some(first_grapheme) = iter.next() {
26149 let is_newline = first_grapheme == "\n";
26150 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26151 offset += first_grapheme.len();
26152 grapheme_len += 1;
26153 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26154 if let Some(grapheme) = iter.peek().copied()
26155 && should_stay_with_preceding_ideograph(grapheme)
26156 {
26157 offset += grapheme.len();
26158 grapheme_len += 1;
26159 }
26160 } else {
26161 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26162 let mut next_word_bound = words.peek().copied();
26163 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26164 next_word_bound = words.next();
26165 }
26166 while let Some(grapheme) = iter.peek().copied() {
26167 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26168 break;
26169 };
26170 if is_grapheme_whitespace(grapheme) != is_whitespace
26171 || (grapheme == "\n") != is_newline
26172 {
26173 break;
26174 };
26175 offset += grapheme.len();
26176 grapheme_len += 1;
26177 iter.next();
26178 }
26179 }
26180 let token = &self.input[..offset];
26181 self.input = &self.input[offset..];
26182 if token == "\n" {
26183 Some(WordBreakToken::Newline)
26184 } else if is_whitespace {
26185 Some(WordBreakToken::InlineWhitespace {
26186 token,
26187 grapheme_len,
26188 })
26189 } else {
26190 Some(WordBreakToken::Word {
26191 token,
26192 grapheme_len,
26193 })
26194 }
26195 } else {
26196 None
26197 }
26198 }
26199}
26200
26201#[test]
26202fn test_word_breaking_tokenizer() {
26203 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26204 ("", &[]),
26205 (" ", &[whitespace(" ", 2)]),
26206 ("Ʒ", &[word("Ʒ", 1)]),
26207 ("Ǽ", &[word("Ǽ", 1)]),
26208 ("⋑", &[word("⋑", 1)]),
26209 ("⋑⋑", &[word("⋑⋑", 2)]),
26210 (
26211 "原理,进而",
26212 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26213 ),
26214 (
26215 "hello world",
26216 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26217 ),
26218 (
26219 "hello, world",
26220 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26221 ),
26222 (
26223 " hello world",
26224 &[
26225 whitespace(" ", 2),
26226 word("hello", 5),
26227 whitespace(" ", 1),
26228 word("world", 5),
26229 ],
26230 ),
26231 (
26232 "这是什么 \n 钢笔",
26233 &[
26234 word("这", 1),
26235 word("是", 1),
26236 word("什", 1),
26237 word("么", 1),
26238 whitespace(" ", 1),
26239 newline(),
26240 whitespace(" ", 1),
26241 word("钢", 1),
26242 word("笔", 1),
26243 ],
26244 ),
26245 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26246 ];
26247
26248 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26249 WordBreakToken::Word {
26250 token,
26251 grapheme_len,
26252 }
26253 }
26254
26255 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26256 WordBreakToken::InlineWhitespace {
26257 token,
26258 grapheme_len,
26259 }
26260 }
26261
26262 fn newline() -> WordBreakToken<'static> {
26263 WordBreakToken::Newline
26264 }
26265
26266 for (input, result) in tests {
26267 assert_eq!(
26268 WordBreakingTokenizer::new(input)
26269 .collect::<Vec<_>>()
26270 .as_slice(),
26271 *result,
26272 );
26273 }
26274}
26275
26276fn wrap_with_prefix(
26277 first_line_prefix: String,
26278 subsequent_lines_prefix: String,
26279 unwrapped_text: String,
26280 wrap_column: usize,
26281 tab_size: NonZeroU32,
26282 preserve_existing_whitespace: bool,
26283) -> String {
26284 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26285 let subsequent_lines_prefix_len =
26286 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26287 let mut wrapped_text = String::new();
26288 let mut current_line = first_line_prefix;
26289 let mut is_first_line = true;
26290
26291 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26292 let mut current_line_len = first_line_prefix_len;
26293 let mut in_whitespace = false;
26294 for token in tokenizer {
26295 let have_preceding_whitespace = in_whitespace;
26296 match token {
26297 WordBreakToken::Word {
26298 token,
26299 grapheme_len,
26300 } => {
26301 in_whitespace = false;
26302 let current_prefix_len = if is_first_line {
26303 first_line_prefix_len
26304 } else {
26305 subsequent_lines_prefix_len
26306 };
26307 if current_line_len + grapheme_len > wrap_column
26308 && current_line_len != current_prefix_len
26309 {
26310 wrapped_text.push_str(current_line.trim_end());
26311 wrapped_text.push('\n');
26312 is_first_line = false;
26313 current_line = subsequent_lines_prefix.clone();
26314 current_line_len = subsequent_lines_prefix_len;
26315 }
26316 current_line.push_str(token);
26317 current_line_len += grapheme_len;
26318 }
26319 WordBreakToken::InlineWhitespace {
26320 mut token,
26321 mut grapheme_len,
26322 } => {
26323 in_whitespace = true;
26324 if have_preceding_whitespace && !preserve_existing_whitespace {
26325 continue;
26326 }
26327 if !preserve_existing_whitespace {
26328 // Keep a single whitespace grapheme as-is
26329 if let Some(first) =
26330 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26331 {
26332 token = first;
26333 } else {
26334 token = " ";
26335 }
26336 grapheme_len = 1;
26337 }
26338 let current_prefix_len = if is_first_line {
26339 first_line_prefix_len
26340 } else {
26341 subsequent_lines_prefix_len
26342 };
26343 if current_line_len + grapheme_len > wrap_column {
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 || preserve_existing_whitespace {
26350 current_line.push_str(token);
26351 current_line_len += grapheme_len;
26352 }
26353 }
26354 WordBreakToken::Newline => {
26355 in_whitespace = true;
26356 let current_prefix_len = if is_first_line {
26357 first_line_prefix_len
26358 } else {
26359 subsequent_lines_prefix_len
26360 };
26361 if preserve_existing_whitespace {
26362 wrapped_text.push_str(current_line.trim_end());
26363 wrapped_text.push('\n');
26364 is_first_line = false;
26365 current_line = subsequent_lines_prefix.clone();
26366 current_line_len = subsequent_lines_prefix_len;
26367 } else if have_preceding_whitespace {
26368 continue;
26369 } else if current_line_len + 1 > wrap_column
26370 && current_line_len != current_prefix_len
26371 {
26372 wrapped_text.push_str(current_line.trim_end());
26373 wrapped_text.push('\n');
26374 is_first_line = false;
26375 current_line = subsequent_lines_prefix.clone();
26376 current_line_len = subsequent_lines_prefix_len;
26377 } else if current_line_len != current_prefix_len {
26378 current_line.push(' ');
26379 current_line_len += 1;
26380 }
26381 }
26382 }
26383 }
26384
26385 if !current_line.is_empty() {
26386 wrapped_text.push_str(¤t_line);
26387 }
26388 wrapped_text
26389}
26390
26391#[test]
26392fn test_wrap_with_prefix() {
26393 assert_eq!(
26394 wrap_with_prefix(
26395 "# ".to_string(),
26396 "# ".to_string(),
26397 "abcdefg".to_string(),
26398 4,
26399 NonZeroU32::new(4).unwrap(),
26400 false,
26401 ),
26402 "# abcdefg"
26403 );
26404 assert_eq!(
26405 wrap_with_prefix(
26406 "".to_string(),
26407 "".to_string(),
26408 "\thello world".to_string(),
26409 8,
26410 NonZeroU32::new(4).unwrap(),
26411 false,
26412 ),
26413 "hello\nworld"
26414 );
26415 assert_eq!(
26416 wrap_with_prefix(
26417 "// ".to_string(),
26418 "// ".to_string(),
26419 "xx \nyy zz aa bb cc".to_string(),
26420 12,
26421 NonZeroU32::new(4).unwrap(),
26422 false,
26423 ),
26424 "// xx yy zz\n// aa bb cc"
26425 );
26426 assert_eq!(
26427 wrap_with_prefix(
26428 String::new(),
26429 String::new(),
26430 "这是什么 \n 钢笔".to_string(),
26431 3,
26432 NonZeroU32::new(4).unwrap(),
26433 false,
26434 ),
26435 "这是什\n么 钢\n笔"
26436 );
26437 assert_eq!(
26438 wrap_with_prefix(
26439 String::new(),
26440 String::new(),
26441 format!("foo{}bar", '\u{2009}'), // thin space
26442 80,
26443 NonZeroU32::new(4).unwrap(),
26444 false,
26445 ),
26446 format!("foo{}bar", '\u{2009}')
26447 );
26448}
26449
26450pub trait CollaborationHub {
26451 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26452 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26453 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26454}
26455
26456impl CollaborationHub for Entity<Project> {
26457 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26458 self.read(cx).collaborators()
26459 }
26460
26461 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26462 self.read(cx).user_store().read(cx).participant_indices()
26463 }
26464
26465 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26466 let this = self.read(cx);
26467 let user_ids = this.collaborators().values().map(|c| c.user_id);
26468 this.user_store().read(cx).participant_names(user_ids, cx)
26469 }
26470}
26471
26472pub trait SemanticsProvider {
26473 fn hover(
26474 &self,
26475 buffer: &Entity<Buffer>,
26476 position: text::Anchor,
26477 cx: &mut App,
26478 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26479
26480 fn inline_values(
26481 &self,
26482 buffer_handle: Entity<Buffer>,
26483 range: Range<text::Anchor>,
26484 cx: &mut App,
26485 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26486
26487 fn applicable_inlay_chunks(
26488 &self,
26489 buffer: &Entity<Buffer>,
26490 ranges: &[Range<text::Anchor>],
26491 cx: &mut App,
26492 ) -> Vec<Range<BufferRow>>;
26493
26494 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26495
26496 fn inlay_hints(
26497 &self,
26498 invalidate: InvalidationStrategy,
26499 buffer: Entity<Buffer>,
26500 ranges: Vec<Range<text::Anchor>>,
26501 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26502 cx: &mut App,
26503 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26504
26505 fn semantic_tokens(
26506 &self,
26507 buffer: Entity<Buffer>,
26508 refresh: Option<RefreshForServer>,
26509 cx: &mut App,
26510 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
26511
26512 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26513
26514 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26515
26516 fn document_highlights(
26517 &self,
26518 buffer: &Entity<Buffer>,
26519 position: text::Anchor,
26520 cx: &mut App,
26521 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26522
26523 fn definitions(
26524 &self,
26525 buffer: &Entity<Buffer>,
26526 position: text::Anchor,
26527 kind: GotoDefinitionKind,
26528 cx: &mut App,
26529 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26530
26531 fn range_for_rename(
26532 &self,
26533 buffer: &Entity<Buffer>,
26534 position: text::Anchor,
26535 cx: &mut App,
26536 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26537
26538 fn perform_rename(
26539 &self,
26540 buffer: &Entity<Buffer>,
26541 position: text::Anchor,
26542 new_name: String,
26543 cx: &mut App,
26544 ) -> Option<Task<Result<ProjectTransaction>>>;
26545}
26546
26547pub trait CompletionProvider {
26548 fn completions(
26549 &self,
26550 excerpt_id: ExcerptId,
26551 buffer: &Entity<Buffer>,
26552 buffer_position: text::Anchor,
26553 trigger: CompletionContext,
26554 window: &mut Window,
26555 cx: &mut Context<Editor>,
26556 ) -> Task<Result<Vec<CompletionResponse>>>;
26557
26558 fn resolve_completions(
26559 &self,
26560 _buffer: Entity<Buffer>,
26561 _completion_indices: Vec<usize>,
26562 _completions: Rc<RefCell<Box<[Completion]>>>,
26563 _cx: &mut Context<Editor>,
26564 ) -> Task<Result<bool>> {
26565 Task::ready(Ok(false))
26566 }
26567
26568 fn apply_additional_edits_for_completion(
26569 &self,
26570 _buffer: Entity<Buffer>,
26571 _completions: Rc<RefCell<Box<[Completion]>>>,
26572 _completion_index: usize,
26573 _push_to_history: bool,
26574 _cx: &mut Context<Editor>,
26575 ) -> Task<Result<Option<language::Transaction>>> {
26576 Task::ready(Ok(None))
26577 }
26578
26579 fn is_completion_trigger(
26580 &self,
26581 buffer: &Entity<Buffer>,
26582 position: language::Anchor,
26583 text: &str,
26584 trigger_in_words: bool,
26585 cx: &mut Context<Editor>,
26586 ) -> bool;
26587
26588 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26589
26590 fn sort_completions(&self) -> bool {
26591 true
26592 }
26593
26594 fn filter_completions(&self) -> bool {
26595 true
26596 }
26597
26598 fn show_snippets(&self) -> bool {
26599 false
26600 }
26601}
26602
26603pub trait CodeActionProvider {
26604 fn id(&self) -> Arc<str>;
26605
26606 fn code_actions(
26607 &self,
26608 buffer: &Entity<Buffer>,
26609 range: Range<text::Anchor>,
26610 window: &mut Window,
26611 cx: &mut App,
26612 ) -> Task<Result<Vec<CodeAction>>>;
26613
26614 fn apply_code_action(
26615 &self,
26616 buffer_handle: Entity<Buffer>,
26617 action: CodeAction,
26618 excerpt_id: ExcerptId,
26619 push_to_history: bool,
26620 window: &mut Window,
26621 cx: &mut App,
26622 ) -> Task<Result<ProjectTransaction>>;
26623}
26624
26625impl CodeActionProvider for Entity<Project> {
26626 fn id(&self) -> Arc<str> {
26627 "project".into()
26628 }
26629
26630 fn code_actions(
26631 &self,
26632 buffer: &Entity<Buffer>,
26633 range: Range<text::Anchor>,
26634 _window: &mut Window,
26635 cx: &mut App,
26636 ) -> Task<Result<Vec<CodeAction>>> {
26637 self.update(cx, |project, cx| {
26638 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26639 let code_actions = project.code_actions(buffer, range, None, cx);
26640 cx.background_spawn(async move {
26641 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26642 Ok(code_lens_actions
26643 .context("code lens fetch")?
26644 .into_iter()
26645 .flatten()
26646 .chain(
26647 code_actions
26648 .context("code action fetch")?
26649 .into_iter()
26650 .flatten(),
26651 )
26652 .collect())
26653 })
26654 })
26655 }
26656
26657 fn apply_code_action(
26658 &self,
26659 buffer_handle: Entity<Buffer>,
26660 action: CodeAction,
26661 _excerpt_id: ExcerptId,
26662 push_to_history: bool,
26663 _window: &mut Window,
26664 cx: &mut App,
26665 ) -> Task<Result<ProjectTransaction>> {
26666 self.update(cx, |project, cx| {
26667 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26668 })
26669 }
26670}
26671
26672fn snippet_completions(
26673 project: &Project,
26674 buffer: &Entity<Buffer>,
26675 buffer_anchor: text::Anchor,
26676 classifier: CharClassifier,
26677 cx: &mut App,
26678) -> Task<Result<CompletionResponse>> {
26679 let languages = buffer.read(cx).languages_at(buffer_anchor);
26680 let snippet_store = project.snippets().read(cx);
26681
26682 let scopes: Vec<_> = languages
26683 .iter()
26684 .filter_map(|language| {
26685 let language_name = language.lsp_id();
26686 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26687
26688 if snippets.is_empty() {
26689 None
26690 } else {
26691 Some((language.default_scope(), snippets))
26692 }
26693 })
26694 .collect();
26695
26696 if scopes.is_empty() {
26697 return Task::ready(Ok(CompletionResponse {
26698 completions: vec![],
26699 display_options: CompletionDisplayOptions::default(),
26700 is_incomplete: false,
26701 }));
26702 }
26703
26704 let snapshot = buffer.read(cx).text_snapshot();
26705 let executor = cx.background_executor().clone();
26706
26707 cx.background_spawn(async move {
26708 let is_word_char = |c| classifier.is_word(c);
26709
26710 let mut is_incomplete = false;
26711 let mut completions: Vec<Completion> = Vec::new();
26712
26713 const MAX_PREFIX_LEN: usize = 128;
26714 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26715 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26716 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26717
26718 let max_buffer_window: String = snapshot
26719 .text_for_range(window_start..buffer_offset)
26720 .collect();
26721
26722 if max_buffer_window.is_empty() {
26723 return Ok(CompletionResponse {
26724 completions: vec![],
26725 display_options: CompletionDisplayOptions::default(),
26726 is_incomplete: true,
26727 });
26728 }
26729
26730 for (_scope, snippets) in scopes.into_iter() {
26731 // Sort snippets by word count to match longer snippet prefixes first.
26732 let mut sorted_snippet_candidates = snippets
26733 .iter()
26734 .enumerate()
26735 .flat_map(|(snippet_ix, snippet)| {
26736 snippet
26737 .prefix
26738 .iter()
26739 .enumerate()
26740 .map(move |(prefix_ix, prefix)| {
26741 let word_count =
26742 snippet_candidate_suffixes(prefix, &is_word_char).count();
26743 ((snippet_ix, prefix_ix), prefix, word_count)
26744 })
26745 })
26746 .collect_vec();
26747 sorted_snippet_candidates
26748 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26749
26750 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26751
26752 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
26753 .take(
26754 sorted_snippet_candidates
26755 .first()
26756 .map(|(_, _, word_count)| *word_count)
26757 .unwrap_or_default(),
26758 )
26759 .collect_vec();
26760
26761 const MAX_RESULTS: usize = 100;
26762 // Each match also remembers how many characters from the buffer it consumed
26763 let mut matches: Vec<(StringMatch, usize)> = vec![];
26764
26765 let mut snippet_list_cutoff_index = 0;
26766 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26767 let word_count = buffer_index + 1;
26768 // Increase `snippet_list_cutoff_index` until we have all of the
26769 // snippets with sufficiently many words.
26770 while sorted_snippet_candidates
26771 .get(snippet_list_cutoff_index)
26772 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26773 *snippet_word_count >= word_count
26774 })
26775 {
26776 snippet_list_cutoff_index += 1;
26777 }
26778
26779 // Take only the candidates with at least `word_count` many words
26780 let snippet_candidates_at_word_len =
26781 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26782
26783 let candidates = snippet_candidates_at_word_len
26784 .iter()
26785 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26786 .enumerate() // index in `sorted_snippet_candidates`
26787 // First char must match
26788 .filter(|(_ix, prefix)| {
26789 itertools::equal(
26790 prefix
26791 .chars()
26792 .next()
26793 .into_iter()
26794 .flat_map(|c| c.to_lowercase()),
26795 buffer_window
26796 .chars()
26797 .next()
26798 .into_iter()
26799 .flat_map(|c| c.to_lowercase()),
26800 )
26801 })
26802 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26803 .collect::<Vec<StringMatchCandidate>>();
26804
26805 matches.extend(
26806 fuzzy::match_strings(
26807 &candidates,
26808 &buffer_window,
26809 buffer_window.chars().any(|c| c.is_uppercase()),
26810 true,
26811 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26812 &Default::default(),
26813 executor.clone(),
26814 )
26815 .await
26816 .into_iter()
26817 .map(|string_match| (string_match, buffer_window.len())),
26818 );
26819
26820 if matches.len() >= MAX_RESULTS {
26821 break;
26822 }
26823 }
26824
26825 let to_lsp = |point: &text::Anchor| {
26826 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26827 point_to_lsp(end)
26828 };
26829 let lsp_end = to_lsp(&buffer_anchor);
26830
26831 if matches.len() >= MAX_RESULTS {
26832 is_incomplete = true;
26833 }
26834
26835 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26836 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26837 sorted_snippet_candidates[string_match.candidate_id];
26838 let snippet = &snippets[snippet_index];
26839 let start = buffer_offset - buffer_window_len;
26840 let start = snapshot.anchor_before(start);
26841 let range = start..buffer_anchor;
26842 let lsp_start = to_lsp(&start);
26843 let lsp_range = lsp::Range {
26844 start: lsp_start,
26845 end: lsp_end,
26846 };
26847 Completion {
26848 replace_range: range,
26849 new_text: snippet.body.clone(),
26850 source: CompletionSource::Lsp {
26851 insert_range: None,
26852 server_id: LanguageServerId(usize::MAX),
26853 resolved: true,
26854 lsp_completion: Box::new(lsp::CompletionItem {
26855 label: snippet.prefix.first().unwrap().clone(),
26856 kind: Some(CompletionItemKind::SNIPPET),
26857 label_details: snippet.description.as_ref().map(|description| {
26858 lsp::CompletionItemLabelDetails {
26859 detail: Some(description.clone()),
26860 description: None,
26861 }
26862 }),
26863 insert_text_format: Some(InsertTextFormat::SNIPPET),
26864 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26865 lsp::InsertReplaceEdit {
26866 new_text: snippet.body.clone(),
26867 insert: lsp_range,
26868 replace: lsp_range,
26869 },
26870 )),
26871 filter_text: Some(snippet.body.clone()),
26872 sort_text: Some(char::MAX.to_string()),
26873 ..lsp::CompletionItem::default()
26874 }),
26875 lsp_defaults: None,
26876 },
26877 label: CodeLabel {
26878 text: matching_prefix.clone(),
26879 runs: Vec::new(),
26880 filter_range: 0..matching_prefix.len(),
26881 },
26882 icon_path: None,
26883 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26884 single_line: snippet.name.clone().into(),
26885 plain_text: snippet
26886 .description
26887 .clone()
26888 .map(|description| description.into()),
26889 }),
26890 insert_text_mode: None,
26891 confirm: None,
26892 match_start: Some(start),
26893 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26894 }
26895 }));
26896 }
26897
26898 Ok(CompletionResponse {
26899 completions,
26900 display_options: CompletionDisplayOptions::default(),
26901 is_incomplete,
26902 })
26903 })
26904}
26905
26906impl CompletionProvider for Entity<Project> {
26907 fn completions(
26908 &self,
26909 _excerpt_id: ExcerptId,
26910 buffer: &Entity<Buffer>,
26911 buffer_position: text::Anchor,
26912 options: CompletionContext,
26913 _window: &mut Window,
26914 cx: &mut Context<Editor>,
26915 ) -> Task<Result<Vec<CompletionResponse>>> {
26916 self.update(cx, |project, cx| {
26917 let task = project.completions(buffer, buffer_position, options, cx);
26918 cx.background_spawn(task)
26919 })
26920 }
26921
26922 fn resolve_completions(
26923 &self,
26924 buffer: Entity<Buffer>,
26925 completion_indices: Vec<usize>,
26926 completions: Rc<RefCell<Box<[Completion]>>>,
26927 cx: &mut Context<Editor>,
26928 ) -> Task<Result<bool>> {
26929 self.update(cx, |project, cx| {
26930 project.lsp_store().update(cx, |lsp_store, cx| {
26931 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26932 })
26933 })
26934 }
26935
26936 fn apply_additional_edits_for_completion(
26937 &self,
26938 buffer: Entity<Buffer>,
26939 completions: Rc<RefCell<Box<[Completion]>>>,
26940 completion_index: usize,
26941 push_to_history: bool,
26942 cx: &mut Context<Editor>,
26943 ) -> Task<Result<Option<language::Transaction>>> {
26944 self.update(cx, |project, cx| {
26945 project.lsp_store().update(cx, |lsp_store, cx| {
26946 lsp_store.apply_additional_edits_for_completion(
26947 buffer,
26948 completions,
26949 completion_index,
26950 push_to_history,
26951 cx,
26952 )
26953 })
26954 })
26955 }
26956
26957 fn is_completion_trigger(
26958 &self,
26959 buffer: &Entity<Buffer>,
26960 position: language::Anchor,
26961 text: &str,
26962 trigger_in_words: bool,
26963 cx: &mut Context<Editor>,
26964 ) -> bool {
26965 let mut chars = text.chars();
26966 let char = if let Some(char) = chars.next() {
26967 char
26968 } else {
26969 return false;
26970 };
26971 if chars.next().is_some() {
26972 return false;
26973 }
26974
26975 let buffer = buffer.read(cx);
26976 let snapshot = buffer.snapshot();
26977 let classifier = snapshot
26978 .char_classifier_at(position)
26979 .scope_context(Some(CharScopeContext::Completion));
26980 if trigger_in_words && classifier.is_word(char) {
26981 return true;
26982 }
26983
26984 buffer.completion_triggers().contains(text)
26985 }
26986
26987 fn show_snippets(&self) -> bool {
26988 true
26989 }
26990}
26991
26992impl SemanticsProvider for WeakEntity<Project> {
26993 fn hover(
26994 &self,
26995 buffer: &Entity<Buffer>,
26996 position: text::Anchor,
26997 cx: &mut App,
26998 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26999 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27000 .ok()
27001 }
27002
27003 fn document_highlights(
27004 &self,
27005 buffer: &Entity<Buffer>,
27006 position: text::Anchor,
27007 cx: &mut App,
27008 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27009 self.update(cx, |project, cx| {
27010 project.document_highlights(buffer, position, cx)
27011 })
27012 .ok()
27013 }
27014
27015 fn definitions(
27016 &self,
27017 buffer: &Entity<Buffer>,
27018 position: text::Anchor,
27019 kind: GotoDefinitionKind,
27020 cx: &mut App,
27021 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27022 self.update(cx, |project, cx| match kind {
27023 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27024 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27025 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27026 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27027 })
27028 .ok()
27029 }
27030
27031 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27032 self.update(cx, |project, cx| {
27033 if project
27034 .active_debug_session(cx)
27035 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27036 {
27037 return true;
27038 }
27039
27040 buffer.update(cx, |buffer, cx| {
27041 project.any_language_server_supports_inlay_hints(buffer, cx)
27042 })
27043 })
27044 .unwrap_or(false)
27045 }
27046
27047 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27048 self.update(cx, |project, cx| {
27049 buffer.update(cx, |buffer, cx| {
27050 project.any_language_server_supports_semantic_tokens(buffer, cx)
27051 })
27052 })
27053 .unwrap_or(false)
27054 }
27055
27056 fn inline_values(
27057 &self,
27058 buffer_handle: Entity<Buffer>,
27059 range: Range<text::Anchor>,
27060 cx: &mut App,
27061 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27062 self.update(cx, |project, cx| {
27063 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27064
27065 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27066 })
27067 .ok()
27068 .flatten()
27069 }
27070
27071 fn applicable_inlay_chunks(
27072 &self,
27073 buffer: &Entity<Buffer>,
27074 ranges: &[Range<text::Anchor>],
27075 cx: &mut App,
27076 ) -> Vec<Range<BufferRow>> {
27077 self.update(cx, |project, cx| {
27078 project.lsp_store().update(cx, |lsp_store, cx| {
27079 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27080 })
27081 })
27082 .unwrap_or_default()
27083 }
27084
27085 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27086 self.update(cx, |project, cx| {
27087 project.lsp_store().update(cx, |lsp_store, _| {
27088 lsp_store.invalidate_inlay_hints(for_buffers)
27089 })
27090 })
27091 .ok();
27092 }
27093
27094 fn inlay_hints(
27095 &self,
27096 invalidate: InvalidationStrategy,
27097 buffer: Entity<Buffer>,
27098 ranges: Vec<Range<text::Anchor>>,
27099 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27100 cx: &mut App,
27101 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27102 self.update(cx, |project, cx| {
27103 project.lsp_store().update(cx, |lsp_store, cx| {
27104 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27105 })
27106 })
27107 .ok()
27108 }
27109
27110 fn semantic_tokens(
27111 &self,
27112 buffer: Entity<Buffer>,
27113 refresh: Option<RefreshForServer>,
27114 cx: &mut App,
27115 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27116 self.update(cx, |this, cx| {
27117 this.lsp_store().update(cx, |lsp_store, cx| {
27118 lsp_store.semantic_tokens(buffer, refresh, cx)
27119 })
27120 })
27121 .ok()
27122 }
27123
27124 fn range_for_rename(
27125 &self,
27126 buffer: &Entity<Buffer>,
27127 position: text::Anchor,
27128 cx: &mut App,
27129 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27130 self.update(cx, |project, cx| {
27131 let buffer = buffer.clone();
27132 let task = project.prepare_rename(buffer.clone(), position, cx);
27133 cx.spawn(async move |_, cx| {
27134 Ok(match task.await? {
27135 PrepareRenameResponse::Success(range) => Some(range),
27136 PrepareRenameResponse::InvalidPosition => None,
27137 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27138 // Fallback on using TreeSitter info to determine identifier range
27139 buffer.read_with(cx, |buffer, _| {
27140 let snapshot = buffer.snapshot();
27141 let (range, kind) = snapshot.surrounding_word(position, None);
27142 if kind != Some(CharKind::Word) {
27143 return None;
27144 }
27145 Some(
27146 snapshot.anchor_before(range.start)
27147 ..snapshot.anchor_after(range.end),
27148 )
27149 })
27150 }
27151 })
27152 })
27153 })
27154 .ok()
27155 }
27156
27157 fn perform_rename(
27158 &self,
27159 buffer: &Entity<Buffer>,
27160 position: text::Anchor,
27161 new_name: String,
27162 cx: &mut App,
27163 ) -> Option<Task<Result<ProjectTransaction>>> {
27164 self.update(cx, |project, cx| {
27165 project.perform_rename(buffer.clone(), position, new_name, cx)
27166 })
27167 .ok()
27168 }
27169}
27170
27171fn consume_contiguous_rows(
27172 contiguous_row_selections: &mut Vec<Selection<Point>>,
27173 selection: &Selection<Point>,
27174 display_map: &DisplaySnapshot,
27175 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27176) -> (MultiBufferRow, MultiBufferRow) {
27177 contiguous_row_selections.push(selection.clone());
27178 let start_row = starting_row(selection, display_map);
27179 let mut end_row = ending_row(selection, display_map);
27180
27181 while let Some(next_selection) = selections.peek() {
27182 if next_selection.start.row <= end_row.0 {
27183 end_row = ending_row(next_selection, display_map);
27184 contiguous_row_selections.push(selections.next().unwrap().clone());
27185 } else {
27186 break;
27187 }
27188 }
27189 (start_row, end_row)
27190}
27191
27192fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27193 if selection.start.column > 0 {
27194 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27195 } else {
27196 MultiBufferRow(selection.start.row)
27197 }
27198}
27199
27200fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27201 if next_selection.end.column > 0 || next_selection.is_empty() {
27202 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27203 } else {
27204 MultiBufferRow(next_selection.end.row)
27205 }
27206}
27207
27208impl EditorSnapshot {
27209 pub fn remote_selections_in_range<'a>(
27210 &'a self,
27211 range: &'a Range<Anchor>,
27212 collaboration_hub: &dyn CollaborationHub,
27213 cx: &'a App,
27214 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27215 let participant_names = collaboration_hub.user_names(cx);
27216 let participant_indices = collaboration_hub.user_participant_indices(cx);
27217 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27218 let collaborators_by_replica_id = collaborators_by_peer_id
27219 .values()
27220 .map(|collaborator| (collaborator.replica_id, collaborator))
27221 .collect::<HashMap<_, _>>();
27222 self.buffer_snapshot()
27223 .selections_in_range(range, false)
27224 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27225 if replica_id == ReplicaId::AGENT {
27226 Some(RemoteSelection {
27227 replica_id,
27228 selection,
27229 cursor_shape,
27230 line_mode,
27231 collaborator_id: CollaboratorId::Agent,
27232 user_name: Some("Agent".into()),
27233 color: cx.theme().players().agent(),
27234 })
27235 } else {
27236 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27237 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27238 let user_name = participant_names.get(&collaborator.user_id).cloned();
27239 Some(RemoteSelection {
27240 replica_id,
27241 selection,
27242 cursor_shape,
27243 line_mode,
27244 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27245 user_name,
27246 color: if let Some(index) = participant_index {
27247 cx.theme().players().color_for_participant(index.0)
27248 } else {
27249 cx.theme().players().absent()
27250 },
27251 })
27252 }
27253 })
27254 }
27255
27256 pub fn hunks_for_ranges(
27257 &self,
27258 ranges: impl IntoIterator<Item = Range<Point>>,
27259 ) -> Vec<MultiBufferDiffHunk> {
27260 let mut hunks = Vec::new();
27261 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27262 HashMap::default();
27263 for query_range in ranges {
27264 let query_rows =
27265 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27266 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27267 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27268 ) {
27269 // Include deleted hunks that are adjacent to the query range, because
27270 // otherwise they would be missed.
27271 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27272 if hunk.status().is_deleted() {
27273 intersects_range |= hunk.row_range.start == query_rows.end;
27274 intersects_range |= hunk.row_range.end == query_rows.start;
27275 }
27276 if intersects_range {
27277 if !processed_buffer_rows
27278 .entry(hunk.buffer_id)
27279 .or_default()
27280 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27281 {
27282 continue;
27283 }
27284 hunks.push(hunk);
27285 }
27286 }
27287 }
27288
27289 hunks
27290 }
27291
27292 fn display_diff_hunks_for_rows<'a>(
27293 &'a self,
27294 display_rows: Range<DisplayRow>,
27295 folded_buffers: &'a HashSet<BufferId>,
27296 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27297 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27298 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27299
27300 self.buffer_snapshot()
27301 .diff_hunks_in_range(buffer_start..buffer_end)
27302 .filter_map(|hunk| {
27303 if folded_buffers.contains(&hunk.buffer_id)
27304 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27305 {
27306 return None;
27307 }
27308
27309 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27310 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27311 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27312 let line_len = self.buffer_snapshot().line_len(last_row);
27313 Point::new(last_row.0, line_len)
27314 } else {
27315 Point::new(hunk.row_range.end.0, 0)
27316 };
27317
27318 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27319 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27320
27321 let display_hunk = if hunk_display_start.column() != 0 {
27322 DisplayDiffHunk::Folded {
27323 display_row: hunk_display_start.row(),
27324 }
27325 } else {
27326 let mut end_row = hunk_display_end.row();
27327 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27328 end_row.0 += 1;
27329 }
27330 let is_created_file = hunk.is_created_file();
27331
27332 DisplayDiffHunk::Unfolded {
27333 status: hunk.status(),
27334 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27335 ..hunk.diff_base_byte_range.end.0,
27336 word_diffs: hunk.word_diffs,
27337 display_row_range: hunk_display_start.row()..end_row,
27338 multi_buffer_range: Anchor::range_in_buffer(
27339 hunk.excerpt_id,
27340 hunk.buffer_range,
27341 ),
27342 is_created_file,
27343 }
27344 };
27345
27346 Some(display_hunk)
27347 })
27348 }
27349
27350 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27351 self.display_snapshot
27352 .buffer_snapshot()
27353 .language_at(position)
27354 }
27355
27356 pub fn is_focused(&self) -> bool {
27357 self.is_focused
27358 }
27359
27360 pub fn placeholder_text(&self) -> Option<String> {
27361 self.placeholder_display_snapshot
27362 .as_ref()
27363 .map(|display_map| display_map.text())
27364 }
27365
27366 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27367 self.scroll_anchor.scroll_position(&self.display_snapshot)
27368 }
27369
27370 pub fn gutter_dimensions(
27371 &self,
27372 font_id: FontId,
27373 font_size: Pixels,
27374 style: &EditorStyle,
27375 window: &mut Window,
27376 cx: &App,
27377 ) -> GutterDimensions {
27378 if self.show_gutter
27379 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27380 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27381 {
27382 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27383 matches!(
27384 ProjectSettings::get_global(cx).git.git_gutter,
27385 GitGutterSetting::TrackedFiles
27386 )
27387 });
27388 let gutter_settings = EditorSettings::get_global(cx).gutter;
27389 let show_line_numbers = self
27390 .show_line_numbers
27391 .unwrap_or(gutter_settings.line_numbers);
27392 let line_gutter_width = if show_line_numbers {
27393 // Avoid flicker-like gutter resizes when the line number gains another digit by
27394 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27395 let min_width_for_number_on_gutter =
27396 ch_advance * gutter_settings.min_line_number_digits as f32;
27397 self.max_line_number_width(style, window)
27398 .max(min_width_for_number_on_gutter)
27399 } else {
27400 0.0.into()
27401 };
27402
27403 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27404 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27405
27406 let git_blame_entries_width =
27407 self.git_blame_gutter_max_author_length
27408 .map(|max_author_length| {
27409 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27410 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27411
27412 /// The number of characters to dedicate to gaps and margins.
27413 const SPACING_WIDTH: usize = 4;
27414
27415 let max_char_count = max_author_length.min(renderer.max_author_length())
27416 + ::git::SHORT_SHA_LENGTH
27417 + MAX_RELATIVE_TIMESTAMP.len()
27418 + SPACING_WIDTH;
27419
27420 ch_advance * max_char_count
27421 });
27422
27423 let is_singleton = self.buffer_snapshot().is_singleton();
27424
27425 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27426 left_padding += if !is_singleton {
27427 ch_width * 4.0
27428 } else if show_runnables || show_breakpoints {
27429 ch_width * 3.0
27430 } else if show_git_gutter && show_line_numbers {
27431 ch_width * 2.0
27432 } else if show_git_gutter || show_line_numbers {
27433 ch_width
27434 } else {
27435 px(0.)
27436 };
27437
27438 let shows_folds = is_singleton && gutter_settings.folds;
27439
27440 let right_padding = if shows_folds && show_line_numbers {
27441 ch_width * 4.0
27442 } else if shows_folds || (!is_singleton && show_line_numbers) {
27443 ch_width * 3.0
27444 } else if show_line_numbers {
27445 ch_width
27446 } else {
27447 px(0.)
27448 };
27449
27450 GutterDimensions {
27451 left_padding,
27452 right_padding,
27453 width: line_gutter_width + left_padding + right_padding,
27454 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27455 git_blame_entries_width,
27456 }
27457 } else if self.offset_content {
27458 GutterDimensions::default_with_margin(font_id, font_size, cx)
27459 } else {
27460 GutterDimensions::default()
27461 }
27462 }
27463
27464 pub fn render_crease_toggle(
27465 &self,
27466 buffer_row: MultiBufferRow,
27467 row_contains_cursor: bool,
27468 editor: Entity<Editor>,
27469 window: &mut Window,
27470 cx: &mut App,
27471 ) -> Option<AnyElement> {
27472 let folded = self.is_line_folded(buffer_row);
27473 let mut is_foldable = false;
27474
27475 if let Some(crease) = self
27476 .crease_snapshot
27477 .query_row(buffer_row, self.buffer_snapshot())
27478 {
27479 is_foldable = true;
27480 match crease {
27481 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27482 if let Some(render_toggle) = render_toggle {
27483 let toggle_callback =
27484 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27485 if folded {
27486 editor.update(cx, |editor, cx| {
27487 editor.fold_at(buffer_row, window, cx)
27488 });
27489 } else {
27490 editor.update(cx, |editor, cx| {
27491 editor.unfold_at(buffer_row, window, cx)
27492 });
27493 }
27494 });
27495 return Some((render_toggle)(
27496 buffer_row,
27497 folded,
27498 toggle_callback,
27499 window,
27500 cx,
27501 ));
27502 }
27503 }
27504 }
27505 }
27506
27507 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27508
27509 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27510 Some(
27511 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27512 .toggle_state(folded)
27513 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27514 if folded {
27515 this.unfold_at(buffer_row, window, cx);
27516 } else {
27517 this.fold_at(buffer_row, window, cx);
27518 }
27519 }))
27520 .into_any_element(),
27521 )
27522 } else {
27523 None
27524 }
27525 }
27526
27527 pub fn render_crease_trailer(
27528 &self,
27529 buffer_row: MultiBufferRow,
27530 window: &mut Window,
27531 cx: &mut App,
27532 ) -> Option<AnyElement> {
27533 let folded = self.is_line_folded(buffer_row);
27534 if let Crease::Inline { render_trailer, .. } = self
27535 .crease_snapshot
27536 .query_row(buffer_row, self.buffer_snapshot())?
27537 {
27538 let render_trailer = render_trailer.as_ref()?;
27539 Some(render_trailer(buffer_row, folded, window, cx))
27540 } else {
27541 None
27542 }
27543 }
27544
27545 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27546 let digit_count = self.widest_line_number().ilog10() + 1;
27547 column_pixels(style, digit_count as usize, window)
27548 }
27549
27550 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27551 ///
27552 /// This is positive if `base` is before `line`.
27553 fn relative_line_delta(
27554 &self,
27555 current_selection_head: DisplayRow,
27556 first_visible_row: DisplayRow,
27557 consider_wrapped_lines: bool,
27558 ) -> i64 {
27559 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27560 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27561
27562 if consider_wrapped_lines {
27563 let wrap_snapshot = self.wrap_snapshot();
27564 let base_wrap_row = wrap_snapshot
27565 .make_wrap_point(current_selection_head, Bias::Left)
27566 .row();
27567 let wrap_row = wrap_snapshot
27568 .make_wrap_point(first_visible_row, Bias::Left)
27569 .row();
27570
27571 wrap_row.0 as i64 - base_wrap_row.0 as i64
27572 } else {
27573 let fold_snapshot = self.fold_snapshot();
27574 let base_fold_row = fold_snapshot
27575 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27576 .row();
27577 let fold_row = fold_snapshot
27578 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27579 .row();
27580
27581 fold_row as i64 - base_fold_row as i64
27582 }
27583 }
27584
27585 /// Returns the unsigned relative line number to display for each row in `rows`.
27586 ///
27587 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27588 pub fn calculate_relative_line_numbers(
27589 &self,
27590 rows: &Range<DisplayRow>,
27591 current_selection_head: DisplayRow,
27592 count_wrapped_lines: bool,
27593 ) -> HashMap<DisplayRow, u32> {
27594 let initial_offset =
27595 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27596
27597 self.row_infos(rows.start)
27598 .take(rows.len())
27599 .enumerate()
27600 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27601 .filter(|(_row, row_info)| {
27602 row_info.buffer_row.is_some()
27603 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27604 })
27605 .enumerate()
27606 .filter_map(|(i, (row, row_info))| {
27607 // We want to ensure here that the current line has absolute
27608 // numbering, even if we are in a soft-wrapped line. With the
27609 // exception that if we are in a deleted line, we should number this
27610 // relative with 0, as otherwise it would have no line number at all
27611 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27612
27613 (relative_line_number != 0
27614 || row_info
27615 .diff_status
27616 .is_some_and(|status| status.is_deleted()))
27617 .then_some((row, relative_line_number))
27618 })
27619 .collect()
27620 }
27621}
27622
27623pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27624 let font_size = style.text.font_size.to_pixels(window.rem_size());
27625 let layout = window.text_system().shape_line(
27626 SharedString::from(" ".repeat(column)),
27627 font_size,
27628 &[TextRun {
27629 len: column,
27630 font: style.text.font(),
27631 color: Hsla::default(),
27632 ..Default::default()
27633 }],
27634 None,
27635 );
27636
27637 layout.width
27638}
27639
27640impl Deref for EditorSnapshot {
27641 type Target = DisplaySnapshot;
27642
27643 fn deref(&self) -> &Self::Target {
27644 &self.display_snapshot
27645 }
27646}
27647
27648#[derive(Clone, Debug, PartialEq, Eq)]
27649pub enum EditorEvent {
27650 /// Emitted when the stored review comments change (added, removed, or updated).
27651 ReviewCommentsChanged {
27652 /// The new total count of review comments.
27653 total_count: usize,
27654 },
27655 InputIgnored {
27656 text: Arc<str>,
27657 },
27658 InputHandled {
27659 utf16_range_to_replace: Option<Range<isize>>,
27660 text: Arc<str>,
27661 },
27662 ExcerptsAdded {
27663 buffer: Entity<Buffer>,
27664 predecessor: ExcerptId,
27665 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27666 },
27667 ExcerptsRemoved {
27668 ids: Vec<ExcerptId>,
27669 removed_buffer_ids: Vec<BufferId>,
27670 },
27671 BufferFoldToggled {
27672 ids: Vec<ExcerptId>,
27673 folded: bool,
27674 },
27675 ExcerptsEdited {
27676 ids: Vec<ExcerptId>,
27677 },
27678 ExcerptsExpanded {
27679 ids: Vec<ExcerptId>,
27680 },
27681 ExpandExcerptsRequested {
27682 excerpt_ids: Vec<ExcerptId>,
27683 lines: u32,
27684 direction: ExpandExcerptDirection,
27685 },
27686 StageOrUnstageRequested {
27687 stage: bool,
27688 hunks: Vec<MultiBufferDiffHunk>,
27689 },
27690 OpenExcerptsRequested {
27691 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
27692 split: bool,
27693 },
27694 RestoreRequested {
27695 hunks: Vec<MultiBufferDiffHunk>,
27696 },
27697 BufferEdited,
27698 Edited {
27699 transaction_id: clock::Lamport,
27700 },
27701 Reparsed(BufferId),
27702 Focused,
27703 FocusedIn,
27704 Blurred,
27705 DirtyChanged,
27706 Saved,
27707 TitleChanged,
27708 SelectionsChanged {
27709 local: bool,
27710 },
27711 ScrollPositionChanged {
27712 local: bool,
27713 autoscroll: bool,
27714 },
27715 TransactionUndone {
27716 transaction_id: clock::Lamport,
27717 },
27718 TransactionBegun {
27719 transaction_id: clock::Lamport,
27720 },
27721 CursorShapeChanged,
27722 BreadcrumbsChanged,
27723 OutlineSymbolsChanged,
27724 PushedToNavHistory {
27725 anchor: Anchor,
27726 is_deactivate: bool,
27727 },
27728}
27729
27730impl EventEmitter<EditorEvent> for Editor {}
27731
27732impl Focusable for Editor {
27733 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27734 self.focus_handle.clone()
27735 }
27736}
27737
27738impl Render for Editor {
27739 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27740 EditorElement::new(&cx.entity(), self.create_style(cx))
27741 }
27742}
27743
27744impl EntityInputHandler for Editor {
27745 fn text_for_range(
27746 &mut self,
27747 range_utf16: Range<usize>,
27748 adjusted_range: &mut Option<Range<usize>>,
27749 _: &mut Window,
27750 cx: &mut Context<Self>,
27751 ) -> Option<String> {
27752 let snapshot = self.buffer.read(cx).read(cx);
27753 let start = snapshot.clip_offset_utf16(
27754 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27755 Bias::Left,
27756 );
27757 let end = snapshot.clip_offset_utf16(
27758 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27759 Bias::Right,
27760 );
27761 if (start.0.0..end.0.0) != range_utf16 {
27762 adjusted_range.replace(start.0.0..end.0.0);
27763 }
27764 Some(snapshot.text_for_range(start..end).collect())
27765 }
27766
27767 fn selected_text_range(
27768 &mut self,
27769 ignore_disabled_input: bool,
27770 _: &mut Window,
27771 cx: &mut Context<Self>,
27772 ) -> Option<UTF16Selection> {
27773 // Prevent the IME menu from appearing when holding down an alphabetic key
27774 // while input is disabled.
27775 if !ignore_disabled_input && !self.input_enabled {
27776 return None;
27777 }
27778
27779 let selection = self
27780 .selections
27781 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27782 let range = selection.range();
27783
27784 Some(UTF16Selection {
27785 range: range.start.0.0..range.end.0.0,
27786 reversed: selection.reversed,
27787 })
27788 }
27789
27790 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27791 let snapshot = self.buffer.read(cx).read(cx);
27792 let range = self
27793 .text_highlights(HighlightKey::InputComposition, cx)?
27794 .1
27795 .first()?;
27796 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27797 }
27798
27799 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27800 self.clear_highlights(HighlightKey::InputComposition, cx);
27801 self.ime_transaction.take();
27802 }
27803
27804 fn replace_text_in_range(
27805 &mut self,
27806 range_utf16: Option<Range<usize>>,
27807 text: &str,
27808 window: &mut Window,
27809 cx: &mut Context<Self>,
27810 ) {
27811 if !self.input_enabled {
27812 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27813 return;
27814 }
27815
27816 self.transact(window, cx, |this, window, cx| {
27817 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27818 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27819 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27820 Some(this.selection_replacement_ranges(range_utf16, cx))
27821 } else {
27822 this.marked_text_ranges(cx)
27823 };
27824
27825 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27826 let newest_selection_id = this.selections.newest_anchor().id;
27827 this.selections
27828 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27829 .iter()
27830 .zip(ranges_to_replace.iter())
27831 .find_map(|(selection, range)| {
27832 if selection.id == newest_selection_id {
27833 Some(
27834 (range.start.0.0 as isize - selection.head().0.0 as isize)
27835 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27836 )
27837 } else {
27838 None
27839 }
27840 })
27841 });
27842
27843 cx.emit(EditorEvent::InputHandled {
27844 utf16_range_to_replace: range_to_replace,
27845 text: text.into(),
27846 });
27847
27848 if let Some(new_selected_ranges) = new_selected_ranges {
27849 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27850 selections.select_ranges(new_selected_ranges)
27851 });
27852 this.backspace(&Default::default(), window, cx);
27853 }
27854
27855 this.handle_input(text, window, cx);
27856 });
27857
27858 if let Some(transaction) = self.ime_transaction {
27859 self.buffer.update(cx, |buffer, cx| {
27860 buffer.group_until_transaction(transaction, cx);
27861 });
27862 }
27863
27864 self.unmark_text(window, cx);
27865 }
27866
27867 fn replace_and_mark_text_in_range(
27868 &mut self,
27869 range_utf16: Option<Range<usize>>,
27870 text: &str,
27871 new_selected_range_utf16: Option<Range<usize>>,
27872 window: &mut Window,
27873 cx: &mut Context<Self>,
27874 ) {
27875 if !self.input_enabled {
27876 return;
27877 }
27878
27879 let transaction = self.transact(window, cx, |this, window, cx| {
27880 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27881 let snapshot = this.buffer.read(cx).read(cx);
27882 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27883 for marked_range in &mut marked_ranges {
27884 marked_range.end = marked_range.start + relative_range_utf16.end;
27885 marked_range.start += relative_range_utf16.start;
27886 marked_range.start =
27887 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27888 marked_range.end =
27889 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27890 }
27891 }
27892 Some(marked_ranges)
27893 } else if let Some(range_utf16) = range_utf16 {
27894 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27895 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27896 Some(this.selection_replacement_ranges(range_utf16, cx))
27897 } else {
27898 None
27899 };
27900
27901 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27902 let newest_selection_id = this.selections.newest_anchor().id;
27903 this.selections
27904 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27905 .iter()
27906 .zip(ranges_to_replace.iter())
27907 .find_map(|(selection, range)| {
27908 if selection.id == newest_selection_id {
27909 Some(
27910 (range.start.0.0 as isize - selection.head().0.0 as isize)
27911 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27912 )
27913 } else {
27914 None
27915 }
27916 })
27917 });
27918
27919 cx.emit(EditorEvent::InputHandled {
27920 utf16_range_to_replace: range_to_replace,
27921 text: text.into(),
27922 });
27923
27924 if let Some(ranges) = ranges_to_replace {
27925 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27926 s.select_ranges(ranges)
27927 });
27928 }
27929
27930 let marked_ranges = {
27931 let snapshot = this.buffer.read(cx).read(cx);
27932 this.selections
27933 .disjoint_anchors_arc()
27934 .iter()
27935 .map(|selection| {
27936 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27937 })
27938 .collect::<Vec<_>>()
27939 };
27940
27941 if text.is_empty() {
27942 this.unmark_text(window, cx);
27943 } else {
27944 this.highlight_text(
27945 HighlightKey::InputComposition,
27946 marked_ranges.clone(),
27947 HighlightStyle {
27948 underline: Some(UnderlineStyle {
27949 thickness: px(1.),
27950 color: None,
27951 wavy: false,
27952 }),
27953 ..Default::default()
27954 },
27955 cx,
27956 );
27957 }
27958
27959 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27960 let use_autoclose = this.use_autoclose;
27961 let use_auto_surround = this.use_auto_surround;
27962 this.set_use_autoclose(false);
27963 this.set_use_auto_surround(false);
27964 this.handle_input(text, window, cx);
27965 this.set_use_autoclose(use_autoclose);
27966 this.set_use_auto_surround(use_auto_surround);
27967
27968 if let Some(new_selected_range) = new_selected_range_utf16 {
27969 let snapshot = this.buffer.read(cx).read(cx);
27970 let new_selected_ranges = marked_ranges
27971 .into_iter()
27972 .map(|marked_range| {
27973 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27974 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27975 insertion_start.0 + new_selected_range.start,
27976 ));
27977 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27978 insertion_start.0 + new_selected_range.end,
27979 ));
27980 snapshot.clip_offset_utf16(new_start, Bias::Left)
27981 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27982 })
27983 .collect::<Vec<_>>();
27984
27985 drop(snapshot);
27986 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27987 selections.select_ranges(new_selected_ranges)
27988 });
27989 }
27990 });
27991
27992 self.ime_transaction = self.ime_transaction.or(transaction);
27993 if let Some(transaction) = self.ime_transaction {
27994 self.buffer.update(cx, |buffer, cx| {
27995 buffer.group_until_transaction(transaction, cx);
27996 });
27997 }
27998
27999 if self
28000 .text_highlights(HighlightKey::InputComposition, cx)
28001 .is_none()
28002 {
28003 self.ime_transaction.take();
28004 }
28005 }
28006
28007 fn bounds_for_range(
28008 &mut self,
28009 range_utf16: Range<usize>,
28010 element_bounds: gpui::Bounds<Pixels>,
28011 window: &mut Window,
28012 cx: &mut Context<Self>,
28013 ) -> Option<gpui::Bounds<Pixels>> {
28014 let text_layout_details = self.text_layout_details(window, cx);
28015 let CharacterDimensions {
28016 em_width,
28017 em_advance,
28018 line_height,
28019 } = self.character_dimensions(window, cx);
28020
28021 let snapshot = self.snapshot(window, cx);
28022 let scroll_position = snapshot.scroll_position();
28023 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28024
28025 let start =
28026 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28027 let x = Pixels::from(
28028 ScrollOffset::from(
28029 snapshot.x_for_display_point(start, &text_layout_details)
28030 + self.gutter_dimensions.full_width(),
28031 ) - scroll_left,
28032 );
28033 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28034
28035 Some(Bounds {
28036 origin: element_bounds.origin + point(x, y),
28037 size: size(em_width, line_height),
28038 })
28039 }
28040
28041 fn character_index_for_point(
28042 &mut self,
28043 point: gpui::Point<Pixels>,
28044 _window: &mut Window,
28045 _cx: &mut Context<Self>,
28046 ) -> Option<usize> {
28047 let position_map = self.last_position_map.as_ref()?;
28048 if !position_map.text_hitbox.contains(&point) {
28049 return None;
28050 }
28051 let display_point = position_map.point_for_position(point).previous_valid;
28052 let anchor = position_map
28053 .snapshot
28054 .display_point_to_anchor(display_point, Bias::Left);
28055 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28056 Some(utf16_offset.0.0)
28057 }
28058
28059 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28060 self.expects_character_input
28061 }
28062}
28063
28064trait SelectionExt {
28065 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28066 fn spanned_rows(
28067 &self,
28068 include_end_if_at_line_start: bool,
28069 map: &DisplaySnapshot,
28070 ) -> Range<MultiBufferRow>;
28071}
28072
28073impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28074 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28075 let start = self
28076 .start
28077 .to_point(map.buffer_snapshot())
28078 .to_display_point(map);
28079 let end = self
28080 .end
28081 .to_point(map.buffer_snapshot())
28082 .to_display_point(map);
28083 if self.reversed {
28084 end..start
28085 } else {
28086 start..end
28087 }
28088 }
28089
28090 fn spanned_rows(
28091 &self,
28092 include_end_if_at_line_start: bool,
28093 map: &DisplaySnapshot,
28094 ) -> Range<MultiBufferRow> {
28095 let start = self.start.to_point(map.buffer_snapshot());
28096 let mut end = self.end.to_point(map.buffer_snapshot());
28097 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28098 end.row -= 1;
28099 }
28100
28101 let buffer_start = map.prev_line_boundary(start).0;
28102 let buffer_end = map.next_line_boundary(end).0;
28103 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28104 }
28105}
28106
28107impl<T: InvalidationRegion> InvalidationStack<T> {
28108 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28109 where
28110 S: Clone + ToOffset,
28111 {
28112 while let Some(region) = self.last() {
28113 let all_selections_inside_invalidation_ranges =
28114 if selections.len() == region.ranges().len() {
28115 selections
28116 .iter()
28117 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28118 .all(|(selection, invalidation_range)| {
28119 let head = selection.head().to_offset(buffer);
28120 invalidation_range.start <= head && invalidation_range.end >= head
28121 })
28122 } else {
28123 false
28124 };
28125
28126 if all_selections_inside_invalidation_ranges {
28127 break;
28128 } else {
28129 self.pop();
28130 }
28131 }
28132 }
28133}
28134
28135#[derive(Clone)]
28136struct ErasedEditorImpl(Entity<Editor>);
28137
28138impl ui_input::ErasedEditor for ErasedEditorImpl {
28139 fn text(&self, cx: &App) -> String {
28140 self.0.read(cx).text(cx)
28141 }
28142
28143 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28144 self.0.update(cx, |this, cx| {
28145 this.set_text(text, window, cx);
28146 })
28147 }
28148
28149 fn clear(&self, window: &mut Window, cx: &mut App) {
28150 self.0.update(cx, |this, cx| this.clear(window, cx));
28151 }
28152
28153 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28154 self.0.update(cx, |this, cx| {
28155 this.set_placeholder_text(text, window, cx);
28156 });
28157 }
28158
28159 fn focus_handle(&self, cx: &App) -> FocusHandle {
28160 self.0.read(cx).focus_handle(cx)
28161 }
28162
28163 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28164 let settings = ThemeSettings::get_global(cx);
28165 let theme_color = cx.theme().colors();
28166
28167 let text_style = TextStyle {
28168 font_family: settings.ui_font.family.clone(),
28169 font_features: settings.ui_font.features.clone(),
28170 font_size: rems(0.875).into(),
28171 font_weight: settings.ui_font.weight,
28172 font_style: FontStyle::Normal,
28173 line_height: relative(1.2),
28174 color: theme_color.text,
28175 ..Default::default()
28176 };
28177 let editor_style = EditorStyle {
28178 background: theme_color.ghost_element_background,
28179 local_player: cx.theme().players().local(),
28180 syntax: cx.theme().syntax().clone(),
28181 text: text_style,
28182 ..Default::default()
28183 };
28184 EditorElement::new(&self.0, editor_style).into_any()
28185 }
28186
28187 fn as_any(&self) -> &dyn Any {
28188 &self.0
28189 }
28190
28191 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28192 self.0.update(cx, |editor, cx| {
28193 let editor_offset = editor.buffer().read(cx).len(cx);
28194 editor.change_selections(
28195 SelectionEffects::scroll(Autoscroll::Next),
28196 window,
28197 cx,
28198 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28199 );
28200 });
28201 }
28202
28203 fn subscribe(
28204 &self,
28205 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28206 window: &mut Window,
28207 cx: &mut App,
28208 ) -> Subscription {
28209 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28210 let event = match event {
28211 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28212 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28213 _ => return,
28214 };
28215 (callback)(event, window, cx);
28216 })
28217 }
28218
28219 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28220 self.0.update(cx, |editor, cx| {
28221 editor.set_masked(masked, cx);
28222 });
28223 }
28224}
28225impl<T> Default for InvalidationStack<T> {
28226 fn default() -> Self {
28227 Self(Default::default())
28228 }
28229}
28230
28231impl<T> Deref for InvalidationStack<T> {
28232 type Target = Vec<T>;
28233
28234 fn deref(&self) -> &Self::Target {
28235 &self.0
28236 }
28237}
28238
28239impl<T> DerefMut for InvalidationStack<T> {
28240 fn deref_mut(&mut self) -> &mut Self::Target {
28241 &mut self.0
28242 }
28243}
28244
28245impl InvalidationRegion for SnippetState {
28246 fn ranges(&self) -> &[Range<Anchor>] {
28247 &self.ranges[self.active_index]
28248 }
28249}
28250
28251fn edit_prediction_edit_text(
28252 current_snapshot: &BufferSnapshot,
28253 edits: &[(Range<Anchor>, impl AsRef<str>)],
28254 edit_preview: &EditPreview,
28255 include_deletions: bool,
28256 cx: &App,
28257) -> HighlightedText {
28258 let edits = edits
28259 .iter()
28260 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28261 .collect::<Vec<_>>();
28262
28263 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28264}
28265
28266fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28267 // Fallback for providers that don't provide edit_preview (like Copilot)
28268 // Just show the raw edit text with basic styling
28269 let mut text = String::new();
28270 let mut highlights = Vec::new();
28271
28272 let insertion_highlight_style = HighlightStyle {
28273 color: Some(cx.theme().colors().text),
28274 ..Default::default()
28275 };
28276
28277 for (_, edit_text) in edits {
28278 let start_offset = text.len();
28279 text.push_str(edit_text);
28280 let end_offset = text.len();
28281
28282 if start_offset < end_offset {
28283 highlights.push((start_offset..end_offset, insertion_highlight_style));
28284 }
28285 }
28286
28287 HighlightedText {
28288 text: text.into(),
28289 highlights,
28290 }
28291}
28292
28293pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28294 match severity {
28295 lsp::DiagnosticSeverity::ERROR => colors.error,
28296 lsp::DiagnosticSeverity::WARNING => colors.warning,
28297 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28298 lsp::DiagnosticSeverity::HINT => colors.info,
28299 _ => colors.ignored,
28300 }
28301}
28302
28303pub fn styled_runs_for_code_label<'a>(
28304 label: &'a CodeLabel,
28305 syntax_theme: &'a theme::SyntaxTheme,
28306 local_player: &'a theme::PlayerColor,
28307) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28308 let fade_out = HighlightStyle {
28309 fade_out: Some(0.35),
28310 ..Default::default()
28311 };
28312
28313 let mut prev_end = label.filter_range.end;
28314 label
28315 .runs
28316 .iter()
28317 .enumerate()
28318 .flat_map(move |(ix, (range, highlight_id))| {
28319 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28320 HighlightStyle {
28321 color: Some(local_player.cursor),
28322 ..Default::default()
28323 }
28324 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28325 HighlightStyle {
28326 background_color: Some(local_player.selection),
28327 ..Default::default()
28328 }
28329 } else if let Some(style) = highlight_id.style(syntax_theme) {
28330 style
28331 } else {
28332 return Default::default();
28333 };
28334 let muted_style = style.highlight(fade_out);
28335
28336 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28337 if range.start >= label.filter_range.end {
28338 if range.start > prev_end {
28339 runs.push((prev_end..range.start, fade_out));
28340 }
28341 runs.push((range.clone(), muted_style));
28342 } else if range.end <= label.filter_range.end {
28343 runs.push((range.clone(), style));
28344 } else {
28345 runs.push((range.start..label.filter_range.end, style));
28346 runs.push((label.filter_range.end..range.end, muted_style));
28347 }
28348 prev_end = cmp::max(prev_end, range.end);
28349
28350 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28351 runs.push((prev_end..label.text.len(), fade_out));
28352 }
28353
28354 runs
28355 })
28356}
28357
28358pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28359 let mut prev_index = 0;
28360 let mut prev_codepoint: Option<char> = None;
28361 text.char_indices()
28362 .chain([(text.len(), '\0')])
28363 .filter_map(move |(index, codepoint)| {
28364 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28365 let is_boundary = index == text.len()
28366 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28367 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28368 if is_boundary {
28369 let chunk = &text[prev_index..index];
28370 prev_index = index;
28371 Some(chunk)
28372 } else {
28373 None
28374 }
28375 })
28376}
28377
28378/// Given a string of text immediately before the cursor, iterates over possible
28379/// strings a snippet could match to. More precisely: returns an iterator over
28380/// suffixes of `text` created by splitting at word boundaries (before & after
28381/// every non-word character).
28382///
28383/// Shorter suffixes are returned first.
28384pub(crate) fn snippet_candidate_suffixes<'a>(
28385 text: &'a str,
28386 is_word_char: &'a dyn Fn(char) -> bool,
28387) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28388 let mut prev_index = text.len();
28389 let mut prev_codepoint = None;
28390 text.char_indices()
28391 .rev()
28392 .chain([(0, '\0')])
28393 .filter_map(move |(index, codepoint)| {
28394 let prev_index = std::mem::replace(&mut prev_index, index);
28395 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28396 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28397 None
28398 } else {
28399 let chunk = &text[prev_index..]; // go to end of string
28400 Some(chunk)
28401 }
28402 })
28403}
28404
28405pub trait RangeToAnchorExt: Sized {
28406 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28407
28408 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28409 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28410 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28411 }
28412}
28413
28414impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28415 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28416 let start_offset = self.start.to_offset(snapshot);
28417 let end_offset = self.end.to_offset(snapshot);
28418 if start_offset == end_offset {
28419 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28420 } else {
28421 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28422 }
28423 }
28424}
28425
28426pub trait RowExt {
28427 fn as_f64(&self) -> f64;
28428
28429 fn next_row(&self) -> Self;
28430
28431 fn previous_row(&self) -> Self;
28432
28433 fn minus(&self, other: Self) -> u32;
28434}
28435
28436impl RowExt for DisplayRow {
28437 fn as_f64(&self) -> f64 {
28438 self.0 as _
28439 }
28440
28441 fn next_row(&self) -> Self {
28442 Self(self.0 + 1)
28443 }
28444
28445 fn previous_row(&self) -> Self {
28446 Self(self.0.saturating_sub(1))
28447 }
28448
28449 fn minus(&self, other: Self) -> u32 {
28450 self.0 - other.0
28451 }
28452}
28453
28454impl RowExt for MultiBufferRow {
28455 fn as_f64(&self) -> f64 {
28456 self.0 as _
28457 }
28458
28459 fn next_row(&self) -> Self {
28460 Self(self.0 + 1)
28461 }
28462
28463 fn previous_row(&self) -> Self {
28464 Self(self.0.saturating_sub(1))
28465 }
28466
28467 fn minus(&self, other: Self) -> u32 {
28468 self.0 - other.0
28469 }
28470}
28471
28472trait RowRangeExt {
28473 type Row;
28474
28475 fn len(&self) -> usize;
28476
28477 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28478}
28479
28480impl RowRangeExt for Range<MultiBufferRow> {
28481 type Row = MultiBufferRow;
28482
28483 fn len(&self) -> usize {
28484 (self.end.0 - self.start.0) as usize
28485 }
28486
28487 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28488 (self.start.0..self.end.0).map(MultiBufferRow)
28489 }
28490}
28491
28492impl RowRangeExt for Range<DisplayRow> {
28493 type Row = DisplayRow;
28494
28495 fn len(&self) -> usize {
28496 (self.end.0 - self.start.0) as usize
28497 }
28498
28499 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28500 (self.start.0..self.end.0).map(DisplayRow)
28501 }
28502}
28503
28504/// If select range has more than one line, we
28505/// just point the cursor to range.start.
28506fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28507 if range.start.row == range.end.row {
28508 range
28509 } else {
28510 range.start..range.start
28511 }
28512}
28513pub struct KillRing(ClipboardItem);
28514impl Global for KillRing {}
28515
28516const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28517
28518enum BreakpointPromptEditAction {
28519 Log,
28520 Condition,
28521 HitCondition,
28522}
28523
28524struct BreakpointPromptEditor {
28525 pub(crate) prompt: Entity<Editor>,
28526 editor: WeakEntity<Editor>,
28527 breakpoint_anchor: Anchor,
28528 breakpoint: Breakpoint,
28529 edit_action: BreakpointPromptEditAction,
28530 block_ids: HashSet<CustomBlockId>,
28531 editor_margins: Arc<Mutex<EditorMargins>>,
28532 _subscriptions: Vec<Subscription>,
28533}
28534
28535impl BreakpointPromptEditor {
28536 const MAX_LINES: u8 = 4;
28537
28538 fn new(
28539 editor: WeakEntity<Editor>,
28540 breakpoint_anchor: Anchor,
28541 breakpoint: Breakpoint,
28542 edit_action: BreakpointPromptEditAction,
28543 window: &mut Window,
28544 cx: &mut Context<Self>,
28545 ) -> Self {
28546 let base_text = match edit_action {
28547 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28548 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28549 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28550 }
28551 .map(|msg| msg.to_string())
28552 .unwrap_or_default();
28553
28554 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28555 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28556
28557 let prompt = cx.new(|cx| {
28558 let mut prompt = Editor::new(
28559 EditorMode::AutoHeight {
28560 min_lines: 1,
28561 max_lines: Some(Self::MAX_LINES as usize),
28562 },
28563 buffer,
28564 None,
28565 window,
28566 cx,
28567 );
28568 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28569 prompt.set_show_cursor_when_unfocused(false, cx);
28570 prompt.set_placeholder_text(
28571 match edit_action {
28572 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28573 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28574 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28575 },
28576 window,
28577 cx,
28578 );
28579
28580 prompt
28581 });
28582
28583 Self {
28584 prompt,
28585 editor,
28586 breakpoint_anchor,
28587 breakpoint,
28588 edit_action,
28589 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28590 block_ids: Default::default(),
28591 _subscriptions: vec![],
28592 }
28593 }
28594
28595 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28596 self.block_ids.extend(block_ids)
28597 }
28598
28599 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28600 if let Some(editor) = self.editor.upgrade() {
28601 let message = self
28602 .prompt
28603 .read(cx)
28604 .buffer
28605 .read(cx)
28606 .as_singleton()
28607 .expect("A multi buffer in breakpoint prompt isn't possible")
28608 .read(cx)
28609 .as_rope()
28610 .to_string();
28611
28612 editor.update(cx, |editor, cx| {
28613 editor.edit_breakpoint_at_anchor(
28614 self.breakpoint_anchor,
28615 self.breakpoint.clone(),
28616 match self.edit_action {
28617 BreakpointPromptEditAction::Log => {
28618 BreakpointEditAction::EditLogMessage(message.into())
28619 }
28620 BreakpointPromptEditAction::Condition => {
28621 BreakpointEditAction::EditCondition(message.into())
28622 }
28623 BreakpointPromptEditAction::HitCondition => {
28624 BreakpointEditAction::EditHitCondition(message.into())
28625 }
28626 },
28627 cx,
28628 );
28629
28630 editor.remove_blocks(self.block_ids.clone(), None, cx);
28631 cx.focus_self(window);
28632 });
28633 }
28634 }
28635
28636 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28637 self.editor
28638 .update(cx, |editor, cx| {
28639 editor.remove_blocks(self.block_ids.clone(), None, cx);
28640 window.focus(&editor.focus_handle, cx);
28641 })
28642 .log_err();
28643 }
28644
28645 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28646 let settings = ThemeSettings::get_global(cx);
28647 let text_style = TextStyle {
28648 color: if self.prompt.read(cx).read_only(cx) {
28649 cx.theme().colors().text_disabled
28650 } else {
28651 cx.theme().colors().text
28652 },
28653 font_family: settings.buffer_font.family.clone(),
28654 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28655 font_size: settings.buffer_font_size(cx).into(),
28656 font_weight: settings.buffer_font.weight,
28657 line_height: relative(settings.buffer_line_height.value()),
28658 ..Default::default()
28659 };
28660 EditorElement::new(
28661 &self.prompt,
28662 EditorStyle {
28663 background: cx.theme().colors().editor_background,
28664 local_player: cx.theme().players().local(),
28665 text: text_style,
28666 ..Default::default()
28667 },
28668 )
28669 }
28670
28671 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
28672 let focus_handle = self.prompt.focus_handle(cx);
28673 IconButton::new("cancel", IconName::Close)
28674 .icon_color(Color::Muted)
28675 .shape(IconButtonShape::Square)
28676 .tooltip(move |_window, cx| {
28677 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
28678 })
28679 .on_click(cx.listener(|this, _, window, cx| {
28680 this.cancel(&menu::Cancel, window, cx);
28681 }))
28682 }
28683
28684 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
28685 let focus_handle = self.prompt.focus_handle(cx);
28686 IconButton::new("confirm", IconName::Return)
28687 .icon_color(Color::Muted)
28688 .shape(IconButtonShape::Square)
28689 .tooltip(move |_window, cx| {
28690 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
28691 })
28692 .on_click(cx.listener(|this, _, window, cx| {
28693 this.confirm(&menu::Confirm, window, cx);
28694 }))
28695 }
28696}
28697
28698impl Render for BreakpointPromptEditor {
28699 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28700 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
28701 let editor_margins = *self.editor_margins.lock();
28702 let gutter_dimensions = editor_margins.gutter;
28703 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
28704 let right_padding = editor_margins.right + px(9.);
28705 h_flex()
28706 .key_context("Editor")
28707 .bg(cx.theme().colors().editor_background)
28708 .border_y_1()
28709 .border_color(cx.theme().status().info_border)
28710 .size_full()
28711 .py(window.line_height() / 2.5)
28712 .pr(right_padding)
28713 .on_action(cx.listener(Self::confirm))
28714 .on_action(cx.listener(Self::cancel))
28715 .child(
28716 WithRemSize::new(ui_font_size)
28717 .h_full()
28718 .w(left_gutter_width)
28719 .flex()
28720 .flex_row()
28721 .flex_shrink_0()
28722 .items_center()
28723 .justify_center()
28724 .gap_1()
28725 .child(self.render_close_button(cx)),
28726 )
28727 .child(
28728 h_flex()
28729 .w_full()
28730 .justify_between()
28731 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28732 .child(
28733 WithRemSize::new(ui_font_size)
28734 .flex()
28735 .flex_row()
28736 .items_center()
28737 .child(self.render_confirm_button(cx)),
28738 ),
28739 )
28740 }
28741}
28742
28743impl Focusable for BreakpointPromptEditor {
28744 fn focus_handle(&self, cx: &App) -> FocusHandle {
28745 self.prompt.focus_handle(cx)
28746 }
28747}
28748
28749fn all_edits_insertions_or_deletions(
28750 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28751 snapshot: &MultiBufferSnapshot,
28752) -> bool {
28753 let mut all_insertions = true;
28754 let mut all_deletions = true;
28755
28756 for (range, new_text) in edits.iter() {
28757 let range_is_empty = range.to_offset(snapshot).is_empty();
28758 let text_is_empty = new_text.is_empty();
28759
28760 if range_is_empty != text_is_empty {
28761 if range_is_empty {
28762 all_deletions = false;
28763 } else {
28764 all_insertions = false;
28765 }
28766 } else {
28767 return false;
28768 }
28769
28770 if !all_insertions && !all_deletions {
28771 return false;
28772 }
28773 }
28774 all_insertions || all_deletions
28775}
28776
28777struct MissingEditPredictionKeybindingTooltip;
28778
28779impl Render for MissingEditPredictionKeybindingTooltip {
28780 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28781 ui::tooltip_container(cx, |container, cx| {
28782 container
28783 .flex_shrink_0()
28784 .max_w_80()
28785 .min_h(rems_from_px(124.))
28786 .justify_between()
28787 .child(
28788 v_flex()
28789 .flex_1()
28790 .text_ui_sm(cx)
28791 .child(Label::new("Conflict with Accept Keybinding"))
28792 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28793 )
28794 .child(
28795 h_flex()
28796 .pb_1()
28797 .gap_1()
28798 .items_end()
28799 .w_full()
28800 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28801 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28802 }))
28803 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28804 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28805 })),
28806 )
28807 })
28808 }
28809}
28810
28811#[derive(Debug, Clone, Copy, PartialEq)]
28812pub struct LineHighlight {
28813 pub background: Background,
28814 pub border: Option<gpui::Hsla>,
28815 pub include_gutter: bool,
28816 pub type_id: Option<TypeId>,
28817}
28818
28819struct LineManipulationResult {
28820 pub new_text: String,
28821 pub line_count_before: usize,
28822 pub line_count_after: usize,
28823}
28824
28825fn render_diff_hunk_controls(
28826 row: u32,
28827 status: &DiffHunkStatus,
28828 hunk_range: Range<Anchor>,
28829 is_created_file: bool,
28830 line_height: Pixels,
28831 editor: &Entity<Editor>,
28832 _window: &mut Window,
28833 cx: &mut App,
28834) -> AnyElement {
28835 h_flex()
28836 .h(line_height)
28837 .mr_1()
28838 .gap_1()
28839 .px_0p5()
28840 .pb_1()
28841 .border_x_1()
28842 .border_b_1()
28843 .border_color(cx.theme().colors().border_variant)
28844 .rounded_b_lg()
28845 .bg(cx.theme().colors().editor_background)
28846 .gap_1()
28847 .block_mouse_except_scroll()
28848 .shadow_md()
28849 .child(if status.has_secondary_hunk() {
28850 Button::new(("stage", row as u64), "Stage")
28851 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28852 .tooltip({
28853 let focus_handle = editor.focus_handle(cx);
28854 move |_window, cx| {
28855 Tooltip::for_action_in(
28856 "Stage Hunk",
28857 &::git::ToggleStaged,
28858 &focus_handle,
28859 cx,
28860 )
28861 }
28862 })
28863 .on_click({
28864 let editor = editor.clone();
28865 move |_event, _window, cx| {
28866 editor.update(cx, |editor, cx| {
28867 editor.stage_or_unstage_diff_hunks(
28868 true,
28869 vec![hunk_range.start..hunk_range.start],
28870 cx,
28871 );
28872 });
28873 }
28874 })
28875 } else {
28876 Button::new(("unstage", row as u64), "Unstage")
28877 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28878 .tooltip({
28879 let focus_handle = editor.focus_handle(cx);
28880 move |_window, cx| {
28881 Tooltip::for_action_in(
28882 "Unstage Hunk",
28883 &::git::ToggleStaged,
28884 &focus_handle,
28885 cx,
28886 )
28887 }
28888 })
28889 .on_click({
28890 let editor = editor.clone();
28891 move |_event, _window, cx| {
28892 editor.update(cx, |editor, cx| {
28893 editor.stage_or_unstage_diff_hunks(
28894 false,
28895 vec![hunk_range.start..hunk_range.start],
28896 cx,
28897 );
28898 });
28899 }
28900 })
28901 })
28902 .child(
28903 Button::new(("restore", row as u64), "Restore")
28904 .tooltip({
28905 let focus_handle = editor.focus_handle(cx);
28906 move |_window, cx| {
28907 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28908 }
28909 })
28910 .on_click({
28911 let editor = editor.clone();
28912 move |_event, window, cx| {
28913 editor.update(cx, |editor, cx| {
28914 let snapshot = editor.snapshot(window, cx);
28915 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28916 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28917 });
28918 }
28919 })
28920 .disabled(is_created_file),
28921 )
28922 .when(
28923 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28924 |el| {
28925 el.child(
28926 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28927 .shape(IconButtonShape::Square)
28928 .icon_size(IconSize::Small)
28929 // .disabled(!has_multiple_hunks)
28930 .tooltip({
28931 let focus_handle = editor.focus_handle(cx);
28932 move |_window, cx| {
28933 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28934 }
28935 })
28936 .on_click({
28937 let editor = editor.clone();
28938 move |_event, window, cx| {
28939 editor.update(cx, |editor, cx| {
28940 let snapshot = editor.snapshot(window, cx);
28941 let position =
28942 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28943 editor.go_to_hunk_before_or_after_position(
28944 &snapshot,
28945 position,
28946 Direction::Next,
28947 true,
28948 window,
28949 cx,
28950 );
28951 editor.expand_selected_diff_hunks(cx);
28952 });
28953 }
28954 }),
28955 )
28956 .child(
28957 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28958 .shape(IconButtonShape::Square)
28959 .icon_size(IconSize::Small)
28960 // .disabled(!has_multiple_hunks)
28961 .tooltip({
28962 let focus_handle = editor.focus_handle(cx);
28963 move |_window, cx| {
28964 Tooltip::for_action_in(
28965 "Previous Hunk",
28966 &GoToPreviousHunk,
28967 &focus_handle,
28968 cx,
28969 )
28970 }
28971 })
28972 .on_click({
28973 let editor = editor.clone();
28974 move |_event, window, cx| {
28975 editor.update(cx, |editor, cx| {
28976 let snapshot = editor.snapshot(window, cx);
28977 let point =
28978 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28979 editor.go_to_hunk_before_or_after_position(
28980 &snapshot,
28981 point,
28982 Direction::Prev,
28983 true,
28984 window,
28985 cx,
28986 );
28987 editor.expand_selected_diff_hunks(cx);
28988 });
28989 }
28990 }),
28991 )
28992 },
28993 )
28994 .into_any_element()
28995}
28996
28997pub fn multibuffer_context_lines(cx: &App) -> u32 {
28998 EditorSettings::try_get(cx)
28999 .map(|settings| settings.excerpt_context_lines)
29000 .unwrap_or(2)
29001 .min(32)
29002}