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::{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::EditorDb;
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};
224pub use zed_actions::editor::RevealInFileManager;
225use zed_actions::editor::{MoveDown, MoveUp};
226
227use crate::{
228 code_context_menus::CompletionsMenuSource,
229 editor_settings::MultiCursorModifier,
230 hover_links::{find_url, find_url_from_range},
231 inlays::{
232 InlineValueCache,
233 inlay_hints::{LspInlayHintData, inlay_hint_settings},
234 },
235 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
236 scroll::{ScrollOffset, ScrollPixelOffset},
237 selections_collection::resolve_selections_wrapping_blocks,
238 semantic_tokens::SemanticTokenState,
239 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
240};
241
242pub const FILE_HEADER_HEIGHT: u32 = 2;
243pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
244pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
245const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
246const MAX_LINE_LEN: usize = 1024;
247const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
248const MAX_SELECTION_HISTORY_LEN: usize = 1024;
249pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
250#[doc(hidden)]
251pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
252pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
253
254pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
255pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
256pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
257pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
258
259pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
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
704#[derive(Copy, Clone, Eq, PartialEq)]
705enum EditPredictionKeybindSurface {
706 Inline,
707 CursorPopoverCompact,
708 CursorPopoverExpanded,
709}
710
711#[derive(Copy, Clone, Eq, PartialEq, Debug)]
712enum EditPredictionKeybindAction {
713 Accept,
714 Preview,
715}
716
717struct EditPredictionKeybindDisplay {
718 #[cfg(test)]
719 accept_keystroke: Option<gpui::KeybindingKeystroke>,
720 #[cfg(test)]
721 preview_keystroke: Option<gpui::KeybindingKeystroke>,
722 displayed_keystroke: Option<gpui::KeybindingKeystroke>,
723 action: EditPredictionKeybindAction,
724 missing_accept_keystroke: bool,
725 show_hold_label: bool,
726}
727
728impl EditPredictionPreview {
729 pub fn released_too_fast(&self) -> bool {
730 match self {
731 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
732 EditPredictionPreview::Active { .. } => false,
733 }
734 }
735
736 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
737 if let EditPredictionPreview::Active {
738 previous_scroll_position,
739 ..
740 } = self
741 {
742 *previous_scroll_position = scroll_position;
743 }
744 }
745}
746
747pub struct ContextMenuOptions {
748 pub min_entries_visible: usize,
749 pub max_entries_visible: usize,
750 pub placement: Option<ContextMenuPlacement>,
751}
752
753#[derive(Debug, Clone, PartialEq, Eq)]
754pub enum ContextMenuPlacement {
755 Above,
756 Below,
757}
758
759#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
760struct EditorActionId(usize);
761
762impl EditorActionId {
763 pub fn post_inc(&mut self) -> Self {
764 let answer = self.0;
765
766 *self = Self(answer + 1);
767
768 Self(answer)
769 }
770}
771
772// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
773// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
774
775type BackgroundHighlight = (
776 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
777 Arc<[Range<Anchor>]>,
778);
779type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
780
781#[derive(Default)]
782struct ScrollbarMarkerState {
783 scrollbar_size: Size<Pixels>,
784 dirty: bool,
785 markers: Arc<[PaintQuad]>,
786 pending_refresh: Option<Task<Result<()>>>,
787}
788
789impl ScrollbarMarkerState {
790 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
791 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
792 }
793}
794
795#[derive(Clone, Copy, PartialEq, Eq)]
796pub enum MinimapVisibility {
797 Disabled,
798 Enabled {
799 /// The configuration currently present in the users settings.
800 setting_configuration: bool,
801 /// Whether to override the currently set visibility from the users setting.
802 toggle_override: bool,
803 },
804}
805
806impl MinimapVisibility {
807 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
808 if mode.is_full() {
809 Self::Enabled {
810 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
811 toggle_override: false,
812 }
813 } else {
814 Self::Disabled
815 }
816 }
817
818 fn hidden(&self) -> Self {
819 match *self {
820 Self::Enabled {
821 setting_configuration,
822 ..
823 } => Self::Enabled {
824 setting_configuration,
825 toggle_override: setting_configuration,
826 },
827 Self::Disabled => Self::Disabled,
828 }
829 }
830
831 fn disabled(&self) -> bool {
832 matches!(*self, Self::Disabled)
833 }
834
835 fn settings_visibility(&self) -> bool {
836 match *self {
837 Self::Enabled {
838 setting_configuration,
839 ..
840 } => setting_configuration,
841 _ => false,
842 }
843 }
844
845 fn visible(&self) -> bool {
846 match *self {
847 Self::Enabled {
848 setting_configuration,
849 toggle_override,
850 } => setting_configuration ^ toggle_override,
851 _ => false,
852 }
853 }
854
855 fn toggle_visibility(&self) -> Self {
856 match *self {
857 Self::Enabled {
858 toggle_override,
859 setting_configuration,
860 } => Self::Enabled {
861 setting_configuration,
862 toggle_override: !toggle_override,
863 },
864 Self::Disabled => Self::Disabled,
865 }
866 }
867}
868
869#[derive(Debug, Clone, Copy, PartialEq, Eq)]
870pub enum BufferSerialization {
871 All,
872 NonDirtyBuffers,
873}
874
875impl BufferSerialization {
876 fn new(restore_unsaved_buffers: bool) -> Self {
877 if restore_unsaved_buffers {
878 Self::All
879 } else {
880 Self::NonDirtyBuffers
881 }
882 }
883}
884
885/// Addons allow storing per-editor state in other crates (e.g. Vim)
886pub trait Addon: 'static {
887 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
888
889 fn render_buffer_header_controls(
890 &self,
891 _: &ExcerptInfo,
892 _: &Window,
893 _: &App,
894 ) -> Option<AnyElement> {
895 None
896 }
897
898 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
899 None
900 }
901
902 fn to_any(&self) -> &dyn std::any::Any;
903
904 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
905 None
906 }
907}
908
909struct ChangeLocation {
910 current: Option<Vec<Anchor>>,
911 original: Vec<Anchor>,
912}
913impl ChangeLocation {
914 fn locations(&self) -> &[Anchor] {
915 self.current.as_ref().unwrap_or(&self.original)
916 }
917}
918
919/// A set of caret positions, registered when the editor was edited.
920pub struct ChangeList {
921 changes: Vec<ChangeLocation>,
922 /// Currently "selected" change.
923 position: Option<usize>,
924}
925
926impl ChangeList {
927 pub fn new() -> Self {
928 Self {
929 changes: Vec::new(),
930 position: None,
931 }
932 }
933
934 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
935 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
936 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
937 if self.changes.is_empty() {
938 return None;
939 }
940
941 let prev = self.position.unwrap_or(self.changes.len());
942 let next = if direction == Direction::Prev {
943 prev.saturating_sub(count)
944 } else {
945 (prev + count).min(self.changes.len() - 1)
946 };
947 self.position = Some(next);
948 self.changes.get(next).map(|change| change.locations())
949 }
950
951 /// Adds a new change to the list, resetting the change list position.
952 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
953 self.position.take();
954 if let Some(last) = self.changes.last_mut()
955 && group
956 {
957 last.current = Some(new_positions)
958 } else {
959 self.changes.push(ChangeLocation {
960 original: new_positions,
961 current: None,
962 });
963 }
964 }
965
966 pub fn last(&self) -> Option<&[Anchor]> {
967 self.changes.last().map(|change| change.locations())
968 }
969
970 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
971 self.changes.last().map(|change| change.original.as_slice())
972 }
973
974 pub fn invert_last_group(&mut self) {
975 if let Some(last) = self.changes.last_mut()
976 && let Some(current) = last.current.as_mut()
977 {
978 mem::swap(&mut last.original, current);
979 }
980 }
981}
982
983#[derive(Clone)]
984struct InlineBlamePopoverState {
985 scroll_handle: ScrollHandle,
986 commit_message: Option<ParsedCommitMessage>,
987 markdown: Entity<Markdown>,
988}
989
990struct InlineBlamePopover {
991 position: gpui::Point<Pixels>,
992 hide_task: Option<Task<()>>,
993 popover_bounds: Option<Bounds<Pixels>>,
994 popover_state: InlineBlamePopoverState,
995 keyboard_grace: bool,
996}
997
998enum SelectionDragState {
999 /// State when no drag related activity is detected.
1000 None,
1001 /// State when the mouse is down on a selection that is about to be dragged.
1002 ReadyToDrag {
1003 selection: Selection<Anchor>,
1004 click_position: gpui::Point<Pixels>,
1005 mouse_down_time: Instant,
1006 },
1007 /// State when the mouse is dragging the selection in the editor.
1008 Dragging {
1009 selection: Selection<Anchor>,
1010 drop_cursor: Selection<Anchor>,
1011 hide_drop_cursor: bool,
1012 },
1013}
1014
1015enum ColumnarSelectionState {
1016 FromMouse {
1017 selection_tail: Anchor,
1018 display_point: Option<DisplayPoint>,
1019 },
1020 FromSelection {
1021 selection_tail: Anchor,
1022 },
1023}
1024
1025/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1026/// a breakpoint on them.
1027#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1028struct PhantomBreakpointIndicator {
1029 display_row: DisplayRow,
1030 /// There's a small debounce between hovering over the line and showing the indicator.
1031 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1032 is_active: bool,
1033 collides_with_existing_breakpoint: bool,
1034}
1035
1036/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1037/// in diff view mode.
1038#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1039pub(crate) struct PhantomDiffReviewIndicator {
1040 /// The starting anchor of the selection (or the only row if not dragging).
1041 pub start: Anchor,
1042 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1043 pub end: Anchor,
1044 /// There's a small debounce between hovering over the line and showing the indicator.
1045 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1046 pub is_active: bool,
1047}
1048
1049#[derive(Clone, Debug)]
1050pub(crate) struct DiffReviewDragState {
1051 pub start_anchor: Anchor,
1052 pub current_anchor: Anchor,
1053}
1054
1055impl DiffReviewDragState {
1056 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1057 let start = self.start_anchor.to_display_point(snapshot).row();
1058 let current = self.current_anchor.to_display_point(snapshot).row();
1059
1060 (start..=current).sorted()
1061 }
1062}
1063
1064/// Identifies a specific hunk in the diff buffer.
1065/// Used as a key to group comments by their location.
1066#[derive(Clone, Debug)]
1067pub struct DiffHunkKey {
1068 /// The file path (relative to worktree) this hunk belongs to.
1069 pub file_path: Arc<util::rel_path::RelPath>,
1070 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1071 pub hunk_start_anchor: Anchor,
1072}
1073
1074/// A review comment stored locally before being sent to the Agent panel.
1075#[derive(Clone)]
1076pub struct StoredReviewComment {
1077 /// Unique identifier for this comment (for edit/delete operations).
1078 pub id: usize,
1079 /// The comment text entered by the user.
1080 pub comment: String,
1081 /// Anchors for the code range being reviewed.
1082 pub range: Range<Anchor>,
1083 /// Timestamp when the comment was created (for chronological ordering).
1084 pub created_at: Instant,
1085 /// Whether this comment is currently being edited inline.
1086 pub is_editing: bool,
1087}
1088
1089impl StoredReviewComment {
1090 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1091 Self {
1092 id,
1093 comment,
1094 range: anchor_range,
1095 created_at: Instant::now(),
1096 is_editing: false,
1097 }
1098 }
1099}
1100
1101/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1102pub(crate) struct DiffReviewOverlay {
1103 pub anchor_range: Range<Anchor>,
1104 /// The block ID for the overlay.
1105 pub block_id: CustomBlockId,
1106 /// The editor entity for the review input.
1107 pub prompt_editor: Entity<Editor>,
1108 /// The hunk key this overlay belongs to.
1109 pub hunk_key: DiffHunkKey,
1110 /// Whether the comments section is expanded.
1111 pub comments_expanded: bool,
1112 /// Editors for comments currently being edited inline.
1113 /// Key: comment ID, Value: Editor entity for inline editing.
1114 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1115 /// Subscriptions for inline edit editors' action handlers.
1116 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1117 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1118 /// The current user's avatar URI for display in comment rows.
1119 pub user_avatar_uri: Option<SharedUri>,
1120 /// Subscription to keep the action handler alive.
1121 _subscription: Subscription,
1122}
1123
1124/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1125///
1126/// See the [module level documentation](self) for more information.
1127pub struct Editor {
1128 focus_handle: FocusHandle,
1129 last_focused_descendant: Option<WeakFocusHandle>,
1130 /// The text buffer being edited
1131 buffer: Entity<MultiBuffer>,
1132 /// Map of how text in the buffer should be displayed.
1133 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1134 pub display_map: Entity<DisplayMap>,
1135 placeholder_display_map: Option<Entity<DisplayMap>>,
1136 pub selections: SelectionsCollection,
1137 pub scroll_manager: ScrollManager,
1138 /// When inline assist editors are linked, they all render cursors because
1139 /// typing enters text into each of them, even the ones that aren't focused.
1140 pub(crate) show_cursor_when_unfocused: bool,
1141 columnar_selection_state: Option<ColumnarSelectionState>,
1142 add_selections_state: Option<AddSelectionsState>,
1143 select_next_state: Option<SelectNextState>,
1144 select_prev_state: Option<SelectNextState>,
1145 selection_history: SelectionHistory,
1146 defer_selection_effects: bool,
1147 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1148 autoclose_regions: Vec<AutocloseRegion>,
1149 snippet_stack: InvalidationStack<SnippetState>,
1150 select_syntax_node_history: SelectSyntaxNodeHistory,
1151 ime_transaction: Option<TransactionId>,
1152 pub diagnostics_max_severity: DiagnosticSeverity,
1153 active_diagnostics: ActiveDiagnostic,
1154 show_inline_diagnostics: bool,
1155 inline_diagnostics_update: Task<()>,
1156 inline_diagnostics_enabled: bool,
1157 diagnostics_enabled: bool,
1158 word_completions_enabled: bool,
1159 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1160 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1161 hard_wrap: Option<usize>,
1162 project: Option<Entity<Project>>,
1163 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1164 completion_provider: Option<Rc<dyn CompletionProvider>>,
1165 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1166 blink_manager: Entity<BlinkManager>,
1167 show_cursor_names: bool,
1168 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1169 pub show_local_selections: bool,
1170 mode: EditorMode,
1171 show_breadcrumbs: bool,
1172 show_gutter: bool,
1173 show_scrollbars: ScrollbarAxes,
1174 minimap_visibility: MinimapVisibility,
1175 offset_content: bool,
1176 disable_expand_excerpt_buttons: bool,
1177 delegate_expand_excerpts: bool,
1178 delegate_stage_and_restore: bool,
1179 delegate_open_excerpts: bool,
1180 enable_lsp_data: bool,
1181 enable_runnables: bool,
1182 show_line_numbers: Option<bool>,
1183 use_relative_line_numbers: Option<bool>,
1184 show_git_diff_gutter: Option<bool>,
1185 show_code_actions: Option<bool>,
1186 show_runnables: Option<bool>,
1187 show_breakpoints: Option<bool>,
1188 show_diff_review_button: bool,
1189 show_wrap_guides: Option<bool>,
1190 show_indent_guides: Option<bool>,
1191 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1192 highlight_order: usize,
1193 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1194 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1195 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1196 scrollbar_marker_state: ScrollbarMarkerState,
1197 active_indent_guides_state: ActiveIndentGuidesState,
1198 nav_history: Option<ItemNavHistory>,
1199 context_menu: RefCell<Option<CodeContextMenu>>,
1200 context_menu_options: Option<ContextMenuOptions>,
1201 mouse_context_menu: Option<MouseContextMenu>,
1202 completion_tasks: Vec<(CompletionId, Task<()>)>,
1203 inline_blame_popover: Option<InlineBlamePopover>,
1204 inline_blame_popover_show_task: Option<Task<()>>,
1205 signature_help_state: SignatureHelpState,
1206 auto_signature_help: Option<bool>,
1207 find_all_references_task_sources: Vec<Anchor>,
1208 next_completion_id: CompletionId,
1209 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1210 code_actions_task: Option<Task<Result<()>>>,
1211 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1212 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1213 debounced_selection_highlight_complete: bool,
1214 document_highlights_task: Option<Task<()>>,
1215 linked_editing_range_task: Option<Task<Option<()>>>,
1216 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1217 pending_rename: Option<RenameState>,
1218 searchable: bool,
1219 cursor_shape: CursorShape,
1220 /// Whether the cursor is offset one character to the left when something is
1221 /// selected (needed for vim visual mode)
1222 cursor_offset_on_selection: bool,
1223 current_line_highlight: Option<CurrentLineHighlight>,
1224 /// Whether to collapse search match ranges to just their start position.
1225 /// When true, navigating to a match positions the cursor at the match
1226 /// without selecting the matched text.
1227 collapse_matches: bool,
1228 autoindent_mode: Option<AutoindentMode>,
1229 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1230 input_enabled: bool,
1231 expects_character_input: bool,
1232 use_modal_editing: bool,
1233 read_only: bool,
1234 leader_id: Option<CollaboratorId>,
1235 remote_id: Option<ViewId>,
1236 pub hover_state: HoverState,
1237 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1238 prev_pressure_stage: Option<PressureStage>,
1239 gutter_hovered: bool,
1240 hovered_link_state: Option<HoveredLinkState>,
1241 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1242 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1243 active_edit_prediction: Option<EditPredictionState>,
1244 /// Used to prevent flickering as the user types while the menu is open
1245 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1246 edit_prediction_settings: EditPredictionSettings,
1247 edit_predictions_hidden_for_vim_mode: bool,
1248 show_edit_predictions_override: Option<bool>,
1249 show_completions_on_input_override: Option<bool>,
1250 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1251 edit_prediction_preview: EditPredictionPreview,
1252 in_leading_whitespace: bool,
1253 next_inlay_id: usize,
1254 next_color_inlay_id: usize,
1255 _subscriptions: Vec<Subscription>,
1256 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1257 gutter_dimensions: GutterDimensions,
1258 style: Option<EditorStyle>,
1259 text_style_refinement: Option<TextStyleRefinement>,
1260 next_editor_action_id: EditorActionId,
1261 editor_actions: Rc<
1262 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1263 >,
1264 use_autoclose: bool,
1265 use_auto_surround: bool,
1266 auto_replace_emoji_shortcode: bool,
1267 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1268 show_git_blame_gutter: bool,
1269 show_git_blame_inline: bool,
1270 show_git_blame_inline_delay_task: Option<Task<()>>,
1271 git_blame_inline_enabled: bool,
1272 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1273 buffer_serialization: Option<BufferSerialization>,
1274 show_selection_menu: Option<bool>,
1275 blame: Option<Entity<GitBlame>>,
1276 blame_subscription: Option<Subscription>,
1277 custom_context_menu: Option<
1278 Box<
1279 dyn 'static
1280 + Fn(
1281 &mut Self,
1282 DisplayPoint,
1283 &mut Window,
1284 &mut Context<Self>,
1285 ) -> Option<Entity<ui::ContextMenu>>,
1286 >,
1287 >,
1288 last_bounds: Option<Bounds<Pixels>>,
1289 last_position_map: Option<Rc<PositionMap>>,
1290 expect_bounds_change: Option<Bounds<Pixels>>,
1291 runnables: RunnableData,
1292 breakpoint_store: Option<Entity<BreakpointStore>>,
1293 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1294 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1295 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1296 /// Active diff review overlays. Multiple overlays can be open simultaneously
1297 /// when hunks have comments stored.
1298 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1299 /// Stored review comments grouped by hunk.
1300 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1301 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1302 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1303 /// Counter for generating unique comment IDs.
1304 next_review_comment_id: usize,
1305 hovered_diff_hunk_row: Option<DisplayRow>,
1306 pull_diagnostics_task: Task<()>,
1307 in_project_search: bool,
1308 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1309 breadcrumb_header: Option<String>,
1310 focused_block: Option<FocusedBlock>,
1311 next_scroll_position: NextScrollCursorCenterTopBottom,
1312 addons: HashMap<TypeId, Box<dyn Addon>>,
1313 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1314 load_diff_task: Option<Shared<Task<()>>>,
1315 /// Whether we are temporarily displaying a diff other than git's
1316 temporary_diff_override: bool,
1317 selection_mark_mode: bool,
1318 toggle_fold_multiple_buffers: Task<()>,
1319 _scroll_cursor_center_top_bottom_task: Task<()>,
1320 serialize_selections: Task<()>,
1321 serialize_folds: Task<()>,
1322 mouse_cursor_hidden: bool,
1323 minimap: Option<Entity<Self>>,
1324 hide_mouse_mode: HideMouseMode,
1325 pub change_list: ChangeList,
1326 inline_value_cache: InlineValueCache,
1327 number_deleted_lines: bool,
1328
1329 selection_drag_state: SelectionDragState,
1330 colors: Option<LspColorData>,
1331 post_scroll_update: Task<()>,
1332 refresh_colors_task: Task<()>,
1333 use_document_folding_ranges: bool,
1334 refresh_folding_ranges_task: Task<()>,
1335 inlay_hints: Option<LspInlayHintData>,
1336 folding_newlines: Task<()>,
1337 select_next_is_case_sensitive: Option<bool>,
1338 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1339 on_local_selections_changed:
1340 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1341 suppress_selection_callback: bool,
1342 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1343 accent_data: Option<AccentData>,
1344 bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1345 semantic_token_state: SemanticTokenState,
1346 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1347 refresh_document_symbols_task: Shared<Task<()>>,
1348 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1349 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1350 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1351 sticky_headers_task: Task<()>,
1352 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1353 pub(crate) colorize_brackets_task: Task<()>,
1354}
1355
1356#[derive(Debug, PartialEq)]
1357struct AccentData {
1358 colors: AccentColors,
1359 overrides: Vec<SharedString>,
1360}
1361
1362fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1363 if debounce_ms > 0 {
1364 Some(Duration::from_millis(debounce_ms))
1365 } else {
1366 None
1367 }
1368}
1369
1370#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1371enum NextScrollCursorCenterTopBottom {
1372 #[default]
1373 Center,
1374 Top,
1375 Bottom,
1376}
1377
1378impl NextScrollCursorCenterTopBottom {
1379 fn next(&self) -> Self {
1380 match self {
1381 Self::Center => Self::Top,
1382 Self::Top => Self::Bottom,
1383 Self::Bottom => Self::Center,
1384 }
1385 }
1386}
1387
1388#[derive(Clone)]
1389pub struct EditorSnapshot {
1390 pub mode: EditorMode,
1391 show_gutter: bool,
1392 offset_content: bool,
1393 show_line_numbers: Option<bool>,
1394 number_deleted_lines: bool,
1395 show_git_diff_gutter: Option<bool>,
1396 show_code_actions: Option<bool>,
1397 show_runnables: Option<bool>,
1398 show_breakpoints: Option<bool>,
1399 git_blame_gutter_max_author_length: Option<usize>,
1400 pub display_snapshot: DisplaySnapshot,
1401 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1402 is_focused: bool,
1403 scroll_anchor: SharedScrollAnchor,
1404 ongoing_scroll: OngoingScroll,
1405 current_line_highlight: CurrentLineHighlight,
1406 gutter_hovered: bool,
1407 semantic_tokens_enabled: bool,
1408}
1409
1410#[derive(Default, Debug, Clone, Copy)]
1411pub struct GutterDimensions {
1412 pub left_padding: Pixels,
1413 pub right_padding: Pixels,
1414 pub width: Pixels,
1415 pub margin: Pixels,
1416 pub git_blame_entries_width: Option<Pixels>,
1417}
1418
1419impl GutterDimensions {
1420 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1421 Self {
1422 margin: Self::default_gutter_margin(font_id, font_size, cx),
1423 ..Default::default()
1424 }
1425 }
1426
1427 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1428 -cx.text_system().descent(font_id, font_size)
1429 }
1430 /// The full width of the space taken up by the gutter.
1431 pub fn full_width(&self) -> Pixels {
1432 self.margin + self.width
1433 }
1434
1435 /// The width of the space reserved for the fold indicators,
1436 /// use alongside 'justify_end' and `gutter_width` to
1437 /// right align content with the line numbers
1438 pub fn fold_area_width(&self) -> Pixels {
1439 self.margin + self.right_padding
1440 }
1441}
1442
1443struct CharacterDimensions {
1444 em_width: Pixels,
1445 em_advance: Pixels,
1446 line_height: Pixels,
1447}
1448
1449#[derive(Debug)]
1450pub struct RemoteSelection {
1451 pub replica_id: ReplicaId,
1452 pub selection: Selection<Anchor>,
1453 pub cursor_shape: CursorShape,
1454 pub collaborator_id: CollaboratorId,
1455 pub line_mode: bool,
1456 pub user_name: Option<SharedString>,
1457 pub color: PlayerColor,
1458}
1459
1460#[derive(Clone, Debug)]
1461struct SelectionHistoryEntry {
1462 selections: Arc<[Selection<Anchor>]>,
1463 select_next_state: Option<SelectNextState>,
1464 select_prev_state: Option<SelectNextState>,
1465 add_selections_state: Option<AddSelectionsState>,
1466}
1467
1468#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1469enum SelectionHistoryMode {
1470 #[default]
1471 Normal,
1472 Undoing,
1473 Redoing,
1474 Skipping,
1475}
1476
1477#[derive(Clone, PartialEq, Eq, Hash)]
1478struct HoveredCursor {
1479 replica_id: ReplicaId,
1480 selection_id: usize,
1481}
1482
1483#[derive(Debug)]
1484/// SelectionEffects controls the side-effects of updating the selection.
1485///
1486/// The default behaviour does "what you mostly want":
1487/// - it pushes to the nav history if the cursor moved by >10 lines
1488/// - it re-triggers completion requests
1489/// - it scrolls to fit
1490///
1491/// You might want to modify these behaviours. For example when doing a "jump"
1492/// like go to definition, we always want to add to nav history; but when scrolling
1493/// in vim mode we never do.
1494///
1495/// Similarly, you might want to disable scrolling if you don't want the viewport to
1496/// move.
1497#[derive(Clone)]
1498pub struct SelectionEffects {
1499 nav_history: Option<bool>,
1500 completions: bool,
1501 scroll: Option<Autoscroll>,
1502}
1503
1504impl Default for SelectionEffects {
1505 fn default() -> Self {
1506 Self {
1507 nav_history: None,
1508 completions: true,
1509 scroll: Some(Autoscroll::fit()),
1510 }
1511 }
1512}
1513impl SelectionEffects {
1514 pub fn scroll(scroll: Autoscroll) -> Self {
1515 Self {
1516 scroll: Some(scroll),
1517 ..Default::default()
1518 }
1519 }
1520
1521 pub fn no_scroll() -> Self {
1522 Self {
1523 scroll: None,
1524 ..Default::default()
1525 }
1526 }
1527
1528 pub fn completions(self, completions: bool) -> Self {
1529 Self {
1530 completions,
1531 ..self
1532 }
1533 }
1534
1535 pub fn nav_history(self, nav_history: bool) -> Self {
1536 Self {
1537 nav_history: Some(nav_history),
1538 ..self
1539 }
1540 }
1541}
1542
1543struct DeferredSelectionEffectsState {
1544 changed: bool,
1545 effects: SelectionEffects,
1546 old_cursor_position: Anchor,
1547 history_entry: SelectionHistoryEntry,
1548}
1549
1550#[derive(Default)]
1551struct SelectionHistory {
1552 #[allow(clippy::type_complexity)]
1553 selections_by_transaction:
1554 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1555 mode: SelectionHistoryMode,
1556 undo_stack: VecDeque<SelectionHistoryEntry>,
1557 redo_stack: VecDeque<SelectionHistoryEntry>,
1558}
1559
1560impl SelectionHistory {
1561 #[track_caller]
1562 fn insert_transaction(
1563 &mut self,
1564 transaction_id: TransactionId,
1565 selections: Arc<[Selection<Anchor>]>,
1566 ) {
1567 if selections.is_empty() {
1568 log::error!(
1569 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1570 std::panic::Location::caller()
1571 );
1572 return;
1573 }
1574 self.selections_by_transaction
1575 .insert(transaction_id, (selections, None));
1576 }
1577
1578 #[allow(clippy::type_complexity)]
1579 fn transaction(
1580 &self,
1581 transaction_id: TransactionId,
1582 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1583 self.selections_by_transaction.get(&transaction_id)
1584 }
1585
1586 #[allow(clippy::type_complexity)]
1587 fn transaction_mut(
1588 &mut self,
1589 transaction_id: TransactionId,
1590 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1591 self.selections_by_transaction.get_mut(&transaction_id)
1592 }
1593
1594 fn push(&mut self, entry: SelectionHistoryEntry) {
1595 if !entry.selections.is_empty() {
1596 match self.mode {
1597 SelectionHistoryMode::Normal => {
1598 self.push_undo(entry);
1599 self.redo_stack.clear();
1600 }
1601 SelectionHistoryMode::Undoing => self.push_redo(entry),
1602 SelectionHistoryMode::Redoing => self.push_undo(entry),
1603 SelectionHistoryMode::Skipping => {}
1604 }
1605 }
1606 }
1607
1608 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1609 if self
1610 .undo_stack
1611 .back()
1612 .is_none_or(|e| e.selections != entry.selections)
1613 {
1614 self.undo_stack.push_back(entry);
1615 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1616 self.undo_stack.pop_front();
1617 }
1618 }
1619 }
1620
1621 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1622 if self
1623 .redo_stack
1624 .back()
1625 .is_none_or(|e| e.selections != entry.selections)
1626 {
1627 self.redo_stack.push_back(entry);
1628 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1629 self.redo_stack.pop_front();
1630 }
1631 }
1632 }
1633}
1634
1635#[derive(Clone, Copy)]
1636pub struct RowHighlightOptions {
1637 pub autoscroll: bool,
1638 pub include_gutter: bool,
1639}
1640
1641impl Default for RowHighlightOptions {
1642 fn default() -> Self {
1643 Self {
1644 autoscroll: Default::default(),
1645 include_gutter: true,
1646 }
1647 }
1648}
1649
1650struct RowHighlight {
1651 index: usize,
1652 range: Range<Anchor>,
1653 color: Hsla,
1654 options: RowHighlightOptions,
1655 type_id: TypeId,
1656}
1657
1658#[derive(Clone, Debug)]
1659struct AddSelectionsState {
1660 groups: Vec<AddSelectionsGroup>,
1661}
1662
1663#[derive(Clone, Debug)]
1664struct AddSelectionsGroup {
1665 above: bool,
1666 stack: Vec<usize>,
1667}
1668
1669#[derive(Clone)]
1670struct SelectNextState {
1671 query: AhoCorasick,
1672 wordwise: bool,
1673 done: bool,
1674}
1675
1676impl std::fmt::Debug for SelectNextState {
1677 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1678 f.debug_struct(std::any::type_name::<Self>())
1679 .field("wordwise", &self.wordwise)
1680 .field("done", &self.done)
1681 .finish()
1682 }
1683}
1684
1685#[derive(Debug)]
1686struct AutocloseRegion {
1687 selection_id: usize,
1688 range: Range<Anchor>,
1689 pair: BracketPair,
1690}
1691
1692#[derive(Debug)]
1693struct SnippetState {
1694 ranges: Vec<Vec<Range<Anchor>>>,
1695 active_index: usize,
1696 choices: Vec<Option<Vec<String>>>,
1697}
1698
1699#[doc(hidden)]
1700pub struct RenameState {
1701 pub range: Range<Anchor>,
1702 pub old_name: Arc<str>,
1703 pub editor: Entity<Editor>,
1704 block_id: CustomBlockId,
1705}
1706
1707struct InvalidationStack<T>(Vec<T>);
1708
1709struct RegisteredEditPredictionDelegate {
1710 provider: Arc<dyn EditPredictionDelegateHandle>,
1711 _subscription: Subscription,
1712}
1713
1714#[derive(Debug, PartialEq, Eq)]
1715pub struct ActiveDiagnosticGroup {
1716 pub active_range: Range<Anchor>,
1717 pub active_message: String,
1718 pub group_id: usize,
1719 pub blocks: HashSet<CustomBlockId>,
1720}
1721
1722#[derive(Debug, PartialEq, Eq)]
1723
1724pub(crate) enum ActiveDiagnostic {
1725 None,
1726 All,
1727 Group(ActiveDiagnosticGroup),
1728}
1729
1730#[derive(Serialize, Deserialize, Clone, Debug)]
1731pub struct ClipboardSelection {
1732 /// The number of bytes in this selection.
1733 pub len: usize,
1734 /// Whether this was a full-line selection.
1735 pub is_entire_line: bool,
1736 /// The indentation of the first line when this content was originally copied.
1737 pub first_line_indent: u32,
1738 #[serde(default)]
1739 pub file_path: Option<PathBuf>,
1740 #[serde(default)]
1741 pub line_range: Option<RangeInclusive<u32>>,
1742}
1743
1744impl ClipboardSelection {
1745 pub fn for_buffer(
1746 len: usize,
1747 is_entire_line: bool,
1748 range: Range<Point>,
1749 buffer: &MultiBufferSnapshot,
1750 project: Option<&Entity<Project>>,
1751 cx: &App,
1752 ) -> Self {
1753 let first_line_indent = buffer
1754 .indent_size_for_line(MultiBufferRow(range.start.row))
1755 .len;
1756
1757 let file_path = util::maybe!({
1758 let project = project?.read(cx);
1759 let file = buffer.file_at(range.start)?;
1760 let project_path = ProjectPath {
1761 worktree_id: file.worktree_id(cx),
1762 path: file.path().clone(),
1763 };
1764 project.absolute_path(&project_path, cx)
1765 });
1766
1767 let line_range = file_path.as_ref().and_then(|_| {
1768 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1769 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1770 if start_excerpt_id == end_excerpt_id {
1771 Some(start_point.row..=end_point.row)
1772 } else {
1773 None
1774 }
1775 });
1776
1777 Self {
1778 len,
1779 is_entire_line,
1780 first_line_indent,
1781 file_path,
1782 line_range,
1783 }
1784 }
1785}
1786
1787// selections, scroll behavior, was newest selection reversed
1788type SelectSyntaxNodeHistoryState = (
1789 Box<[Selection<Anchor>]>,
1790 SelectSyntaxNodeScrollBehavior,
1791 bool,
1792);
1793
1794#[derive(Default)]
1795struct SelectSyntaxNodeHistory {
1796 stack: Vec<SelectSyntaxNodeHistoryState>,
1797 // disable temporarily to allow changing selections without losing the stack
1798 pub disable_clearing: bool,
1799}
1800
1801impl SelectSyntaxNodeHistory {
1802 pub fn try_clear(&mut self) {
1803 if !self.disable_clearing {
1804 self.stack.clear();
1805 }
1806 }
1807
1808 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1809 self.stack.push(selection);
1810 }
1811
1812 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1813 self.stack.pop()
1814 }
1815}
1816
1817enum SelectSyntaxNodeScrollBehavior {
1818 CursorTop,
1819 FitSelection,
1820 CursorBottom,
1821}
1822
1823#[derive(Debug, Clone, Copy)]
1824pub(crate) struct NavigationData {
1825 cursor_anchor: Anchor,
1826 cursor_position: Point,
1827 scroll_anchor: ScrollAnchor,
1828 scroll_top_row: u32,
1829}
1830
1831#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1832pub enum GotoDefinitionKind {
1833 Symbol,
1834 Declaration,
1835 Type,
1836 Implementation,
1837}
1838
1839pub enum FormatTarget {
1840 Buffers(HashSet<Entity<Buffer>>),
1841 Ranges(Vec<Range<MultiBufferPoint>>),
1842}
1843
1844pub(crate) struct FocusedBlock {
1845 id: BlockId,
1846 focus_handle: WeakFocusHandle,
1847}
1848
1849#[derive(Clone, Debug)]
1850pub enum JumpData {
1851 MultiBufferRow {
1852 row: MultiBufferRow,
1853 line_offset_from_top: u32,
1854 },
1855 MultiBufferPoint {
1856 excerpt_id: ExcerptId,
1857 position: Point,
1858 anchor: text::Anchor,
1859 line_offset_from_top: u32,
1860 },
1861}
1862
1863pub enum MultibufferSelectionMode {
1864 First,
1865 All,
1866}
1867
1868#[derive(Clone, Copy, Debug, Default)]
1869pub struct RewrapOptions {
1870 pub override_language_settings: bool,
1871 pub preserve_existing_whitespace: bool,
1872}
1873
1874impl Editor {
1875 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1876 let buffer = cx.new(|cx| Buffer::local("", cx));
1877 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1878 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1879 }
1880
1881 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1882 let buffer = cx.new(|cx| Buffer::local("", cx));
1883 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1884 Self::new(EditorMode::full(), buffer, None, window, cx)
1885 }
1886
1887 pub fn auto_height(
1888 min_lines: usize,
1889 max_lines: usize,
1890 window: &mut Window,
1891 cx: &mut Context<Self>,
1892 ) -> Self {
1893 let buffer = cx.new(|cx| Buffer::local("", cx));
1894 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1895 Self::new(
1896 EditorMode::AutoHeight {
1897 min_lines,
1898 max_lines: Some(max_lines),
1899 },
1900 buffer,
1901 None,
1902 window,
1903 cx,
1904 )
1905 }
1906
1907 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1908 /// The editor grows as tall as needed to fit its content.
1909 pub fn auto_height_unbounded(
1910 min_lines: usize,
1911 window: &mut Window,
1912 cx: &mut Context<Self>,
1913 ) -> Self {
1914 let buffer = cx.new(|cx| Buffer::local("", cx));
1915 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1916 Self::new(
1917 EditorMode::AutoHeight {
1918 min_lines,
1919 max_lines: None,
1920 },
1921 buffer,
1922 None,
1923 window,
1924 cx,
1925 )
1926 }
1927
1928 pub fn for_buffer(
1929 buffer: Entity<Buffer>,
1930 project: Option<Entity<Project>>,
1931 window: &mut Window,
1932 cx: &mut Context<Self>,
1933 ) -> Self {
1934 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1935 Self::new(EditorMode::full(), buffer, project, window, cx)
1936 }
1937
1938 pub fn for_multibuffer(
1939 buffer: Entity<MultiBuffer>,
1940 project: Option<Entity<Project>>,
1941 window: &mut Window,
1942 cx: &mut Context<Self>,
1943 ) -> Self {
1944 Self::new(EditorMode::full(), buffer, project, window, cx)
1945 }
1946
1947 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1948 let mut clone = Self::new(
1949 self.mode.clone(),
1950 self.buffer.clone(),
1951 self.project.clone(),
1952 window,
1953 cx,
1954 );
1955 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1956 let snapshot = display_map.snapshot(cx);
1957 clone.display_map.update(cx, |display_map, cx| {
1958 display_map.set_state(&snapshot, cx);
1959 });
1960 snapshot
1961 });
1962 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1963 clone.folds_did_change(cx);
1964 clone.selections.clone_state(&self.selections);
1965 clone
1966 .scroll_manager
1967 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1968 clone.searchable = self.searchable;
1969 clone.read_only = self.read_only;
1970 clone.buffers_with_disabled_indent_guides =
1971 self.buffers_with_disabled_indent_guides.clone();
1972 clone
1973 }
1974
1975 pub fn new(
1976 mode: EditorMode,
1977 buffer: Entity<MultiBuffer>,
1978 project: Option<Entity<Project>>,
1979 window: &mut Window,
1980 cx: &mut Context<Self>,
1981 ) -> Self {
1982 Editor::new_internal(mode, buffer, project, None, window, cx)
1983 }
1984
1985 pub fn refresh_sticky_headers(
1986 &mut self,
1987 display_snapshot: &DisplaySnapshot,
1988 cx: &mut Context<Editor>,
1989 ) {
1990 if !self.mode.is_full() {
1991 return;
1992 }
1993 let multi_buffer = display_snapshot.buffer_snapshot();
1994 let scroll_anchor = self
1995 .scroll_manager
1996 .native_anchor(display_snapshot, cx)
1997 .anchor;
1998 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
1999 return;
2000 };
2001 let buffer = buffer.clone();
2002
2003 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2004 let max_row = buffer.max_point().row;
2005 let start_row = buffer_visible_start.row.min(max_row);
2006 let end_row = (buffer_visible_start.row + 10).min(max_row);
2007
2008 let syntax = self.style(cx).syntax.clone();
2009 let background_task = cx.background_spawn(async move {
2010 buffer
2011 .outline_items_containing(
2012 Point::new(start_row, 0)..Point::new(end_row, 0),
2013 true,
2014 Some(syntax.as_ref()),
2015 )
2016 .into_iter()
2017 .map(|outline_item| OutlineItem {
2018 depth: outline_item.depth,
2019 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2020 source_range_for_text: Anchor::range_in_buffer(
2021 excerpt_id,
2022 outline_item.source_range_for_text,
2023 ),
2024 text: outline_item.text,
2025 highlight_ranges: outline_item.highlight_ranges,
2026 name_ranges: outline_item.name_ranges,
2027 body_range: outline_item
2028 .body_range
2029 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2030 annotation_range: outline_item
2031 .annotation_range
2032 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2033 })
2034 .collect()
2035 });
2036 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2037 let sticky_headers = background_task.await;
2038 this.update(cx, |this, cx| {
2039 this.sticky_headers = Some(sticky_headers);
2040 cx.notify();
2041 })
2042 .ok();
2043 });
2044 }
2045
2046 fn new_internal(
2047 mode: EditorMode,
2048 multi_buffer: Entity<MultiBuffer>,
2049 project: Option<Entity<Project>>,
2050 display_map: Option<Entity<DisplayMap>>,
2051 window: &mut Window,
2052 cx: &mut Context<Self>,
2053 ) -> Self {
2054 debug_assert!(
2055 display_map.is_none() || mode.is_minimap(),
2056 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2057 );
2058
2059 let full_mode = mode.is_full();
2060 let is_minimap = mode.is_minimap();
2061 let diagnostics_max_severity = if full_mode {
2062 EditorSettings::get_global(cx)
2063 .diagnostics_max_severity
2064 .unwrap_or(DiagnosticSeverity::Hint)
2065 } else {
2066 DiagnosticSeverity::Off
2067 };
2068 let style = window.text_style();
2069 let font_size = style.font_size.to_pixels(window.rem_size());
2070 let editor = cx.entity().downgrade();
2071 let fold_placeholder = FoldPlaceholder {
2072 constrain_width: false,
2073 render: Arc::new(move |fold_id, fold_range, cx| {
2074 let editor = editor.clone();
2075 FoldPlaceholder::fold_element(fold_id, cx)
2076 .cursor_pointer()
2077 .child("⋯")
2078 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2079 .on_click(move |_, _window, cx| {
2080 editor
2081 .update(cx, |editor, cx| {
2082 editor.unfold_ranges(
2083 &[fold_range.start..fold_range.end],
2084 true,
2085 false,
2086 cx,
2087 );
2088 cx.stop_propagation();
2089 })
2090 .ok();
2091 })
2092 .into_any()
2093 }),
2094 merge_adjacent: true,
2095 ..FoldPlaceholder::default()
2096 };
2097 let display_map = display_map.unwrap_or_else(|| {
2098 cx.new(|cx| {
2099 DisplayMap::new(
2100 multi_buffer.clone(),
2101 style.font(),
2102 font_size,
2103 None,
2104 FILE_HEADER_HEIGHT,
2105 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2106 fold_placeholder,
2107 diagnostics_max_severity,
2108 cx,
2109 )
2110 })
2111 });
2112
2113 let selections = SelectionsCollection::new();
2114
2115 let blink_manager = cx.new(|cx| {
2116 let mut blink_manager = BlinkManager::new(
2117 CURSOR_BLINK_INTERVAL,
2118 |cx| EditorSettings::get_global(cx).cursor_blink,
2119 cx,
2120 );
2121 if is_minimap {
2122 blink_manager.disable(cx);
2123 }
2124 blink_manager
2125 });
2126
2127 let soft_wrap_mode_override =
2128 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2129
2130 let mut project_subscriptions = Vec::new();
2131 if full_mode && let Some(project) = project.as_ref() {
2132 project_subscriptions.push(cx.subscribe_in(
2133 project,
2134 window,
2135 |editor, _, event, window, cx| match event {
2136 project::Event::RefreshCodeLens => {
2137 // we always query lens with actions, without storing them, always refreshing them
2138 }
2139 project::Event::RefreshInlayHints {
2140 server_id,
2141 request_id,
2142 } => {
2143 editor.refresh_inlay_hints(
2144 InlayHintRefreshReason::RefreshRequested {
2145 server_id: *server_id,
2146 request_id: *request_id,
2147 },
2148 cx,
2149 );
2150 }
2151 project::Event::RefreshSemanticTokens {
2152 server_id,
2153 request_id,
2154 } => {
2155 editor.refresh_semantic_tokens(
2156 None,
2157 Some(RefreshForServer {
2158 server_id: *server_id,
2159 request_id: *request_id,
2160 }),
2161 cx,
2162 );
2163 }
2164 project::Event::LanguageServerRemoved(_) => {
2165 editor.registered_buffers.clear();
2166 editor.register_visible_buffers(cx);
2167 editor.invalidate_semantic_tokens(None);
2168 editor.refresh_runnables(None, window, cx);
2169 editor.update_lsp_data(None, window, cx);
2170 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2171 }
2172 project::Event::SnippetEdit(id, snippet_edits) => {
2173 // todo(lw): Non singletons
2174 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2175 let snapshot = buffer.read(cx).snapshot();
2176 let focus_handle = editor.focus_handle(cx);
2177 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2178 for (range, snippet) in snippet_edits {
2179 let buffer_range =
2180 language::range_from_lsp(*range).to_offset(&snapshot);
2181 editor
2182 .insert_snippet(
2183 &[MultiBufferOffset(buffer_range.start)
2184 ..MultiBufferOffset(buffer_range.end)],
2185 snippet.clone(),
2186 window,
2187 cx,
2188 )
2189 .ok();
2190 }
2191 }
2192 }
2193 }
2194 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2195 let buffer_id = *buffer_id;
2196 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2197 editor.register_buffer(buffer_id, cx);
2198 editor.refresh_runnables(Some(buffer_id), window, cx);
2199 editor.update_lsp_data(Some(buffer_id), window, cx);
2200 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2201 refresh_linked_ranges(editor, window, cx);
2202 editor.refresh_code_actions(window, cx);
2203 editor.refresh_document_highlights(cx);
2204 }
2205 }
2206
2207 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2208 let Some(workspace) = editor.workspace() else {
2209 return;
2210 };
2211 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2212 else {
2213 return;
2214 };
2215
2216 if active_editor.entity_id() == cx.entity_id() {
2217 let entity_id = cx.entity_id();
2218 workspace.update(cx, |this, cx| {
2219 this.panes_mut()
2220 .iter_mut()
2221 .filter(|pane| pane.entity_id() != entity_id)
2222 .for_each(|p| {
2223 p.update(cx, |pane, _| {
2224 pane.nav_history_mut().rename_item(
2225 entity_id,
2226 project_path.clone(),
2227 abs_path.clone().into(),
2228 );
2229 })
2230 });
2231 });
2232
2233 Self::open_transaction_for_hidden_buffers(
2234 workspace,
2235 transaction.clone(),
2236 "Rename".to_string(),
2237 window,
2238 cx,
2239 );
2240 }
2241 }
2242
2243 project::Event::WorkspaceEditApplied(transaction) => {
2244 let Some(workspace) = editor.workspace() else {
2245 return;
2246 };
2247 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2248 else {
2249 return;
2250 };
2251
2252 if active_editor.entity_id() == cx.entity_id() {
2253 Self::open_transaction_for_hidden_buffers(
2254 workspace,
2255 transaction.clone(),
2256 "LSP Edit".to_string(),
2257 window,
2258 cx,
2259 );
2260 }
2261 }
2262
2263 _ => {}
2264 },
2265 ));
2266 if let Some(task_inventory) = project
2267 .read(cx)
2268 .task_store()
2269 .read(cx)
2270 .task_inventory()
2271 .cloned()
2272 {
2273 project_subscriptions.push(cx.observe_in(
2274 &task_inventory,
2275 window,
2276 |editor, _, window, cx| {
2277 editor.refresh_runnables(None, window, cx);
2278 },
2279 ));
2280 };
2281
2282 project_subscriptions.push(cx.subscribe_in(
2283 &project.read(cx).breakpoint_store(),
2284 window,
2285 |editor, _, event, window, cx| match event {
2286 BreakpointStoreEvent::ClearDebugLines => {
2287 editor.clear_row_highlights::<ActiveDebugLine>();
2288 editor.refresh_inline_values(cx);
2289 }
2290 BreakpointStoreEvent::SetDebugLine => {
2291 if editor.go_to_active_debug_line(window, cx) {
2292 cx.stop_propagation();
2293 }
2294
2295 editor.refresh_inline_values(cx);
2296 }
2297 _ => {}
2298 },
2299 ));
2300 let git_store = project.read(cx).git_store().clone();
2301 let project = project.clone();
2302 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2303 if let GitStoreEvent::RepositoryAdded = event {
2304 this.load_diff_task = Some(
2305 update_uncommitted_diff_for_buffer(
2306 cx.entity(),
2307 &project,
2308 this.buffer.read(cx).all_buffers(),
2309 this.buffer.clone(),
2310 cx,
2311 )
2312 .shared(),
2313 );
2314 }
2315 }));
2316 }
2317
2318 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2319
2320 let inlay_hint_settings =
2321 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2322 let focus_handle = cx.focus_handle();
2323 if !is_minimap {
2324 cx.on_focus(&focus_handle, window, Self::handle_focus)
2325 .detach();
2326 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2327 .detach();
2328 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2329 .detach();
2330 cx.on_blur(&focus_handle, window, Self::handle_blur)
2331 .detach();
2332 cx.observe_pending_input(window, Self::observe_pending_input)
2333 .detach();
2334 }
2335
2336 let show_indent_guides =
2337 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2338 Some(false)
2339 } else {
2340 None
2341 };
2342
2343 let breakpoint_store = match (&mode, project.as_ref()) {
2344 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2345 _ => None,
2346 };
2347
2348 let mut code_action_providers = Vec::new();
2349 let mut load_uncommitted_diff = None;
2350 if let Some(project) = project.clone() {
2351 load_uncommitted_diff = Some(
2352 update_uncommitted_diff_for_buffer(
2353 cx.entity(),
2354 &project,
2355 multi_buffer.read(cx).all_buffers(),
2356 multi_buffer.clone(),
2357 cx,
2358 )
2359 .shared(),
2360 );
2361 code_action_providers.push(Rc::new(project) as Rc<_>);
2362 }
2363
2364 let mut editor = Self {
2365 focus_handle,
2366 show_cursor_when_unfocused: false,
2367 last_focused_descendant: None,
2368 buffer: multi_buffer.clone(),
2369 display_map: display_map.clone(),
2370 placeholder_display_map: None,
2371 selections,
2372 scroll_manager: ScrollManager::new(cx),
2373 columnar_selection_state: None,
2374 add_selections_state: None,
2375 select_next_state: None,
2376 select_prev_state: None,
2377 selection_history: SelectionHistory::default(),
2378 defer_selection_effects: false,
2379 deferred_selection_effects_state: None,
2380 autoclose_regions: Vec::new(),
2381 snippet_stack: InvalidationStack::default(),
2382 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2383 ime_transaction: None,
2384 active_diagnostics: ActiveDiagnostic::None,
2385 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2386 inline_diagnostics_update: Task::ready(()),
2387 inline_diagnostics: Vec::new(),
2388 soft_wrap_mode_override,
2389 diagnostics_max_severity,
2390 hard_wrap: None,
2391 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2392 semantics_provider: project
2393 .as_ref()
2394 .map(|project| Rc::new(project.downgrade()) as _),
2395 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2396 project,
2397 blink_manager: blink_manager.clone(),
2398 show_local_selections: true,
2399 show_scrollbars: ScrollbarAxes {
2400 horizontal: full_mode,
2401 vertical: full_mode,
2402 },
2403 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2404 offset_content: !matches!(mode, EditorMode::SingleLine),
2405 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2406 show_gutter: full_mode,
2407 show_line_numbers: (!full_mode).then_some(false),
2408 use_relative_line_numbers: None,
2409 disable_expand_excerpt_buttons: !full_mode,
2410 delegate_expand_excerpts: false,
2411 delegate_stage_and_restore: false,
2412 delegate_open_excerpts: false,
2413 enable_lsp_data: true,
2414 enable_runnables: true,
2415 show_git_diff_gutter: None,
2416 show_code_actions: None,
2417 show_runnables: None,
2418 show_breakpoints: None,
2419 show_diff_review_button: false,
2420 show_wrap_guides: None,
2421 show_indent_guides,
2422 buffers_with_disabled_indent_guides: HashSet::default(),
2423 highlight_order: 0,
2424 highlighted_rows: HashMap::default(),
2425 background_highlights: HashMap::default(),
2426 gutter_highlights: HashMap::default(),
2427 scrollbar_marker_state: ScrollbarMarkerState::default(),
2428 active_indent_guides_state: ActiveIndentGuidesState::default(),
2429 nav_history: None,
2430 context_menu: RefCell::new(None),
2431 context_menu_options: None,
2432 mouse_context_menu: None,
2433 completion_tasks: Vec::new(),
2434 inline_blame_popover: None,
2435 inline_blame_popover_show_task: None,
2436 signature_help_state: SignatureHelpState::default(),
2437 auto_signature_help: None,
2438 find_all_references_task_sources: Vec::new(),
2439 next_completion_id: 0,
2440 next_inlay_id: 0,
2441 code_action_providers,
2442 available_code_actions: None,
2443 code_actions_task: None,
2444 quick_selection_highlight_task: None,
2445 debounced_selection_highlight_task: None,
2446 debounced_selection_highlight_complete: false,
2447 document_highlights_task: None,
2448 linked_editing_range_task: None,
2449 pending_rename: None,
2450 searchable: !is_minimap,
2451 cursor_shape: EditorSettings::get_global(cx)
2452 .cursor_shape
2453 .unwrap_or_default(),
2454 cursor_offset_on_selection: false,
2455 current_line_highlight: None,
2456 autoindent_mode: Some(AutoindentMode::EachLine),
2457 collapse_matches: false,
2458 workspace: None,
2459 input_enabled: !is_minimap,
2460 expects_character_input: !is_minimap,
2461 use_modal_editing: full_mode,
2462 read_only: is_minimap,
2463 use_autoclose: true,
2464 use_auto_surround: true,
2465 auto_replace_emoji_shortcode: false,
2466 jsx_tag_auto_close_enabled_in_any_buffer: false,
2467 leader_id: None,
2468 remote_id: None,
2469 hover_state: HoverState::default(),
2470 pending_mouse_down: None,
2471 prev_pressure_stage: None,
2472 hovered_link_state: None,
2473 edit_prediction_provider: None,
2474 active_edit_prediction: None,
2475 stale_edit_prediction_in_menu: None,
2476 edit_prediction_preview: EditPredictionPreview::Inactive {
2477 released_too_fast: false,
2478 },
2479 inline_diagnostics_enabled: full_mode,
2480 diagnostics_enabled: full_mode,
2481 word_completions_enabled: full_mode,
2482 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2483 gutter_hovered: false,
2484 pixel_position_of_newest_cursor: None,
2485 last_bounds: None,
2486 last_position_map: None,
2487 expect_bounds_change: None,
2488 gutter_dimensions: GutterDimensions::default(),
2489 style: None,
2490 show_cursor_names: false,
2491 hovered_cursors: HashMap::default(),
2492 next_editor_action_id: EditorActionId::default(),
2493 editor_actions: Rc::default(),
2494 edit_predictions_hidden_for_vim_mode: false,
2495 show_edit_predictions_override: None,
2496 show_completions_on_input_override: None,
2497 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2498 edit_prediction_settings: EditPredictionSettings::Disabled,
2499 in_leading_whitespace: false,
2500 custom_context_menu: None,
2501 show_git_blame_gutter: false,
2502 show_git_blame_inline: false,
2503 show_selection_menu: None,
2504 show_git_blame_inline_delay_task: None,
2505 git_blame_inline_enabled: full_mode
2506 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2507 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2508 buffer_serialization: is_minimap.not().then(|| {
2509 BufferSerialization::new(
2510 ProjectSettings::get_global(cx)
2511 .session
2512 .restore_unsaved_buffers,
2513 )
2514 }),
2515 blame: None,
2516 blame_subscription: None,
2517
2518 breakpoint_store,
2519 gutter_breakpoint_indicator: (None, None),
2520 gutter_diff_review_indicator: (None, None),
2521 diff_review_drag_state: None,
2522 diff_review_overlays: Vec::new(),
2523 stored_review_comments: Vec::new(),
2524 next_review_comment_id: 0,
2525 hovered_diff_hunk_row: None,
2526 _subscriptions: (!is_minimap)
2527 .then(|| {
2528 vec![
2529 cx.observe(&multi_buffer, Self::on_buffer_changed),
2530 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2531 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2532 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2533 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2534 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2535 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2536 cx.observe_window_activation(window, |editor, window, cx| {
2537 let active = window.is_window_active();
2538 editor.blink_manager.update(cx, |blink_manager, cx| {
2539 if active {
2540 blink_manager.enable(cx);
2541 } else {
2542 blink_manager.disable(cx);
2543 }
2544 });
2545 if active {
2546 editor.show_mouse_cursor(cx);
2547 }
2548 }),
2549 ]
2550 })
2551 .unwrap_or_default(),
2552 runnables: RunnableData::new(),
2553 pull_diagnostics_task: Task::ready(()),
2554 colors: None,
2555 refresh_colors_task: Task::ready(()),
2556 use_document_folding_ranges: false,
2557 refresh_folding_ranges_task: Task::ready(()),
2558 inlay_hints: None,
2559 next_color_inlay_id: 0,
2560 post_scroll_update: Task::ready(()),
2561 linked_edit_ranges: Default::default(),
2562 in_project_search: false,
2563 previous_search_ranges: None,
2564 breadcrumb_header: None,
2565 focused_block: None,
2566 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2567 addons: HashMap::default(),
2568 registered_buffers: HashMap::default(),
2569 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2570 selection_mark_mode: false,
2571 toggle_fold_multiple_buffers: Task::ready(()),
2572 serialize_selections: Task::ready(()),
2573 serialize_folds: Task::ready(()),
2574 text_style_refinement: None,
2575 load_diff_task: load_uncommitted_diff,
2576 temporary_diff_override: false,
2577 mouse_cursor_hidden: false,
2578 minimap: None,
2579 hide_mouse_mode: EditorSettings::get_global(cx)
2580 .hide_mouse
2581 .unwrap_or_default(),
2582 change_list: ChangeList::new(),
2583 mode,
2584 selection_drag_state: SelectionDragState::None,
2585 folding_newlines: Task::ready(()),
2586 lookup_key: None,
2587 select_next_is_case_sensitive: None,
2588 on_local_selections_changed: None,
2589 suppress_selection_callback: false,
2590 applicable_language_settings: HashMap::default(),
2591 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2592 accent_data: None,
2593 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2594 number_deleted_lines: false,
2595 refresh_matching_bracket_highlights_task: Task::ready(()),
2596 refresh_document_symbols_task: Task::ready(()).shared(),
2597 lsp_document_symbols: HashMap::default(),
2598 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2599 outline_symbols_at_cursor: None,
2600 sticky_headers_task: Task::ready(()),
2601 sticky_headers: None,
2602 colorize_brackets_task: Task::ready(()),
2603 };
2604
2605 if is_minimap {
2606 return editor;
2607 }
2608
2609 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2610 editor.accent_data = editor.fetch_accent_data(cx);
2611
2612 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2613 editor
2614 ._subscriptions
2615 .push(cx.observe(breakpoints, |_, _, cx| {
2616 cx.notify();
2617 }));
2618 }
2619 editor._subscriptions.extend(project_subscriptions);
2620
2621 editor._subscriptions.push(cx.subscribe_in(
2622 &cx.entity(),
2623 window,
2624 |editor, _, e: &EditorEvent, window, cx| match e {
2625 EditorEvent::ScrollPositionChanged { local, .. } => {
2626 if *local {
2627 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2628 editor.inline_blame_popover.take();
2629 let snapshot = editor.snapshot(window, cx);
2630 let new_anchor = editor
2631 .scroll_manager
2632 .native_anchor(&snapshot.display_snapshot, cx);
2633 editor.update_restoration_data(cx, move |data| {
2634 data.scroll_position = (
2635 new_anchor.top_row(snapshot.buffer_snapshot()),
2636 new_anchor.offset,
2637 );
2638 });
2639
2640 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2641 cx.background_executor()
2642 .timer(Duration::from_millis(50))
2643 .await;
2644 editor
2645 .update_in(cx, |editor, window, cx| {
2646 editor.update_data_on_scroll(window, cx)
2647 })
2648 .ok();
2649 });
2650 }
2651 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2652 }
2653 EditorEvent::Edited { .. } => {
2654 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2655 .map(|vim_mode| vim_mode.0)
2656 .unwrap_or(false);
2657 if !vim_mode {
2658 let display_map = editor.display_snapshot(cx);
2659 let selections = editor.selections.all_adjusted_display(&display_map);
2660 let pop_state = editor
2661 .change_list
2662 .last()
2663 .map(|previous| {
2664 previous.len() == selections.len()
2665 && previous.iter().enumerate().all(|(ix, p)| {
2666 p.to_display_point(&display_map).row()
2667 == selections[ix].head().row()
2668 })
2669 })
2670 .unwrap_or(false);
2671 let new_positions = selections
2672 .into_iter()
2673 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2674 .collect();
2675 editor
2676 .change_list
2677 .push_to_change_list(pop_state, new_positions);
2678 }
2679 }
2680 _ => (),
2681 },
2682 ));
2683
2684 if let Some(dap_store) = editor
2685 .project
2686 .as_ref()
2687 .map(|project| project.read(cx).dap_store())
2688 {
2689 let weak_editor = cx.weak_entity();
2690
2691 editor
2692 ._subscriptions
2693 .push(
2694 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2695 let session_entity = cx.entity();
2696 weak_editor
2697 .update(cx, |editor, cx| {
2698 editor._subscriptions.push(
2699 cx.subscribe(&session_entity, Self::on_debug_session_event),
2700 );
2701 })
2702 .ok();
2703 }),
2704 );
2705
2706 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2707 editor
2708 ._subscriptions
2709 .push(cx.subscribe(&session, Self::on_debug_session_event));
2710 }
2711 }
2712
2713 // skip adding the initial selection to selection history
2714 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2715 editor.end_selection(window, cx);
2716 editor.selection_history.mode = SelectionHistoryMode::Normal;
2717
2718 editor.scroll_manager.show_scrollbars(window, cx);
2719 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2720
2721 if full_mode {
2722 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2723 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2724
2725 if editor.git_blame_inline_enabled {
2726 editor.start_git_blame_inline(false, window, cx);
2727 }
2728
2729 editor.go_to_active_debug_line(window, cx);
2730
2731 editor.minimap =
2732 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2733 editor.colors = Some(LspColorData::new(cx));
2734 editor.use_document_folding_ranges = true;
2735 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2736
2737 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2738 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2739 }
2740 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2741 }
2742
2743 editor
2744 }
2745
2746 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2747 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2748 }
2749
2750 pub fn deploy_mouse_context_menu(
2751 &mut self,
2752 position: gpui::Point<Pixels>,
2753 context_menu: Entity<ContextMenu>,
2754 window: &mut Window,
2755 cx: &mut Context<Self>,
2756 ) {
2757 self.mouse_context_menu = Some(MouseContextMenu::new(
2758 self,
2759 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2760 context_menu,
2761 window,
2762 cx,
2763 ));
2764 }
2765
2766 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2767 self.mouse_context_menu
2768 .as_ref()
2769 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2770 }
2771
2772 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2773 if self
2774 .selections
2775 .pending_anchor()
2776 .is_some_and(|pending_selection| {
2777 let snapshot = self.buffer().read(cx).snapshot(cx);
2778 pending_selection.range().includes(range, &snapshot)
2779 })
2780 {
2781 return true;
2782 }
2783
2784 self.selections
2785 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2786 .into_iter()
2787 .any(|selection| {
2788 // This is needed to cover a corner case, if we just check for an existing
2789 // selection in the fold range, having a cursor at the start of the fold
2790 // marks it as selected. Non-empty selections don't cause this.
2791 let length = selection.end - selection.start;
2792 length > 0
2793 })
2794 }
2795
2796 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2797 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2798 }
2799
2800 fn key_context_internal(
2801 &self,
2802 has_active_edit_prediction: bool,
2803 window: &mut Window,
2804 cx: &mut App,
2805 ) -> KeyContext {
2806 let mut key_context = KeyContext::new_with_defaults();
2807 key_context.add("Editor");
2808 let mode = match self.mode {
2809 EditorMode::SingleLine => "single_line",
2810 EditorMode::AutoHeight { .. } => "auto_height",
2811 EditorMode::Minimap { .. } => "minimap",
2812 EditorMode::Full { .. } => "full",
2813 };
2814
2815 if EditorSettings::jupyter_enabled(cx) {
2816 key_context.add("jupyter");
2817 }
2818
2819 key_context.set("mode", mode);
2820 if self.pending_rename.is_some() {
2821 key_context.add("renaming");
2822 }
2823
2824 if let Some(snippet_stack) = self.snippet_stack.last() {
2825 key_context.add("in_snippet");
2826
2827 if snippet_stack.active_index > 0 {
2828 key_context.add("has_previous_tabstop");
2829 }
2830
2831 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2832 key_context.add("has_next_tabstop");
2833 }
2834 }
2835
2836 match self.context_menu.borrow().as_ref() {
2837 Some(CodeContextMenu::Completions(menu)) => {
2838 if menu.visible() {
2839 key_context.add("menu");
2840 key_context.add("showing_completions");
2841 }
2842 }
2843 Some(CodeContextMenu::CodeActions(menu)) => {
2844 if menu.visible() {
2845 key_context.add("menu");
2846 key_context.add("showing_code_actions")
2847 }
2848 }
2849 None => {}
2850 }
2851
2852 if self.signature_help_state.has_multiple_signatures() {
2853 key_context.add("showing_signature_help");
2854 }
2855
2856 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2857 if !self.focus_handle(cx).contains_focused(window, cx)
2858 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2859 {
2860 for addon in self.addons.values() {
2861 addon.extend_key_context(&mut key_context, cx)
2862 }
2863 }
2864
2865 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2866 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2867 Some(
2868 file.full_path(cx)
2869 .extension()?
2870 .to_string_lossy()
2871 .to_lowercase(),
2872 )
2873 }) {
2874 key_context.set("extension", extension);
2875 }
2876 } else {
2877 key_context.add("multibuffer");
2878 }
2879
2880 if has_active_edit_prediction {
2881 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2882 key_context.add("copilot_suggestion");
2883 }
2884
2885 if self.in_leading_whitespace {
2886 key_context.add("in_leading_whitespace");
2887 }
2888
2889 if self.selection_mark_mode {
2890 key_context.add("selection_mode");
2891 }
2892
2893 let disjoint = self.selections.disjoint_anchors();
2894 let snapshot = self.snapshot(window, cx);
2895 let snapshot = snapshot.buffer_snapshot();
2896 if self.mode == EditorMode::SingleLine
2897 && let [selection] = disjoint
2898 && selection.start == selection.end
2899 && selection.end.to_offset(snapshot) == snapshot.len()
2900 {
2901 key_context.add("end_of_input");
2902 }
2903
2904 if self.has_any_expanded_diff_hunks(cx) {
2905 key_context.add("diffs_expanded");
2906 }
2907
2908 key_context
2909 }
2910
2911 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2912 self.last_bounds.as_ref()
2913 }
2914
2915 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2916 if self.mouse_cursor_hidden {
2917 self.mouse_cursor_hidden = false;
2918 cx.notify();
2919 }
2920 }
2921
2922 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2923 let hide_mouse_cursor = match origin {
2924 HideMouseCursorOrigin::TypingAction => {
2925 matches!(
2926 self.hide_mouse_mode,
2927 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2928 )
2929 }
2930 HideMouseCursorOrigin::MovementAction => {
2931 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2932 }
2933 };
2934 if self.mouse_cursor_hidden != hide_mouse_cursor {
2935 self.mouse_cursor_hidden = hide_mouse_cursor;
2936 cx.notify();
2937 }
2938 }
2939
2940 fn accept_edit_prediction_keystroke(
2941 &self,
2942 granularity: EditPredictionGranularity,
2943 window: &mut Window,
2944 cx: &mut App,
2945 ) -> Option<gpui::KeybindingKeystroke> {
2946 let key_context = self.key_context_internal(self.has_active_edit_prediction(), window, cx);
2947
2948 let bindings =
2949 match granularity {
2950 EditPredictionGranularity::Word => window
2951 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2952 EditPredictionGranularity::Line => window
2953 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2954 EditPredictionGranularity::Full => {
2955 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2956 }
2957 };
2958
2959 bindings
2960 .into_iter()
2961 .rev()
2962 .find_map(|binding| match binding.keystrokes() {
2963 [keystroke, ..] => Some(keystroke.clone()),
2964 _ => None,
2965 })
2966 }
2967
2968 fn preview_edit_prediction_keystroke(
2969 &self,
2970 window: &mut Window,
2971 cx: &mut App,
2972 ) -> Option<gpui::KeybindingKeystroke> {
2973 let key_context = self.key_context_internal(self.has_active_edit_prediction(), window, cx);
2974 let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
2975 bindings
2976 .into_iter()
2977 .rev()
2978 .find_map(|binding| match binding.keystrokes() {
2979 [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
2980 _ => None,
2981 })
2982 }
2983
2984 fn edit_prediction_cursor_popover_prefers_preview(
2985 &self,
2986 completion: &EditPredictionState,
2987 ) -> bool {
2988 match &completion.completion {
2989 EditPrediction::Edit {
2990 edits, snapshot, ..
2991 } => {
2992 let mut start_row: Option<u32> = None;
2993 let mut end_row: Option<u32> = None;
2994
2995 for (range, text) in edits {
2996 let edit_start_row = range.start.text_anchor.to_point(snapshot).row;
2997 let old_end_row = range.end.text_anchor.to_point(snapshot).row;
2998 let inserted_newline_count = text
2999 .as_ref()
3000 .chars()
3001 .filter(|character| *character == '\n')
3002 .count() as u32;
3003 let deleted_newline_count = old_end_row - edit_start_row;
3004 let preview_end_row = edit_start_row + inserted_newline_count;
3005
3006 start_row =
3007 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3008 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3009
3010 if deleted_newline_count > 1 {
3011 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3012 }
3013 }
3014
3015 start_row
3016 .zip(end_row)
3017 .is_some_and(|(start_row, end_row)| end_row > start_row)
3018 }
3019 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3020 }
3021 }
3022
3023 fn edit_prediction_keybind_display(
3024 &self,
3025 surface: EditPredictionKeybindSurface,
3026 window: &mut Window,
3027 cx: &mut App,
3028 ) -> EditPredictionKeybindDisplay {
3029 let accept_keystroke =
3030 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3031 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3032
3033 let action = match surface {
3034 EditPredictionKeybindSurface::Inline
3035 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3036 if self.edit_prediction_requires_modifier() {
3037 EditPredictionKeybindAction::Preview
3038 } else {
3039 EditPredictionKeybindAction::Accept
3040 }
3041 }
3042 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3043 .active_edit_prediction
3044 .as_ref()
3045 .filter(|completion| {
3046 self.edit_prediction_cursor_popover_prefers_preview(completion)
3047 })
3048 .map_or(EditPredictionKeybindAction::Accept, |_| {
3049 EditPredictionKeybindAction::Preview
3050 }),
3051 };
3052 #[cfg(test)]
3053 let preview_copy = preview_keystroke.clone();
3054 #[cfg(test)]
3055 let accept_copy = accept_keystroke.clone();
3056
3057 let displayed_keystroke = match surface {
3058 EditPredictionKeybindSurface::Inline => match action {
3059 EditPredictionKeybindAction::Accept => accept_keystroke,
3060 EditPredictionKeybindAction::Preview => preview_keystroke,
3061 },
3062 EditPredictionKeybindSurface::CursorPopoverCompact
3063 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3064 EditPredictionKeybindAction::Accept => accept_keystroke,
3065 EditPredictionKeybindAction::Preview => {
3066 preview_keystroke.or_else(|| accept_keystroke.clone())
3067 }
3068 },
3069 };
3070
3071 let missing_accept_keystroke = displayed_keystroke.is_none();
3072
3073 EditPredictionKeybindDisplay {
3074 #[cfg(test)]
3075 accept_keystroke: accept_copy,
3076 #[cfg(test)]
3077 preview_keystroke: preview_copy,
3078 displayed_keystroke,
3079 action,
3080 missing_accept_keystroke,
3081 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3082 && self.edit_prediction_preview.released_too_fast(),
3083 }
3084 }
3085
3086 pub fn new_file(
3087 workspace: &mut Workspace,
3088 _: &workspace::NewFile,
3089 window: &mut Window,
3090 cx: &mut Context<Workspace>,
3091 ) {
3092 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3093 "Failed to create buffer",
3094 window,
3095 cx,
3096 |e, _, _| match e.error_code() {
3097 ErrorCode::RemoteUpgradeRequired => Some(format!(
3098 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3099 e.error_tag("required").unwrap_or("the latest version")
3100 )),
3101 _ => None,
3102 },
3103 );
3104 }
3105
3106 pub fn new_in_workspace(
3107 workspace: &mut Workspace,
3108 window: &mut Window,
3109 cx: &mut Context<Workspace>,
3110 ) -> Task<Result<Entity<Editor>>> {
3111 let project = workspace.project().clone();
3112 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3113
3114 cx.spawn_in(window, async move |workspace, cx| {
3115 let buffer = create.await?;
3116 workspace.update_in(cx, |workspace, window, cx| {
3117 let editor =
3118 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3119 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3120 editor
3121 })
3122 })
3123 }
3124
3125 fn new_file_vertical(
3126 workspace: &mut Workspace,
3127 _: &workspace::NewFileSplitVertical,
3128 window: &mut Window,
3129 cx: &mut Context<Workspace>,
3130 ) {
3131 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3132 }
3133
3134 fn new_file_horizontal(
3135 workspace: &mut Workspace,
3136 _: &workspace::NewFileSplitHorizontal,
3137 window: &mut Window,
3138 cx: &mut Context<Workspace>,
3139 ) {
3140 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3141 }
3142
3143 fn new_file_split(
3144 workspace: &mut Workspace,
3145 action: &workspace::NewFileSplit,
3146 window: &mut Window,
3147 cx: &mut Context<Workspace>,
3148 ) {
3149 Self::new_file_in_direction(workspace, action.0, window, cx)
3150 }
3151
3152 fn new_file_in_direction(
3153 workspace: &mut Workspace,
3154 direction: SplitDirection,
3155 window: &mut Window,
3156 cx: &mut Context<Workspace>,
3157 ) {
3158 let project = workspace.project().clone();
3159 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3160
3161 cx.spawn_in(window, async move |workspace, cx| {
3162 let buffer = create.await?;
3163 workspace.update_in(cx, move |workspace, window, cx| {
3164 workspace.split_item(
3165 direction,
3166 Box::new(
3167 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3168 ),
3169 window,
3170 cx,
3171 )
3172 })?;
3173 anyhow::Ok(())
3174 })
3175 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3176 match e.error_code() {
3177 ErrorCode::RemoteUpgradeRequired => Some(format!(
3178 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3179 e.error_tag("required").unwrap_or("the latest version")
3180 )),
3181 _ => None,
3182 }
3183 });
3184 }
3185
3186 pub fn leader_id(&self) -> Option<CollaboratorId> {
3187 self.leader_id
3188 }
3189
3190 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3191 &self.buffer
3192 }
3193
3194 pub fn project(&self) -> Option<&Entity<Project>> {
3195 self.project.as_ref()
3196 }
3197
3198 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3199 self.workspace.as_ref()?.0.upgrade()
3200 }
3201
3202 /// Detaches a task and shows an error notification in the workspace if available,
3203 /// otherwise just logs the error.
3204 pub fn detach_and_notify_err<R, E>(
3205 &self,
3206 task: Task<Result<R, E>>,
3207 window: &mut Window,
3208 cx: &mut App,
3209 ) where
3210 E: std::fmt::Debug + std::fmt::Display + 'static,
3211 R: 'static,
3212 {
3213 if let Some(workspace) = self.workspace() {
3214 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3215 } else {
3216 task.detach_and_log_err(cx);
3217 }
3218 }
3219
3220 /// Returns the workspace serialization ID if this editor should be serialized.
3221 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3222 self.workspace
3223 .as_ref()
3224 .filter(|_| self.should_serialize_buffer())
3225 .and_then(|workspace| workspace.1)
3226 }
3227
3228 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3229 self.buffer().read(cx).title(cx)
3230 }
3231
3232 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3233 let git_blame_gutter_max_author_length = self
3234 .render_git_blame_gutter(cx)
3235 .then(|| {
3236 if let Some(blame) = self.blame.as_ref() {
3237 let max_author_length =
3238 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3239 Some(max_author_length)
3240 } else {
3241 None
3242 }
3243 })
3244 .flatten();
3245
3246 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3247
3248 EditorSnapshot {
3249 mode: self.mode.clone(),
3250 show_gutter: self.show_gutter,
3251 offset_content: self.offset_content,
3252 show_line_numbers: self.show_line_numbers,
3253 number_deleted_lines: self.number_deleted_lines,
3254 show_git_diff_gutter: self.show_git_diff_gutter,
3255 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3256 show_code_actions: self.show_code_actions,
3257 show_runnables: self.show_runnables,
3258 show_breakpoints: self.show_breakpoints,
3259 git_blame_gutter_max_author_length,
3260 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3261 display_snapshot,
3262 placeholder_display_snapshot: self
3263 .placeholder_display_map
3264 .as_ref()
3265 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3266 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3267 is_focused: self.focus_handle.is_focused(window),
3268 current_line_highlight: self
3269 .current_line_highlight
3270 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3271 gutter_hovered: self.gutter_hovered,
3272 }
3273 }
3274
3275 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3276 self.buffer.read(cx).language_at(point, cx)
3277 }
3278
3279 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3280 self.buffer.read(cx).read(cx).file_at(point).cloned()
3281 }
3282
3283 pub fn active_excerpt(
3284 &self,
3285 cx: &App,
3286 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3287 self.buffer
3288 .read(cx)
3289 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3290 }
3291
3292 pub fn mode(&self) -> &EditorMode {
3293 &self.mode
3294 }
3295
3296 pub fn set_mode(&mut self, mode: EditorMode) {
3297 self.mode = mode;
3298 }
3299
3300 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3301 self.collaboration_hub.as_deref()
3302 }
3303
3304 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3305 self.collaboration_hub = Some(hub);
3306 }
3307
3308 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3309 self.in_project_search = in_project_search;
3310 }
3311
3312 pub fn set_custom_context_menu(
3313 &mut self,
3314 f: impl 'static
3315 + Fn(
3316 &mut Self,
3317 DisplayPoint,
3318 &mut Window,
3319 &mut Context<Self>,
3320 ) -> Option<Entity<ui::ContextMenu>>,
3321 ) {
3322 self.custom_context_menu = Some(Box::new(f))
3323 }
3324
3325 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3326 self.completion_provider = provider;
3327 }
3328
3329 #[cfg(any(test, feature = "test-support"))]
3330 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3331 self.completion_provider.clone()
3332 }
3333
3334 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3335 self.semantics_provider.clone()
3336 }
3337
3338 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3339 self.semantics_provider = provider;
3340 }
3341
3342 pub fn set_edit_prediction_provider<T>(
3343 &mut self,
3344 provider: Option<Entity<T>>,
3345 window: &mut Window,
3346 cx: &mut Context<Self>,
3347 ) where
3348 T: EditPredictionDelegate,
3349 {
3350 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3351 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3352 if this.focus_handle.is_focused(window) {
3353 this.update_visible_edit_prediction(window, cx);
3354 }
3355 }),
3356 provider: Arc::new(provider),
3357 });
3358 self.update_edit_prediction_settings(cx);
3359 self.refresh_edit_prediction(false, false, window, cx);
3360 }
3361
3362 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3363 self.placeholder_display_map
3364 .as_ref()
3365 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3366 }
3367
3368 pub fn set_placeholder_text(
3369 &mut self,
3370 placeholder_text: &str,
3371 window: &mut Window,
3372 cx: &mut Context<Self>,
3373 ) {
3374 let multibuffer = cx
3375 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3376
3377 let style = window.text_style();
3378
3379 self.placeholder_display_map = Some(cx.new(|cx| {
3380 DisplayMap::new(
3381 multibuffer,
3382 style.font(),
3383 style.font_size.to_pixels(window.rem_size()),
3384 None,
3385 FILE_HEADER_HEIGHT,
3386 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3387 Default::default(),
3388 DiagnosticSeverity::Off,
3389 cx,
3390 )
3391 }));
3392 cx.notify();
3393 }
3394
3395 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3396 self.cursor_shape = cursor_shape;
3397
3398 // Disrupt blink for immediate user feedback that the cursor shape has changed
3399 self.blink_manager.update(cx, BlinkManager::show_cursor);
3400
3401 cx.notify();
3402 }
3403
3404 pub fn cursor_shape(&self) -> CursorShape {
3405 self.cursor_shape
3406 }
3407
3408 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3409 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3410 }
3411
3412 pub fn set_current_line_highlight(
3413 &mut self,
3414 current_line_highlight: Option<CurrentLineHighlight>,
3415 ) {
3416 self.current_line_highlight = current_line_highlight;
3417 }
3418
3419 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3420 self.collapse_matches = collapse_matches;
3421 }
3422
3423 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3424 if self.collapse_matches {
3425 return range.start..range.start;
3426 }
3427 range.clone()
3428 }
3429
3430 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3431 self.display_map.read(cx).clip_at_line_ends
3432 }
3433
3434 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3435 if self.display_map.read(cx).clip_at_line_ends != clip {
3436 self.display_map
3437 .update(cx, |map, _| map.clip_at_line_ends = clip);
3438 }
3439 }
3440
3441 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3442 self.input_enabled = input_enabled;
3443 }
3444
3445 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3446 self.expects_character_input = expects_character_input;
3447 }
3448
3449 pub fn set_edit_predictions_hidden_for_vim_mode(
3450 &mut self,
3451 hidden: bool,
3452 window: &mut Window,
3453 cx: &mut Context<Self>,
3454 ) {
3455 if hidden != self.edit_predictions_hidden_for_vim_mode {
3456 self.edit_predictions_hidden_for_vim_mode = hidden;
3457 if hidden {
3458 self.update_visible_edit_prediction(window, cx);
3459 } else {
3460 self.refresh_edit_prediction(true, false, window, cx);
3461 }
3462 }
3463 }
3464
3465 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3466 self.menu_edit_predictions_policy = value;
3467 }
3468
3469 pub fn set_autoindent(&mut self, autoindent: bool) {
3470 if autoindent {
3471 self.autoindent_mode = Some(AutoindentMode::EachLine);
3472 } else {
3473 self.autoindent_mode = None;
3474 }
3475 }
3476
3477 pub fn capability(&self, cx: &App) -> Capability {
3478 if self.read_only {
3479 Capability::ReadOnly
3480 } else {
3481 self.buffer.read(cx).capability()
3482 }
3483 }
3484
3485 pub fn read_only(&self, cx: &App) -> bool {
3486 self.read_only || self.buffer.read(cx).read_only()
3487 }
3488
3489 pub fn set_read_only(&mut self, read_only: bool) {
3490 self.read_only = read_only;
3491 }
3492
3493 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3494 self.use_autoclose = autoclose;
3495 }
3496
3497 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3498 self.use_auto_surround = auto_surround;
3499 }
3500
3501 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3502 self.auto_replace_emoji_shortcode = auto_replace;
3503 }
3504
3505 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3506 self.buffer_serialization = should_serialize.then(|| {
3507 BufferSerialization::new(
3508 ProjectSettings::get_global(cx)
3509 .session
3510 .restore_unsaved_buffers,
3511 )
3512 })
3513 }
3514
3515 fn should_serialize_buffer(&self) -> bool {
3516 self.buffer_serialization.is_some()
3517 }
3518
3519 pub fn toggle_edit_predictions(
3520 &mut self,
3521 _: &ToggleEditPrediction,
3522 window: &mut Window,
3523 cx: &mut Context<Self>,
3524 ) {
3525 if self.show_edit_predictions_override.is_some() {
3526 self.set_show_edit_predictions(None, window, cx);
3527 } else {
3528 let show_edit_predictions = !self.edit_predictions_enabled();
3529 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3530 }
3531 }
3532
3533 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3534 self.show_completions_on_input_override = show_completions_on_input;
3535 }
3536
3537 pub fn set_show_edit_predictions(
3538 &mut self,
3539 show_edit_predictions: Option<bool>,
3540 window: &mut Window,
3541 cx: &mut Context<Self>,
3542 ) {
3543 self.show_edit_predictions_override = show_edit_predictions;
3544 self.update_edit_prediction_settings(cx);
3545
3546 if let Some(false) = show_edit_predictions {
3547 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3548 } else {
3549 self.refresh_edit_prediction(false, true, window, cx);
3550 }
3551 }
3552
3553 fn edit_predictions_disabled_in_scope(
3554 &self,
3555 buffer: &Entity<Buffer>,
3556 buffer_position: language::Anchor,
3557 cx: &App,
3558 ) -> bool {
3559 let snapshot = buffer.read(cx).snapshot();
3560 let settings = snapshot.settings_at(buffer_position, cx);
3561
3562 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3563 return false;
3564 };
3565
3566 scope.override_name().is_some_and(|scope_name| {
3567 settings
3568 .edit_predictions_disabled_in
3569 .iter()
3570 .any(|s| s == scope_name)
3571 })
3572 }
3573
3574 pub fn set_use_modal_editing(&mut self, to: bool) {
3575 self.use_modal_editing = to;
3576 }
3577
3578 pub fn use_modal_editing(&self) -> bool {
3579 self.use_modal_editing
3580 }
3581
3582 fn selections_did_change(
3583 &mut self,
3584 local: bool,
3585 old_cursor_position: &Anchor,
3586 effects: SelectionEffects,
3587 window: &mut Window,
3588 cx: &mut Context<Self>,
3589 ) {
3590 window.invalidate_character_coordinates();
3591
3592 // Copy selections to primary selection buffer
3593 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3594 if local {
3595 let selections = self
3596 .selections
3597 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3598 let buffer_handle = self.buffer.read(cx).read(cx);
3599
3600 let mut text = String::new();
3601 for (index, selection) in selections.iter().enumerate() {
3602 let text_for_selection = buffer_handle
3603 .text_for_range(selection.start..selection.end)
3604 .collect::<String>();
3605
3606 text.push_str(&text_for_selection);
3607 if index != selections.len() - 1 {
3608 text.push('\n');
3609 }
3610 }
3611
3612 if !text.is_empty() {
3613 cx.write_to_primary(ClipboardItem::new_string(text));
3614 }
3615 }
3616
3617 let selection_anchors = self.selections.disjoint_anchors_arc();
3618
3619 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3620 self.buffer.update(cx, |buffer, cx| {
3621 buffer.set_active_selections(
3622 &selection_anchors,
3623 self.selections.line_mode(),
3624 self.cursor_shape,
3625 cx,
3626 )
3627 });
3628 }
3629 let display_map = self
3630 .display_map
3631 .update(cx, |display_map, cx| display_map.snapshot(cx));
3632 let buffer = display_map.buffer_snapshot();
3633 if self.selections.count() == 1 {
3634 self.add_selections_state = None;
3635 }
3636 self.select_next_state = None;
3637 self.select_prev_state = None;
3638 self.select_syntax_node_history.try_clear();
3639 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3640 self.snippet_stack.invalidate(&selection_anchors, buffer);
3641 self.take_rename(false, window, cx);
3642
3643 let newest_selection = self.selections.newest_anchor();
3644 let new_cursor_position = newest_selection.head();
3645 let selection_start = newest_selection.start;
3646
3647 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3648 self.push_to_nav_history(
3649 *old_cursor_position,
3650 Some(new_cursor_position.to_point(buffer)),
3651 false,
3652 effects.nav_history == Some(true),
3653 cx,
3654 );
3655 }
3656
3657 if local {
3658 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3659 self.register_buffer(buffer_id, cx);
3660 }
3661
3662 let mut context_menu = self.context_menu.borrow_mut();
3663 let completion_menu = match context_menu.as_ref() {
3664 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3665 Some(CodeContextMenu::CodeActions(_)) => {
3666 *context_menu = None;
3667 None
3668 }
3669 None => None,
3670 };
3671 let completion_position = completion_menu.map(|menu| menu.initial_position);
3672 drop(context_menu);
3673
3674 if effects.completions
3675 && let Some(completion_position) = completion_position
3676 {
3677 let start_offset = selection_start.to_offset(buffer);
3678 let position_matches = start_offset == completion_position.to_offset(buffer);
3679 let continue_showing = if let Some((snap, ..)) =
3680 buffer.point_to_buffer_offset(completion_position)
3681 && !snap.capability.editable()
3682 {
3683 false
3684 } else if position_matches {
3685 if self.snippet_stack.is_empty() {
3686 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3687 == Some(CharKind::Word)
3688 } else {
3689 // Snippet choices can be shown even when the cursor is in whitespace.
3690 // Dismissing the menu with actions like backspace is handled by
3691 // invalidation regions.
3692 true
3693 }
3694 } else {
3695 false
3696 };
3697
3698 if continue_showing {
3699 self.open_or_update_completions_menu(None, None, false, window, cx);
3700 } else {
3701 self.hide_context_menu(window, cx);
3702 }
3703 }
3704
3705 hide_hover(self, cx);
3706
3707 if old_cursor_position.to_display_point(&display_map).row()
3708 != new_cursor_position.to_display_point(&display_map).row()
3709 {
3710 self.available_code_actions.take();
3711 }
3712 self.refresh_code_actions(window, cx);
3713 self.refresh_document_highlights(cx);
3714 refresh_linked_ranges(self, window, cx);
3715
3716 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3717 self.refresh_matching_bracket_highlights(&display_map, cx);
3718 self.refresh_outline_symbols_at_cursor(cx);
3719 self.update_visible_edit_prediction(window, cx);
3720 self.inline_blame_popover.take();
3721 if self.git_blame_inline_enabled {
3722 self.start_inline_blame_timer(window, cx);
3723 }
3724 }
3725
3726 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3727
3728 if local && !self.suppress_selection_callback {
3729 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3730 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3731 callback(cursor_position, window, cx);
3732 }
3733 }
3734
3735 cx.emit(EditorEvent::SelectionsChanged { local });
3736
3737 let selections = &self.selections.disjoint_anchors_arc();
3738 if selections.len() == 1 {
3739 cx.emit(SearchEvent::ActiveMatchChanged)
3740 }
3741 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3742 let inmemory_selections = selections
3743 .iter()
3744 .map(|s| {
3745 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3746 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3747 })
3748 .collect();
3749 self.update_restoration_data(cx, |data| {
3750 data.selections = inmemory_selections;
3751 });
3752
3753 if WorkspaceSettings::get(None, cx).restore_on_startup
3754 != RestoreOnStartupBehavior::EmptyTab
3755 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3756 {
3757 let snapshot = self.buffer().read(cx).snapshot(cx);
3758 let selections = selections.clone();
3759 let background_executor = cx.background_executor().clone();
3760 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3761 let db = EditorDb::global(cx);
3762 self.serialize_selections = cx.background_spawn(async move {
3763 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3764 let db_selections = selections
3765 .iter()
3766 .map(|selection| {
3767 (
3768 selection.start.to_offset(&snapshot).0,
3769 selection.end.to_offset(&snapshot).0,
3770 )
3771 })
3772 .collect();
3773
3774 db.save_editor_selections(editor_id, workspace_id, db_selections)
3775 .await
3776 .with_context(|| {
3777 format!(
3778 "persisting editor selections for editor {editor_id}, \
3779 workspace {workspace_id:?}"
3780 )
3781 })
3782 .log_err();
3783 });
3784 }
3785 }
3786
3787 cx.notify();
3788 }
3789
3790 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3791 use text::ToOffset as _;
3792 use text::ToPoint as _;
3793
3794 if self.mode.is_minimap()
3795 || WorkspaceSettings::get(None, cx).restore_on_startup
3796 == RestoreOnStartupBehavior::EmptyTab
3797 {
3798 return;
3799 }
3800
3801 if !self.buffer().read(cx).is_singleton() {
3802 return;
3803 }
3804
3805 let display_snapshot = self
3806 .display_map
3807 .update(cx, |display_map, cx| display_map.snapshot(cx));
3808 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3809 return;
3810 };
3811 let inmemory_folds = display_snapshot
3812 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3813 .map(|fold| {
3814 fold.range.start.text_anchor.to_point(&snapshot)
3815 ..fold.range.end.text_anchor.to_point(&snapshot)
3816 })
3817 .collect();
3818 self.update_restoration_data(cx, |data| {
3819 data.folds = inmemory_folds;
3820 });
3821
3822 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3823 return;
3824 };
3825
3826 // Get file path for path-based fold storage (survives tab close)
3827 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3828 project::File::from_dyn(buffer.read(cx).file())
3829 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3830 }) else {
3831 return;
3832 };
3833
3834 let background_executor = cx.background_executor().clone();
3835 const FINGERPRINT_LEN: usize = 32;
3836 let db_folds = display_snapshot
3837 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3838 .map(|fold| {
3839 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3840 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3841
3842 // Extract fingerprints - content at fold boundaries for validation on restore
3843 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3844 // content that might change independently.
3845 // start_fp: first min(32, fold_len) bytes of fold content
3846 // end_fp: last min(32, fold_len) bytes of fold content
3847 // Clip to character boundaries to handle multibyte UTF-8 characters.
3848 let fold_len = end - start;
3849 let start_fp_end = snapshot
3850 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3851 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3852 let end_fp_start = snapshot
3853 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3854 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3855
3856 (start, end, start_fp, end_fp)
3857 })
3858 .collect::<Vec<_>>();
3859 let db = EditorDb::global(cx);
3860 self.serialize_folds = cx.background_spawn(async move {
3861 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3862 if db_folds.is_empty() {
3863 // No folds - delete any persisted folds for this file
3864 db.delete_file_folds(workspace_id, file_path)
3865 .await
3866 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3867 .log_err();
3868 } else {
3869 db.save_file_folds(workspace_id, file_path, db_folds)
3870 .await
3871 .with_context(|| {
3872 format!("persisting file folds for workspace {workspace_id:?}")
3873 })
3874 .log_err();
3875 }
3876 });
3877 }
3878
3879 pub fn sync_selections(
3880 &mut self,
3881 other: Entity<Editor>,
3882 cx: &mut Context<Self>,
3883 ) -> gpui::Subscription {
3884 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3885 if !other_selections.is_empty() {
3886 self.selections
3887 .change_with(&self.display_snapshot(cx), |selections| {
3888 selections.select_anchors(other_selections);
3889 });
3890 }
3891
3892 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3893 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3894 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3895 if other_selections.is_empty() {
3896 return;
3897 }
3898 let snapshot = this.display_snapshot(cx);
3899 this.selections.change_with(&snapshot, |selections| {
3900 selections.select_anchors(other_selections);
3901 });
3902 }
3903 });
3904
3905 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3906 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3907 let these_selections = this.selections.disjoint_anchors().to_vec();
3908 if these_selections.is_empty() {
3909 return;
3910 }
3911 other.update(cx, |other_editor, cx| {
3912 let snapshot = other_editor.display_snapshot(cx);
3913 other_editor
3914 .selections
3915 .change_with(&snapshot, |selections| {
3916 selections.select_anchors(these_selections);
3917 })
3918 });
3919 }
3920 });
3921
3922 Subscription::join(other_subscription, this_subscription)
3923 }
3924
3925 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3926 if self.buffer().read(cx).is_singleton() {
3927 return;
3928 }
3929 let snapshot = self.buffer.read(cx).snapshot(cx);
3930 let buffer_ids: HashSet<BufferId> = self
3931 .selections
3932 .disjoint_anchor_ranges()
3933 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3934 .collect();
3935 for buffer_id in buffer_ids {
3936 self.unfold_buffer(buffer_id, cx);
3937 }
3938 }
3939
3940 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3941 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3942 /// effects of selection change occur at the end of the transaction.
3943 pub fn change_selections<R>(
3944 &mut self,
3945 effects: SelectionEffects,
3946 window: &mut Window,
3947 cx: &mut Context<Self>,
3948 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3949 ) -> R {
3950 let snapshot = self.display_snapshot(cx);
3951 if let Some(state) = &mut self.deferred_selection_effects_state {
3952 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3953 state.effects.completions = effects.completions;
3954 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3955 let (changed, result) = self.selections.change_with(&snapshot, change);
3956 state.changed |= changed;
3957 return result;
3958 }
3959 let mut state = DeferredSelectionEffectsState {
3960 changed: false,
3961 effects,
3962 old_cursor_position: self.selections.newest_anchor().head(),
3963 history_entry: SelectionHistoryEntry {
3964 selections: self.selections.disjoint_anchors_arc(),
3965 select_next_state: self.select_next_state.clone(),
3966 select_prev_state: self.select_prev_state.clone(),
3967 add_selections_state: self.add_selections_state.clone(),
3968 },
3969 };
3970 let (changed, result) = self.selections.change_with(&snapshot, change);
3971 state.changed = state.changed || changed;
3972 if self.defer_selection_effects {
3973 self.deferred_selection_effects_state = Some(state);
3974 } else {
3975 self.apply_selection_effects(state, window, cx);
3976 }
3977 result
3978 }
3979
3980 /// Defers the effects of selection change, so that the effects of multiple calls to
3981 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3982 /// to selection history and the state of popovers based on selection position aren't
3983 /// erroneously updated.
3984 pub fn with_selection_effects_deferred<R>(
3985 &mut self,
3986 window: &mut Window,
3987 cx: &mut Context<Self>,
3988 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3989 ) -> R {
3990 let already_deferred = self.defer_selection_effects;
3991 self.defer_selection_effects = true;
3992 let result = update(self, window, cx);
3993 if !already_deferred {
3994 self.defer_selection_effects = false;
3995 if let Some(state) = self.deferred_selection_effects_state.take() {
3996 self.apply_selection_effects(state, window, cx);
3997 }
3998 }
3999 result
4000 }
4001
4002 fn apply_selection_effects(
4003 &mut self,
4004 state: DeferredSelectionEffectsState,
4005 window: &mut Window,
4006 cx: &mut Context<Self>,
4007 ) {
4008 if state.changed {
4009 self.selection_history.push(state.history_entry);
4010
4011 if let Some(autoscroll) = state.effects.scroll {
4012 self.request_autoscroll(autoscroll, cx);
4013 }
4014
4015 let old_cursor_position = &state.old_cursor_position;
4016
4017 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4018
4019 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4020 self.show_signature_help_auto(window, cx);
4021 }
4022 }
4023 }
4024
4025 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4026 where
4027 I: IntoIterator<Item = (Range<S>, T)>,
4028 S: ToOffset,
4029 T: Into<Arc<str>>,
4030 {
4031 if self.read_only(cx) {
4032 return;
4033 }
4034
4035 self.buffer
4036 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4037 }
4038
4039 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4040 where
4041 I: IntoIterator<Item = (Range<S>, T)>,
4042 S: ToOffset,
4043 T: Into<Arc<str>>,
4044 {
4045 if self.read_only(cx) {
4046 return;
4047 }
4048
4049 self.buffer.update(cx, |buffer, cx| {
4050 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4051 });
4052 }
4053
4054 pub fn edit_with_block_indent<I, S, T>(
4055 &mut self,
4056 edits: I,
4057 original_indent_columns: Vec<Option<u32>>,
4058 cx: &mut Context<Self>,
4059 ) where
4060 I: IntoIterator<Item = (Range<S>, T)>,
4061 S: ToOffset,
4062 T: Into<Arc<str>>,
4063 {
4064 if self.read_only(cx) {
4065 return;
4066 }
4067
4068 self.buffer.update(cx, |buffer, cx| {
4069 buffer.edit(
4070 edits,
4071 Some(AutoindentMode::Block {
4072 original_indent_columns,
4073 }),
4074 cx,
4075 )
4076 });
4077 }
4078
4079 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4080 self.hide_context_menu(window, cx);
4081
4082 match phase {
4083 SelectPhase::Begin {
4084 position,
4085 add,
4086 click_count,
4087 } => self.begin_selection(position, add, click_count, window, cx),
4088 SelectPhase::BeginColumnar {
4089 position,
4090 goal_column,
4091 reset,
4092 mode,
4093 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4094 SelectPhase::Extend {
4095 position,
4096 click_count,
4097 } => self.extend_selection(position, click_count, window, cx),
4098 SelectPhase::Update {
4099 position,
4100 goal_column,
4101 scroll_delta,
4102 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4103 SelectPhase::End => self.end_selection(window, cx),
4104 }
4105 }
4106
4107 fn extend_selection(
4108 &mut self,
4109 position: DisplayPoint,
4110 click_count: usize,
4111 window: &mut Window,
4112 cx: &mut Context<Self>,
4113 ) {
4114 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4115 let tail = self
4116 .selections
4117 .newest::<MultiBufferOffset>(&display_map)
4118 .tail();
4119 let click_count = click_count.max(match self.selections.select_mode() {
4120 SelectMode::Character => 1,
4121 SelectMode::Word(_) => 2,
4122 SelectMode::Line(_) => 3,
4123 SelectMode::All => 4,
4124 });
4125 self.begin_selection(position, false, click_count, window, cx);
4126
4127 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4128
4129 let current_selection = match self.selections.select_mode() {
4130 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4131 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4132 };
4133
4134 let mut pending_selection = self
4135 .selections
4136 .pending_anchor()
4137 .cloned()
4138 .expect("extend_selection not called with pending selection");
4139
4140 if pending_selection
4141 .start
4142 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4143 == Ordering::Greater
4144 {
4145 pending_selection.start = current_selection.start;
4146 }
4147 if pending_selection
4148 .end
4149 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4150 == Ordering::Less
4151 {
4152 pending_selection.end = current_selection.end;
4153 pending_selection.reversed = true;
4154 }
4155
4156 let mut pending_mode = self.selections.pending_mode().unwrap();
4157 match &mut pending_mode {
4158 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4159 _ => {}
4160 }
4161
4162 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4163 SelectionEffects::scroll(Autoscroll::fit())
4164 } else {
4165 SelectionEffects::no_scroll()
4166 };
4167
4168 self.change_selections(effects, window, cx, |s| {
4169 s.set_pending(pending_selection.clone(), pending_mode);
4170 s.set_is_extending(true);
4171 });
4172 }
4173
4174 fn begin_selection(
4175 &mut self,
4176 position: DisplayPoint,
4177 add: bool,
4178 click_count: usize,
4179 window: &mut Window,
4180 cx: &mut Context<Self>,
4181 ) {
4182 if !self.focus_handle.is_focused(window) {
4183 self.last_focused_descendant = None;
4184 window.focus(&self.focus_handle, cx);
4185 }
4186
4187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4188 let buffer = display_map.buffer_snapshot();
4189 let position = display_map.clip_point(position, Bias::Left);
4190
4191 let start;
4192 let end;
4193 let mode;
4194 let mut auto_scroll;
4195 match click_count {
4196 1 => {
4197 start = buffer.anchor_before(position.to_point(&display_map));
4198 end = start;
4199 mode = SelectMode::Character;
4200 auto_scroll = true;
4201 }
4202 2 => {
4203 let position = display_map
4204 .clip_point(position, Bias::Left)
4205 .to_offset(&display_map, Bias::Left);
4206 let (range, _) = buffer.surrounding_word(position, None);
4207 start = buffer.anchor_before(range.start);
4208 end = buffer.anchor_before(range.end);
4209 mode = SelectMode::Word(start..end);
4210 auto_scroll = true;
4211 }
4212 3 => {
4213 let position = display_map
4214 .clip_point(position, Bias::Left)
4215 .to_point(&display_map);
4216 let line_start = display_map.prev_line_boundary(position).0;
4217 let next_line_start = buffer.clip_point(
4218 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4219 Bias::Left,
4220 );
4221 start = buffer.anchor_before(line_start);
4222 end = buffer.anchor_before(next_line_start);
4223 mode = SelectMode::Line(start..end);
4224 auto_scroll = true;
4225 }
4226 _ => {
4227 start = buffer.anchor_before(MultiBufferOffset(0));
4228 end = buffer.anchor_before(buffer.len());
4229 mode = SelectMode::All;
4230 auto_scroll = false;
4231 }
4232 }
4233 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4234
4235 let point_to_delete: Option<usize> = {
4236 let selected_points: Vec<Selection<Point>> =
4237 self.selections.disjoint_in_range(start..end, &display_map);
4238
4239 if !add || click_count > 1 {
4240 None
4241 } else if !selected_points.is_empty() {
4242 Some(selected_points[0].id)
4243 } else {
4244 let clicked_point_already_selected =
4245 self.selections.disjoint_anchors().iter().find(|selection| {
4246 selection.start.to_point(buffer) == start.to_point(buffer)
4247 || selection.end.to_point(buffer) == end.to_point(buffer)
4248 });
4249
4250 clicked_point_already_selected.map(|selection| selection.id)
4251 }
4252 };
4253
4254 let selections_count = self.selections.count();
4255 let effects = if auto_scroll {
4256 SelectionEffects::default()
4257 } else {
4258 SelectionEffects::no_scroll()
4259 };
4260
4261 self.change_selections(effects, window, cx, |s| {
4262 if let Some(point_to_delete) = point_to_delete {
4263 s.delete(point_to_delete);
4264
4265 if selections_count == 1 {
4266 s.set_pending_anchor_range(start..end, mode);
4267 }
4268 } else {
4269 if !add {
4270 s.clear_disjoint();
4271 }
4272
4273 s.set_pending_anchor_range(start..end, mode);
4274 }
4275 });
4276 }
4277
4278 fn begin_columnar_selection(
4279 &mut self,
4280 position: DisplayPoint,
4281 goal_column: u32,
4282 reset: bool,
4283 mode: ColumnarMode,
4284 window: &mut Window,
4285 cx: &mut Context<Self>,
4286 ) {
4287 if !self.focus_handle.is_focused(window) {
4288 self.last_focused_descendant = None;
4289 window.focus(&self.focus_handle, cx);
4290 }
4291
4292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4293
4294 if reset {
4295 let pointer_position = display_map
4296 .buffer_snapshot()
4297 .anchor_before(position.to_point(&display_map));
4298
4299 self.change_selections(
4300 SelectionEffects::scroll(Autoscroll::newest()),
4301 window,
4302 cx,
4303 |s| {
4304 s.clear_disjoint();
4305 s.set_pending_anchor_range(
4306 pointer_position..pointer_position,
4307 SelectMode::Character,
4308 );
4309 },
4310 );
4311 };
4312
4313 let tail = self.selections.newest::<Point>(&display_map).tail();
4314 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4315 self.columnar_selection_state = match mode {
4316 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4317 selection_tail: selection_anchor,
4318 display_point: if reset {
4319 if position.column() != goal_column {
4320 Some(DisplayPoint::new(position.row(), goal_column))
4321 } else {
4322 None
4323 }
4324 } else {
4325 None
4326 },
4327 }),
4328 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4329 selection_tail: selection_anchor,
4330 }),
4331 };
4332
4333 if !reset {
4334 self.select_columns(position, goal_column, &display_map, window, cx);
4335 }
4336 }
4337
4338 fn update_selection(
4339 &mut self,
4340 position: DisplayPoint,
4341 goal_column: u32,
4342 scroll_delta: gpui::Point<f32>,
4343 window: &mut Window,
4344 cx: &mut Context<Self>,
4345 ) {
4346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4347
4348 if self.columnar_selection_state.is_some() {
4349 self.select_columns(position, goal_column, &display_map, window, cx);
4350 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4351 let buffer = display_map.buffer_snapshot();
4352 let head;
4353 let tail;
4354 let mode = self.selections.pending_mode().unwrap();
4355 match &mode {
4356 SelectMode::Character => {
4357 head = position.to_point(&display_map);
4358 tail = pending.tail().to_point(buffer);
4359 }
4360 SelectMode::Word(original_range) => {
4361 let offset = display_map
4362 .clip_point(position, Bias::Left)
4363 .to_offset(&display_map, Bias::Left);
4364 let original_range = original_range.to_offset(buffer);
4365
4366 let head_offset = if buffer.is_inside_word(offset, None)
4367 || original_range.contains(&offset)
4368 {
4369 let (word_range, _) = buffer.surrounding_word(offset, None);
4370 if word_range.start < original_range.start {
4371 word_range.start
4372 } else {
4373 word_range.end
4374 }
4375 } else {
4376 offset
4377 };
4378
4379 head = head_offset.to_point(buffer);
4380 if head_offset <= original_range.start {
4381 tail = original_range.end.to_point(buffer);
4382 } else {
4383 tail = original_range.start.to_point(buffer);
4384 }
4385 }
4386 SelectMode::Line(original_range) => {
4387 let original_range = original_range.to_point(display_map.buffer_snapshot());
4388
4389 let position = display_map
4390 .clip_point(position, Bias::Left)
4391 .to_point(&display_map);
4392 let line_start = display_map.prev_line_boundary(position).0;
4393 let next_line_start = buffer.clip_point(
4394 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4395 Bias::Left,
4396 );
4397
4398 if line_start < original_range.start {
4399 head = line_start
4400 } else {
4401 head = next_line_start
4402 }
4403
4404 if head <= original_range.start {
4405 tail = original_range.end;
4406 } else {
4407 tail = original_range.start;
4408 }
4409 }
4410 SelectMode::All => {
4411 return;
4412 }
4413 };
4414
4415 if head < tail {
4416 pending.start = buffer.anchor_before(head);
4417 pending.end = buffer.anchor_before(tail);
4418 pending.reversed = true;
4419 } else {
4420 pending.start = buffer.anchor_before(tail);
4421 pending.end = buffer.anchor_before(head);
4422 pending.reversed = false;
4423 }
4424
4425 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4426 s.set_pending(pending.clone(), mode);
4427 });
4428 } else {
4429 log::error!("update_selection dispatched with no pending selection");
4430 return;
4431 }
4432
4433 self.apply_scroll_delta(scroll_delta, window, cx);
4434 cx.notify();
4435 }
4436
4437 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4438 self.columnar_selection_state.take();
4439 if let Some(pending_mode) = self.selections.pending_mode() {
4440 let selections = self
4441 .selections
4442 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4443 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4444 s.select(selections);
4445 s.clear_pending();
4446 if s.is_extending() {
4447 s.set_is_extending(false);
4448 } else {
4449 s.set_select_mode(pending_mode);
4450 }
4451 });
4452 }
4453 }
4454
4455 fn select_columns(
4456 &mut self,
4457 head: DisplayPoint,
4458 goal_column: u32,
4459 display_map: &DisplaySnapshot,
4460 window: &mut Window,
4461 cx: &mut Context<Self>,
4462 ) {
4463 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4464 return;
4465 };
4466
4467 let tail = match columnar_state {
4468 ColumnarSelectionState::FromMouse {
4469 selection_tail,
4470 display_point,
4471 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4472 ColumnarSelectionState::FromSelection { selection_tail } => {
4473 selection_tail.to_display_point(display_map)
4474 }
4475 };
4476
4477 let start_row = cmp::min(tail.row(), head.row());
4478 let end_row = cmp::max(tail.row(), head.row());
4479 let start_column = cmp::min(tail.column(), goal_column);
4480 let end_column = cmp::max(tail.column(), goal_column);
4481 let reversed = start_column < tail.column();
4482
4483 let selection_ranges = (start_row.0..=end_row.0)
4484 .map(DisplayRow)
4485 .filter_map(|row| {
4486 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4487 || start_column <= display_map.line_len(row))
4488 && !display_map.is_block_line(row)
4489 {
4490 let start = display_map
4491 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4492 .to_point(display_map);
4493 let end = display_map
4494 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4495 .to_point(display_map);
4496 if reversed {
4497 Some(end..start)
4498 } else {
4499 Some(start..end)
4500 }
4501 } else {
4502 None
4503 }
4504 })
4505 .collect::<Vec<_>>();
4506 if selection_ranges.is_empty() {
4507 return;
4508 }
4509
4510 let ranges = match columnar_state {
4511 ColumnarSelectionState::FromMouse { .. } => {
4512 let mut non_empty_ranges = selection_ranges
4513 .iter()
4514 .filter(|selection_range| selection_range.start != selection_range.end)
4515 .peekable();
4516 if non_empty_ranges.peek().is_some() {
4517 non_empty_ranges.cloned().collect()
4518 } else {
4519 selection_ranges
4520 }
4521 }
4522 _ => selection_ranges,
4523 };
4524
4525 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4526 s.select_ranges(ranges);
4527 });
4528 cx.notify();
4529 }
4530
4531 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4532 self.selections
4533 .all_adjusted(snapshot)
4534 .iter()
4535 .any(|selection| !selection.is_empty())
4536 }
4537
4538 pub fn has_pending_nonempty_selection(&self) -> bool {
4539 let pending_nonempty_selection = match self.selections.pending_anchor() {
4540 Some(Selection { start, end, .. }) => start != end,
4541 None => false,
4542 };
4543
4544 pending_nonempty_selection
4545 || (self.columnar_selection_state.is_some()
4546 && self.selections.disjoint_anchors().len() > 1)
4547 }
4548
4549 pub fn has_pending_selection(&self) -> bool {
4550 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4551 }
4552
4553 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4554 self.selection_mark_mode = false;
4555 self.selection_drag_state = SelectionDragState::None;
4556
4557 if self.dismiss_menus_and_popups(true, window, cx) {
4558 cx.notify();
4559 return;
4560 }
4561 if self.clear_expanded_diff_hunks(cx) {
4562 cx.notify();
4563 return;
4564 }
4565 if self.show_git_blame_gutter {
4566 self.show_git_blame_gutter = false;
4567 cx.notify();
4568 return;
4569 }
4570
4571 if self.mode.is_full()
4572 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4573 {
4574 cx.notify();
4575 return;
4576 }
4577
4578 cx.propagate();
4579 }
4580
4581 pub fn dismiss_menus_and_popups(
4582 &mut self,
4583 is_user_requested: bool,
4584 window: &mut Window,
4585 cx: &mut Context<Self>,
4586 ) -> bool {
4587 let mut dismissed = false;
4588
4589 dismissed |= self.take_rename(false, window, cx).is_some();
4590 dismissed |= self.hide_blame_popover(true, cx);
4591 dismissed |= hide_hover(self, cx);
4592 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4593 dismissed |= self.hide_context_menu(window, cx).is_some();
4594 dismissed |= self.mouse_context_menu.take().is_some();
4595 dismissed |= is_user_requested
4596 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4597 dismissed |= self.snippet_stack.pop().is_some();
4598 if self.diff_review_drag_state.is_some() {
4599 self.cancel_diff_review_drag(cx);
4600 dismissed = true;
4601 }
4602 if !self.diff_review_overlays.is_empty() {
4603 self.dismiss_all_diff_review_overlays(cx);
4604 dismissed = true;
4605 }
4606
4607 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4608 self.dismiss_diagnostics(cx);
4609 dismissed = true;
4610 }
4611
4612 dismissed
4613 }
4614
4615 fn linked_editing_ranges_for(
4616 &self,
4617 selection: Range<text::Anchor>,
4618 cx: &App,
4619 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4620 if self.linked_edit_ranges.is_empty() {
4621 return None;
4622 }
4623 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4624 selection.end.buffer_id.and_then(|end_buffer_id| {
4625 if selection.start.buffer_id != Some(end_buffer_id) {
4626 return None;
4627 }
4628 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4629 let snapshot = buffer.read(cx).snapshot();
4630 self.linked_edit_ranges
4631 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4632 .map(|ranges| (ranges, snapshot, buffer))
4633 })?;
4634 use text::ToOffset as TO;
4635 // find offset from the start of current range to current cursor position
4636 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4637
4638 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4639 let start_difference = start_offset - start_byte_offset;
4640 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4641 let end_difference = end_offset - start_byte_offset;
4642
4643 // Current range has associated linked ranges.
4644 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4645 for range in linked_ranges.iter() {
4646 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4647 let end_offset = start_offset + end_difference;
4648 let start_offset = start_offset + start_difference;
4649 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4650 continue;
4651 }
4652 if self.selections.disjoint_anchor_ranges().any(|s| {
4653 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4654 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4655 {
4656 return false;
4657 }
4658 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4659 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4660 }) {
4661 continue;
4662 }
4663 let start = buffer_snapshot.anchor_after(start_offset);
4664 let end = buffer_snapshot.anchor_after(end_offset);
4665 linked_edits
4666 .entry(buffer.clone())
4667 .or_default()
4668 .push(start..end);
4669 }
4670 Some(linked_edits)
4671 }
4672
4673 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4674 let text: Arc<str> = text.into();
4675
4676 if self.read_only(cx) {
4677 return;
4678 }
4679
4680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4681
4682 self.unfold_buffers_with_selections(cx);
4683
4684 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4685 let mut bracket_inserted = false;
4686 let mut edits = Vec::new();
4687 let mut linked_edits = LinkedEdits::new();
4688 let mut new_selections = Vec::with_capacity(selections.len());
4689 let mut new_autoclose_regions = Vec::new();
4690 let snapshot = self.buffer.read(cx).read(cx);
4691 let mut clear_linked_edit_ranges = false;
4692 let mut all_selections_read_only = true;
4693 let mut has_adjacent_edits = false;
4694 let mut in_adjacent_group = false;
4695
4696 let mut regions = self
4697 .selections_with_autoclose_regions(selections, &snapshot)
4698 .peekable();
4699
4700 while let Some((selection, autoclose_region)) = regions.next() {
4701 if snapshot
4702 .point_to_buffer_point(selection.head())
4703 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4704 {
4705 continue;
4706 }
4707 if snapshot
4708 .point_to_buffer_point(selection.tail())
4709 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4710 {
4711 // note, ideally we'd clip the tail to the closest writeable region towards the head
4712 continue;
4713 }
4714 all_selections_read_only = false;
4715
4716 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4717 // Determine if the inserted text matches the opening or closing
4718 // bracket of any of this language's bracket pairs.
4719 let mut bracket_pair = None;
4720 let mut is_bracket_pair_start = false;
4721 let mut is_bracket_pair_end = false;
4722 if !text.is_empty() {
4723 let mut bracket_pair_matching_end = None;
4724 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4725 // and they are removing the character that triggered IME popup.
4726 for (pair, enabled) in scope.brackets() {
4727 if !pair.close && !pair.surround {
4728 continue;
4729 }
4730
4731 if enabled && pair.start.ends_with(text.as_ref()) {
4732 let prefix_len = pair.start.len() - text.len();
4733 let preceding_text_matches_prefix = prefix_len == 0
4734 || (selection.start.column >= (prefix_len as u32)
4735 && snapshot.contains_str_at(
4736 Point::new(
4737 selection.start.row,
4738 selection.start.column - (prefix_len as u32),
4739 ),
4740 &pair.start[..prefix_len],
4741 ));
4742 if preceding_text_matches_prefix {
4743 bracket_pair = Some(pair.clone());
4744 is_bracket_pair_start = true;
4745 break;
4746 }
4747 }
4748 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4749 {
4750 // take first bracket pair matching end, but don't break in case a later bracket
4751 // pair matches start
4752 bracket_pair_matching_end = Some(pair.clone());
4753 }
4754 }
4755 if let Some(end) = bracket_pair_matching_end
4756 && bracket_pair.is_none()
4757 {
4758 bracket_pair = Some(end);
4759 is_bracket_pair_end = true;
4760 }
4761 }
4762
4763 if let Some(bracket_pair) = bracket_pair {
4764 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4765 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4766 let auto_surround =
4767 self.use_auto_surround && snapshot_settings.use_auto_surround;
4768 if selection.is_empty() {
4769 if is_bracket_pair_start {
4770 // If the inserted text is a suffix of an opening bracket and the
4771 // selection is preceded by the rest of the opening bracket, then
4772 // insert the closing bracket.
4773 let following_text_allows_autoclose = snapshot
4774 .chars_at(selection.start)
4775 .next()
4776 .is_none_or(|c| scope.should_autoclose_before(c));
4777
4778 let preceding_text_allows_autoclose = selection.start.column == 0
4779 || snapshot
4780 .reversed_chars_at(selection.start)
4781 .next()
4782 .is_none_or(|c| {
4783 bracket_pair.start != bracket_pair.end
4784 || !snapshot
4785 .char_classifier_at(selection.start)
4786 .is_word(c)
4787 });
4788
4789 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4790 && bracket_pair.start.len() == 1
4791 {
4792 let target = bracket_pair.start.chars().next().unwrap();
4793 let mut byte_offset = 0u32;
4794 let current_line_count = snapshot
4795 .reversed_chars_at(selection.start)
4796 .take_while(|&c| c != '\n')
4797 .filter(|c| {
4798 byte_offset += c.len_utf8() as u32;
4799 if *c != target {
4800 return false;
4801 }
4802
4803 let point = Point::new(
4804 selection.start.row,
4805 selection.start.column.saturating_sub(byte_offset),
4806 );
4807
4808 let is_enabled = snapshot
4809 .language_scope_at(point)
4810 .and_then(|scope| {
4811 scope
4812 .brackets()
4813 .find(|(pair, _)| {
4814 pair.start == bracket_pair.start
4815 })
4816 .map(|(_, enabled)| enabled)
4817 })
4818 .unwrap_or(true);
4819
4820 let is_delimiter = snapshot
4821 .language_scope_at(Point::new(
4822 point.row,
4823 point.column + 1,
4824 ))
4825 .and_then(|scope| {
4826 scope
4827 .brackets()
4828 .find(|(pair, _)| {
4829 pair.start == bracket_pair.start
4830 })
4831 .map(|(_, enabled)| !enabled)
4832 })
4833 .unwrap_or(false);
4834
4835 is_enabled && !is_delimiter
4836 })
4837 .count();
4838 current_line_count % 2 == 1
4839 } else {
4840 false
4841 };
4842
4843 if autoclose
4844 && bracket_pair.close
4845 && following_text_allows_autoclose
4846 && preceding_text_allows_autoclose
4847 && !is_closing_quote
4848 {
4849 let anchor = snapshot.anchor_before(selection.end);
4850 new_selections.push((selection.map(|_| anchor), text.len()));
4851 new_autoclose_regions.push((
4852 anchor,
4853 text.len(),
4854 selection.id,
4855 bracket_pair.clone(),
4856 ));
4857 edits.push((
4858 selection.range(),
4859 format!("{}{}", text, bracket_pair.end).into(),
4860 ));
4861 bracket_inserted = true;
4862 continue;
4863 }
4864 }
4865
4866 if let Some(region) = autoclose_region {
4867 // If the selection is followed by an auto-inserted closing bracket,
4868 // then don't insert that closing bracket again; just move the selection
4869 // past the closing bracket.
4870 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4871 && text.as_ref() == region.pair.end.as_str()
4872 && snapshot.contains_str_at(region.range.end, text.as_ref());
4873 if should_skip {
4874 let anchor = snapshot.anchor_after(selection.end);
4875 new_selections
4876 .push((selection.map(|_| anchor), region.pair.end.len()));
4877 continue;
4878 }
4879 }
4880
4881 let always_treat_brackets_as_autoclosed = snapshot
4882 .language_settings_at(selection.start, cx)
4883 .always_treat_brackets_as_autoclosed;
4884 if always_treat_brackets_as_autoclosed
4885 && is_bracket_pair_end
4886 && snapshot.contains_str_at(selection.end, text.as_ref())
4887 {
4888 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4889 // and the inserted text is a closing bracket and the selection is followed
4890 // by the closing bracket then move the selection past the closing bracket.
4891 let anchor = snapshot.anchor_after(selection.end);
4892 new_selections.push((selection.map(|_| anchor), text.len()));
4893 continue;
4894 }
4895 }
4896 // If an opening bracket is 1 character long and is typed while
4897 // text is selected, then surround that text with the bracket pair.
4898 else if auto_surround
4899 && bracket_pair.surround
4900 && is_bracket_pair_start
4901 && bracket_pair.start.chars().count() == 1
4902 {
4903 edits.push((selection.start..selection.start, text.clone()));
4904 edits.push((
4905 selection.end..selection.end,
4906 bracket_pair.end.as_str().into(),
4907 ));
4908 bracket_inserted = true;
4909 new_selections.push((
4910 Selection {
4911 id: selection.id,
4912 start: snapshot.anchor_after(selection.start),
4913 end: snapshot.anchor_before(selection.end),
4914 reversed: selection.reversed,
4915 goal: selection.goal,
4916 },
4917 0,
4918 ));
4919 continue;
4920 }
4921 }
4922 }
4923
4924 if self.auto_replace_emoji_shortcode
4925 && selection.is_empty()
4926 && text.as_ref().ends_with(':')
4927 && let Some(possible_emoji_short_code) =
4928 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4929 && !possible_emoji_short_code.is_empty()
4930 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4931 {
4932 let emoji_shortcode_start = Point::new(
4933 selection.start.row,
4934 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4935 );
4936
4937 // Remove shortcode from buffer
4938 edits.push((
4939 emoji_shortcode_start..selection.start,
4940 "".to_string().into(),
4941 ));
4942 new_selections.push((
4943 Selection {
4944 id: selection.id,
4945 start: snapshot.anchor_after(emoji_shortcode_start),
4946 end: snapshot.anchor_before(selection.start),
4947 reversed: selection.reversed,
4948 goal: selection.goal,
4949 },
4950 0,
4951 ));
4952
4953 // Insert emoji
4954 let selection_start_anchor = snapshot.anchor_after(selection.start);
4955 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4956 edits.push((selection.start..selection.end, emoji.to_string().into()));
4957
4958 continue;
4959 }
4960
4961 let next_is_adjacent = regions
4962 .peek()
4963 .is_some_and(|(next, _)| selection.end == next.start);
4964
4965 // If not handling any auto-close operation, then just replace the selected
4966 // text with the given input and move the selection to the end of the
4967 // newly inserted text.
4968 let anchor = if in_adjacent_group || next_is_adjacent {
4969 // After edits the right bias would shift those anchor to the next visible fragment
4970 // but we want to resolve to the previous one
4971 snapshot.anchor_before(selection.end)
4972 } else {
4973 snapshot.anchor_after(selection.end)
4974 };
4975
4976 if !self.linked_edit_ranges.is_empty() {
4977 let start_anchor = snapshot.anchor_before(selection.start);
4978
4979 let is_word_char = text.chars().next().is_none_or(|char| {
4980 let classifier = snapshot
4981 .char_classifier_at(start_anchor.to_offset(&snapshot))
4982 .scope_context(Some(CharScopeContext::LinkedEdit));
4983 classifier.is_word(char)
4984 });
4985 let is_dot = text.as_ref() == ".";
4986 let should_apply_linked_edit = is_word_char || is_dot;
4987
4988 if should_apply_linked_edit {
4989 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
4990 linked_edits.push(&self, anchor_range, text.clone(), cx);
4991 } else {
4992 clear_linked_edit_ranges = true;
4993 }
4994 }
4995
4996 new_selections.push((selection.map(|_| anchor), 0));
4997 edits.push((selection.start..selection.end, text.clone()));
4998
4999 has_adjacent_edits |= next_is_adjacent;
5000 in_adjacent_group = next_is_adjacent;
5001 }
5002
5003 if all_selections_read_only {
5004 return;
5005 }
5006
5007 drop(regions);
5008 drop(snapshot);
5009
5010 self.transact(window, cx, |this, window, cx| {
5011 if clear_linked_edit_ranges {
5012 this.linked_edit_ranges.clear();
5013 }
5014 let initial_buffer_versions =
5015 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5016
5017 this.buffer.update(cx, |buffer, cx| {
5018 if has_adjacent_edits {
5019 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5020 } else {
5021 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5022 }
5023 });
5024 linked_edits.apply(cx);
5025 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5026 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5027 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5028 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5029 new_anchor_selections,
5030 &map,
5031 )
5032 .zip(new_selection_deltas)
5033 .map(|(selection, delta)| Selection {
5034 id: selection.id,
5035 start: selection.start + delta,
5036 end: selection.end + delta,
5037 reversed: selection.reversed,
5038 goal: SelectionGoal::None,
5039 })
5040 .collect::<Vec<_>>();
5041
5042 let mut i = 0;
5043 for (position, delta, selection_id, pair) in new_autoclose_regions {
5044 let position = position.to_offset(map.buffer_snapshot()) + delta;
5045 let start = map.buffer_snapshot().anchor_before(position);
5046 let end = map.buffer_snapshot().anchor_after(position);
5047 while let Some(existing_state) = this.autoclose_regions.get(i) {
5048 match existing_state
5049 .range
5050 .start
5051 .cmp(&start, map.buffer_snapshot())
5052 {
5053 Ordering::Less => i += 1,
5054 Ordering::Greater => break,
5055 Ordering::Equal => {
5056 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5057 Ordering::Less => i += 1,
5058 Ordering::Equal => break,
5059 Ordering::Greater => break,
5060 }
5061 }
5062 }
5063 }
5064 this.autoclose_regions.insert(
5065 i,
5066 AutocloseRegion {
5067 selection_id,
5068 range: start..end,
5069 pair,
5070 },
5071 );
5072 }
5073
5074 let had_active_edit_prediction = this.has_active_edit_prediction();
5075 this.change_selections(
5076 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5077 window,
5078 cx,
5079 |s| s.select(new_selections),
5080 );
5081
5082 if !bracket_inserted
5083 && let Some(on_type_format_task) =
5084 this.trigger_on_type_formatting(text.to_string(), window, cx)
5085 {
5086 on_type_format_task.detach_and_log_err(cx);
5087 }
5088
5089 let editor_settings = EditorSettings::get_global(cx);
5090 if bracket_inserted
5091 && (editor_settings.auto_signature_help
5092 || editor_settings.show_signature_help_after_edits)
5093 {
5094 this.show_signature_help(&ShowSignatureHelp, window, cx);
5095 }
5096
5097 let trigger_in_words =
5098 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5099 if this.hard_wrap.is_some() {
5100 let latest: Range<Point> = this.selections.newest(&map).range();
5101 if latest.is_empty()
5102 && this
5103 .buffer()
5104 .read(cx)
5105 .snapshot(cx)
5106 .line_len(MultiBufferRow(latest.start.row))
5107 == latest.start.column
5108 {
5109 this.rewrap_impl(
5110 RewrapOptions {
5111 override_language_settings: true,
5112 preserve_existing_whitespace: true,
5113 },
5114 cx,
5115 )
5116 }
5117 }
5118 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5119 refresh_linked_ranges(this, window, cx);
5120 this.refresh_edit_prediction(true, false, window, cx);
5121 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5122 });
5123 }
5124
5125 fn find_possible_emoji_shortcode_at_position(
5126 snapshot: &MultiBufferSnapshot,
5127 position: Point,
5128 ) -> Option<String> {
5129 let mut chars = Vec::new();
5130 let mut found_colon = false;
5131 for char in snapshot.reversed_chars_at(position).take(100) {
5132 // Found a possible emoji shortcode in the middle of the buffer
5133 if found_colon {
5134 if char.is_whitespace() {
5135 chars.reverse();
5136 return Some(chars.iter().collect());
5137 }
5138 // If the previous character is not a whitespace, we are in the middle of a word
5139 // and we only want to complete the shortcode if the word is made up of other emojis
5140 let mut containing_word = String::new();
5141 for ch in snapshot
5142 .reversed_chars_at(position)
5143 .skip(chars.len() + 1)
5144 .take(100)
5145 {
5146 if ch.is_whitespace() {
5147 break;
5148 }
5149 containing_word.push(ch);
5150 }
5151 let containing_word = containing_word.chars().rev().collect::<String>();
5152 if util::word_consists_of_emojis(containing_word.as_str()) {
5153 chars.reverse();
5154 return Some(chars.iter().collect());
5155 }
5156 }
5157
5158 if char.is_whitespace() || !char.is_ascii() {
5159 return None;
5160 }
5161 if char == ':' {
5162 found_colon = true;
5163 } else {
5164 chars.push(char);
5165 }
5166 }
5167 // Found a possible emoji shortcode at the beginning of the buffer
5168 chars.reverse();
5169 Some(chars.iter().collect())
5170 }
5171
5172 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5173 if self.read_only(cx) {
5174 return;
5175 }
5176
5177 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5178 self.transact(window, cx, |this, window, cx| {
5179 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5180 let selections = this
5181 .selections
5182 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5183 let multi_buffer = this.buffer.read(cx);
5184 let buffer = multi_buffer.snapshot(cx);
5185 selections
5186 .iter()
5187 .map(|selection| {
5188 let start_point = selection.start.to_point(&buffer);
5189 let mut existing_indent =
5190 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5191 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5192 let start = selection.start;
5193 let end = selection.end;
5194 let selection_is_empty = start == end;
5195 let language_scope = buffer.language_scope_at(start);
5196 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5197 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5198 &buffer,
5199 start..end,
5200 language,
5201 )
5202 || NewlineConfig::insert_extra_newline_tree_sitter(
5203 &buffer,
5204 start..end,
5205 );
5206
5207 let mut newline_config = NewlineConfig::Newline {
5208 additional_indent: IndentSize::spaces(0),
5209 extra_line_additional_indent: if needs_extra_newline {
5210 Some(IndentSize::spaces(0))
5211 } else {
5212 None
5213 },
5214 prevent_auto_indent: false,
5215 };
5216
5217 let comment_delimiter = maybe!({
5218 if !selection_is_empty {
5219 return None;
5220 }
5221
5222 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5223 return None;
5224 }
5225
5226 return comment_delimiter_for_newline(
5227 &start_point,
5228 &buffer,
5229 language,
5230 );
5231 });
5232
5233 let doc_delimiter = maybe!({
5234 if !selection_is_empty {
5235 return None;
5236 }
5237
5238 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5239 return None;
5240 }
5241
5242 return documentation_delimiter_for_newline(
5243 &start_point,
5244 &buffer,
5245 language,
5246 &mut newline_config,
5247 );
5248 });
5249
5250 let list_delimiter = maybe!({
5251 if !selection_is_empty {
5252 return None;
5253 }
5254
5255 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5256 return None;
5257 }
5258
5259 return list_delimiter_for_newline(
5260 &start_point,
5261 &buffer,
5262 language,
5263 &mut newline_config,
5264 );
5265 });
5266
5267 (
5268 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5269 newline_config,
5270 )
5271 } else {
5272 (
5273 None,
5274 NewlineConfig::Newline {
5275 additional_indent: IndentSize::spaces(0),
5276 extra_line_additional_indent: None,
5277 prevent_auto_indent: false,
5278 },
5279 )
5280 };
5281
5282 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5283 NewlineConfig::ClearCurrentLine => {
5284 let row_start =
5285 buffer.point_to_offset(Point::new(start_point.row, 0));
5286 (row_start, String::new(), false)
5287 }
5288 NewlineConfig::UnindentCurrentLine { continuation } => {
5289 let row_start =
5290 buffer.point_to_offset(Point::new(start_point.row, 0));
5291 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5292 let tab_size_indent = IndentSize::spaces(tab_size.get());
5293 let reduced_indent =
5294 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5295 let mut new_text = String::new();
5296 new_text.extend(reduced_indent.chars());
5297 new_text.push_str(continuation);
5298 (row_start, new_text, true)
5299 }
5300 NewlineConfig::Newline {
5301 additional_indent,
5302 extra_line_additional_indent,
5303 prevent_auto_indent,
5304 } => {
5305 let auto_indent_mode =
5306 buffer.language_settings_at(start, cx).auto_indent;
5307 let preserve_indent =
5308 auto_indent_mode != language::AutoIndentMode::None;
5309 let apply_syntax_indent =
5310 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5311 let capacity_for_delimiter =
5312 delimiter.as_deref().map(str::len).unwrap_or_default();
5313 let existing_indent_len = if preserve_indent {
5314 existing_indent.len as usize
5315 } else {
5316 0
5317 };
5318 let extra_line_len = extra_line_additional_indent
5319 .map(|i| 1 + existing_indent_len + i.len as usize)
5320 .unwrap_or(0);
5321 let mut new_text = String::with_capacity(
5322 1 + capacity_for_delimiter
5323 + existing_indent_len
5324 + additional_indent.len as usize
5325 + extra_line_len,
5326 );
5327 new_text.push('\n');
5328 if preserve_indent {
5329 new_text.extend(existing_indent.chars());
5330 }
5331 new_text.extend(additional_indent.chars());
5332 if let Some(delimiter) = &delimiter {
5333 new_text.push_str(delimiter);
5334 }
5335 if let Some(extra_indent) = extra_line_additional_indent {
5336 new_text.push('\n');
5337 if preserve_indent {
5338 new_text.extend(existing_indent.chars());
5339 }
5340 new_text.extend(extra_indent.chars());
5341 }
5342 (
5343 start,
5344 new_text,
5345 *prevent_auto_indent || !apply_syntax_indent,
5346 )
5347 }
5348 };
5349
5350 let anchor = buffer.anchor_after(end);
5351 let new_selection = selection.map(|_| anchor);
5352 (
5353 ((edit_start..end, new_text), prevent_auto_indent),
5354 (newline_config.has_extra_line(), new_selection),
5355 )
5356 })
5357 .unzip()
5358 };
5359
5360 let mut auto_indent_edits = Vec::new();
5361 let mut edits = Vec::new();
5362 for (edit, prevent_auto_indent) in edits_with_flags {
5363 if prevent_auto_indent {
5364 edits.push(edit);
5365 } else {
5366 auto_indent_edits.push(edit);
5367 }
5368 }
5369 if !edits.is_empty() {
5370 this.edit(edits, cx);
5371 }
5372 if !auto_indent_edits.is_empty() {
5373 this.edit_with_autoindent(auto_indent_edits, cx);
5374 }
5375
5376 let buffer = this.buffer.read(cx).snapshot(cx);
5377 let new_selections = selection_info
5378 .into_iter()
5379 .map(|(extra_newline_inserted, new_selection)| {
5380 let mut cursor = new_selection.end.to_point(&buffer);
5381 if extra_newline_inserted {
5382 cursor.row -= 1;
5383 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5384 }
5385 new_selection.map(|_| cursor)
5386 })
5387 .collect();
5388
5389 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5390 this.refresh_edit_prediction(true, false, window, cx);
5391 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5392 task.detach_and_log_err(cx);
5393 }
5394 });
5395 }
5396
5397 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5398 if self.read_only(cx) {
5399 return;
5400 }
5401
5402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5403
5404 let buffer = self.buffer.read(cx);
5405 let snapshot = buffer.snapshot(cx);
5406
5407 let mut edits = Vec::new();
5408 let mut rows = Vec::new();
5409
5410 for (rows_inserted, selection) in self
5411 .selections
5412 .all_adjusted(&self.display_snapshot(cx))
5413 .into_iter()
5414 .enumerate()
5415 {
5416 let cursor = selection.head();
5417 let row = cursor.row;
5418
5419 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5420
5421 let newline = "\n".to_string();
5422 edits.push((start_of_line..start_of_line, newline));
5423
5424 rows.push(row + rows_inserted as u32);
5425 }
5426
5427 self.transact(window, cx, |editor, window, cx| {
5428 editor.edit(edits, cx);
5429
5430 editor.change_selections(Default::default(), window, cx, |s| {
5431 let mut index = 0;
5432 s.move_cursors_with(&mut |map, _, _| {
5433 let row = rows[index];
5434 index += 1;
5435
5436 let point = Point::new(row, 0);
5437 let boundary = map.next_line_boundary(point).1;
5438 let clipped = map.clip_point(boundary, Bias::Left);
5439
5440 (clipped, SelectionGoal::None)
5441 });
5442 });
5443
5444 let mut indent_edits = Vec::new();
5445 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5446 for row in rows {
5447 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5448 for (row, indent) in indents {
5449 if indent.len == 0 {
5450 continue;
5451 }
5452
5453 let text = match indent.kind {
5454 IndentKind::Space => " ".repeat(indent.len as usize),
5455 IndentKind::Tab => "\t".repeat(indent.len as usize),
5456 };
5457 let point = Point::new(row.0, 0);
5458 indent_edits.push((point..point, text));
5459 }
5460 }
5461 editor.edit(indent_edits, cx);
5462 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5463 format.detach_and_log_err(cx);
5464 }
5465 });
5466 }
5467
5468 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5469 if self.read_only(cx) {
5470 return;
5471 }
5472
5473 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5474
5475 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5476 let mut rows = Vec::new();
5477 let mut rows_inserted = 0;
5478
5479 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5480 let cursor = selection.head();
5481 let row = cursor.row;
5482
5483 let point = Point::new(row, 0);
5484 let Some((buffer_handle, buffer_point, _)) =
5485 self.buffer.read(cx).point_to_buffer_point(point, cx)
5486 else {
5487 continue;
5488 };
5489
5490 buffer_edits
5491 .entry(buffer_handle.entity_id())
5492 .or_insert_with(|| (buffer_handle, Vec::new()))
5493 .1
5494 .push(buffer_point);
5495
5496 rows_inserted += 1;
5497 rows.push(row + rows_inserted);
5498 }
5499
5500 self.transact(window, cx, |editor, window, cx| {
5501 for (_, (buffer_handle, points)) in &buffer_edits {
5502 buffer_handle.update(cx, |buffer, cx| {
5503 let edits: Vec<_> = points
5504 .iter()
5505 .map(|point| {
5506 let target = Point::new(point.row + 1, 0);
5507 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5508 (start_of_line..start_of_line, "\n")
5509 })
5510 .collect();
5511 buffer.edit(edits, None, cx);
5512 });
5513 }
5514
5515 editor.change_selections(Default::default(), window, cx, |s| {
5516 let mut index = 0;
5517 s.move_cursors_with(&mut |map, _, _| {
5518 let row = rows[index];
5519 index += 1;
5520
5521 let point = Point::new(row, 0);
5522 let boundary = map.next_line_boundary(point).1;
5523 let clipped = map.clip_point(boundary, Bias::Left);
5524
5525 (clipped, SelectionGoal::None)
5526 });
5527 });
5528
5529 let mut indent_edits = Vec::new();
5530 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5531 for row in rows {
5532 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5533 for (row, indent) in indents {
5534 if indent.len == 0 {
5535 continue;
5536 }
5537
5538 let text = match indent.kind {
5539 IndentKind::Space => " ".repeat(indent.len as usize),
5540 IndentKind::Tab => "\t".repeat(indent.len as usize),
5541 };
5542 let point = Point::new(row.0, 0);
5543 indent_edits.push((point..point, text));
5544 }
5545 }
5546 editor.edit(indent_edits, cx);
5547 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5548 format.detach_and_log_err(cx);
5549 }
5550 });
5551 }
5552
5553 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5554 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5555 original_indent_columns: Vec::new(),
5556 });
5557 self.replace_selections(text, autoindent, window, cx, false);
5558 }
5559
5560 /// Replaces the editor's selections with the provided `text`, applying the
5561 /// given `autoindent_mode` (`None` will skip autoindentation).
5562 ///
5563 /// Early returns if the editor is in read-only mode, without applying any
5564 /// edits.
5565 fn replace_selections(
5566 &mut self,
5567 text: &str,
5568 autoindent_mode: Option<AutoindentMode>,
5569 window: &mut Window,
5570 cx: &mut Context<Self>,
5571 apply_linked_edits: bool,
5572 ) {
5573 if self.read_only(cx) {
5574 return;
5575 }
5576
5577 let text: Arc<str> = text.into();
5578 self.transact(window, cx, |this, window, cx| {
5579 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5580 let linked_edits = if apply_linked_edits {
5581 this.linked_edits_for_selections(text.clone(), cx)
5582 } else {
5583 LinkedEdits::new()
5584 };
5585
5586 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5587 let anchors = {
5588 let snapshot = buffer.read(cx);
5589 old_selections
5590 .iter()
5591 .map(|s| {
5592 let anchor = snapshot.anchor_after(s.head());
5593 s.map(|_| anchor)
5594 })
5595 .collect::<Vec<_>>()
5596 };
5597 buffer.edit(
5598 old_selections
5599 .iter()
5600 .map(|s| (s.start..s.end, text.clone())),
5601 autoindent_mode,
5602 cx,
5603 );
5604 anchors
5605 });
5606
5607 linked_edits.apply(cx);
5608
5609 this.change_selections(Default::default(), window, cx, |s| {
5610 s.select_anchors(selection_anchors);
5611 });
5612
5613 if apply_linked_edits {
5614 refresh_linked_ranges(this, window, cx);
5615 }
5616
5617 cx.notify();
5618 });
5619 }
5620
5621 /// Collects linked edits for the current selections, pairing each linked
5622 /// range with `text`.
5623 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5624 let mut linked_edits = LinkedEdits::new();
5625 if !self.linked_edit_ranges.is_empty() {
5626 for selection in self.selections.disjoint_anchors() {
5627 let start = selection.start.text_anchor;
5628 let end = selection.end.text_anchor;
5629 linked_edits.push(self, start..end, text.clone(), cx);
5630 }
5631 }
5632 linked_edits
5633 }
5634
5635 /// Deletes the content covered by the current selections and applies
5636 /// linked edits.
5637 pub fn delete_selections_with_linked_edits(
5638 &mut self,
5639 window: &mut Window,
5640 cx: &mut Context<Self>,
5641 ) {
5642 self.replace_selections("", None, window, cx, true);
5643 }
5644
5645 #[cfg(any(test, feature = "test-support"))]
5646 pub fn set_linked_edit_ranges_for_testing(
5647 &mut self,
5648 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5649 cx: &mut Context<Self>,
5650 ) -> Option<()> {
5651 let Some((buffer, _)) = self
5652 .buffer
5653 .read(cx)
5654 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5655 else {
5656 return None;
5657 };
5658 let buffer = buffer.read(cx);
5659 let buffer_id = buffer.remote_id();
5660 let mut linked_ranges = Vec::with_capacity(ranges.len());
5661 for (base_range, linked_ranges_points) in ranges {
5662 let base_anchor =
5663 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5664 let linked_anchors = linked_ranges_points
5665 .into_iter()
5666 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5667 .collect();
5668 linked_ranges.push((base_anchor, linked_anchors));
5669 }
5670 let mut map = HashMap::default();
5671 map.insert(buffer_id, linked_ranges);
5672 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5673 Some(())
5674 }
5675
5676 fn trigger_completion_on_input(
5677 &mut self,
5678 text: &str,
5679 trigger_in_words: bool,
5680 window: &mut Window,
5681 cx: &mut Context<Self>,
5682 ) {
5683 let completions_source = self
5684 .context_menu
5685 .borrow()
5686 .as_ref()
5687 .and_then(|menu| match menu {
5688 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5689 CodeContextMenu::CodeActions(_) => None,
5690 });
5691
5692 match completions_source {
5693 Some(CompletionsMenuSource::Words { .. }) => {
5694 self.open_or_update_completions_menu(
5695 Some(CompletionsMenuSource::Words {
5696 ignore_threshold: false,
5697 }),
5698 None,
5699 trigger_in_words,
5700 window,
5701 cx,
5702 );
5703 }
5704 _ => self.open_or_update_completions_menu(
5705 None,
5706 Some(text.to_owned()).filter(|x| !x.is_empty()),
5707 true,
5708 window,
5709 cx,
5710 ),
5711 }
5712 }
5713
5714 /// If any empty selections is touching the start of its innermost containing autoclose
5715 /// region, expand it to select the brackets.
5716 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5717 let selections = self
5718 .selections
5719 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5720 let buffer = self.buffer.read(cx).read(cx);
5721 let new_selections = self
5722 .selections_with_autoclose_regions(selections, &buffer)
5723 .map(|(mut selection, region)| {
5724 if !selection.is_empty() {
5725 return selection;
5726 }
5727
5728 if let Some(region) = region {
5729 let mut range = region.range.to_offset(&buffer);
5730 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5731 range.start -= region.pair.start.len();
5732 if buffer.contains_str_at(range.start, ®ion.pair.start)
5733 && buffer.contains_str_at(range.end, ®ion.pair.end)
5734 {
5735 range.end += region.pair.end.len();
5736 selection.start = range.start;
5737 selection.end = range.end;
5738
5739 return selection;
5740 }
5741 }
5742 }
5743
5744 let always_treat_brackets_as_autoclosed = buffer
5745 .language_settings_at(selection.start, cx)
5746 .always_treat_brackets_as_autoclosed;
5747
5748 if !always_treat_brackets_as_autoclosed {
5749 return selection;
5750 }
5751
5752 if let Some(scope) = buffer.language_scope_at(selection.start) {
5753 for (pair, enabled) in scope.brackets() {
5754 if !enabled || !pair.close {
5755 continue;
5756 }
5757
5758 if buffer.contains_str_at(selection.start, &pair.end) {
5759 let pair_start_len = pair.start.len();
5760 if buffer.contains_str_at(
5761 selection.start.saturating_sub_usize(pair_start_len),
5762 &pair.start,
5763 ) {
5764 selection.start -= pair_start_len;
5765 selection.end += pair.end.len();
5766
5767 return selection;
5768 }
5769 }
5770 }
5771 }
5772
5773 selection
5774 })
5775 .collect();
5776
5777 drop(buffer);
5778 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5779 selections.select(new_selections)
5780 });
5781 }
5782
5783 /// Iterate the given selections, and for each one, find the smallest surrounding
5784 /// autoclose region. This uses the ordering of the selections and the autoclose
5785 /// regions to avoid repeated comparisons.
5786 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5787 &'a self,
5788 selections: impl IntoIterator<Item = Selection<D>>,
5789 buffer: &'a MultiBufferSnapshot,
5790 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5791 let mut i = 0;
5792 let mut regions = self.autoclose_regions.as_slice();
5793 selections.into_iter().map(move |selection| {
5794 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5795
5796 let mut enclosing = None;
5797 while let Some(pair_state) = regions.get(i) {
5798 if pair_state.range.end.to_offset(buffer) < range.start {
5799 regions = ®ions[i + 1..];
5800 i = 0;
5801 } else if pair_state.range.start.to_offset(buffer) > range.end {
5802 break;
5803 } else {
5804 if pair_state.selection_id == selection.id {
5805 enclosing = Some(pair_state);
5806 }
5807 i += 1;
5808 }
5809 }
5810
5811 (selection, enclosing)
5812 })
5813 }
5814
5815 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5816 fn invalidate_autoclose_regions(
5817 &mut self,
5818 mut selections: &[Selection<Anchor>],
5819 buffer: &MultiBufferSnapshot,
5820 ) {
5821 self.autoclose_regions.retain(|state| {
5822 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5823 return false;
5824 }
5825
5826 let mut i = 0;
5827 while let Some(selection) = selections.get(i) {
5828 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5829 selections = &selections[1..];
5830 continue;
5831 }
5832 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5833 break;
5834 }
5835 if selection.id == state.selection_id {
5836 return true;
5837 } else {
5838 i += 1;
5839 }
5840 }
5841 false
5842 });
5843 }
5844
5845 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5846 let offset = position.to_offset(buffer);
5847 let (word_range, kind) =
5848 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5849 if offset > word_range.start && kind == Some(CharKind::Word) {
5850 Some(
5851 buffer
5852 .text_for_range(word_range.start..offset)
5853 .collect::<String>(),
5854 )
5855 } else {
5856 None
5857 }
5858 }
5859
5860 pub fn visible_excerpts(
5861 &self,
5862 lsp_related_only: bool,
5863 cx: &mut Context<Editor>,
5864 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5865 let project = self.project().cloned();
5866 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5867 let multi_buffer = self.buffer().read(cx);
5868 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5869 multi_buffer_snapshot
5870 .range_to_buffer_ranges(
5871 self.multi_buffer_visible_range(&display_snapshot, cx)
5872 .to_inclusive(),
5873 )
5874 .into_iter()
5875 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5876 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5877 if !lsp_related_only {
5878 return Some((
5879 excerpt_id,
5880 (
5881 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5882 buffer.version().clone(),
5883 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5884 ),
5885 ));
5886 }
5887
5888 let project = project.as_ref()?.read(cx);
5889 let buffer_file = project::File::from_dyn(buffer.file())?;
5890 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5891 let worktree_entry = buffer_worktree
5892 .read(cx)
5893 .entry_for_id(buffer_file.project_entry_id()?)?;
5894 if worktree_entry.is_ignored {
5895 None
5896 } else {
5897 Some((
5898 excerpt_id,
5899 (
5900 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5901 buffer.version().clone(),
5902 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5903 ),
5904 ))
5905 }
5906 })
5907 .collect()
5908 }
5909
5910 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5911 TextLayoutDetails {
5912 text_system: window.text_system().clone(),
5913 editor_style: self.style.clone().unwrap(),
5914 rem_size: window.rem_size(),
5915 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5916 visible_rows: self.visible_line_count(),
5917 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5918 }
5919 }
5920
5921 fn trigger_on_type_formatting(
5922 &self,
5923 input: String,
5924 window: &mut Window,
5925 cx: &mut Context<Self>,
5926 ) -> Option<Task<Result<()>>> {
5927 if input.chars().count() != 1 {
5928 return None;
5929 }
5930
5931 let project = self.project()?;
5932 let position = self.selections.newest_anchor().head();
5933 let (buffer, buffer_position) = self
5934 .buffer
5935 .read(cx)
5936 .text_anchor_for_position(position, cx)?;
5937
5938 let settings = language_settings::language_settings(
5939 buffer
5940 .read(cx)
5941 .language_at(buffer_position)
5942 .map(|l| l.name()),
5943 buffer.read(cx).file(),
5944 cx,
5945 );
5946 if !settings.use_on_type_format {
5947 return None;
5948 }
5949
5950 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5951 // hence we do LSP request & edit on host side only — add formats to host's history.
5952 let push_to_lsp_host_history = true;
5953 // If this is not the host, append its history with new edits.
5954 let push_to_client_history = project.read(cx).is_via_collab();
5955
5956 let on_type_formatting = project.update(cx, |project, cx| {
5957 project.on_type_format(
5958 buffer.clone(),
5959 buffer_position,
5960 input,
5961 push_to_lsp_host_history,
5962 cx,
5963 )
5964 });
5965 Some(cx.spawn_in(window, async move |editor, cx| {
5966 if let Some(transaction) = on_type_formatting.await? {
5967 if push_to_client_history {
5968 buffer.update(cx, |buffer, _| {
5969 buffer.push_transaction(transaction, Instant::now());
5970 buffer.finalize_last_transaction();
5971 });
5972 }
5973 editor.update(cx, |editor, cx| {
5974 editor.refresh_document_highlights(cx);
5975 })?;
5976 }
5977 Ok(())
5978 }))
5979 }
5980
5981 pub fn show_word_completions(
5982 &mut self,
5983 _: &ShowWordCompletions,
5984 window: &mut Window,
5985 cx: &mut Context<Self>,
5986 ) {
5987 self.open_or_update_completions_menu(
5988 Some(CompletionsMenuSource::Words {
5989 ignore_threshold: true,
5990 }),
5991 None,
5992 false,
5993 window,
5994 cx,
5995 );
5996 }
5997
5998 pub fn show_completions(
5999 &mut self,
6000 _: &ShowCompletions,
6001 window: &mut Window,
6002 cx: &mut Context<Self>,
6003 ) {
6004 self.open_or_update_completions_menu(None, None, false, window, cx);
6005 }
6006
6007 fn open_or_update_completions_menu(
6008 &mut self,
6009 requested_source: Option<CompletionsMenuSource>,
6010 trigger: Option<String>,
6011 trigger_in_words: bool,
6012 window: &mut Window,
6013 cx: &mut Context<Self>,
6014 ) {
6015 if self.pending_rename.is_some() {
6016 return;
6017 }
6018
6019 let completions_source = self
6020 .context_menu
6021 .borrow()
6022 .as_ref()
6023 .and_then(|menu| match menu {
6024 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6025 CodeContextMenu::CodeActions(_) => None,
6026 });
6027
6028 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6029
6030 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6031 // inserted and selected. To handle that case, the start of the selection is used so that
6032 // the menu starts with all choices.
6033 let position = self
6034 .selections
6035 .newest_anchor()
6036 .start
6037 .bias_right(&multibuffer_snapshot);
6038 if position.diff_base_anchor.is_some() {
6039 return;
6040 }
6041 let buffer_position = multibuffer_snapshot.anchor_before(position);
6042 let Some(buffer) = buffer_position
6043 .text_anchor
6044 .buffer_id
6045 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
6046 else {
6047 return;
6048 };
6049 let buffer_snapshot = buffer.read(cx).snapshot();
6050
6051 let menu_is_open = matches!(
6052 self.context_menu.borrow().as_ref(),
6053 Some(CodeContextMenu::Completions(_))
6054 );
6055
6056 let language = buffer_snapshot
6057 .language_at(buffer_position.text_anchor)
6058 .map(|language| language.name());
6059
6060 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
6061 let completion_settings = language_settings.completions.clone();
6062
6063 let show_completions_on_input = self
6064 .show_completions_on_input_override
6065 .unwrap_or(language_settings.show_completions_on_input);
6066 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6067 return;
6068 }
6069
6070 let query: Option<Arc<String>> =
6071 Self::completion_query(&multibuffer_snapshot, buffer_position)
6072 .map(|query| query.into());
6073
6074 drop(multibuffer_snapshot);
6075
6076 // Hide the current completions menu when query is empty. Without this, cached
6077 // completions from before the trigger char may be reused (#32774).
6078 if query.is_none() && menu_is_open {
6079 self.hide_context_menu(window, cx);
6080 }
6081
6082 let mut ignore_word_threshold = false;
6083 let provider = match requested_source {
6084 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6085 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6086 ignore_word_threshold = ignore_threshold;
6087 None
6088 }
6089 Some(CompletionsMenuSource::SnippetChoices)
6090 | Some(CompletionsMenuSource::SnippetsOnly) => {
6091 log::error!("bug: SnippetChoices requested_source is not handled");
6092 None
6093 }
6094 };
6095
6096 let sort_completions = provider
6097 .as_ref()
6098 .is_some_and(|provider| provider.sort_completions());
6099
6100 let filter_completions = provider
6101 .as_ref()
6102 .is_none_or(|provider| provider.filter_completions());
6103
6104 let was_snippets_only = matches!(
6105 completions_source,
6106 Some(CompletionsMenuSource::SnippetsOnly)
6107 );
6108
6109 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6110 if filter_completions {
6111 menu.filter(
6112 query.clone().unwrap_or_default(),
6113 buffer_position.text_anchor,
6114 &buffer,
6115 provider.clone(),
6116 window,
6117 cx,
6118 );
6119 }
6120 // When `is_incomplete` is false, no need to re-query completions when the current query
6121 // is a suffix of the initial query.
6122 let was_complete = !menu.is_incomplete;
6123 if was_complete && !was_snippets_only {
6124 // If the new query is a suffix of the old query (typing more characters) and
6125 // the previous result was complete, the existing completions can be filtered.
6126 //
6127 // Note that snippet completions are always complete.
6128 let query_matches = match (&menu.initial_query, &query) {
6129 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6130 (None, _) => true,
6131 _ => false,
6132 };
6133 if query_matches {
6134 let position_matches = if menu.initial_position == position {
6135 true
6136 } else {
6137 let snapshot = self.buffer.read(cx).read(cx);
6138 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6139 };
6140 if position_matches {
6141 return;
6142 }
6143 }
6144 }
6145 };
6146
6147 let Anchor {
6148 excerpt_id: buffer_excerpt_id,
6149 text_anchor: buffer_position,
6150 ..
6151 } = buffer_position;
6152
6153 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6154 buffer_snapshot.surrounding_word(buffer_position, None)
6155 {
6156 let word_to_exclude = buffer_snapshot
6157 .text_for_range(word_range.clone())
6158 .collect::<String>();
6159 (
6160 buffer_snapshot.anchor_before(word_range.start)
6161 ..buffer_snapshot.anchor_after(buffer_position),
6162 Some(word_to_exclude),
6163 )
6164 } else {
6165 (buffer_position..buffer_position, None)
6166 };
6167
6168 let show_completion_documentation = buffer_snapshot
6169 .settings_at(buffer_position, cx)
6170 .show_completion_documentation;
6171
6172 // The document can be large, so stay in reasonable bounds when searching for words,
6173 // otherwise completion pop-up might be slow to appear.
6174 const WORD_LOOKUP_ROWS: u32 = 5_000;
6175 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6176 let min_word_search = buffer_snapshot.clip_point(
6177 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6178 Bias::Left,
6179 );
6180 let max_word_search = buffer_snapshot.clip_point(
6181 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6182 Bias::Right,
6183 );
6184 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6185 ..buffer_snapshot.point_to_offset(max_word_search);
6186
6187 let skip_digits = query
6188 .as_ref()
6189 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6190
6191 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6192 trigger.as_ref().is_none_or(|trigger| {
6193 provider.is_completion_trigger(
6194 &buffer,
6195 position.text_anchor,
6196 trigger,
6197 trigger_in_words,
6198 cx,
6199 )
6200 })
6201 });
6202
6203 let provider_responses = if let Some(provider) = &provider
6204 && load_provider_completions
6205 {
6206 let trigger_character =
6207 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6208 let completion_context = CompletionContext {
6209 trigger_kind: match &trigger_character {
6210 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6211 None => CompletionTriggerKind::INVOKED,
6212 },
6213 trigger_character,
6214 };
6215
6216 provider.completions(
6217 buffer_excerpt_id,
6218 &buffer,
6219 buffer_position,
6220 completion_context,
6221 window,
6222 cx,
6223 )
6224 } else {
6225 Task::ready(Ok(Vec::new()))
6226 };
6227
6228 let load_word_completions = if !self.word_completions_enabled {
6229 false
6230 } else if requested_source
6231 == Some(CompletionsMenuSource::Words {
6232 ignore_threshold: true,
6233 })
6234 {
6235 true
6236 } else {
6237 load_provider_completions
6238 && completion_settings.words != WordsCompletionMode::Disabled
6239 && (ignore_word_threshold || {
6240 let words_min_length = completion_settings.words_min_length;
6241 // check whether word has at least `words_min_length` characters
6242 let query_chars = query.iter().flat_map(|q| q.chars());
6243 query_chars.take(words_min_length).count() == words_min_length
6244 })
6245 };
6246
6247 let mut words = if load_word_completions {
6248 cx.background_spawn({
6249 let buffer_snapshot = buffer_snapshot.clone();
6250 async move {
6251 buffer_snapshot.words_in_range(WordsQuery {
6252 fuzzy_contents: None,
6253 range: word_search_range,
6254 skip_digits,
6255 })
6256 }
6257 })
6258 } else {
6259 Task::ready(BTreeMap::default())
6260 };
6261
6262 let snippets = if let Some(provider) = &provider
6263 && provider.show_snippets()
6264 && let Some(project) = self.project()
6265 {
6266 let char_classifier = buffer_snapshot
6267 .char_classifier_at(buffer_position)
6268 .scope_context(Some(CharScopeContext::Completion));
6269 project.update(cx, |project, cx| {
6270 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6271 })
6272 } else {
6273 Task::ready(Ok(CompletionResponse {
6274 completions: Vec::new(),
6275 display_options: Default::default(),
6276 is_incomplete: false,
6277 }))
6278 };
6279
6280 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6281
6282 let id = post_inc(&mut self.next_completion_id);
6283 let task = cx.spawn_in(window, async move |editor, cx| {
6284 let Ok(()) = editor.update(cx, |this, _| {
6285 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6286 }) else {
6287 return;
6288 };
6289
6290 // TODO: Ideally completions from different sources would be selectively re-queried, so
6291 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6292 let mut completions = Vec::new();
6293 let mut is_incomplete = false;
6294 let mut display_options: Option<CompletionDisplayOptions> = None;
6295 if let Some(provider_responses) = provider_responses.await.log_err()
6296 && !provider_responses.is_empty()
6297 {
6298 for response in provider_responses {
6299 completions.extend(response.completions);
6300 is_incomplete = is_incomplete || response.is_incomplete;
6301 match display_options.as_mut() {
6302 None => {
6303 display_options = Some(response.display_options);
6304 }
6305 Some(options) => options.merge(&response.display_options),
6306 }
6307 }
6308 if completion_settings.words == WordsCompletionMode::Fallback {
6309 words = Task::ready(BTreeMap::default());
6310 }
6311 }
6312 let display_options = display_options.unwrap_or_default();
6313
6314 let mut words = words.await;
6315 if let Some(word_to_exclude) = &word_to_exclude {
6316 words.remove(word_to_exclude);
6317 }
6318 for lsp_completion in &completions {
6319 words.remove(&lsp_completion.new_text);
6320 }
6321 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6322 replace_range: word_replace_range.clone(),
6323 new_text: word.clone(),
6324 label: CodeLabel::plain(word, None),
6325 match_start: None,
6326 snippet_deduplication_key: None,
6327 icon_path: None,
6328 documentation: None,
6329 source: CompletionSource::BufferWord {
6330 word_range,
6331 resolved: false,
6332 },
6333 insert_text_mode: Some(InsertTextMode::AS_IS),
6334 confirm: None,
6335 }));
6336
6337 completions.extend(
6338 snippets
6339 .await
6340 .into_iter()
6341 .flat_map(|response| response.completions),
6342 );
6343
6344 let menu = if completions.is_empty() {
6345 None
6346 } else {
6347 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6348 let languages = editor
6349 .workspace
6350 .as_ref()
6351 .and_then(|(workspace, _)| workspace.upgrade())
6352 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6353 let menu = CompletionsMenu::new(
6354 id,
6355 requested_source.unwrap_or(if load_provider_completions {
6356 CompletionsMenuSource::Normal
6357 } else {
6358 CompletionsMenuSource::SnippetsOnly
6359 }),
6360 sort_completions,
6361 show_completion_documentation,
6362 position,
6363 query.clone(),
6364 is_incomplete,
6365 buffer.clone(),
6366 completions.into(),
6367 editor
6368 .context_menu()
6369 .borrow_mut()
6370 .as_ref()
6371 .map(|menu| menu.primary_scroll_handle()),
6372 display_options,
6373 snippet_sort_order,
6374 languages,
6375 language,
6376 cx,
6377 );
6378
6379 let query = if filter_completions { query } else { None };
6380 let matches_task = menu.do_async_filtering(
6381 query.unwrap_or_default(),
6382 buffer_position,
6383 &buffer,
6384 cx,
6385 );
6386 (menu, matches_task)
6387 }) else {
6388 return;
6389 };
6390
6391 let matches = matches_task.await;
6392
6393 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6394 // Newer menu already set, so exit.
6395 if let Some(CodeContextMenu::Completions(prev_menu)) =
6396 editor.context_menu.borrow().as_ref()
6397 && prev_menu.id > id
6398 {
6399 return;
6400 };
6401
6402 // Only valid to take prev_menu because either the new menu is immediately set
6403 // below, or the menu is hidden.
6404 if let Some(CodeContextMenu::Completions(prev_menu)) =
6405 editor.context_menu.borrow_mut().take()
6406 {
6407 let position_matches =
6408 if prev_menu.initial_position == menu.initial_position {
6409 true
6410 } else {
6411 let snapshot = editor.buffer.read(cx).read(cx);
6412 prev_menu.initial_position.to_offset(&snapshot)
6413 == menu.initial_position.to_offset(&snapshot)
6414 };
6415 if position_matches {
6416 // Preserve markdown cache before `set_filter_results` because it will
6417 // try to populate the documentation cache.
6418 menu.preserve_markdown_cache(prev_menu);
6419 }
6420 };
6421
6422 menu.set_filter_results(matches, provider, window, cx);
6423 }) else {
6424 return;
6425 };
6426
6427 menu.visible().then_some(menu)
6428 };
6429
6430 editor
6431 .update_in(cx, |editor, window, cx| {
6432 if editor.focus_handle.is_focused(window)
6433 && let Some(menu) = menu
6434 {
6435 *editor.context_menu.borrow_mut() =
6436 Some(CodeContextMenu::Completions(menu));
6437
6438 crate::hover_popover::hide_hover(editor, cx);
6439 if editor.show_edit_predictions_in_menu() {
6440 editor.update_visible_edit_prediction(window, cx);
6441 } else {
6442 editor
6443 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6444 }
6445
6446 cx.notify();
6447 return;
6448 }
6449
6450 if editor.completion_tasks.len() <= 1 {
6451 // If there are no more completion tasks and the last menu was empty, we should hide it.
6452 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6453 // If it was already hidden and we don't show edit predictions in the menu,
6454 // we should also show the edit prediction when available.
6455 if was_hidden && editor.show_edit_predictions_in_menu() {
6456 editor.update_visible_edit_prediction(window, cx);
6457 }
6458 }
6459 })
6460 .ok();
6461 });
6462
6463 self.completion_tasks.push((id, task));
6464 }
6465
6466 #[cfg(any(test, feature = "test-support"))]
6467 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6468 let menu = self.context_menu.borrow();
6469 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6470 let completions = menu.completions.borrow();
6471 Some(completions.to_vec())
6472 } else {
6473 None
6474 }
6475 }
6476
6477 pub fn with_completions_menu_matching_id<R>(
6478 &self,
6479 id: CompletionId,
6480 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6481 ) -> R {
6482 let mut context_menu = self.context_menu.borrow_mut();
6483 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6484 return f(None);
6485 };
6486 if completions_menu.id != id {
6487 return f(None);
6488 }
6489 f(Some(completions_menu))
6490 }
6491
6492 pub fn confirm_completion(
6493 &mut self,
6494 action: &ConfirmCompletion,
6495 window: &mut Window,
6496 cx: &mut Context<Self>,
6497 ) -> Option<Task<Result<()>>> {
6498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6499 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6500 }
6501
6502 pub fn confirm_completion_insert(
6503 &mut self,
6504 _: &ConfirmCompletionInsert,
6505 window: &mut Window,
6506 cx: &mut Context<Self>,
6507 ) -> Option<Task<Result<()>>> {
6508 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6509 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6510 }
6511
6512 pub fn confirm_completion_replace(
6513 &mut self,
6514 _: &ConfirmCompletionReplace,
6515 window: &mut Window,
6516 cx: &mut Context<Self>,
6517 ) -> Option<Task<Result<()>>> {
6518 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6519 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6520 }
6521
6522 pub fn compose_completion(
6523 &mut self,
6524 action: &ComposeCompletion,
6525 window: &mut Window,
6526 cx: &mut Context<Self>,
6527 ) -> Option<Task<Result<()>>> {
6528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6529 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6530 }
6531
6532 fn do_completion(
6533 &mut self,
6534 item_ix: Option<usize>,
6535 intent: CompletionIntent,
6536 window: &mut Window,
6537 cx: &mut Context<Editor>,
6538 ) -> Option<Task<Result<()>>> {
6539 use language::ToOffset as _;
6540
6541 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6542 else {
6543 return None;
6544 };
6545
6546 let candidate_id = {
6547 let entries = completions_menu.entries.borrow();
6548 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6549 if self.show_edit_predictions_in_menu() {
6550 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6551 }
6552 mat.candidate_id
6553 };
6554
6555 let completion = completions_menu
6556 .completions
6557 .borrow()
6558 .get(candidate_id)?
6559 .clone();
6560 cx.stop_propagation();
6561
6562 let buffer_handle = completions_menu.buffer.clone();
6563
6564 let CompletionEdit {
6565 new_text,
6566 snippet,
6567 replace_range,
6568 } = process_completion_for_edit(
6569 &completion,
6570 intent,
6571 &buffer_handle,
6572 &completions_menu.initial_position.text_anchor,
6573 cx,
6574 );
6575
6576 let buffer = buffer_handle.read(cx);
6577 let snapshot = self.buffer.read(cx).snapshot(cx);
6578 let newest_anchor = self.selections.newest_anchor();
6579 let replace_range_multibuffer = {
6580 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6581 excerpt.map_range_from_buffer(replace_range.clone())
6582 };
6583 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6584 return None;
6585 }
6586
6587 let old_text = buffer
6588 .text_for_range(replace_range.clone())
6589 .collect::<String>();
6590 let lookbehind = newest_anchor
6591 .start
6592 .text_anchor
6593 .to_offset(buffer)
6594 .saturating_sub(replace_range.start.0);
6595 let lookahead = replace_range
6596 .end
6597 .0
6598 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6599 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6600 let suffix = &old_text[lookbehind.min(old_text.len())..];
6601
6602 let selections = self
6603 .selections
6604 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6605 let mut ranges = Vec::new();
6606 let mut all_commit_ranges = Vec::new();
6607 let mut linked_edits = LinkedEdits::new();
6608
6609 let text: Arc<str> = new_text.clone().into();
6610 for selection in &selections {
6611 let range = if selection.id == newest_anchor.id {
6612 replace_range_multibuffer.clone()
6613 } else {
6614 let mut range = selection.range();
6615
6616 // if prefix is present, don't duplicate it
6617 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6618 range.start = range.start.saturating_sub_usize(lookbehind);
6619
6620 // if suffix is also present, mimic the newest cursor and replace it
6621 if selection.id != newest_anchor.id
6622 && snapshot.contains_str_at(range.end, suffix)
6623 {
6624 range.end += lookahead;
6625 }
6626 }
6627 range
6628 };
6629
6630 ranges.push(range.clone());
6631
6632 let start_anchor = snapshot.anchor_before(range.start);
6633 let end_anchor = snapshot.anchor_after(range.end);
6634 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6635 all_commit_ranges.push(anchor_range.clone());
6636
6637 if !self.linked_edit_ranges.is_empty() {
6638 linked_edits.push(&self, anchor_range, text.clone(), cx);
6639 }
6640 }
6641
6642 let common_prefix_len = old_text
6643 .chars()
6644 .zip(new_text.chars())
6645 .take_while(|(a, b)| a == b)
6646 .map(|(a, _)| a.len_utf8())
6647 .sum::<usize>();
6648
6649 cx.emit(EditorEvent::InputHandled {
6650 utf16_range_to_replace: None,
6651 text: new_text[common_prefix_len..].into(),
6652 });
6653
6654 self.transact(window, cx, |editor, window, cx| {
6655 if let Some(mut snippet) = snippet {
6656 snippet.text = new_text.to_string();
6657 editor
6658 .insert_snippet(&ranges, snippet, window, cx)
6659 .log_err();
6660 } else {
6661 editor.buffer.update(cx, |multi_buffer, cx| {
6662 let auto_indent = match completion.insert_text_mode {
6663 Some(InsertTextMode::AS_IS) => None,
6664 _ => editor.autoindent_mode.clone(),
6665 };
6666 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6667 multi_buffer.edit(edits, auto_indent, cx);
6668 });
6669 }
6670 linked_edits.apply(cx);
6671 editor.refresh_edit_prediction(true, false, window, cx);
6672 });
6673 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6674
6675 let show_new_completions_on_confirm = completion
6676 .confirm
6677 .as_ref()
6678 .is_some_and(|confirm| confirm(intent, window, cx));
6679 if show_new_completions_on_confirm {
6680 self.open_or_update_completions_menu(None, None, false, window, cx);
6681 }
6682
6683 let provider = self.completion_provider.as_ref()?;
6684
6685 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6686 let command = lsp_store.as_ref().and_then(|lsp_store| {
6687 let CompletionSource::Lsp {
6688 lsp_completion,
6689 server_id,
6690 ..
6691 } = &completion.source
6692 else {
6693 return None;
6694 };
6695 let lsp_command = lsp_completion.command.as_ref()?;
6696 let available_commands = lsp_store
6697 .read(cx)
6698 .lsp_server_capabilities
6699 .get(server_id)
6700 .and_then(|server_capabilities| {
6701 server_capabilities
6702 .execute_command_provider
6703 .as_ref()
6704 .map(|options| options.commands.as_slice())
6705 })?;
6706 if available_commands.contains(&lsp_command.command) {
6707 Some(CodeAction {
6708 server_id: *server_id,
6709 range: language::Anchor::MIN..language::Anchor::MIN,
6710 lsp_action: LspAction::Command(lsp_command.clone()),
6711 resolved: false,
6712 })
6713 } else {
6714 None
6715 }
6716 });
6717
6718 drop(completion);
6719 let apply_edits = provider.apply_additional_edits_for_completion(
6720 buffer_handle.clone(),
6721 completions_menu.completions.clone(),
6722 candidate_id,
6723 true,
6724 all_commit_ranges,
6725 cx,
6726 );
6727
6728 let editor_settings = EditorSettings::get_global(cx);
6729 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6730 // After the code completion is finished, users often want to know what signatures are needed.
6731 // so we should automatically call signature_help
6732 self.show_signature_help(&ShowSignatureHelp, window, cx);
6733 }
6734
6735 Some(cx.spawn_in(window, async move |editor, cx| {
6736 apply_edits.await?;
6737
6738 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6739 let title = command.lsp_action.title().to_owned();
6740 let project_transaction = lsp_store
6741 .update(cx, |lsp_store, cx| {
6742 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6743 })
6744 .await
6745 .context("applying post-completion command")?;
6746 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6747 Self::open_project_transaction(
6748 &editor,
6749 workspace.downgrade(),
6750 project_transaction,
6751 title,
6752 cx,
6753 )
6754 .await?;
6755 }
6756 }
6757
6758 Ok(())
6759 }))
6760 }
6761
6762 pub fn toggle_code_actions(
6763 &mut self,
6764 action: &ToggleCodeActions,
6765 window: &mut Window,
6766 cx: &mut Context<Self>,
6767 ) {
6768 let quick_launch = action.quick_launch;
6769 let mut context_menu = self.context_menu.borrow_mut();
6770 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6771 if code_actions.deployed_from == action.deployed_from {
6772 // Toggle if we're selecting the same one
6773 *context_menu = None;
6774 cx.notify();
6775 return;
6776 } else {
6777 // Otherwise, clear it and start a new one
6778 *context_menu = None;
6779 cx.notify();
6780 }
6781 }
6782 drop(context_menu);
6783 let snapshot = self.snapshot(window, cx);
6784 let deployed_from = action.deployed_from.clone();
6785 let action = action.clone();
6786 self.completion_tasks.clear();
6787 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6788
6789 let multibuffer_point = match &action.deployed_from {
6790 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6791 DisplayPoint::new(*row, 0).to_point(&snapshot)
6792 }
6793 _ => self
6794 .selections
6795 .newest::<Point>(&snapshot.display_snapshot)
6796 .head(),
6797 };
6798 let Some((buffer, buffer_row)) = snapshot
6799 .buffer_snapshot()
6800 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6801 .and_then(|(buffer_snapshot, range)| {
6802 self.buffer()
6803 .read(cx)
6804 .buffer(buffer_snapshot.remote_id())
6805 .map(|buffer| (buffer, range.start.row))
6806 })
6807 else {
6808 return;
6809 };
6810 let buffer_id = buffer.read(cx).remote_id();
6811 let tasks = self
6812 .runnables
6813 .runnables((buffer_id, buffer_row))
6814 .map(|t| Arc::new(t.to_owned()));
6815
6816 if !self.focus_handle.is_focused(window) {
6817 return;
6818 }
6819 let project = self.project.clone();
6820
6821 let code_actions_task = match deployed_from {
6822 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6823 _ => self.code_actions(buffer_row, window, cx),
6824 };
6825
6826 let runnable_task = match deployed_from {
6827 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6828 _ => {
6829 let mut task_context_task = Task::ready(None);
6830 if let Some(tasks) = &tasks
6831 && let Some(project) = project
6832 {
6833 task_context_task =
6834 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6835 }
6836
6837 cx.spawn_in(window, {
6838 let buffer = buffer.clone();
6839 async move |editor, cx| {
6840 let task_context = task_context_task.await;
6841
6842 let resolved_tasks =
6843 tasks
6844 .zip(task_context.clone())
6845 .map(|(tasks, task_context)| ResolvedTasks {
6846 templates: tasks.resolve(&task_context).collect(),
6847 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6848 multibuffer_point.row,
6849 tasks.column,
6850 )),
6851 });
6852 let debug_scenarios = editor
6853 .update(cx, |editor, cx| {
6854 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6855 })?
6856 .await;
6857 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6858 }
6859 })
6860 }
6861 };
6862
6863 cx.spawn_in(window, async move |editor, cx| {
6864 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6865 let code_actions = code_actions_task.await;
6866 let spawn_straight_away = quick_launch
6867 && resolved_tasks
6868 .as_ref()
6869 .is_some_and(|tasks| tasks.templates.len() == 1)
6870 && code_actions
6871 .as_ref()
6872 .is_none_or(|actions| actions.is_empty())
6873 && debug_scenarios.is_empty();
6874
6875 editor.update_in(cx, |editor, window, cx| {
6876 crate::hover_popover::hide_hover(editor, cx);
6877 let actions = CodeActionContents::new(
6878 resolved_tasks,
6879 code_actions,
6880 debug_scenarios,
6881 task_context.unwrap_or_default(),
6882 );
6883
6884 // Don't show the menu if there are no actions available
6885 if actions.is_empty() {
6886 cx.notify();
6887 return Task::ready(Ok(()));
6888 }
6889
6890 *editor.context_menu.borrow_mut() =
6891 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6892 buffer,
6893 actions,
6894 selected_item: Default::default(),
6895 scroll_handle: UniformListScrollHandle::default(),
6896 deployed_from,
6897 }));
6898 cx.notify();
6899 if spawn_straight_away
6900 && let Some(task) = editor.confirm_code_action(
6901 &ConfirmCodeAction { item_ix: Some(0) },
6902 window,
6903 cx,
6904 )
6905 {
6906 return task;
6907 }
6908
6909 Task::ready(Ok(()))
6910 })
6911 })
6912 .detach_and_log_err(cx);
6913 }
6914
6915 fn debug_scenarios(
6916 &mut self,
6917 resolved_tasks: &Option<ResolvedTasks>,
6918 buffer: &Entity<Buffer>,
6919 cx: &mut App,
6920 ) -> Task<Vec<task::DebugScenario>> {
6921 maybe!({
6922 let project = self.project()?;
6923 let dap_store = project.read(cx).dap_store();
6924 let mut scenarios = vec![];
6925 let resolved_tasks = resolved_tasks.as_ref()?;
6926 let buffer = buffer.read(cx);
6927 let language = buffer.language()?;
6928 let file = buffer.file();
6929 let debug_adapter = language_settings(language.name().into(), file, cx)
6930 .debuggers
6931 .first()
6932 .map(SharedString::from)
6933 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6934
6935 dap_store.update(cx, |dap_store, cx| {
6936 for (_, task) in &resolved_tasks.templates {
6937 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6938 task.original_task().clone(),
6939 debug_adapter.clone().into(),
6940 task.display_label().to_owned().into(),
6941 cx,
6942 );
6943 scenarios.push(maybe_scenario);
6944 }
6945 });
6946 Some(cx.background_spawn(async move {
6947 futures::future::join_all(scenarios)
6948 .await
6949 .into_iter()
6950 .flatten()
6951 .collect::<Vec<_>>()
6952 }))
6953 })
6954 .unwrap_or_else(|| Task::ready(vec![]))
6955 }
6956
6957 fn code_actions(
6958 &mut self,
6959 buffer_row: u32,
6960 window: &mut Window,
6961 cx: &mut Context<Self>,
6962 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6963 let mut task = self.code_actions_task.take();
6964 cx.spawn_in(window, async move |editor, cx| {
6965 while let Some(prev_task) = task {
6966 prev_task.await.log_err();
6967 task = editor
6968 .update(cx, |this, _| this.code_actions_task.take())
6969 .ok()?;
6970 }
6971
6972 editor
6973 .update(cx, |editor, cx| {
6974 editor
6975 .available_code_actions
6976 .clone()
6977 .and_then(|(location, code_actions)| {
6978 let snapshot = location.buffer.read(cx).snapshot();
6979 let point_range = location.range.to_point(&snapshot);
6980 let point_range = point_range.start.row..=point_range.end.row;
6981 if point_range.contains(&buffer_row) {
6982 Some(code_actions)
6983 } else {
6984 None
6985 }
6986 })
6987 })
6988 .ok()
6989 .flatten()
6990 })
6991 }
6992
6993 pub fn confirm_code_action(
6994 &mut self,
6995 action: &ConfirmCodeAction,
6996 window: &mut Window,
6997 cx: &mut Context<Self>,
6998 ) -> Option<Task<Result<()>>> {
6999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7000
7001 let actions_menu =
7002 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7003 menu
7004 } else {
7005 return None;
7006 };
7007
7008 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7009 let action = actions_menu.actions.get(action_ix)?;
7010 let title = action.label();
7011 let buffer = actions_menu.buffer;
7012 let workspace = self.workspace()?;
7013
7014 match action {
7015 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7016 workspace.update(cx, |workspace, cx| {
7017 workspace.schedule_resolved_task(
7018 task_source_kind,
7019 resolved_task,
7020 false,
7021 window,
7022 cx,
7023 );
7024
7025 Some(Task::ready(Ok(())))
7026 })
7027 }
7028 CodeActionsItem::CodeAction {
7029 excerpt_id,
7030 action,
7031 provider,
7032 } => {
7033 let apply_code_action =
7034 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
7035 let workspace = workspace.downgrade();
7036 Some(cx.spawn_in(window, async move |editor, cx| {
7037 let project_transaction = apply_code_action.await?;
7038 Self::open_project_transaction(
7039 &editor,
7040 workspace,
7041 project_transaction,
7042 title,
7043 cx,
7044 )
7045 .await
7046 }))
7047 }
7048 CodeActionsItem::DebugScenario(scenario) => {
7049 let context = actions_menu.actions.context.into();
7050
7051 workspace.update(cx, |workspace, cx| {
7052 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7053 workspace.start_debug_session(
7054 scenario,
7055 context,
7056 Some(buffer),
7057 None,
7058 window,
7059 cx,
7060 );
7061 });
7062 Some(Task::ready(Ok(())))
7063 }
7064 }
7065 }
7066
7067 fn open_transaction_for_hidden_buffers(
7068 workspace: Entity<Workspace>,
7069 transaction: ProjectTransaction,
7070 title: String,
7071 window: &mut Window,
7072 cx: &mut Context<Self>,
7073 ) {
7074 if transaction.0.is_empty() {
7075 return;
7076 }
7077
7078 let edited_buffers_already_open = {
7079 let other_editors: Vec<Entity<Editor>> = workspace
7080 .read(cx)
7081 .panes()
7082 .iter()
7083 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7084 .filter(|editor| editor.entity_id() != cx.entity_id())
7085 .collect();
7086
7087 transaction.0.keys().all(|buffer| {
7088 other_editors.iter().any(|editor| {
7089 let multi_buffer = editor.read(cx).buffer();
7090 multi_buffer.read(cx).is_singleton()
7091 && multi_buffer
7092 .read(cx)
7093 .as_singleton()
7094 .map_or(false, |singleton| {
7095 singleton.entity_id() == buffer.entity_id()
7096 })
7097 })
7098 })
7099 };
7100 if !edited_buffers_already_open {
7101 let workspace = workspace.downgrade();
7102 cx.defer_in(window, move |_, window, cx| {
7103 cx.spawn_in(window, async move |editor, cx| {
7104 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7105 .await
7106 .ok()
7107 })
7108 .detach();
7109 });
7110 }
7111 }
7112
7113 pub async fn open_project_transaction(
7114 editor: &WeakEntity<Editor>,
7115 workspace: WeakEntity<Workspace>,
7116 transaction: ProjectTransaction,
7117 title: String,
7118 cx: &mut AsyncWindowContext,
7119 ) -> Result<()> {
7120 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7121 cx.update(|_, cx| {
7122 entries.sort_unstable_by_key(|(buffer, _)| {
7123 buffer.read(cx).file().map(|f| f.path().clone())
7124 });
7125 })?;
7126 if entries.is_empty() {
7127 return Ok(());
7128 }
7129
7130 // If the project transaction's edits are all contained within this editor, then
7131 // avoid opening a new editor to display them.
7132
7133 if let [(buffer, transaction)] = &*entries {
7134 let excerpt = editor.update(cx, |editor, cx| {
7135 editor
7136 .buffer()
7137 .read(cx)
7138 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7139 })?;
7140 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7141 && excerpted_buffer == *buffer
7142 {
7143 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7144 let excerpt_range = excerpt_range.to_offset(buffer);
7145 buffer
7146 .edited_ranges_for_transaction::<usize>(transaction)
7147 .all(|range| {
7148 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7149 })
7150 });
7151
7152 if all_edits_within_excerpt {
7153 return Ok(());
7154 }
7155 }
7156 }
7157
7158 let mut ranges_to_highlight = Vec::new();
7159 let excerpt_buffer = cx.new(|cx| {
7160 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7161 for (buffer_handle, transaction) in &entries {
7162 let edited_ranges = buffer_handle
7163 .read(cx)
7164 .edited_ranges_for_transaction::<Point>(transaction)
7165 .collect::<Vec<_>>();
7166 let (ranges, _) = multibuffer.set_excerpts_for_path(
7167 PathKey::for_buffer(buffer_handle, cx),
7168 buffer_handle.clone(),
7169 edited_ranges,
7170 multibuffer_context_lines(cx),
7171 cx,
7172 );
7173
7174 ranges_to_highlight.extend(ranges);
7175 }
7176 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7177 multibuffer
7178 });
7179
7180 workspace.update_in(cx, |workspace, window, cx| {
7181 let project = workspace.project().clone();
7182 let editor =
7183 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7184 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7185 editor.update(cx, |editor, cx| {
7186 editor.highlight_background(
7187 HighlightKey::Editor,
7188 &ranges_to_highlight,
7189 |_, theme| theme.colors().editor_highlighted_line_background,
7190 cx,
7191 );
7192 });
7193 })?;
7194
7195 Ok(())
7196 }
7197
7198 pub fn clear_code_action_providers(&mut self) {
7199 self.code_action_providers.clear();
7200 self.available_code_actions.take();
7201 }
7202
7203 pub fn add_code_action_provider(
7204 &mut self,
7205 provider: Rc<dyn CodeActionProvider>,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 if self
7210 .code_action_providers
7211 .iter()
7212 .any(|existing_provider| existing_provider.id() == provider.id())
7213 {
7214 return;
7215 }
7216
7217 self.code_action_providers.push(provider);
7218 self.refresh_code_actions(window, cx);
7219 }
7220
7221 pub fn remove_code_action_provider(
7222 &mut self,
7223 id: Arc<str>,
7224 window: &mut Window,
7225 cx: &mut Context<Self>,
7226 ) {
7227 self.code_action_providers
7228 .retain(|provider| provider.id() != id);
7229 self.refresh_code_actions(window, cx);
7230 }
7231
7232 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7233 !self.code_action_providers.is_empty()
7234 && EditorSettings::get_global(cx).toolbar.code_actions
7235 }
7236
7237 pub fn has_available_code_actions(&self) -> bool {
7238 self.available_code_actions
7239 .as_ref()
7240 .is_some_and(|(_, actions)| !actions.is_empty())
7241 }
7242
7243 fn render_inline_code_actions(
7244 &self,
7245 icon_size: ui::IconSize,
7246 display_row: DisplayRow,
7247 is_active: bool,
7248 cx: &mut Context<Self>,
7249 ) -> AnyElement {
7250 let show_tooltip = !self.context_menu_visible();
7251 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7252 .icon_size(icon_size)
7253 .shape(ui::IconButtonShape::Square)
7254 .icon_color(ui::Color::Hidden)
7255 .toggle_state(is_active)
7256 .when(show_tooltip, |this| {
7257 this.tooltip({
7258 let focus_handle = self.focus_handle.clone();
7259 move |_window, cx| {
7260 Tooltip::for_action_in(
7261 "Toggle Code Actions",
7262 &ToggleCodeActions {
7263 deployed_from: None,
7264 quick_launch: false,
7265 },
7266 &focus_handle,
7267 cx,
7268 )
7269 }
7270 })
7271 })
7272 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7273 window.focus(&editor.focus_handle(cx), cx);
7274 editor.toggle_code_actions(
7275 &crate::actions::ToggleCodeActions {
7276 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7277 display_row,
7278 )),
7279 quick_launch: false,
7280 },
7281 window,
7282 cx,
7283 );
7284 }))
7285 .into_any_element()
7286 }
7287
7288 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7289 &self.context_menu
7290 }
7291
7292 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7293 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7294 cx.background_executor()
7295 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7296 .await;
7297
7298 let (start_buffer, start, _, end, newest_selection) = this
7299 .update(cx, |this, cx| {
7300 let newest_selection = this.selections.newest_anchor().clone();
7301 if newest_selection.head().diff_base_anchor.is_some() {
7302 return None;
7303 }
7304 let display_snapshot = this.display_snapshot(cx);
7305 let newest_selection_adjusted =
7306 this.selections.newest_adjusted(&display_snapshot);
7307 let buffer = this.buffer.read(cx);
7308
7309 let (start_buffer, start) =
7310 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7311 let (end_buffer, end) =
7312 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7313
7314 Some((start_buffer, start, end_buffer, end, newest_selection))
7315 })?
7316 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7317 .context(
7318 "Expected selection to lie in a single buffer when refreshing code actions",
7319 )?;
7320 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7321 let providers = this.code_action_providers.clone();
7322 let tasks = this
7323 .code_action_providers
7324 .iter()
7325 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7326 .collect::<Vec<_>>();
7327 (providers, tasks)
7328 })?;
7329
7330 let mut actions = Vec::new();
7331 for (provider, provider_actions) in
7332 providers.into_iter().zip(future::join_all(tasks).await)
7333 {
7334 if let Some(provider_actions) = provider_actions.log_err() {
7335 actions.extend(provider_actions.into_iter().map(|action| {
7336 AvailableCodeAction {
7337 excerpt_id: newest_selection.start.excerpt_id,
7338 action,
7339 provider: provider.clone(),
7340 }
7341 }));
7342 }
7343 }
7344
7345 this.update(cx, |this, cx| {
7346 this.available_code_actions = if actions.is_empty() {
7347 None
7348 } else {
7349 Some((
7350 Location {
7351 buffer: start_buffer,
7352 range: start..end,
7353 },
7354 actions.into(),
7355 ))
7356 };
7357 cx.notify();
7358 })
7359 }));
7360 }
7361
7362 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7363 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7364 self.show_git_blame_inline = false;
7365
7366 self.show_git_blame_inline_delay_task =
7367 Some(cx.spawn_in(window, async move |this, cx| {
7368 cx.background_executor().timer(delay).await;
7369
7370 this.update(cx, |this, cx| {
7371 this.show_git_blame_inline = true;
7372 cx.notify();
7373 })
7374 .log_err();
7375 }));
7376 }
7377 }
7378
7379 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7380 let snapshot = self.snapshot(window, cx);
7381 let cursor = self
7382 .selections
7383 .newest::<Point>(&snapshot.display_snapshot)
7384 .head();
7385 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7386 else {
7387 return;
7388 };
7389
7390 if self.blame.is_none() {
7391 self.start_git_blame(true, window, cx);
7392 }
7393 let Some(blame) = self.blame.as_ref() else {
7394 return;
7395 };
7396
7397 let row_info = RowInfo {
7398 buffer_id: Some(buffer.remote_id()),
7399 buffer_row: Some(point.row),
7400 ..Default::default()
7401 };
7402 let Some((buffer, blame_entry)) = blame
7403 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7404 .flatten()
7405 else {
7406 return;
7407 };
7408
7409 let anchor = self.selections.newest_anchor().head();
7410 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7411 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7412 self.show_blame_popover(
7413 buffer,
7414 &blame_entry,
7415 position + last_bounds.origin,
7416 true,
7417 cx,
7418 );
7419 };
7420 }
7421
7422 fn show_blame_popover(
7423 &mut self,
7424 buffer: BufferId,
7425 blame_entry: &BlameEntry,
7426 position: gpui::Point<Pixels>,
7427 ignore_timeout: bool,
7428 cx: &mut Context<Self>,
7429 ) {
7430 if let Some(state) = &mut self.inline_blame_popover {
7431 state.hide_task.take();
7432 } else {
7433 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7434 let blame_entry = blame_entry.clone();
7435 let show_task = cx.spawn(async move |editor, cx| {
7436 if !ignore_timeout {
7437 cx.background_executor()
7438 .timer(std::time::Duration::from_millis(blame_popover_delay))
7439 .await;
7440 }
7441 editor
7442 .update(cx, |editor, cx| {
7443 editor.inline_blame_popover_show_task.take();
7444 let Some(blame) = editor.blame.as_ref() else {
7445 return;
7446 };
7447 let blame = blame.read(cx);
7448 let details = blame.details_for_entry(buffer, &blame_entry);
7449 let markdown = cx.new(|cx| {
7450 Markdown::new(
7451 details
7452 .as_ref()
7453 .map(|message| message.message.clone())
7454 .unwrap_or_default(),
7455 None,
7456 None,
7457 cx,
7458 )
7459 });
7460 editor.inline_blame_popover = Some(InlineBlamePopover {
7461 position,
7462 hide_task: None,
7463 popover_bounds: None,
7464 popover_state: InlineBlamePopoverState {
7465 scroll_handle: ScrollHandle::new(),
7466 commit_message: details,
7467 markdown,
7468 },
7469 keyboard_grace: ignore_timeout,
7470 });
7471 cx.notify();
7472 })
7473 .ok();
7474 });
7475 self.inline_blame_popover_show_task = Some(show_task);
7476 }
7477 }
7478
7479 pub fn has_mouse_context_menu(&self) -> bool {
7480 self.mouse_context_menu.is_some()
7481 }
7482
7483 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7484 self.inline_blame_popover_show_task.take();
7485 if let Some(state) = &mut self.inline_blame_popover {
7486 let hide_task = cx.spawn(async move |editor, cx| {
7487 if !ignore_timeout {
7488 cx.background_executor()
7489 .timer(std::time::Duration::from_millis(100))
7490 .await;
7491 }
7492 editor
7493 .update(cx, |editor, cx| {
7494 editor.inline_blame_popover.take();
7495 cx.notify();
7496 })
7497 .ok();
7498 });
7499 state.hide_task = Some(hide_task);
7500 true
7501 } else {
7502 false
7503 }
7504 }
7505
7506 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7507 if self.pending_rename.is_some() {
7508 return None;
7509 }
7510
7511 let provider = self.semantics_provider.clone()?;
7512 let buffer = self.buffer.read(cx);
7513 let newest_selection = self.selections.newest_anchor().clone();
7514 let cursor_position = newest_selection.head();
7515 let (cursor_buffer, cursor_buffer_position) =
7516 buffer.text_anchor_for_position(cursor_position, cx)?;
7517 let (tail_buffer, tail_buffer_position) =
7518 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7519 if cursor_buffer != tail_buffer {
7520 return None;
7521 }
7522
7523 let snapshot = cursor_buffer.read(cx).snapshot();
7524 let word_ranges = cx.background_spawn(async move {
7525 // this might look odd to put on the background thread, but
7526 // `surrounding_word` can be quite expensive as it calls into
7527 // tree-sitter language scopes
7528 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7529 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7530 (start_word_range, end_word_range)
7531 });
7532
7533 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7534 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7535 let (start_word_range, end_word_range) = word_ranges.await;
7536 if start_word_range != end_word_range {
7537 this.update(cx, |this, cx| {
7538 this.document_highlights_task.take();
7539 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7540 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7541 })
7542 .ok();
7543 return;
7544 }
7545 cx.background_executor()
7546 .timer(Duration::from_millis(debounce))
7547 .await;
7548
7549 let highlights = if let Some(highlights) = cx.update(|cx| {
7550 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7551 }) {
7552 highlights.await.log_err()
7553 } else {
7554 None
7555 };
7556
7557 if let Some(highlights) = highlights {
7558 this.update(cx, |this, cx| {
7559 if this.pending_rename.is_some() {
7560 return;
7561 }
7562
7563 let buffer = this.buffer.read(cx);
7564 if buffer
7565 .text_anchor_for_position(cursor_position, cx)
7566 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7567 {
7568 return;
7569 }
7570
7571 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7572 let mut write_ranges = Vec::new();
7573 let mut read_ranges = Vec::new();
7574 for highlight in highlights {
7575 let buffer_id = cursor_buffer.read(cx).remote_id();
7576 for (excerpt_id, _, excerpt_range) in
7577 buffer.excerpts_for_buffer(buffer_id, cx)
7578 {
7579 let start = highlight
7580 .range
7581 .start
7582 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7583 let end = highlight
7584 .range
7585 .end
7586 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7587 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7588 continue;
7589 }
7590
7591 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7592 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7593 write_ranges.push(range);
7594 } else {
7595 read_ranges.push(range);
7596 }
7597 }
7598 }
7599
7600 this.highlight_background(
7601 HighlightKey::DocumentHighlightRead,
7602 &read_ranges,
7603 |_, theme| theme.colors().editor_document_highlight_read_background,
7604 cx,
7605 );
7606 this.highlight_background(
7607 HighlightKey::DocumentHighlightWrite,
7608 &write_ranges,
7609 |_, theme| theme.colors().editor_document_highlight_write_background,
7610 cx,
7611 );
7612 cx.notify();
7613 })
7614 .log_err();
7615 }
7616 }));
7617 None
7618 }
7619
7620 fn prepare_highlight_query_from_selection(
7621 &mut self,
7622 snapshot: &DisplaySnapshot,
7623 cx: &mut Context<Editor>,
7624 ) -> Option<(String, Range<Anchor>)> {
7625 if matches!(self.mode, EditorMode::SingleLine) {
7626 return None;
7627 }
7628 if !EditorSettings::get_global(cx).selection_highlight {
7629 return None;
7630 }
7631 if self.selections.count() != 1 || self.selections.line_mode() {
7632 return None;
7633 }
7634 let selection = self.selections.newest::<Point>(&snapshot);
7635 // If the selection spans multiple rows OR it is empty
7636 if selection.start.row != selection.end.row
7637 || selection.start.column == selection.end.column
7638 {
7639 return None;
7640 }
7641 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7642 let query = snapshot
7643 .buffer_snapshot()
7644 .text_for_range(selection_anchor_range.clone())
7645 .collect::<String>();
7646 if query.trim().is_empty() {
7647 return None;
7648 }
7649 Some((query, selection_anchor_range))
7650 }
7651
7652 #[ztracing::instrument(skip_all)]
7653 fn update_selection_occurrence_highlights(
7654 &mut self,
7655 multi_buffer_snapshot: MultiBufferSnapshot,
7656 query_text: String,
7657 query_range: Range<Anchor>,
7658 multi_buffer_range_to_query: Range<Point>,
7659 use_debounce: bool,
7660 window: &mut Window,
7661 cx: &mut Context<Editor>,
7662 ) -> Task<()> {
7663 cx.spawn_in(window, async move |editor, cx| {
7664 if use_debounce {
7665 cx.background_executor()
7666 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7667 .await;
7668 }
7669 let match_task = cx.background_spawn(async move {
7670 let buffer_ranges = multi_buffer_snapshot
7671 .range_to_buffer_ranges(
7672 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7673 )
7674 .into_iter()
7675 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7676 let mut match_ranges = Vec::new();
7677 let Ok(regex) = project::search::SearchQuery::text(
7678 query_text,
7679 false,
7680 false,
7681 false,
7682 Default::default(),
7683 Default::default(),
7684 false,
7685 None,
7686 ) else {
7687 return Vec::default();
7688 };
7689 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7690 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7691 match_ranges.extend(
7692 regex
7693 .search(
7694 buffer_snapshot,
7695 Some(search_range.start.0..search_range.end.0),
7696 )
7697 .await
7698 .into_iter()
7699 .filter_map(|match_range| {
7700 let match_start = buffer_snapshot
7701 .anchor_after(search_range.start + match_range.start);
7702 let match_end = buffer_snapshot
7703 .anchor_before(search_range.start + match_range.end);
7704 let match_anchor_range =
7705 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7706 (match_anchor_range != query_range).then_some(match_anchor_range)
7707 }),
7708 );
7709 }
7710 match_ranges
7711 });
7712 let match_ranges = match_task.await;
7713 editor
7714 .update_in(cx, |editor, _, cx| {
7715 if use_debounce {
7716 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7717 editor.debounced_selection_highlight_complete = true;
7718 } else if editor.debounced_selection_highlight_complete {
7719 return;
7720 }
7721 if !match_ranges.is_empty() {
7722 editor.highlight_background(
7723 HighlightKey::SelectedTextHighlight,
7724 &match_ranges,
7725 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7726 cx,
7727 )
7728 }
7729 })
7730 .log_err();
7731 })
7732 }
7733
7734 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7735 struct NewlineFold;
7736 let type_id = std::any::TypeId::of::<NewlineFold>();
7737 if !self.mode.is_single_line() {
7738 return;
7739 }
7740 let snapshot = self.snapshot(window, cx);
7741 if snapshot.buffer_snapshot().max_point().row == 0 {
7742 return;
7743 }
7744 let task = cx.background_spawn(async move {
7745 let new_newlines = snapshot
7746 .buffer_chars_at(MultiBufferOffset(0))
7747 .filter_map(|(c, i)| {
7748 if c == '\n' {
7749 Some(
7750 snapshot.buffer_snapshot().anchor_after(i)
7751 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7752 )
7753 } else {
7754 None
7755 }
7756 })
7757 .collect::<Vec<_>>();
7758 let existing_newlines = snapshot
7759 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7760 .filter_map(|fold| {
7761 if fold.placeholder.type_tag == Some(type_id) {
7762 Some(fold.range.start..fold.range.end)
7763 } else {
7764 None
7765 }
7766 })
7767 .collect::<Vec<_>>();
7768
7769 (new_newlines, existing_newlines)
7770 });
7771 self.folding_newlines = cx.spawn(async move |this, cx| {
7772 let (new_newlines, existing_newlines) = task.await;
7773 if new_newlines == existing_newlines {
7774 return;
7775 }
7776 let placeholder = FoldPlaceholder {
7777 render: Arc::new(move |_, _, cx| {
7778 div()
7779 .bg(cx.theme().status().hint_background)
7780 .border_b_1()
7781 .size_full()
7782 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7783 .border_color(cx.theme().status().hint)
7784 .child("\\n")
7785 .into_any()
7786 }),
7787 constrain_width: false,
7788 merge_adjacent: false,
7789 type_tag: Some(type_id),
7790 collapsed_text: None,
7791 };
7792 let creases = new_newlines
7793 .into_iter()
7794 .map(|range| Crease::simple(range, placeholder.clone()))
7795 .collect();
7796 this.update(cx, |this, cx| {
7797 this.display_map.update(cx, |display_map, cx| {
7798 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7799 display_map.fold(creases, cx);
7800 });
7801 })
7802 .ok();
7803 });
7804 }
7805
7806 #[ztracing::instrument(skip_all)]
7807 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7808 if !self.lsp_data_enabled() {
7809 return;
7810 }
7811 let cursor = self.selections.newest_anchor().head();
7812 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7813
7814 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7815 self.outline_symbols_at_cursor =
7816 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7817 cx.emit(EditorEvent::OutlineSymbolsChanged);
7818 cx.notify();
7819 } else {
7820 let syntax = cx.theme().syntax().clone();
7821 let background_task = cx.background_spawn(async move {
7822 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7823 });
7824 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7825 cx.spawn(async move |this, cx| {
7826 let symbols = background_task.await;
7827 this.update(cx, |this, cx| {
7828 this.outline_symbols_at_cursor = symbols;
7829 cx.emit(EditorEvent::OutlineSymbolsChanged);
7830 cx.notify();
7831 })
7832 .ok();
7833 });
7834 }
7835 }
7836
7837 #[ztracing::instrument(skip_all)]
7838 fn refresh_selected_text_highlights(
7839 &mut self,
7840 snapshot: &DisplaySnapshot,
7841 on_buffer_edit: bool,
7842 window: &mut Window,
7843 cx: &mut Context<Editor>,
7844 ) {
7845 let Some((query_text, query_range)) =
7846 self.prepare_highlight_query_from_selection(snapshot, cx)
7847 else {
7848 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7849 self.quick_selection_highlight_task.take();
7850 self.debounced_selection_highlight_task.take();
7851 self.debounced_selection_highlight_complete = false;
7852 return;
7853 };
7854 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7855 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7856 let query_changed = self
7857 .quick_selection_highlight_task
7858 .as_ref()
7859 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7860 if query_changed {
7861 self.debounced_selection_highlight_complete = false;
7862 }
7863 if on_buffer_edit || query_changed {
7864 self.quick_selection_highlight_task = Some((
7865 query_range.clone(),
7866 self.update_selection_occurrence_highlights(
7867 snapshot.buffer.clone(),
7868 query_text.clone(),
7869 query_range.clone(),
7870 self.multi_buffer_visible_range(&display_snapshot, cx),
7871 false,
7872 window,
7873 cx,
7874 ),
7875 ));
7876 }
7877 if on_buffer_edit
7878 || self
7879 .debounced_selection_highlight_task
7880 .as_ref()
7881 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7882 {
7883 let multi_buffer_start = multi_buffer_snapshot
7884 .anchor_before(MultiBufferOffset(0))
7885 .to_point(&multi_buffer_snapshot);
7886 let multi_buffer_end = multi_buffer_snapshot
7887 .anchor_after(multi_buffer_snapshot.len())
7888 .to_point(&multi_buffer_snapshot);
7889 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7890 self.debounced_selection_highlight_task = Some((
7891 query_range.clone(),
7892 self.update_selection_occurrence_highlights(
7893 snapshot.buffer.clone(),
7894 query_text,
7895 query_range,
7896 multi_buffer_full_range,
7897 true,
7898 window,
7899 cx,
7900 ),
7901 ));
7902 }
7903 }
7904
7905 pub fn multi_buffer_visible_range(
7906 &self,
7907 display_snapshot: &DisplaySnapshot,
7908 cx: &App,
7909 ) -> Range<Point> {
7910 let visible_start = self
7911 .scroll_manager
7912 .native_anchor(display_snapshot, cx)
7913 .anchor
7914 .to_point(display_snapshot.buffer_snapshot())
7915 .to_display_point(display_snapshot);
7916
7917 let mut target_end = visible_start;
7918 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7919
7920 visible_start.to_point(display_snapshot)
7921 ..display_snapshot
7922 .clip_point(target_end, Bias::Right)
7923 .to_point(display_snapshot)
7924 }
7925
7926 pub fn refresh_edit_prediction(
7927 &mut self,
7928 debounce: bool,
7929 user_requested: bool,
7930 window: &mut Window,
7931 cx: &mut Context<Self>,
7932 ) -> Option<()> {
7933 if self.leader_id.is_some() {
7934 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7935 return None;
7936 }
7937
7938 let cursor = self.selections.newest_anchor().head();
7939 let (buffer, cursor_buffer_position) =
7940 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7941
7942 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7943 return None;
7944 }
7945
7946 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7947 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7948 return None;
7949 }
7950
7951 self.update_visible_edit_prediction(window, cx);
7952
7953 if !user_requested
7954 && (!self.should_show_edit_predictions()
7955 || !self.is_focused(window)
7956 || buffer.read(cx).is_empty())
7957 {
7958 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7959 return None;
7960 }
7961
7962 self.edit_prediction_provider()?
7963 .refresh(buffer, cursor_buffer_position, debounce, cx);
7964 Some(())
7965 }
7966
7967 fn show_edit_predictions_in_menu(&self) -> bool {
7968 match self.edit_prediction_settings {
7969 EditPredictionSettings::Disabled => false,
7970 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7971 }
7972 }
7973
7974 pub fn edit_predictions_enabled(&self) -> bool {
7975 match self.edit_prediction_settings {
7976 EditPredictionSettings::Disabled => false,
7977 EditPredictionSettings::Enabled { .. } => true,
7978 }
7979 }
7980
7981 fn edit_prediction_requires_modifier(&self) -> bool {
7982 match self.edit_prediction_settings {
7983 EditPredictionSettings::Disabled => false,
7984 EditPredictionSettings::Enabled {
7985 preview_requires_modifier,
7986 ..
7987 } => preview_requires_modifier,
7988 }
7989 }
7990
7991 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7992 if self.edit_prediction_provider.is_none() {
7993 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7994 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7995 return;
7996 }
7997
7998 let selection = self.selections.newest_anchor();
7999 let cursor = selection.head();
8000
8001 if let Some((buffer, cursor_buffer_position)) =
8002 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8003 {
8004 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8005 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8006 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8007 return;
8008 }
8009 self.edit_prediction_settings =
8010 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8011 }
8012 }
8013
8014 fn edit_prediction_settings_at_position(
8015 &self,
8016 buffer: &Entity<Buffer>,
8017 buffer_position: language::Anchor,
8018 cx: &App,
8019 ) -> EditPredictionSettings {
8020 if !self.mode.is_full()
8021 || !self.show_edit_predictions_override.unwrap_or(true)
8022 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8023 {
8024 return EditPredictionSettings::Disabled;
8025 }
8026
8027 let buffer = buffer.read(cx);
8028
8029 let file = buffer.file();
8030
8031 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
8032 return EditPredictionSettings::Disabled;
8033 };
8034
8035 let by_provider = matches!(
8036 self.menu_edit_predictions_policy,
8037 MenuEditPredictionsPolicy::ByProvider
8038 );
8039
8040 let show_in_menu = by_provider
8041 && self
8042 .edit_prediction_provider
8043 .as_ref()
8044 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8045
8046 let preview_requires_modifier =
8047 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8048
8049 EditPredictionSettings::Enabled {
8050 show_in_menu,
8051 preview_requires_modifier,
8052 }
8053 }
8054
8055 fn should_show_edit_predictions(&self) -> bool {
8056 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8057 }
8058
8059 pub fn edit_prediction_preview_is_active(&self) -> bool {
8060 matches!(
8061 self.edit_prediction_preview,
8062 EditPredictionPreview::Active { .. }
8063 )
8064 }
8065
8066 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8067 let cursor = self.selections.newest_anchor().head();
8068 if let Some((buffer, cursor_position)) =
8069 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8070 {
8071 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8072 } else {
8073 false
8074 }
8075 }
8076
8077 pub fn supports_minimap(&self, cx: &App) -> bool {
8078 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8079 }
8080
8081 fn edit_predictions_enabled_in_buffer(
8082 &self,
8083 buffer: &Entity<Buffer>,
8084 buffer_position: language::Anchor,
8085 cx: &App,
8086 ) -> bool {
8087 maybe!({
8088 if self.read_only(cx) || self.leader_id.is_some() {
8089 return Some(false);
8090 }
8091 let provider = self.edit_prediction_provider()?;
8092 if !provider.is_enabled(buffer, buffer_position, cx) {
8093 return Some(false);
8094 }
8095 let buffer = buffer.read(cx);
8096 let Some(file) = buffer.file() else {
8097 return Some(true);
8098 };
8099 let settings = all_language_settings(Some(file), cx);
8100 Some(settings.edit_predictions_enabled_for_file(file, cx))
8101 })
8102 .unwrap_or(false)
8103 }
8104
8105 pub fn show_edit_prediction(
8106 &mut self,
8107 _: &ShowEditPrediction,
8108 window: &mut Window,
8109 cx: &mut Context<Self>,
8110 ) {
8111 if !self.has_active_edit_prediction() {
8112 self.refresh_edit_prediction(false, true, window, cx);
8113 return;
8114 }
8115
8116 self.update_visible_edit_prediction(window, cx);
8117 }
8118
8119 pub fn display_cursor_names(
8120 &mut self,
8121 _: &DisplayCursorNames,
8122 window: &mut Window,
8123 cx: &mut Context<Self>,
8124 ) {
8125 self.show_cursor_names(window, cx);
8126 }
8127
8128 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8129 self.show_cursor_names = true;
8130 cx.notify();
8131 cx.spawn_in(window, async move |this, cx| {
8132 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8133 this.update(cx, |this, cx| {
8134 this.show_cursor_names = false;
8135 cx.notify()
8136 })
8137 .ok()
8138 })
8139 .detach();
8140 }
8141
8142 pub fn accept_partial_edit_prediction(
8143 &mut self,
8144 granularity: EditPredictionGranularity,
8145 window: &mut Window,
8146 cx: &mut Context<Self>,
8147 ) {
8148 if self.show_edit_predictions_in_menu() {
8149 self.hide_context_menu(window, cx);
8150 }
8151
8152 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8153 return;
8154 };
8155
8156 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8157 return;
8158 }
8159
8160 match &active_edit_prediction.completion {
8161 EditPrediction::MoveWithin { target, .. } => {
8162 let target = *target;
8163
8164 if matches!(granularity, EditPredictionGranularity::Full) {
8165 if let Some(position_map) = &self.last_position_map {
8166 let target_row = target.to_display_point(&position_map.snapshot).row();
8167 let is_visible = position_map.visible_row_range.contains(&target_row);
8168
8169 if is_visible || !self.edit_prediction_requires_modifier() {
8170 self.unfold_ranges(&[target..target], true, false, cx);
8171 self.change_selections(
8172 SelectionEffects::scroll(Autoscroll::newest()),
8173 window,
8174 cx,
8175 |selections| {
8176 selections.select_anchor_ranges([target..target]);
8177 },
8178 );
8179 self.clear_row_highlights::<EditPredictionPreview>();
8180 self.edit_prediction_preview
8181 .set_previous_scroll_position(None);
8182 } else {
8183 // Highlight and request scroll
8184 self.edit_prediction_preview
8185 .set_previous_scroll_position(Some(
8186 position_map.snapshot.scroll_anchor,
8187 ));
8188 self.highlight_rows::<EditPredictionPreview>(
8189 target..target,
8190 cx.theme().colors().editor_highlighted_line_background,
8191 RowHighlightOptions {
8192 autoscroll: true,
8193 ..Default::default()
8194 },
8195 cx,
8196 );
8197 self.request_autoscroll(Autoscroll::fit(), cx);
8198 }
8199 }
8200 } else {
8201 self.change_selections(
8202 SelectionEffects::scroll(Autoscroll::newest()),
8203 window,
8204 cx,
8205 |selections| {
8206 selections.select_anchor_ranges([target..target]);
8207 },
8208 );
8209 }
8210 }
8211 EditPrediction::MoveOutside { snapshot, target } => {
8212 if let Some(workspace) = self.workspace() {
8213 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8214 .detach_and_log_err(cx);
8215 }
8216 }
8217 EditPrediction::Edit {
8218 edits,
8219 cursor_position,
8220 ..
8221 } => {
8222 self.report_edit_prediction_event(
8223 active_edit_prediction.completion_id.clone(),
8224 true,
8225 cx,
8226 );
8227
8228 match granularity {
8229 EditPredictionGranularity::Full => {
8230 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8231
8232 // Compute fallback cursor position BEFORE applying the edit,
8233 // so the anchor tracks through the edit correctly
8234 let fallback_cursor_target = {
8235 let snapshot = self.buffer.read(cx).snapshot(cx);
8236 edits.last().unwrap().0.end.bias_right(&snapshot)
8237 };
8238
8239 self.buffer.update(cx, |buffer, cx| {
8240 buffer.edit(edits.iter().cloned(), None, cx)
8241 });
8242
8243 if let Some(provider) = self.edit_prediction_provider() {
8244 provider.accept(cx);
8245 }
8246
8247 // Resolve cursor position after the edit is applied
8248 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8249 // The anchor tracks through the edit, then we add the offset
8250 let snapshot = self.buffer.read(cx).snapshot(cx);
8251 let base_offset = anchor.to_offset(&snapshot).0;
8252 let target_offset =
8253 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8254 snapshot.anchor_after(target_offset)
8255 } else {
8256 fallback_cursor_target
8257 };
8258
8259 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8260 s.select_anchor_ranges([cursor_target..cursor_target]);
8261 });
8262
8263 let selections = self.selections.disjoint_anchors_arc();
8264 if let Some(transaction_id_now) =
8265 self.buffer.read(cx).last_transaction_id(cx)
8266 {
8267 if transaction_id_prev != Some(transaction_id_now) {
8268 self.selection_history
8269 .insert_transaction(transaction_id_now, selections);
8270 }
8271 }
8272
8273 self.update_visible_edit_prediction(window, cx);
8274 if self.active_edit_prediction.is_none() {
8275 self.refresh_edit_prediction(true, true, window, cx);
8276 }
8277 cx.notify();
8278 }
8279 _ => {
8280 let snapshot = self.buffer.read(cx).snapshot(cx);
8281 let cursor_offset = self
8282 .selections
8283 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8284 .head();
8285
8286 let insertion = edits.iter().find_map(|(range, text)| {
8287 let range = range.to_offset(&snapshot);
8288 if range.is_empty() && range.start == cursor_offset {
8289 Some(text)
8290 } else {
8291 None
8292 }
8293 });
8294
8295 if let Some(text) = insertion {
8296 let text_to_insert = match granularity {
8297 EditPredictionGranularity::Word => {
8298 let mut partial = text
8299 .chars()
8300 .by_ref()
8301 .take_while(|c| c.is_alphabetic())
8302 .collect::<String>();
8303 if partial.is_empty() {
8304 partial = text
8305 .chars()
8306 .by_ref()
8307 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8308 .collect::<String>();
8309 }
8310 partial
8311 }
8312 EditPredictionGranularity::Line => {
8313 if let Some(line) = text.split_inclusive('\n').next() {
8314 line.to_string()
8315 } else {
8316 text.to_string()
8317 }
8318 }
8319 EditPredictionGranularity::Full => unreachable!(),
8320 };
8321
8322 cx.emit(EditorEvent::InputHandled {
8323 utf16_range_to_replace: None,
8324 text: text_to_insert.clone().into(),
8325 });
8326
8327 self.replace_selections(&text_to_insert, None, window, cx, false);
8328 self.refresh_edit_prediction(true, true, window, cx);
8329 cx.notify();
8330 } else {
8331 self.accept_partial_edit_prediction(
8332 EditPredictionGranularity::Full,
8333 window,
8334 cx,
8335 );
8336 }
8337 }
8338 }
8339 }
8340 }
8341 }
8342
8343 pub fn accept_next_word_edit_prediction(
8344 &mut self,
8345 _: &AcceptNextWordEditPrediction,
8346 window: &mut Window,
8347 cx: &mut Context<Self>,
8348 ) {
8349 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8350 }
8351
8352 pub fn accept_next_line_edit_prediction(
8353 &mut self,
8354 _: &AcceptNextLineEditPrediction,
8355 window: &mut Window,
8356 cx: &mut Context<Self>,
8357 ) {
8358 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8359 }
8360
8361 pub fn accept_edit_prediction(
8362 &mut self,
8363 _: &AcceptEditPrediction,
8364 window: &mut Window,
8365 cx: &mut Context<Self>,
8366 ) {
8367 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8368 }
8369
8370 fn discard_edit_prediction(
8371 &mut self,
8372 reason: EditPredictionDiscardReason,
8373 cx: &mut Context<Self>,
8374 ) -> bool {
8375 if reason == EditPredictionDiscardReason::Rejected {
8376 let completion_id = self
8377 .active_edit_prediction
8378 .as_ref()
8379 .and_then(|active_completion| active_completion.completion_id.clone());
8380
8381 self.report_edit_prediction_event(completion_id, false, cx);
8382 }
8383
8384 if let Some(provider) = self.edit_prediction_provider() {
8385 provider.discard(reason, cx);
8386 }
8387
8388 self.take_active_edit_prediction(cx)
8389 }
8390
8391 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8392 let Some(provider) = self.edit_prediction_provider() else {
8393 return;
8394 };
8395
8396 let Some((_, buffer, _)) = self
8397 .buffer
8398 .read(cx)
8399 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8400 else {
8401 return;
8402 };
8403
8404 let extension = buffer
8405 .read(cx)
8406 .file()
8407 .and_then(|file| Some(file.path().extension()?.to_string()));
8408
8409 let event_type = match accepted {
8410 true => "Edit Prediction Accepted",
8411 false => "Edit Prediction Discarded",
8412 };
8413 telemetry::event!(
8414 event_type,
8415 provider = provider.name(),
8416 prediction_id = id,
8417 suggestion_accepted = accepted,
8418 file_extension = extension,
8419 );
8420 }
8421
8422 fn open_editor_at_anchor(
8423 snapshot: &language::BufferSnapshot,
8424 target: language::Anchor,
8425 workspace: &Entity<Workspace>,
8426 window: &mut Window,
8427 cx: &mut App,
8428 ) -> Task<Result<()>> {
8429 workspace.update(cx, |workspace, cx| {
8430 let path = snapshot.file().map(|file| file.full_path(cx));
8431 let Some(path) =
8432 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8433 else {
8434 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8435 };
8436 let target = text::ToPoint::to_point(&target, snapshot);
8437 let item = workspace.open_path(path, None, true, window, cx);
8438 window.spawn(cx, async move |cx| {
8439 let Some(editor) = item.await?.downcast::<Editor>() else {
8440 return Ok(());
8441 };
8442 editor
8443 .update_in(cx, |editor, window, cx| {
8444 editor.go_to_singleton_buffer_point(target, window, cx);
8445 })
8446 .ok();
8447 anyhow::Ok(())
8448 })
8449 })
8450 }
8451
8452 pub fn has_active_edit_prediction(&self) -> bool {
8453 self.active_edit_prediction.is_some()
8454 }
8455
8456 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8457 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8458 return false;
8459 };
8460
8461 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8462 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8463 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8464 true
8465 }
8466
8467 /// Returns true when we're displaying the edit prediction popover below the cursor
8468 /// like we are not previewing and the LSP autocomplete menu is visible
8469 /// or we are in `when_holding_modifier` mode.
8470 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8471 if self.edit_prediction_preview_is_active()
8472 || !self.show_edit_predictions_in_menu()
8473 || !self.edit_predictions_enabled()
8474 {
8475 return false;
8476 }
8477
8478 if self.has_visible_completions_menu() {
8479 return true;
8480 }
8481
8482 has_completion && self.edit_prediction_requires_modifier()
8483 }
8484
8485 fn handle_modifiers_changed(
8486 &mut self,
8487 modifiers: Modifiers,
8488 position_map: &PositionMap,
8489 window: &mut Window,
8490 cx: &mut Context<Self>,
8491 ) {
8492 // Ensure that the edit prediction preview is updated, even when not
8493 // enabled, if there's an active edit prediction preview.
8494 if self.show_edit_predictions_in_menu()
8495 || matches!(
8496 self.edit_prediction_preview,
8497 EditPredictionPreview::Active { .. }
8498 )
8499 {
8500 self.update_edit_prediction_preview(&modifiers, window, cx);
8501 }
8502
8503 self.update_selection_mode(&modifiers, position_map, window, cx);
8504
8505 let mouse_position = window.mouse_position();
8506 if !position_map.text_hitbox.is_hovered(window) {
8507 return;
8508 }
8509
8510 self.update_hovered_link(
8511 position_map.point_for_position(mouse_position),
8512 Some(mouse_position),
8513 &position_map.snapshot,
8514 modifiers,
8515 window,
8516 cx,
8517 )
8518 }
8519
8520 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8521 match EditorSettings::get_global(cx).multi_cursor_modifier {
8522 MultiCursorModifier::Alt => modifiers.secondary(),
8523 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8524 }
8525 }
8526
8527 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8528 match EditorSettings::get_global(cx).multi_cursor_modifier {
8529 MultiCursorModifier::Alt => modifiers.alt,
8530 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8531 }
8532 }
8533
8534 fn columnar_selection_mode(
8535 modifiers: &Modifiers,
8536 cx: &mut Context<Self>,
8537 ) -> Option<ColumnarMode> {
8538 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8539 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8540 Some(ColumnarMode::FromMouse)
8541 } else if Self::is_alt_pressed(modifiers, cx) {
8542 Some(ColumnarMode::FromSelection)
8543 } else {
8544 None
8545 }
8546 } else {
8547 None
8548 }
8549 }
8550
8551 fn update_selection_mode(
8552 &mut self,
8553 modifiers: &Modifiers,
8554 position_map: &PositionMap,
8555 window: &mut Window,
8556 cx: &mut Context<Self>,
8557 ) {
8558 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8559 return;
8560 };
8561 if self.selections.pending_anchor().is_none() {
8562 return;
8563 }
8564
8565 let mouse_position = window.mouse_position();
8566 let point_for_position = position_map.point_for_position(mouse_position);
8567 let position = point_for_position.previous_valid;
8568
8569 self.select(
8570 SelectPhase::BeginColumnar {
8571 position,
8572 reset: false,
8573 mode,
8574 goal_column: point_for_position.exact_unclipped.column(),
8575 },
8576 window,
8577 cx,
8578 );
8579 }
8580
8581 fn update_edit_prediction_preview(
8582 &mut self,
8583 modifiers: &Modifiers,
8584 window: &mut Window,
8585 cx: &mut Context<Self>,
8586 ) {
8587 let mut modifiers_held = false;
8588
8589 let key_context = self.key_context_internal(self.has_active_edit_prediction(), window, cx);
8590 let actions: [&dyn Action; 3] = [
8591 &AcceptEditPrediction,
8592 &AcceptNextWordEditPrediction,
8593 &AcceptNextLineEditPrediction,
8594 ];
8595
8596 for action in actions {
8597 let bindings = window.bindings_for_action_in_context(action, key_context.clone());
8598 for binding in bindings {
8599 if let Some(keystroke) = binding.keystrokes().first() {
8600 modifiers_held = modifiers_held
8601 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8602 }
8603 }
8604 }
8605
8606 if modifiers_held {
8607 if matches!(
8608 self.edit_prediction_preview,
8609 EditPredictionPreview::Inactive { .. }
8610 ) {
8611 self.edit_prediction_preview = EditPredictionPreview::Active {
8612 previous_scroll_position: None,
8613 since: Instant::now(),
8614 };
8615
8616 self.update_visible_edit_prediction(window, cx);
8617 cx.notify();
8618 }
8619 } else if let EditPredictionPreview::Active {
8620 previous_scroll_position,
8621 since,
8622 } = self.edit_prediction_preview
8623 {
8624 if let (Some(previous_scroll_position), Some(position_map)) =
8625 (previous_scroll_position, self.last_position_map.as_ref())
8626 {
8627 self.set_scroll_position(
8628 previous_scroll_position
8629 .scroll_position(&position_map.snapshot.display_snapshot),
8630 window,
8631 cx,
8632 );
8633 }
8634
8635 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8636 released_too_fast: since.elapsed() < Duration::from_millis(200),
8637 };
8638 self.clear_row_highlights::<EditPredictionPreview>();
8639 self.update_visible_edit_prediction(window, cx);
8640 cx.notify();
8641 }
8642 }
8643
8644 fn update_visible_edit_prediction(
8645 &mut self,
8646 _window: &mut Window,
8647 cx: &mut Context<Self>,
8648 ) -> Option<()> {
8649 if self.ime_transaction.is_some() {
8650 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8651 return None;
8652 }
8653
8654 let selection = self.selections.newest_anchor();
8655 let cursor = selection.head();
8656 let multibuffer = self.buffer.read(cx).snapshot(cx);
8657
8658 // Check project-level disable_ai setting for the current buffer
8659 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8660 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8661 return None;
8662 }
8663 }
8664 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8665 let excerpt_id = cursor.excerpt_id;
8666
8667 let show_in_menu = self.show_edit_predictions_in_menu();
8668 let completions_menu_has_precedence = !show_in_menu
8669 && (self.context_menu.borrow().is_some()
8670 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8671
8672 if completions_menu_has_precedence
8673 || !offset_selection.is_empty()
8674 || self
8675 .active_edit_prediction
8676 .as_ref()
8677 .is_some_and(|completion| {
8678 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8679 return false;
8680 };
8681 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8682 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8683 !invalidation_range.contains(&offset_selection.head())
8684 })
8685 {
8686 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8687 return None;
8688 }
8689
8690 self.take_active_edit_prediction(cx);
8691 let Some(provider) = self.edit_prediction_provider() else {
8692 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8693 return None;
8694 };
8695
8696 let (buffer, cursor_buffer_position) =
8697 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8698
8699 self.edit_prediction_settings =
8700 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8701
8702 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8703
8704 if self.in_leading_whitespace {
8705 let cursor_point = cursor.to_point(&multibuffer);
8706 let mut suggested_indent = None;
8707 multibuffer.suggested_indents_callback(
8708 cursor_point.row..cursor_point.row + 1,
8709 &mut |_, indent| {
8710 suggested_indent = Some(indent);
8711 ControlFlow::Break(())
8712 },
8713 cx,
8714 );
8715
8716 if let Some(indent) = suggested_indent
8717 && indent.len == cursor_point.column
8718 {
8719 self.in_leading_whitespace = false;
8720 }
8721 }
8722
8723 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8724
8725 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8726 {
8727 edit_prediction_types::EditPrediction::Local {
8728 id,
8729 edits,
8730 cursor_position,
8731 edit_preview,
8732 } => (id, edits, cursor_position, edit_preview),
8733 edit_prediction_types::EditPrediction::Jump {
8734 id,
8735 snapshot,
8736 target,
8737 } => {
8738 if let Some(provider) = &self.edit_prediction_provider {
8739 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8740 }
8741 self.stale_edit_prediction_in_menu = None;
8742 self.active_edit_prediction = Some(EditPredictionState {
8743 inlay_ids: vec![],
8744 completion: EditPrediction::MoveOutside { snapshot, target },
8745 completion_id: id,
8746 invalidation_range: None,
8747 });
8748 cx.notify();
8749 return Some(());
8750 }
8751 };
8752
8753 let edits = edits
8754 .into_iter()
8755 .flat_map(|(range, new_text)| {
8756 Some((
8757 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8758 new_text,
8759 ))
8760 })
8761 .collect::<Vec<_>>();
8762 if edits.is_empty() {
8763 return None;
8764 }
8765
8766 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8767 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8768 Some((anchor, predicted.offset))
8769 });
8770
8771 let first_edit_start = edits.first().unwrap().0.start;
8772 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8773 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8774
8775 let last_edit_end = edits.last().unwrap().0.end;
8776 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8777 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8778
8779 let cursor_row = cursor.to_point(&multibuffer).row;
8780
8781 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8782
8783 let mut inlay_ids = Vec::new();
8784 let invalidation_row_range;
8785 let move_invalidation_row_range = if cursor_row < edit_start_row {
8786 Some(cursor_row..edit_end_row)
8787 } else if cursor_row > edit_end_row {
8788 Some(edit_start_row..cursor_row)
8789 } else {
8790 None
8791 };
8792 let supports_jump = self
8793 .edit_prediction_provider
8794 .as_ref()
8795 .map(|provider| provider.provider.supports_jump_to_edit())
8796 .unwrap_or(true);
8797
8798 let is_move = supports_jump
8799 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8800 let completion = if is_move {
8801 if let Some(provider) = &self.edit_prediction_provider {
8802 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8803 }
8804 invalidation_row_range =
8805 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8806 let target = first_edit_start;
8807 EditPrediction::MoveWithin { target, snapshot }
8808 } else {
8809 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8810 && !self.edit_predictions_hidden_for_vim_mode;
8811
8812 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8813 if provider.show_tab_accept_marker() {
8814 EditDisplayMode::TabAccept
8815 } else {
8816 EditDisplayMode::Inline
8817 }
8818 } else {
8819 EditDisplayMode::DiffPopover
8820 };
8821
8822 if show_completions_in_buffer {
8823 if let Some(provider) = &self.edit_prediction_provider {
8824 let suggestion_display_type = match display_mode {
8825 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8826 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8827 SuggestionDisplayType::GhostText
8828 }
8829 };
8830 provider.provider.did_show(suggestion_display_type, cx);
8831 }
8832 if edits
8833 .iter()
8834 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8835 {
8836 let mut inlays = Vec::new();
8837 for (range, new_text) in &edits {
8838 let inlay = Inlay::edit_prediction(
8839 post_inc(&mut self.next_inlay_id),
8840 range.start,
8841 new_text.as_ref(),
8842 );
8843 inlay_ids.push(inlay.id);
8844 inlays.push(inlay);
8845 }
8846
8847 self.splice_inlays(&[], inlays, cx);
8848 } else {
8849 let background_color = cx.theme().status().deleted_background;
8850 self.highlight_text(
8851 HighlightKey::EditPredictionHighlight,
8852 edits.iter().map(|(range, _)| range.clone()).collect(),
8853 HighlightStyle {
8854 background_color: Some(background_color),
8855 ..Default::default()
8856 },
8857 cx,
8858 );
8859 }
8860 }
8861
8862 invalidation_row_range = edit_start_row..edit_end_row;
8863
8864 EditPrediction::Edit {
8865 edits,
8866 cursor_position,
8867 edit_preview,
8868 display_mode,
8869 snapshot,
8870 }
8871 };
8872
8873 let invalidation_range = multibuffer
8874 .anchor_before(Point::new(invalidation_row_range.start, 0))
8875 ..multibuffer.anchor_after(Point::new(
8876 invalidation_row_range.end,
8877 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8878 ));
8879
8880 self.stale_edit_prediction_in_menu = None;
8881 self.active_edit_prediction = Some(EditPredictionState {
8882 inlay_ids,
8883 completion,
8884 completion_id,
8885 invalidation_range: Some(invalidation_range),
8886 });
8887
8888 cx.notify();
8889
8890 Some(())
8891 }
8892
8893 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8894 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8895 }
8896
8897 /// Get all display points of breakpoints that will be rendered within editor
8898 ///
8899 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8900 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8901 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8902 fn active_breakpoints(
8903 &self,
8904 range: Range<DisplayRow>,
8905 window: &mut Window,
8906 cx: &mut Context<Self>,
8907 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8908 let mut breakpoint_display_points = HashMap::default();
8909
8910 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8911 return breakpoint_display_points;
8912 };
8913
8914 let snapshot = self.snapshot(window, cx);
8915
8916 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8917 let Some(project) = self.project() else {
8918 return breakpoint_display_points;
8919 };
8920
8921 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8922 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8923
8924 for (buffer_snapshot, range, excerpt_id) in
8925 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8926 {
8927 let Some(buffer) = project
8928 .read(cx)
8929 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8930 else {
8931 continue;
8932 };
8933 let breakpoints = breakpoint_store.read(cx).breakpoints(
8934 &buffer,
8935 Some(
8936 buffer_snapshot.anchor_before(range.start)
8937 ..buffer_snapshot.anchor_after(range.end),
8938 ),
8939 buffer_snapshot,
8940 cx,
8941 );
8942 for (breakpoint, state) in breakpoints {
8943 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8944 let position = multi_buffer_anchor
8945 .to_point(&multi_buffer_snapshot)
8946 .to_display_point(&snapshot);
8947
8948 breakpoint_display_points.insert(
8949 position.row(),
8950 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8951 );
8952 }
8953 }
8954
8955 breakpoint_display_points
8956 }
8957
8958 fn breakpoint_context_menu(
8959 &self,
8960 anchor: Anchor,
8961 window: &mut Window,
8962 cx: &mut Context<Self>,
8963 ) -> Entity<ui::ContextMenu> {
8964 let weak_editor = cx.weak_entity();
8965 let focus_handle = self.focus_handle(cx);
8966
8967 let row = self
8968 .buffer
8969 .read(cx)
8970 .snapshot(cx)
8971 .summary_for_anchor::<Point>(&anchor)
8972 .row;
8973
8974 let breakpoint = self
8975 .breakpoint_at_row(row, window, cx)
8976 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8977
8978 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8979 "Edit Log Breakpoint"
8980 } else {
8981 "Set Log Breakpoint"
8982 };
8983
8984 let condition_breakpoint_msg = if breakpoint
8985 .as_ref()
8986 .is_some_and(|bp| bp.1.condition.is_some())
8987 {
8988 "Edit Condition Breakpoint"
8989 } else {
8990 "Set Condition Breakpoint"
8991 };
8992
8993 let hit_condition_breakpoint_msg = if breakpoint
8994 .as_ref()
8995 .is_some_and(|bp| bp.1.hit_condition.is_some())
8996 {
8997 "Edit Hit Condition Breakpoint"
8998 } else {
8999 "Set Hit Condition Breakpoint"
9000 };
9001
9002 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9003 "Unset Breakpoint"
9004 } else {
9005 "Set Breakpoint"
9006 };
9007
9008 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9009
9010 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9011 BreakpointState::Enabled => Some("Disable"),
9012 BreakpointState::Disabled => Some("Enable"),
9013 });
9014
9015 let (anchor, breakpoint) =
9016 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9017
9018 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9019 menu.on_blur_subscription(Subscription::new(|| {}))
9020 .context(focus_handle)
9021 .when(run_to_cursor, |this| {
9022 let weak_editor = weak_editor.clone();
9023 this.entry("Run to Cursor", None, move |window, cx| {
9024 weak_editor
9025 .update(cx, |editor, cx| {
9026 editor.change_selections(
9027 SelectionEffects::no_scroll(),
9028 window,
9029 cx,
9030 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9031 );
9032 })
9033 .ok();
9034
9035 window.dispatch_action(Box::new(RunToCursor), cx);
9036 })
9037 .separator()
9038 })
9039 .when_some(toggle_state_msg, |this, msg| {
9040 this.entry(msg, None, {
9041 let weak_editor = weak_editor.clone();
9042 let breakpoint = breakpoint.clone();
9043 move |_window, cx| {
9044 weak_editor
9045 .update(cx, |this, cx| {
9046 this.edit_breakpoint_at_anchor(
9047 anchor,
9048 breakpoint.as_ref().clone(),
9049 BreakpointEditAction::InvertState,
9050 cx,
9051 );
9052 })
9053 .log_err();
9054 }
9055 })
9056 })
9057 .entry(set_breakpoint_msg, None, {
9058 let weak_editor = weak_editor.clone();
9059 let breakpoint = breakpoint.clone();
9060 move |_window, cx| {
9061 weak_editor
9062 .update(cx, |this, cx| {
9063 this.edit_breakpoint_at_anchor(
9064 anchor,
9065 breakpoint.as_ref().clone(),
9066 BreakpointEditAction::Toggle,
9067 cx,
9068 );
9069 })
9070 .log_err();
9071 }
9072 })
9073 .entry(log_breakpoint_msg, None, {
9074 let breakpoint = breakpoint.clone();
9075 let weak_editor = weak_editor.clone();
9076 move |window, cx| {
9077 weak_editor
9078 .update(cx, |this, cx| {
9079 this.add_edit_breakpoint_block(
9080 anchor,
9081 breakpoint.as_ref(),
9082 BreakpointPromptEditAction::Log,
9083 window,
9084 cx,
9085 );
9086 })
9087 .log_err();
9088 }
9089 })
9090 .entry(condition_breakpoint_msg, None, {
9091 let breakpoint = breakpoint.clone();
9092 let weak_editor = weak_editor.clone();
9093 move |window, cx| {
9094 weak_editor
9095 .update(cx, |this, cx| {
9096 this.add_edit_breakpoint_block(
9097 anchor,
9098 breakpoint.as_ref(),
9099 BreakpointPromptEditAction::Condition,
9100 window,
9101 cx,
9102 );
9103 })
9104 .log_err();
9105 }
9106 })
9107 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9108 weak_editor
9109 .update(cx, |this, cx| {
9110 this.add_edit_breakpoint_block(
9111 anchor,
9112 breakpoint.as_ref(),
9113 BreakpointPromptEditAction::HitCondition,
9114 window,
9115 cx,
9116 );
9117 })
9118 .log_err();
9119 })
9120 })
9121 }
9122
9123 fn render_breakpoint(
9124 &self,
9125 position: Anchor,
9126 row: DisplayRow,
9127 breakpoint: &Breakpoint,
9128 state: Option<BreakpointSessionState>,
9129 cx: &mut Context<Self>,
9130 ) -> IconButton {
9131 let is_rejected = state.is_some_and(|s| !s.verified);
9132 // Is it a breakpoint that shows up when hovering over gutter?
9133 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9134 (false, false),
9135 |PhantomBreakpointIndicator {
9136 is_active,
9137 display_row,
9138 collides_with_existing_breakpoint,
9139 }| {
9140 (
9141 is_active && display_row == row,
9142 collides_with_existing_breakpoint,
9143 )
9144 },
9145 );
9146
9147 let (color, icon) = {
9148 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9149 (false, false) => ui::IconName::DebugBreakpoint,
9150 (true, false) => ui::IconName::DebugLogBreakpoint,
9151 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9152 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9153 };
9154
9155 let theme_colors = cx.theme().colors();
9156
9157 let color = if is_phantom {
9158 if collides_with_existing {
9159 Color::Custom(
9160 theme_colors
9161 .debugger_accent
9162 .blend(theme_colors.text.opacity(0.6)),
9163 )
9164 } else {
9165 Color::Hint
9166 }
9167 } else if is_rejected {
9168 Color::Disabled
9169 } else {
9170 Color::Debugger
9171 };
9172
9173 (color, icon)
9174 };
9175
9176 let breakpoint = Arc::from(breakpoint.clone());
9177
9178 let alt_as_text = gpui::Keystroke {
9179 modifiers: Modifiers::secondary_key(),
9180 ..Default::default()
9181 };
9182 let primary_action_text = if breakpoint.is_disabled() {
9183 "Enable breakpoint"
9184 } else if is_phantom && !collides_with_existing {
9185 "Set breakpoint"
9186 } else {
9187 "Unset breakpoint"
9188 };
9189 let focus_handle = self.focus_handle.clone();
9190
9191 let meta = if is_rejected {
9192 SharedString::from("No executable code is associated with this line.")
9193 } else if collides_with_existing && !breakpoint.is_disabled() {
9194 SharedString::from(format!(
9195 "{alt_as_text}-click to disable,\nright-click for more options."
9196 ))
9197 } else {
9198 SharedString::from("Right-click for more options.")
9199 };
9200 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9201 .icon_size(IconSize::XSmall)
9202 .size(ui::ButtonSize::None)
9203 .when(is_rejected, |this| {
9204 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9205 })
9206 .icon_color(color)
9207 .style(ButtonStyle::Transparent)
9208 .on_click(cx.listener({
9209 move |editor, event: &ClickEvent, window, cx| {
9210 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9211 BreakpointEditAction::InvertState
9212 } else {
9213 BreakpointEditAction::Toggle
9214 };
9215
9216 window.focus(&editor.focus_handle(cx), cx);
9217 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9218 editor.edit_breakpoint_at_anchor(
9219 position,
9220 breakpoint.as_ref().clone(),
9221 edit_action,
9222 cx,
9223 );
9224 }
9225 }))
9226 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9227 editor.set_breakpoint_context_menu(
9228 row,
9229 Some(position),
9230 event.position(),
9231 window,
9232 cx,
9233 );
9234 }))
9235 .tooltip(move |_window, cx| {
9236 Tooltip::with_meta_in(
9237 primary_action_text,
9238 Some(&ToggleBreakpoint),
9239 meta.clone(),
9240 &focus_handle,
9241 cx,
9242 )
9243 })
9244 }
9245
9246 fn build_tasks_context(
9247 project: &Entity<Project>,
9248 buffer: &Entity<Buffer>,
9249 buffer_row: u32,
9250 tasks: &Arc<RunnableTasks>,
9251 cx: &mut Context<Self>,
9252 ) -> Task<Option<task::TaskContext>> {
9253 let position = Point::new(buffer_row, tasks.column);
9254 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9255 let location = Location {
9256 buffer: buffer.clone(),
9257 range: range_start..range_start,
9258 };
9259 // Fill in the environmental variables from the tree-sitter captures
9260 let mut captured_task_variables = TaskVariables::default();
9261 for (capture_name, value) in tasks.extra_variables.clone() {
9262 captured_task_variables.insert(
9263 task::VariableName::Custom(capture_name.into()),
9264 value.clone(),
9265 );
9266 }
9267 project.update(cx, |project, cx| {
9268 project.task_store().update(cx, |task_store, cx| {
9269 task_store.task_context_for_location(captured_task_variables, location, cx)
9270 })
9271 })
9272 }
9273
9274 pub fn context_menu_visible(&self) -> bool {
9275 !self.edit_prediction_preview_is_active()
9276 && self
9277 .context_menu
9278 .borrow()
9279 .as_ref()
9280 .is_some_and(|menu| menu.visible())
9281 }
9282
9283 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9284 self.context_menu
9285 .borrow()
9286 .as_ref()
9287 .map(|menu| menu.origin())
9288 }
9289
9290 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9291 self.context_menu_options = Some(options);
9292 }
9293
9294 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9295 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9296
9297 fn render_edit_prediction_popover(
9298 &mut self,
9299 text_bounds: &Bounds<Pixels>,
9300 content_origin: gpui::Point<Pixels>,
9301 right_margin: Pixels,
9302 editor_snapshot: &EditorSnapshot,
9303 visible_row_range: Range<DisplayRow>,
9304 scroll_top: ScrollOffset,
9305 scroll_bottom: ScrollOffset,
9306 line_layouts: &[LineWithInvisibles],
9307 line_height: Pixels,
9308 scroll_position: gpui::Point<ScrollOffset>,
9309 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9310 newest_selection_head: Option<DisplayPoint>,
9311 editor_width: Pixels,
9312 style: &EditorStyle,
9313 window: &mut Window,
9314 cx: &mut App,
9315 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9316 if self.mode().is_minimap() {
9317 return None;
9318 }
9319 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9320
9321 if self.edit_prediction_visible_in_cursor_popover(true) {
9322 return None;
9323 }
9324
9325 match &active_edit_prediction.completion {
9326 EditPrediction::MoveWithin { target, .. } => {
9327 let target_display_point = target.to_display_point(editor_snapshot);
9328
9329 if self.edit_prediction_requires_modifier() {
9330 if !self.edit_prediction_preview_is_active() {
9331 return None;
9332 }
9333
9334 self.render_edit_prediction_modifier_jump_popover(
9335 text_bounds,
9336 content_origin,
9337 visible_row_range,
9338 line_layouts,
9339 line_height,
9340 scroll_pixel_position,
9341 newest_selection_head,
9342 target_display_point,
9343 window,
9344 cx,
9345 )
9346 } else {
9347 self.render_edit_prediction_eager_jump_popover(
9348 text_bounds,
9349 content_origin,
9350 editor_snapshot,
9351 visible_row_range,
9352 scroll_top,
9353 scroll_bottom,
9354 line_height,
9355 scroll_pixel_position,
9356 target_display_point,
9357 editor_width,
9358 window,
9359 cx,
9360 )
9361 }
9362 }
9363 EditPrediction::Edit {
9364 display_mode: EditDisplayMode::Inline,
9365 ..
9366 } => None,
9367 EditPrediction::Edit {
9368 display_mode: EditDisplayMode::TabAccept,
9369 edits,
9370 ..
9371 } => {
9372 let range = &edits.first()?.0;
9373 let target_display_point = range.end.to_display_point(editor_snapshot);
9374
9375 self.render_edit_prediction_end_of_line_popover(
9376 "Accept",
9377 editor_snapshot,
9378 visible_row_range,
9379 target_display_point,
9380 line_height,
9381 scroll_pixel_position,
9382 content_origin,
9383 editor_width,
9384 window,
9385 cx,
9386 )
9387 }
9388 EditPrediction::Edit {
9389 edits,
9390 edit_preview,
9391 display_mode: EditDisplayMode::DiffPopover,
9392 snapshot,
9393 ..
9394 } => self.render_edit_prediction_diff_popover(
9395 text_bounds,
9396 content_origin,
9397 right_margin,
9398 editor_snapshot,
9399 visible_row_range,
9400 line_layouts,
9401 line_height,
9402 scroll_position,
9403 scroll_pixel_position,
9404 newest_selection_head,
9405 editor_width,
9406 style,
9407 edits,
9408 edit_preview,
9409 snapshot,
9410 window,
9411 cx,
9412 ),
9413 EditPrediction::MoveOutside { snapshot, .. } => {
9414 let mut element = self
9415 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9416 .into_any();
9417
9418 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9419 let origin_x = text_bounds.size.width - size.width - px(30.);
9420 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9421 element.prepaint_at(origin, window, cx);
9422
9423 Some((element, origin))
9424 }
9425 }
9426 }
9427
9428 fn render_edit_prediction_modifier_jump_popover(
9429 &mut self,
9430 text_bounds: &Bounds<Pixels>,
9431 content_origin: gpui::Point<Pixels>,
9432 visible_row_range: Range<DisplayRow>,
9433 line_layouts: &[LineWithInvisibles],
9434 line_height: Pixels,
9435 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9436 newest_selection_head: Option<DisplayPoint>,
9437 target_display_point: DisplayPoint,
9438 window: &mut Window,
9439 cx: &mut App,
9440 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9441 let scrolled_content_origin =
9442 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9443
9444 const SCROLL_PADDING_Y: Pixels = px(12.);
9445
9446 if target_display_point.row() < visible_row_range.start {
9447 return self.render_edit_prediction_scroll_popover(
9448 &|_| SCROLL_PADDING_Y,
9449 IconName::ArrowUp,
9450 visible_row_range,
9451 line_layouts,
9452 newest_selection_head,
9453 scrolled_content_origin,
9454 window,
9455 cx,
9456 );
9457 } else if target_display_point.row() >= visible_row_range.end {
9458 return self.render_edit_prediction_scroll_popover(
9459 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9460 IconName::ArrowDown,
9461 visible_row_range,
9462 line_layouts,
9463 newest_selection_head,
9464 scrolled_content_origin,
9465 window,
9466 cx,
9467 );
9468 }
9469
9470 const POLE_WIDTH: Pixels = px(2.);
9471
9472 let line_layout =
9473 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9474 let target_column = target_display_point.column() as usize;
9475
9476 let target_x = line_layout.x_for_index(target_column);
9477 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9478 - scroll_pixel_position.y;
9479
9480 let flag_on_right = target_x < text_bounds.size.width / 2.;
9481
9482 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9483 border_color.l += 0.001;
9484
9485 let mut element = v_flex()
9486 .items_end()
9487 .when(flag_on_right, |el| el.items_start())
9488 .child(if flag_on_right {
9489 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9490 .rounded_bl(px(0.))
9491 .rounded_tl(px(0.))
9492 .border_l_2()
9493 .border_color(border_color)
9494 } else {
9495 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9496 .rounded_br(px(0.))
9497 .rounded_tr(px(0.))
9498 .border_r_2()
9499 .border_color(border_color)
9500 })
9501 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9502 .into_any();
9503
9504 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9505
9506 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9507 - point(
9508 if flag_on_right {
9509 POLE_WIDTH
9510 } else {
9511 size.width - POLE_WIDTH
9512 },
9513 size.height - line_height,
9514 );
9515
9516 origin.x = origin.x.max(content_origin.x);
9517
9518 element.prepaint_at(origin, window, cx);
9519
9520 Some((element, origin))
9521 }
9522
9523 fn render_edit_prediction_scroll_popover(
9524 &mut self,
9525 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9526 scroll_icon: IconName,
9527 visible_row_range: Range<DisplayRow>,
9528 line_layouts: &[LineWithInvisibles],
9529 newest_selection_head: Option<DisplayPoint>,
9530 scrolled_content_origin: gpui::Point<Pixels>,
9531 window: &mut Window,
9532 cx: &mut App,
9533 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9534 let mut element = self
9535 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9536 .into_any();
9537
9538 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9539
9540 let cursor = newest_selection_head?;
9541 let cursor_row_layout =
9542 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9543 let cursor_column = cursor.column() as usize;
9544
9545 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9546
9547 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9548
9549 element.prepaint_at(origin, window, cx);
9550 Some((element, origin))
9551 }
9552
9553 fn render_edit_prediction_eager_jump_popover(
9554 &mut self,
9555 text_bounds: &Bounds<Pixels>,
9556 content_origin: gpui::Point<Pixels>,
9557 editor_snapshot: &EditorSnapshot,
9558 visible_row_range: Range<DisplayRow>,
9559 scroll_top: ScrollOffset,
9560 scroll_bottom: ScrollOffset,
9561 line_height: Pixels,
9562 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9563 target_display_point: DisplayPoint,
9564 editor_width: Pixels,
9565 window: &mut Window,
9566 cx: &mut App,
9567 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9568 if target_display_point.row().as_f64() < scroll_top {
9569 let mut element = self
9570 .render_edit_prediction_line_popover(
9571 "Jump to Edit",
9572 Some(IconName::ArrowUp),
9573 window,
9574 cx,
9575 )
9576 .into_any();
9577
9578 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9579 let offset = point(
9580 (text_bounds.size.width - size.width) / 2.,
9581 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9582 );
9583
9584 let origin = text_bounds.origin + offset;
9585 element.prepaint_at(origin, window, cx);
9586 Some((element, origin))
9587 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9588 let mut element = self
9589 .render_edit_prediction_line_popover(
9590 "Jump to Edit",
9591 Some(IconName::ArrowDown),
9592 window,
9593 cx,
9594 )
9595 .into_any();
9596
9597 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9598 let offset = point(
9599 (text_bounds.size.width - size.width) / 2.,
9600 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9601 );
9602
9603 let origin = text_bounds.origin + offset;
9604 element.prepaint_at(origin, window, cx);
9605 Some((element, origin))
9606 } else {
9607 self.render_edit_prediction_end_of_line_popover(
9608 "Jump to Edit",
9609 editor_snapshot,
9610 visible_row_range,
9611 target_display_point,
9612 line_height,
9613 scroll_pixel_position,
9614 content_origin,
9615 editor_width,
9616 window,
9617 cx,
9618 )
9619 }
9620 }
9621
9622 fn render_edit_prediction_end_of_line_popover(
9623 self: &mut Editor,
9624 label: &'static str,
9625 editor_snapshot: &EditorSnapshot,
9626 visible_row_range: Range<DisplayRow>,
9627 target_display_point: DisplayPoint,
9628 line_height: Pixels,
9629 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9630 content_origin: gpui::Point<Pixels>,
9631 editor_width: Pixels,
9632 window: &mut Window,
9633 cx: &mut App,
9634 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9635 let target_line_end = DisplayPoint::new(
9636 target_display_point.row(),
9637 editor_snapshot.line_len(target_display_point.row()),
9638 );
9639
9640 let mut element = self
9641 .render_edit_prediction_line_popover(label, None, window, cx)
9642 .into_any();
9643
9644 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9645
9646 let line_origin =
9647 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9648
9649 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9650 let mut origin = start_point
9651 + line_origin
9652 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9653 origin.x = origin.x.max(content_origin.x);
9654
9655 let max_x = content_origin.x + editor_width - size.width;
9656
9657 if origin.x > max_x {
9658 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9659
9660 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9661 origin.y += offset;
9662 IconName::ArrowUp
9663 } else {
9664 origin.y -= offset;
9665 IconName::ArrowDown
9666 };
9667
9668 element = self
9669 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9670 .into_any();
9671
9672 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9673
9674 origin.x = content_origin.x + editor_width - size.width - px(2.);
9675 }
9676
9677 element.prepaint_at(origin, window, cx);
9678 Some((element, origin))
9679 }
9680
9681 fn render_edit_prediction_diff_popover(
9682 self: &Editor,
9683 text_bounds: &Bounds<Pixels>,
9684 content_origin: gpui::Point<Pixels>,
9685 right_margin: Pixels,
9686 editor_snapshot: &EditorSnapshot,
9687 visible_row_range: Range<DisplayRow>,
9688 line_layouts: &[LineWithInvisibles],
9689 line_height: Pixels,
9690 scroll_position: gpui::Point<ScrollOffset>,
9691 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9692 newest_selection_head: Option<DisplayPoint>,
9693 editor_width: Pixels,
9694 style: &EditorStyle,
9695 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9696 edit_preview: &Option<language::EditPreview>,
9697 snapshot: &language::BufferSnapshot,
9698 window: &mut Window,
9699 cx: &mut App,
9700 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9701 let edit_start = edits
9702 .first()
9703 .unwrap()
9704 .0
9705 .start
9706 .to_display_point(editor_snapshot);
9707 let edit_end = edits
9708 .last()
9709 .unwrap()
9710 .0
9711 .end
9712 .to_display_point(editor_snapshot);
9713
9714 let is_visible = visible_row_range.contains(&edit_start.row())
9715 || visible_row_range.contains(&edit_end.row());
9716 if !is_visible {
9717 return None;
9718 }
9719
9720 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9721 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9722 } else {
9723 // Fallback for providers without edit_preview
9724 crate::edit_prediction_fallback_text(edits, cx)
9725 };
9726
9727 let styled_text = highlighted_edits.to_styled_text(&style.text);
9728 let line_count = highlighted_edits.text.lines().count();
9729
9730 const BORDER_WIDTH: Pixels = px(1.);
9731
9732 let keybind = self.render_edit_prediction_keybind(window, cx);
9733 let has_keybind = keybind.is_some();
9734
9735 let mut element = h_flex()
9736 .items_start()
9737 .child(
9738 h_flex()
9739 .bg(cx.theme().colors().editor_background)
9740 .border(BORDER_WIDTH)
9741 .shadow_xs()
9742 .border_color(cx.theme().colors().border)
9743 .rounded_l_lg()
9744 .when(line_count > 1, |el| el.rounded_br_lg())
9745 .pr_1()
9746 .child(styled_text),
9747 )
9748 .child(
9749 h_flex()
9750 .h(line_height + BORDER_WIDTH * 2.)
9751 .px_1p5()
9752 .gap_1()
9753 // Workaround: For some reason, there's a gap if we don't do this
9754 .ml(-BORDER_WIDTH)
9755 .shadow(vec![gpui::BoxShadow {
9756 color: gpui::black().opacity(0.05),
9757 offset: point(px(1.), px(1.)),
9758 blur_radius: px(2.),
9759 spread_radius: px(0.),
9760 }])
9761 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9762 .border(BORDER_WIDTH)
9763 .border_color(cx.theme().colors().border)
9764 .rounded_r_lg()
9765 .id("edit_prediction_diff_popover_keybind")
9766 .when(!has_keybind, |el| {
9767 let status_colors = cx.theme().status();
9768
9769 el.bg(status_colors.error_background)
9770 .border_color(status_colors.error.opacity(0.6))
9771 .child(Icon::new(IconName::Info).color(Color::Error))
9772 .cursor_default()
9773 .hoverable_tooltip(move |_window, cx| {
9774 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9775 })
9776 })
9777 .children(keybind),
9778 )
9779 .into_any();
9780
9781 let longest_row =
9782 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9783 let longest_line_width = if visible_row_range.contains(&longest_row) {
9784 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9785 } else {
9786 layout_line(
9787 longest_row,
9788 editor_snapshot,
9789 style,
9790 editor_width,
9791 |_| false,
9792 window,
9793 cx,
9794 )
9795 .width
9796 };
9797
9798 let viewport_bounds =
9799 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9800 right: -right_margin,
9801 ..Default::default()
9802 });
9803
9804 let x_after_longest = Pixels::from(
9805 ScrollPixelOffset::from(
9806 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9807 ) - scroll_pixel_position.x,
9808 );
9809
9810 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9811
9812 // Fully visible if it can be displayed within the window (allow overlapping other
9813 // panes). However, this is only allowed if the popover starts within text_bounds.
9814 let can_position_to_the_right = x_after_longest < text_bounds.right()
9815 && x_after_longest + element_bounds.width < viewport_bounds.right();
9816
9817 let mut origin = if can_position_to_the_right {
9818 point(
9819 x_after_longest,
9820 text_bounds.origin.y
9821 + Pixels::from(
9822 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9823 - scroll_pixel_position.y,
9824 ),
9825 )
9826 } else {
9827 let cursor_row = newest_selection_head.map(|head| head.row());
9828 let above_edit = edit_start
9829 .row()
9830 .0
9831 .checked_sub(line_count as u32)
9832 .map(DisplayRow);
9833 let below_edit = Some(edit_end.row() + 1);
9834 let above_cursor =
9835 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9836 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9837
9838 // Place the edit popover adjacent to the edit if there is a location
9839 // available that is onscreen and does not obscure the cursor. Otherwise,
9840 // place it adjacent to the cursor.
9841 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9842 .into_iter()
9843 .flatten()
9844 .find(|&start_row| {
9845 let end_row = start_row + line_count as u32;
9846 visible_row_range.contains(&start_row)
9847 && visible_row_range.contains(&end_row)
9848 && cursor_row
9849 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9850 })?;
9851
9852 content_origin
9853 + point(
9854 Pixels::from(-scroll_pixel_position.x),
9855 Pixels::from(
9856 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9857 ),
9858 )
9859 };
9860
9861 origin.x -= BORDER_WIDTH;
9862
9863 window.with_content_mask(
9864 Some(gpui::ContentMask {
9865 bounds: *text_bounds,
9866 }),
9867 |window| {
9868 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9869 },
9870 );
9871
9872 // Do not return an element, since it will already be drawn due to defer_draw.
9873 None
9874 }
9875
9876 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9877 px(30.)
9878 }
9879
9880 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9881 if self.read_only(cx) {
9882 cx.theme().players().read_only()
9883 } else {
9884 self.style.as_ref().unwrap().local_player
9885 }
9886 }
9887
9888 fn render_edit_prediction_inline_keystroke(
9889 &self,
9890 keystroke: &gpui::KeybindingKeystroke,
9891 modifiers_color: Color,
9892 cx: &App,
9893 ) -> AnyElement {
9894 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9895
9896 h_flex()
9897 .px_0p5()
9898 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9899 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9900 .text_size(TextSize::XSmall.rems(cx))
9901 .child(h_flex().children(ui::render_modifiers(
9902 keystroke.modifiers(),
9903 PlatformStyle::platform(),
9904 Some(modifiers_color),
9905 Some(IconSize::XSmall.rems().into()),
9906 true,
9907 )))
9908 .when(is_platform_style_mac, |parent| {
9909 parent.child(keystroke.key().to_string())
9910 })
9911 .when(!is_platform_style_mac, |parent| {
9912 parent.child(
9913 Key::new(util::capitalize(keystroke.key()), Some(Color::Default))
9914 .size(Some(IconSize::XSmall.rems().into())),
9915 )
9916 })
9917 .into_any()
9918 }
9919
9920 fn render_edit_prediction_popover_keystroke(
9921 &self,
9922 keystroke: &gpui::KeybindingKeystroke,
9923 color: Color,
9924 cx: &App,
9925 ) -> AnyElement {
9926 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9927
9928 if keystroke.modifiers().modified() {
9929 h_flex()
9930 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9931 .when(is_platform_style_mac, |parent| parent.gap_1())
9932 .child(h_flex().children(ui::render_modifiers(
9933 keystroke.modifiers(),
9934 PlatformStyle::platform(),
9935 Some(color),
9936 None,
9937 false,
9938 )))
9939 .into_any()
9940 } else {
9941 Key::new(util::capitalize(keystroke.key()), Some(color))
9942 .size(Some(IconSize::XSmall.rems().into()))
9943 .into_any_element()
9944 }
9945 }
9946
9947 fn render_edit_prediction_keybind(
9948 &self,
9949 window: &mut Window,
9950 cx: &mut App,
9951 ) -> Option<AnyElement> {
9952 let keybind_display =
9953 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
9954 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
9955
9956 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
9957 Color::Accent
9958 } else {
9959 Color::Muted
9960 };
9961
9962 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
9963 }
9964
9965 fn render_edit_prediction_line_popover(
9966 &self,
9967 label: impl Into<SharedString>,
9968 icon: Option<IconName>,
9969 window: &mut Window,
9970 cx: &mut App,
9971 ) -> Stateful<Div> {
9972 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9973
9974 let keybind = self.render_edit_prediction_keybind(window, cx);
9975 let has_keybind = keybind.is_some();
9976 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9977
9978 h_flex()
9979 .id("ep-line-popover")
9980 .py_0p5()
9981 .pl_1()
9982 .pr(padding_right)
9983 .gap_1()
9984 .rounded_md()
9985 .border_1()
9986 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9987 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9988 .shadow_xs()
9989 .when(!has_keybind, |el| {
9990 let status_colors = cx.theme().status();
9991
9992 el.bg(status_colors.error_background)
9993 .border_color(status_colors.error.opacity(0.6))
9994 .pl_2()
9995 .child(Icon::new(icons.error).color(Color::Error))
9996 .cursor_default()
9997 .hoverable_tooltip(move |_window, cx| {
9998 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9999 })
10000 })
10001 .children(keybind)
10002 .child(
10003 Label::new(label)
10004 .size(LabelSize::Small)
10005 .when(!has_keybind, |el| {
10006 el.color(cx.theme().status().error.into()).strikethrough()
10007 }),
10008 )
10009 .when(!has_keybind, |el| {
10010 el.child(
10011 h_flex().ml_1().child(
10012 Icon::new(IconName::Info)
10013 .size(IconSize::Small)
10014 .color(cx.theme().status().error.into()),
10015 ),
10016 )
10017 })
10018 .when_some(icon, |element, icon| {
10019 element.child(
10020 div()
10021 .mt(px(1.5))
10022 .child(Icon::new(icon).size(IconSize::Small)),
10023 )
10024 })
10025 }
10026
10027 fn render_edit_prediction_jump_outside_popover(
10028 &self,
10029 snapshot: &BufferSnapshot,
10030 window: &mut Window,
10031 cx: &mut App,
10032 ) -> Stateful<Div> {
10033 let keybind = self.render_edit_prediction_keybind(window, cx);
10034 let has_keybind = keybind.is_some();
10035 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10036
10037 let file_name = snapshot
10038 .file()
10039 .map(|file| SharedString::new(file.file_name(cx)))
10040 .unwrap_or(SharedString::new_static("untitled"));
10041
10042 h_flex()
10043 .id("ep-jump-outside-popover")
10044 .py_1()
10045 .px_2()
10046 .gap_1()
10047 .rounded_md()
10048 .border_1()
10049 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10050 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10051 .shadow_xs()
10052 .when(!has_keybind, |el| {
10053 let status_colors = cx.theme().status();
10054
10055 el.bg(status_colors.error_background)
10056 .border_color(status_colors.error.opacity(0.6))
10057 .pl_2()
10058 .child(Icon::new(icons.error).color(Color::Error))
10059 .cursor_default()
10060 .hoverable_tooltip(move |_window, cx| {
10061 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10062 })
10063 })
10064 .children(keybind)
10065 .child(
10066 Label::new(file_name)
10067 .size(LabelSize::Small)
10068 .buffer_font(cx)
10069 .when(!has_keybind, |el| {
10070 el.color(cx.theme().status().error.into()).strikethrough()
10071 }),
10072 )
10073 .when(!has_keybind, |el| {
10074 el.child(
10075 h_flex().ml_1().child(
10076 Icon::new(IconName::Info)
10077 .size(IconSize::Small)
10078 .color(cx.theme().status().error.into()),
10079 ),
10080 )
10081 })
10082 .child(
10083 div()
10084 .mt(px(1.5))
10085 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10086 )
10087 }
10088
10089 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10090 let accent_color = cx.theme().colors().text_accent;
10091 let editor_bg_color = cx.theme().colors().editor_background;
10092 editor_bg_color.blend(accent_color.opacity(0.1))
10093 }
10094
10095 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10096 let accent_color = cx.theme().colors().text_accent;
10097 let editor_bg_color = cx.theme().colors().editor_background;
10098 editor_bg_color.blend(accent_color.opacity(0.6))
10099 }
10100 fn get_prediction_provider_icons(
10101 provider: &Option<RegisteredEditPredictionDelegate>,
10102 cx: &App,
10103 ) -> edit_prediction_types::EditPredictionIconSet {
10104 match provider {
10105 Some(provider) => provider.provider.icons(cx),
10106 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10107 }
10108 }
10109
10110 fn render_edit_prediction_cursor_popover(
10111 &self,
10112 min_width: Pixels,
10113 max_width: Pixels,
10114 cursor_point: Point,
10115 style: &EditorStyle,
10116 window: &mut Window,
10117 cx: &mut Context<Editor>,
10118 ) -> Option<AnyElement> {
10119 let provider = self.edit_prediction_provider.as_ref()?;
10120 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10121
10122 let is_refreshing = provider.provider.is_refreshing(cx);
10123
10124 fn pending_completion_container(icon: IconName) -> Div {
10125 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10126 }
10127
10128 let completion = match &self.active_edit_prediction {
10129 Some(prediction) => {
10130 if !self.has_visible_completions_menu() {
10131 const RADIUS: Pixels = px(6.);
10132 const BORDER_WIDTH: Pixels = px(1.);
10133 let keybind_display = self.edit_prediction_keybind_display(
10134 EditPredictionKeybindSurface::CursorPopoverCompact,
10135 window,
10136 cx,
10137 );
10138
10139 return Some(
10140 h_flex()
10141 .elevation_2(cx)
10142 .border(BORDER_WIDTH)
10143 .border_color(cx.theme().colors().border)
10144 .when(keybind_display.missing_accept_keystroke, |el| {
10145 el.border_color(cx.theme().status().error)
10146 })
10147 .rounded(RADIUS)
10148 .rounded_tl(px(0.))
10149 .overflow_hidden()
10150 .child(div().px_1p5().child(match &prediction.completion {
10151 EditPrediction::MoveWithin { target, snapshot } => {
10152 use text::ToPoint as _;
10153 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10154 {
10155 Icon::new(icons.down)
10156 } else {
10157 Icon::new(icons.up)
10158 }
10159 }
10160 EditPrediction::MoveOutside { .. } => {
10161 // TODO [zeta2] custom icon for external jump?
10162 Icon::new(icons.base)
10163 }
10164 EditPrediction::Edit { .. } => Icon::new(icons.base),
10165 }))
10166 .child(
10167 h_flex()
10168 .gap_1()
10169 .py_1()
10170 .px_2()
10171 .rounded_r(RADIUS - BORDER_WIDTH)
10172 .border_l_1()
10173 .border_color(cx.theme().colors().border)
10174 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10175 .when(keybind_display.show_hold_label, |el| {
10176 el.child(
10177 Label::new("Hold")
10178 .size(LabelSize::Small)
10179 .when(
10180 keybind_display.missing_accept_keystroke,
10181 |el| el.strikethrough(),
10182 )
10183 .line_height_style(LineHeightStyle::UiLabel),
10184 )
10185 })
10186 .id("edit_prediction_cursor_popover_keybind")
10187 .when(keybind_display.missing_accept_keystroke, |el| {
10188 let status_colors = cx.theme().status();
10189
10190 el.bg(status_colors.error_background)
10191 .border_color(status_colors.error.opacity(0.6))
10192 .child(Icon::new(IconName::Info).color(Color::Error))
10193 .cursor_default()
10194 .hoverable_tooltip(move |_window, cx| {
10195 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10196 .into()
10197 })
10198 })
10199 .when_some(
10200 keybind_display.displayed_keystroke.as_ref(),
10201 |el, compact_keystroke| {
10202 el.child(self.render_edit_prediction_popover_keystroke(
10203 compact_keystroke,
10204 Color::Default,
10205 cx,
10206 ))
10207 },
10208 ),
10209 )
10210 .into_any(),
10211 );
10212 }
10213
10214 self.render_edit_prediction_cursor_popover_preview(
10215 prediction,
10216 cursor_point,
10217 style,
10218 cx,
10219 )?
10220 }
10221
10222 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10223 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10224 stale_completion,
10225 cursor_point,
10226 style,
10227 cx,
10228 )?,
10229
10230 None => pending_completion_container(icons.base)
10231 .child(Label::new("...").size(LabelSize::Small)),
10232 },
10233
10234 None => pending_completion_container(icons.base)
10235 .child(Label::new("...").size(LabelSize::Small)),
10236 };
10237
10238 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10239 completion
10240 .with_animation(
10241 "loading-completion",
10242 Animation::new(Duration::from_secs(2))
10243 .repeat()
10244 .with_easing(pulsating_between(0.4, 0.8)),
10245 |label, delta| label.opacity(delta),
10246 )
10247 .into_any_element()
10248 } else {
10249 completion.into_any_element()
10250 };
10251
10252 let has_completion = self.active_edit_prediction.is_some();
10253 let keybind_display = self.edit_prediction_keybind_display(
10254 EditPredictionKeybindSurface::CursorPopoverExpanded,
10255 window,
10256 cx,
10257 );
10258
10259 Some(
10260 h_flex()
10261 .min_w(min_width)
10262 .max_w(max_width)
10263 .flex_1()
10264 .elevation_2(cx)
10265 .border_color(cx.theme().colors().border)
10266 .child(
10267 div()
10268 .flex_1()
10269 .py_1()
10270 .px_2()
10271 .overflow_hidden()
10272 .child(completion),
10273 )
10274 .when_some(
10275 keybind_display.displayed_keystroke.as_ref(),
10276 |el, keystroke| {
10277 let key_color = if !has_completion {
10278 Color::Muted
10279 } else {
10280 Color::Default
10281 };
10282
10283 if keybind_display.action == EditPredictionKeybindAction::Preview {
10284 el.child(
10285 h_flex()
10286 .h_full()
10287 .border_l_1()
10288 .rounded_r_lg()
10289 .border_color(cx.theme().colors().border)
10290 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10291 .gap_1()
10292 .py_1()
10293 .px_2()
10294 .child(self.render_edit_prediction_popover_keystroke(
10295 keystroke, key_color, cx,
10296 ))
10297 .child(Label::new("Preview").into_any_element())
10298 .opacity(if has_completion { 1.0 } else { 0.4 }),
10299 )
10300 } else {
10301 el.child(
10302 h_flex()
10303 .h_full()
10304 .border_l_1()
10305 .rounded_r_lg()
10306 .border_color(cx.theme().colors().border)
10307 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10308 .gap_1()
10309 .py_1()
10310 .px_2()
10311 .child(self.render_edit_prediction_popover_keystroke(
10312 keystroke, key_color, cx,
10313 ))
10314 .opacity(if has_completion { 1.0 } else { 0.4 }),
10315 )
10316 }
10317 },
10318 )
10319 .into_any(),
10320 )
10321 }
10322
10323 fn render_edit_prediction_cursor_popover_preview(
10324 &self,
10325 completion: &EditPredictionState,
10326 cursor_point: Point,
10327 style: &EditorStyle,
10328 cx: &mut Context<Editor>,
10329 ) -> Option<Div> {
10330 use text::ToPoint as _;
10331
10332 fn render_relative_row_jump(
10333 prefix: impl Into<String>,
10334 current_row: u32,
10335 target_row: u32,
10336 ) -> Div {
10337 let (row_diff, arrow) = if target_row < current_row {
10338 (current_row - target_row, IconName::ArrowUp)
10339 } else {
10340 (target_row - current_row, IconName::ArrowDown)
10341 };
10342
10343 h_flex()
10344 .child(
10345 Label::new(format!("{}{}", prefix.into(), row_diff))
10346 .color(Color::Muted)
10347 .size(LabelSize::Small),
10348 )
10349 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10350 }
10351
10352 let supports_jump = self
10353 .edit_prediction_provider
10354 .as_ref()
10355 .map(|provider| provider.provider.supports_jump_to_edit())
10356 .unwrap_or(true);
10357
10358 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10359
10360 match &completion.completion {
10361 EditPrediction::MoveWithin {
10362 target, snapshot, ..
10363 } => {
10364 if !supports_jump {
10365 return None;
10366 }
10367
10368 Some(
10369 h_flex()
10370 .px_2()
10371 .gap_2()
10372 .flex_1()
10373 .child(
10374 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10375 Icon::new(icons.down)
10376 } else {
10377 Icon::new(icons.up)
10378 },
10379 )
10380 .child(Label::new("Jump to Edit")),
10381 )
10382 }
10383 EditPrediction::MoveOutside { snapshot, .. } => {
10384 let file_name = snapshot
10385 .file()
10386 .map(|file| file.file_name(cx))
10387 .unwrap_or("untitled");
10388 Some(
10389 h_flex()
10390 .px_2()
10391 .gap_2()
10392 .flex_1()
10393 .child(Icon::new(icons.base))
10394 .child(Label::new(format!("Jump to {file_name}"))),
10395 )
10396 }
10397 EditPrediction::Edit {
10398 edits,
10399 edit_preview,
10400 snapshot,
10401 ..
10402 } => {
10403 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10404
10405 let (highlighted_edits, has_more_lines) =
10406 if let Some(edit_preview) = edit_preview.as_ref() {
10407 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10408 .first_line_preview()
10409 } else {
10410 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10411 };
10412
10413 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10414 .with_default_highlights(&style.text, highlighted_edits.highlights);
10415
10416 let preview = h_flex()
10417 .gap_1()
10418 .min_w_16()
10419 .child(styled_text)
10420 .when(has_more_lines, |parent| parent.child("…"));
10421
10422 let left = if supports_jump && first_edit_row != cursor_point.row {
10423 render_relative_row_jump("", cursor_point.row, first_edit_row)
10424 .into_any_element()
10425 } else {
10426 Icon::new(icons.base).into_any_element()
10427 };
10428
10429 Some(
10430 h_flex()
10431 .h_full()
10432 .flex_1()
10433 .gap_2()
10434 .pr_1()
10435 .overflow_x_hidden()
10436 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10437 .child(left)
10438 .child(preview),
10439 )
10440 }
10441 }
10442 }
10443
10444 pub fn render_context_menu(
10445 &mut self,
10446 max_height_in_lines: u32,
10447 window: &mut Window,
10448 cx: &mut Context<Editor>,
10449 ) -> Option<AnyElement> {
10450 let menu = self.context_menu.borrow();
10451 let menu = menu.as_ref()?;
10452 if !menu.visible() {
10453 return None;
10454 };
10455 self.style
10456 .as_ref()
10457 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10458 }
10459
10460 fn render_context_menu_aside(
10461 &mut self,
10462 max_size: Size<Pixels>,
10463 window: &mut Window,
10464 cx: &mut Context<Editor>,
10465 ) -> Option<AnyElement> {
10466 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10467 if menu.visible() {
10468 menu.render_aside(max_size, window, cx)
10469 } else {
10470 None
10471 }
10472 })
10473 }
10474
10475 fn hide_context_menu(
10476 &mut self,
10477 window: &mut Window,
10478 cx: &mut Context<Self>,
10479 ) -> Option<CodeContextMenu> {
10480 cx.notify();
10481 self.completion_tasks.clear();
10482 let context_menu = self.context_menu.borrow_mut().take();
10483 self.stale_edit_prediction_in_menu.take();
10484 self.update_visible_edit_prediction(window, cx);
10485 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10486 && let Some(completion_provider) = &self.completion_provider
10487 {
10488 completion_provider.selection_changed(None, window, cx);
10489 }
10490 context_menu
10491 }
10492
10493 fn show_snippet_choices(
10494 &mut self,
10495 choices: &Vec<String>,
10496 selection: Range<Anchor>,
10497 cx: &mut Context<Self>,
10498 ) {
10499 let Some((_, buffer, _)) = self
10500 .buffer()
10501 .read(cx)
10502 .excerpt_containing(selection.start, cx)
10503 else {
10504 return;
10505 };
10506 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10507 else {
10508 return;
10509 };
10510 if buffer != end_buffer {
10511 log::error!("expected anchor range to have matching buffer IDs");
10512 return;
10513 }
10514
10515 let id = post_inc(&mut self.next_completion_id);
10516 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10517 let mut context_menu = self.context_menu.borrow_mut();
10518 let old_menu = context_menu.take();
10519 *context_menu = Some(CodeContextMenu::Completions(
10520 CompletionsMenu::new_snippet_choices(
10521 id,
10522 true,
10523 choices,
10524 selection,
10525 buffer,
10526 old_menu.map(|menu| menu.primary_scroll_handle()),
10527 snippet_sort_order,
10528 ),
10529 ));
10530 }
10531
10532 pub fn insert_snippet(
10533 &mut self,
10534 insertion_ranges: &[Range<MultiBufferOffset>],
10535 snippet: Snippet,
10536 window: &mut Window,
10537 cx: &mut Context<Self>,
10538 ) -> Result<()> {
10539 struct Tabstop<T> {
10540 is_end_tabstop: bool,
10541 ranges: Vec<Range<T>>,
10542 choices: Option<Vec<String>>,
10543 }
10544
10545 let tabstops = self.buffer.update(cx, |buffer, cx| {
10546 let snippet_text: Arc<str> = snippet.text.clone().into();
10547 let edits = insertion_ranges
10548 .iter()
10549 .cloned()
10550 .map(|range| (range, snippet_text.clone()));
10551 let autoindent_mode = AutoindentMode::Block {
10552 original_indent_columns: Vec::new(),
10553 };
10554 buffer.edit(edits, Some(autoindent_mode), cx);
10555
10556 let snapshot = &*buffer.read(cx);
10557 let snippet = &snippet;
10558 snippet
10559 .tabstops
10560 .iter()
10561 .map(|tabstop| {
10562 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10563 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10564 });
10565 let mut tabstop_ranges = tabstop
10566 .ranges
10567 .iter()
10568 .flat_map(|tabstop_range| {
10569 let mut delta = 0_isize;
10570 insertion_ranges.iter().map(move |insertion_range| {
10571 let insertion_start = insertion_range.start + delta;
10572 delta += snippet.text.len() as isize
10573 - (insertion_range.end - insertion_range.start) as isize;
10574
10575 let start =
10576 (insertion_start + tabstop_range.start).min(snapshot.len());
10577 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10578 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10579 })
10580 })
10581 .collect::<Vec<_>>();
10582 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10583
10584 Tabstop {
10585 is_end_tabstop,
10586 ranges: tabstop_ranges,
10587 choices: tabstop.choices.clone(),
10588 }
10589 })
10590 .collect::<Vec<_>>()
10591 });
10592 if let Some(tabstop) = tabstops.first() {
10593 self.change_selections(Default::default(), window, cx, |s| {
10594 // Reverse order so that the first range is the newest created selection.
10595 // Completions will use it and autoscroll will prioritize it.
10596 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10597 });
10598
10599 if let Some(choices) = &tabstop.choices
10600 && let Some(selection) = tabstop.ranges.first()
10601 {
10602 self.show_snippet_choices(choices, selection.clone(), cx)
10603 }
10604
10605 // If we're already at the last tabstop and it's at the end of the snippet,
10606 // we're done, we don't need to keep the state around.
10607 if !tabstop.is_end_tabstop {
10608 let choices = tabstops
10609 .iter()
10610 .map(|tabstop| tabstop.choices.clone())
10611 .collect();
10612
10613 let ranges = tabstops
10614 .into_iter()
10615 .map(|tabstop| tabstop.ranges)
10616 .collect::<Vec<_>>();
10617
10618 self.snippet_stack.push(SnippetState {
10619 active_index: 0,
10620 ranges,
10621 choices,
10622 });
10623 }
10624
10625 // Check whether the just-entered snippet ends with an auto-closable bracket.
10626 if self.autoclose_regions.is_empty() {
10627 let snapshot = self.buffer.read(cx).snapshot(cx);
10628 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10629 let selection_head = selection.head();
10630 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10631 continue;
10632 };
10633
10634 let mut bracket_pair = None;
10635 let max_lookup_length = scope
10636 .brackets()
10637 .map(|(pair, _)| {
10638 pair.start
10639 .as_str()
10640 .chars()
10641 .count()
10642 .max(pair.end.as_str().chars().count())
10643 })
10644 .max();
10645 if let Some(max_lookup_length) = max_lookup_length {
10646 let next_text = snapshot
10647 .chars_at(selection_head)
10648 .take(max_lookup_length)
10649 .collect::<String>();
10650 let prev_text = snapshot
10651 .reversed_chars_at(selection_head)
10652 .take(max_lookup_length)
10653 .collect::<String>();
10654
10655 for (pair, enabled) in scope.brackets() {
10656 if enabled
10657 && pair.close
10658 && prev_text.starts_with(pair.start.as_str())
10659 && next_text.starts_with(pair.end.as_str())
10660 {
10661 bracket_pair = Some(pair.clone());
10662 break;
10663 }
10664 }
10665 }
10666
10667 if let Some(pair) = bracket_pair {
10668 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10669 let autoclose_enabled =
10670 self.use_autoclose && snapshot_settings.use_autoclose;
10671 if autoclose_enabled {
10672 let start = snapshot.anchor_after(selection_head);
10673 let end = snapshot.anchor_after(selection_head);
10674 self.autoclose_regions.push(AutocloseRegion {
10675 selection_id: selection.id,
10676 range: start..end,
10677 pair,
10678 });
10679 }
10680 }
10681 }
10682 }
10683 }
10684 Ok(())
10685 }
10686
10687 pub fn move_to_next_snippet_tabstop(
10688 &mut self,
10689 window: &mut Window,
10690 cx: &mut Context<Self>,
10691 ) -> bool {
10692 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10693 }
10694
10695 pub fn move_to_prev_snippet_tabstop(
10696 &mut self,
10697 window: &mut Window,
10698 cx: &mut Context<Self>,
10699 ) -> bool {
10700 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10701 }
10702
10703 pub fn move_to_snippet_tabstop(
10704 &mut self,
10705 bias: Bias,
10706 window: &mut Window,
10707 cx: &mut Context<Self>,
10708 ) -> bool {
10709 if let Some(mut snippet) = self.snippet_stack.pop() {
10710 match bias {
10711 Bias::Left => {
10712 if snippet.active_index > 0 {
10713 snippet.active_index -= 1;
10714 } else {
10715 self.snippet_stack.push(snippet);
10716 return false;
10717 }
10718 }
10719 Bias::Right => {
10720 if snippet.active_index + 1 < snippet.ranges.len() {
10721 snippet.active_index += 1;
10722 } else {
10723 self.snippet_stack.push(snippet);
10724 return false;
10725 }
10726 }
10727 }
10728 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10729 self.change_selections(Default::default(), window, cx, |s| {
10730 // Reverse order so that the first range is the newest created selection.
10731 // Completions will use it and autoscroll will prioritize it.
10732 s.select_ranges(current_ranges.iter().rev().cloned())
10733 });
10734
10735 if let Some(choices) = &snippet.choices[snippet.active_index]
10736 && let Some(selection) = current_ranges.first()
10737 {
10738 self.show_snippet_choices(choices, selection.clone(), cx);
10739 }
10740
10741 // If snippet state is not at the last tabstop, push it back on the stack
10742 if snippet.active_index + 1 < snippet.ranges.len() {
10743 self.snippet_stack.push(snippet);
10744 }
10745 return true;
10746 }
10747 }
10748
10749 false
10750 }
10751
10752 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10753 self.transact(window, cx, |this, window, cx| {
10754 this.select_all(&SelectAll, window, cx);
10755 this.insert("", window, cx);
10756 });
10757 }
10758
10759 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10760 if self.read_only(cx) {
10761 return;
10762 }
10763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10764 self.transact(window, cx, |this, window, cx| {
10765 this.select_autoclose_pair(window, cx);
10766
10767 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10768
10769 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10770 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10771 for selection in &mut selections {
10772 if selection.is_empty() {
10773 let old_head = selection.head();
10774 let mut new_head =
10775 movement::left(&display_map, old_head.to_display_point(&display_map))
10776 .to_point(&display_map);
10777 if let Some((buffer, line_buffer_range)) = display_map
10778 .buffer_snapshot()
10779 .buffer_line_for_row(MultiBufferRow(old_head.row))
10780 {
10781 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10782 let indent_len = match indent_size.kind {
10783 IndentKind::Space => {
10784 buffer.settings_at(line_buffer_range.start, cx).tab_size
10785 }
10786 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10787 };
10788 if old_head.column <= indent_size.len && old_head.column > 0 {
10789 let indent_len = indent_len.get();
10790 new_head = cmp::min(
10791 new_head,
10792 MultiBufferPoint::new(
10793 old_head.row,
10794 ((old_head.column - 1) / indent_len) * indent_len,
10795 ),
10796 );
10797 }
10798 }
10799
10800 selection.set_head(new_head, SelectionGoal::None);
10801 }
10802 }
10803
10804 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10805 this.insert("", window, cx);
10806 linked_edits.apply_with_left_expansion(cx);
10807 this.refresh_edit_prediction(true, false, window, cx);
10808 refresh_linked_ranges(this, window, cx);
10809 });
10810 }
10811
10812 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10813 if self.read_only(cx) {
10814 return;
10815 }
10816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10817 self.transact(window, cx, |this, window, cx| {
10818 this.change_selections(Default::default(), window, cx, |s| {
10819 s.move_with(&mut |map, selection| {
10820 if selection.is_empty() {
10821 let cursor = movement::right(map, selection.head());
10822 selection.end = cursor;
10823 selection.reversed = true;
10824 selection.goal = SelectionGoal::None;
10825 }
10826 })
10827 });
10828 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10829 this.insert("", window, cx);
10830 linked_edits.apply(cx);
10831 this.refresh_edit_prediction(true, false, window, cx);
10832 refresh_linked_ranges(this, window, cx);
10833 });
10834 }
10835
10836 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10837 if self.mode.is_single_line() {
10838 cx.propagate();
10839 return;
10840 }
10841
10842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10843 if self.move_to_prev_snippet_tabstop(window, cx) {
10844 return;
10845 }
10846 self.outdent(&Outdent, window, cx);
10847 }
10848
10849 pub fn next_snippet_tabstop(
10850 &mut self,
10851 _: &NextSnippetTabstop,
10852 window: &mut Window,
10853 cx: &mut Context<Self>,
10854 ) {
10855 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10856 cx.propagate();
10857 return;
10858 }
10859
10860 if self.move_to_next_snippet_tabstop(window, cx) {
10861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10862 return;
10863 }
10864 cx.propagate();
10865 }
10866
10867 pub fn previous_snippet_tabstop(
10868 &mut self,
10869 _: &PreviousSnippetTabstop,
10870 window: &mut Window,
10871 cx: &mut Context<Self>,
10872 ) {
10873 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10874 cx.propagate();
10875 return;
10876 }
10877
10878 if self.move_to_prev_snippet_tabstop(window, cx) {
10879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10880 return;
10881 }
10882 cx.propagate();
10883 }
10884
10885 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10886 if self.mode.is_single_line() {
10887 cx.propagate();
10888 return;
10889 }
10890
10891 if self.move_to_next_snippet_tabstop(window, cx) {
10892 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10893 return;
10894 }
10895 if self.read_only(cx) {
10896 return;
10897 }
10898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10899 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10900 let buffer = self.buffer.read(cx);
10901 let snapshot = buffer.snapshot(cx);
10902 let rows_iter = selections.iter().map(|s| s.head().row);
10903 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10904
10905 let has_some_cursor_in_whitespace = selections
10906 .iter()
10907 .filter(|selection| selection.is_empty())
10908 .any(|selection| {
10909 let cursor = selection.head();
10910 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10911 cursor.column < current_indent.len
10912 });
10913
10914 let mut edits = Vec::new();
10915 let mut prev_edited_row = 0;
10916 let mut row_delta = 0;
10917 for selection in &mut selections {
10918 if selection.start.row != prev_edited_row {
10919 row_delta = 0;
10920 }
10921 prev_edited_row = selection.end.row;
10922
10923 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10924 if selection.is_empty() {
10925 let cursor = selection.head();
10926 let settings = buffer.language_settings_at(cursor, cx);
10927 if settings.indent_list_on_tab {
10928 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10929 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10930 row_delta = Self::indent_selection(
10931 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10932 );
10933 continue;
10934 }
10935 }
10936 }
10937 }
10938
10939 // If the selection is non-empty, then increase the indentation of the selected lines.
10940 if !selection.is_empty() {
10941 row_delta =
10942 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10943 continue;
10944 }
10945
10946 let cursor = selection.head();
10947 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10948 if let Some(suggested_indent) =
10949 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10950 {
10951 // Don't do anything if already at suggested indent
10952 // and there is any other cursor which is not
10953 if has_some_cursor_in_whitespace
10954 && cursor.column == current_indent.len
10955 && current_indent.len == suggested_indent.len
10956 {
10957 continue;
10958 }
10959
10960 // Adjust line and move cursor to suggested indent
10961 // if cursor is not at suggested indent
10962 if cursor.column < suggested_indent.len
10963 && cursor.column <= current_indent.len
10964 && current_indent.len <= suggested_indent.len
10965 {
10966 selection.start = Point::new(cursor.row, suggested_indent.len);
10967 selection.end = selection.start;
10968 if row_delta == 0 {
10969 edits.extend(Buffer::edit_for_indent_size_adjustment(
10970 cursor.row,
10971 current_indent,
10972 suggested_indent,
10973 ));
10974 row_delta = suggested_indent.len - current_indent.len;
10975 }
10976 continue;
10977 }
10978
10979 // If current indent is more than suggested indent
10980 // only move cursor to current indent and skip indent
10981 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10982 selection.start = Point::new(cursor.row, current_indent.len);
10983 selection.end = selection.start;
10984 continue;
10985 }
10986 }
10987
10988 // Otherwise, insert a hard or soft tab.
10989 let settings = buffer.language_settings_at(cursor, cx);
10990 let tab_size = if settings.hard_tabs {
10991 IndentSize::tab()
10992 } else {
10993 let tab_size = settings.tab_size.get();
10994 let indent_remainder = snapshot
10995 .text_for_range(Point::new(cursor.row, 0)..cursor)
10996 .flat_map(str::chars)
10997 .fold(row_delta % tab_size, |counter: u32, c| {
10998 if c == '\t' {
10999 0
11000 } else {
11001 (counter + 1) % tab_size
11002 }
11003 });
11004
11005 let chars_to_next_tab_stop = tab_size - indent_remainder;
11006 IndentSize::spaces(chars_to_next_tab_stop)
11007 };
11008 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11009 selection.end = selection.start;
11010 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11011 row_delta += tab_size.len;
11012 }
11013
11014 self.transact(window, cx, |this, window, cx| {
11015 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11016 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11017 this.refresh_edit_prediction(true, false, window, cx);
11018 });
11019 }
11020
11021 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11022 if self.read_only(cx) {
11023 return;
11024 }
11025 if self.mode.is_single_line() {
11026 cx.propagate();
11027 return;
11028 }
11029
11030 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11031 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11032 let mut prev_edited_row = 0;
11033 let mut row_delta = 0;
11034 let mut edits = Vec::new();
11035 let buffer = self.buffer.read(cx);
11036 let snapshot = buffer.snapshot(cx);
11037 for selection in &mut selections {
11038 if selection.start.row != prev_edited_row {
11039 row_delta = 0;
11040 }
11041 prev_edited_row = selection.end.row;
11042
11043 row_delta =
11044 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11045 }
11046
11047 self.transact(window, cx, |this, window, cx| {
11048 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11049 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11050 });
11051 }
11052
11053 fn indent_selection(
11054 buffer: &MultiBuffer,
11055 snapshot: &MultiBufferSnapshot,
11056 selection: &mut Selection<Point>,
11057 edits: &mut Vec<(Range<Point>, String)>,
11058 delta_for_start_row: u32,
11059 cx: &App,
11060 ) -> u32 {
11061 let settings = buffer.language_settings_at(selection.start, cx);
11062 let tab_size = settings.tab_size.get();
11063 let indent_kind = if settings.hard_tabs {
11064 IndentKind::Tab
11065 } else {
11066 IndentKind::Space
11067 };
11068 let mut start_row = selection.start.row;
11069 let mut end_row = selection.end.row + 1;
11070
11071 // If a selection ends at the beginning of a line, don't indent
11072 // that last line.
11073 if selection.end.column == 0 && selection.end.row > selection.start.row {
11074 end_row -= 1;
11075 }
11076
11077 // Avoid re-indenting a row that has already been indented by a
11078 // previous selection, but still update this selection's column
11079 // to reflect that indentation.
11080 if delta_for_start_row > 0 {
11081 start_row += 1;
11082 selection.start.column += delta_for_start_row;
11083 if selection.end.row == selection.start.row {
11084 selection.end.column += delta_for_start_row;
11085 }
11086 }
11087
11088 let mut delta_for_end_row = 0;
11089 let has_multiple_rows = start_row + 1 != end_row;
11090 for row in start_row..end_row {
11091 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11092 let indent_delta = match (current_indent.kind, indent_kind) {
11093 (IndentKind::Space, IndentKind::Space) => {
11094 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11095 IndentSize::spaces(columns_to_next_tab_stop)
11096 }
11097 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11098 (_, IndentKind::Tab) => IndentSize::tab(),
11099 };
11100
11101 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11102 0
11103 } else {
11104 selection.start.column
11105 };
11106 let row_start = Point::new(row, start);
11107 edits.push((
11108 row_start..row_start,
11109 indent_delta.chars().collect::<String>(),
11110 ));
11111
11112 // Update this selection's endpoints to reflect the indentation.
11113 if row == selection.start.row {
11114 selection.start.column += indent_delta.len;
11115 }
11116 if row == selection.end.row {
11117 selection.end.column += indent_delta.len;
11118 delta_for_end_row = indent_delta.len;
11119 }
11120 }
11121
11122 if selection.start.row == selection.end.row {
11123 delta_for_start_row + delta_for_end_row
11124 } else {
11125 delta_for_end_row
11126 }
11127 }
11128
11129 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11130 if self.read_only(cx) {
11131 return;
11132 }
11133 if self.mode.is_single_line() {
11134 cx.propagate();
11135 return;
11136 }
11137
11138 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11139 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11140 let selections = self.selections.all::<Point>(&display_map);
11141 let mut deletion_ranges = Vec::new();
11142 let mut last_outdent = None;
11143 {
11144 let buffer = self.buffer.read(cx);
11145 let snapshot = buffer.snapshot(cx);
11146 for selection in &selections {
11147 let settings = buffer.language_settings_at(selection.start, cx);
11148 let tab_size = settings.tab_size.get();
11149 let mut rows = selection.spanned_rows(false, &display_map);
11150
11151 // Avoid re-outdenting a row that has already been outdented by a
11152 // previous selection.
11153 if let Some(last_row) = last_outdent
11154 && last_row == rows.start
11155 {
11156 rows.start = rows.start.next_row();
11157 }
11158 let has_multiple_rows = rows.len() > 1;
11159 for row in rows.iter_rows() {
11160 let indent_size = snapshot.indent_size_for_line(row);
11161 if indent_size.len > 0 {
11162 let deletion_len = match indent_size.kind {
11163 IndentKind::Space => {
11164 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11165 if columns_to_prev_tab_stop == 0 {
11166 tab_size
11167 } else {
11168 columns_to_prev_tab_stop
11169 }
11170 }
11171 IndentKind::Tab => 1,
11172 };
11173 let start = if has_multiple_rows
11174 || deletion_len > selection.start.column
11175 || indent_size.len < selection.start.column
11176 {
11177 0
11178 } else {
11179 selection.start.column - deletion_len
11180 };
11181 deletion_ranges.push(
11182 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11183 );
11184 last_outdent = Some(row);
11185 }
11186 }
11187 }
11188 }
11189
11190 self.transact(window, cx, |this, window, cx| {
11191 this.buffer.update(cx, |buffer, cx| {
11192 let empty_str: Arc<str> = Arc::default();
11193 buffer.edit(
11194 deletion_ranges
11195 .into_iter()
11196 .map(|range| (range, empty_str.clone())),
11197 None,
11198 cx,
11199 );
11200 });
11201 let selections = this
11202 .selections
11203 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11204 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11205 });
11206 }
11207
11208 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11209 if self.read_only(cx) {
11210 return;
11211 }
11212 if self.mode.is_single_line() {
11213 cx.propagate();
11214 return;
11215 }
11216
11217 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11218 let selections = self
11219 .selections
11220 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11221 .into_iter()
11222 .map(|s| s.range());
11223
11224 self.transact(window, cx, |this, window, cx| {
11225 this.buffer.update(cx, |buffer, cx| {
11226 buffer.autoindent_ranges(selections, cx);
11227 });
11228 let selections = this
11229 .selections
11230 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11231 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11232 });
11233 }
11234
11235 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11236 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11237 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11238 let selections = self.selections.all::<Point>(&display_map);
11239
11240 let mut new_cursors = Vec::new();
11241 let mut edit_ranges = Vec::new();
11242 let mut selections = selections.iter().peekable();
11243 while let Some(selection) = selections.next() {
11244 let mut rows = selection.spanned_rows(false, &display_map);
11245
11246 // Accumulate contiguous regions of rows that we want to delete.
11247 while let Some(next_selection) = selections.peek() {
11248 let next_rows = next_selection.spanned_rows(false, &display_map);
11249 if next_rows.start <= rows.end {
11250 rows.end = next_rows.end;
11251 selections.next().unwrap();
11252 } else {
11253 break;
11254 }
11255 }
11256
11257 let buffer = display_map.buffer_snapshot();
11258 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11259 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11260 // If there's a line after the range, delete the \n from the end of the row range
11261 (
11262 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11263 rows.end,
11264 )
11265 } else {
11266 // If there isn't a line after the range, delete the \n from the line before the
11267 // start of the row range
11268 edit_start = edit_start.saturating_sub_usize(1);
11269 (buffer.len(), rows.start.previous_row())
11270 };
11271
11272 let text_layout_details = self.text_layout_details(window, cx);
11273 let x = display_map.x_for_display_point(
11274 selection.head().to_display_point(&display_map),
11275 &text_layout_details,
11276 );
11277 let row = Point::new(target_row.0, 0)
11278 .to_display_point(&display_map)
11279 .row();
11280 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11281
11282 new_cursors.push((
11283 selection.id,
11284 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11285 SelectionGoal::None,
11286 ));
11287 edit_ranges.push(edit_start..edit_end);
11288 }
11289
11290 self.transact(window, cx, |this, window, cx| {
11291 let buffer = this.buffer.update(cx, |buffer, cx| {
11292 let empty_str: Arc<str> = Arc::default();
11293 buffer.edit(
11294 edit_ranges
11295 .into_iter()
11296 .map(|range| (range, empty_str.clone())),
11297 None,
11298 cx,
11299 );
11300 buffer.snapshot(cx)
11301 });
11302 let new_selections = new_cursors
11303 .into_iter()
11304 .map(|(id, cursor, goal)| {
11305 let cursor = cursor.to_point(&buffer);
11306 Selection {
11307 id,
11308 start: cursor,
11309 end: cursor,
11310 reversed: false,
11311 goal,
11312 }
11313 })
11314 .collect();
11315
11316 this.change_selections(Default::default(), window, cx, |s| {
11317 s.select(new_selections);
11318 });
11319 });
11320 }
11321
11322 pub fn join_lines_impl(
11323 &mut self,
11324 insert_whitespace: bool,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 if self.read_only(cx) {
11329 return;
11330 }
11331 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11332 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11333 let start = MultiBufferRow(selection.start.row);
11334 // Treat single line selections as if they include the next line. Otherwise this action
11335 // would do nothing for single line selections individual cursors.
11336 let end = if selection.start.row == selection.end.row {
11337 MultiBufferRow(selection.start.row + 1)
11338 } else if selection.end.column == 0 {
11339 // If the selection ends at the start of a line, it's logically at the end of the
11340 // previous line (plus its newline).
11341 // Don't include the end line unless there's only one line selected.
11342 if selection.start.row + 1 == selection.end.row {
11343 MultiBufferRow(selection.end.row)
11344 } else {
11345 MultiBufferRow(selection.end.row - 1)
11346 }
11347 } else {
11348 MultiBufferRow(selection.end.row)
11349 };
11350
11351 if let Some(last_row_range) = row_ranges.last_mut()
11352 && start <= last_row_range.end
11353 {
11354 last_row_range.end = end;
11355 continue;
11356 }
11357 row_ranges.push(start..end);
11358 }
11359
11360 let snapshot = self.buffer.read(cx).snapshot(cx);
11361 let mut cursor_positions = Vec::new();
11362 for row_range in &row_ranges {
11363 let anchor = snapshot.anchor_before(Point::new(
11364 row_range.end.previous_row().0,
11365 snapshot.line_len(row_range.end.previous_row()),
11366 ));
11367 cursor_positions.push(anchor..anchor);
11368 }
11369
11370 self.transact(window, cx, |this, window, cx| {
11371 for row_range in row_ranges.into_iter().rev() {
11372 for row in row_range.iter_rows().rev() {
11373 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11374 let next_line_row = row.next_row();
11375 let indent = snapshot.indent_size_for_line(next_line_row);
11376 let mut join_start_column = indent.len;
11377
11378 if let Some(language_scope) =
11379 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11380 {
11381 let line_end =
11382 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11383 let line_text_after_indent = snapshot
11384 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11385 .collect::<String>();
11386
11387 if !line_text_after_indent.is_empty() {
11388 let block_prefix = language_scope
11389 .block_comment()
11390 .map(|c| c.prefix.as_ref())
11391 .filter(|p| !p.is_empty());
11392 let doc_prefix = language_scope
11393 .documentation_comment()
11394 .map(|c| c.prefix.as_ref())
11395 .filter(|p| !p.is_empty());
11396 let all_prefixes = language_scope
11397 .line_comment_prefixes()
11398 .iter()
11399 .map(|p| p.as_ref())
11400 .chain(block_prefix)
11401 .chain(doc_prefix)
11402 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11403
11404 let mut longest_prefix_len = None;
11405 for prefix in all_prefixes {
11406 let trimmed = prefix.trim_end();
11407 if line_text_after_indent.starts_with(trimmed) {
11408 let candidate_len =
11409 if line_text_after_indent.starts_with(prefix) {
11410 prefix.len()
11411 } else {
11412 trimmed.len()
11413 };
11414 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11415 longest_prefix_len = Some(candidate_len);
11416 }
11417 }
11418 }
11419
11420 if let Some(prefix_len) = longest_prefix_len {
11421 join_start_column =
11422 join_start_column.saturating_add(prefix_len as u32);
11423 }
11424 }
11425 }
11426
11427 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11428
11429 let replace = if snapshot.line_len(next_line_row) > join_start_column
11430 && insert_whitespace
11431 {
11432 " "
11433 } else {
11434 ""
11435 };
11436
11437 this.buffer.update(cx, |buffer, cx| {
11438 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11439 });
11440 }
11441 }
11442
11443 this.change_selections(Default::default(), window, cx, |s| {
11444 s.select_anchor_ranges(cursor_positions)
11445 });
11446 });
11447 }
11448
11449 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11450 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11451 self.join_lines_impl(true, window, cx);
11452 }
11453
11454 pub fn sort_lines_case_sensitive(
11455 &mut self,
11456 _: &SortLinesCaseSensitive,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11461 }
11462
11463 pub fn sort_lines_by_length(
11464 &mut self,
11465 _: &SortLinesByLength,
11466 window: &mut Window,
11467 cx: &mut Context<Self>,
11468 ) {
11469 self.manipulate_immutable_lines(window, cx, |lines| {
11470 lines.sort_by_key(|&line| line.chars().count())
11471 })
11472 }
11473
11474 pub fn sort_lines_case_insensitive(
11475 &mut self,
11476 _: &SortLinesCaseInsensitive,
11477 window: &mut Window,
11478 cx: &mut Context<Self>,
11479 ) {
11480 self.manipulate_immutable_lines(window, cx, |lines| {
11481 lines.sort_by_key(|line| line.to_lowercase())
11482 })
11483 }
11484
11485 pub fn unique_lines_case_insensitive(
11486 &mut self,
11487 _: &UniqueLinesCaseInsensitive,
11488 window: &mut Window,
11489 cx: &mut Context<Self>,
11490 ) {
11491 self.manipulate_immutable_lines(window, cx, |lines| {
11492 let mut seen = HashSet::default();
11493 lines.retain(|line| seen.insert(line.to_lowercase()));
11494 })
11495 }
11496
11497 pub fn unique_lines_case_sensitive(
11498 &mut self,
11499 _: &UniqueLinesCaseSensitive,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 self.manipulate_immutable_lines(window, cx, |lines| {
11504 let mut seen = HashSet::default();
11505 lines.retain(|line| seen.insert(*line));
11506 })
11507 }
11508
11509 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11510 let snapshot = self.buffer.read(cx).snapshot(cx);
11511 for selection in self.selections.disjoint_anchors_arc().iter() {
11512 if snapshot
11513 .language_at(selection.start)
11514 .and_then(|lang| lang.config().wrap_characters.as_ref())
11515 .is_some()
11516 {
11517 return true;
11518 }
11519 }
11520 false
11521 }
11522
11523 fn wrap_selections_in_tag(
11524 &mut self,
11525 _: &WrapSelectionsInTag,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11530
11531 let snapshot = self.buffer.read(cx).snapshot(cx);
11532
11533 let mut edits = Vec::new();
11534 let mut boundaries = Vec::new();
11535
11536 for selection in self
11537 .selections
11538 .all_adjusted(&self.display_snapshot(cx))
11539 .iter()
11540 {
11541 let Some(wrap_config) = snapshot
11542 .language_at(selection.start)
11543 .and_then(|lang| lang.config().wrap_characters.clone())
11544 else {
11545 continue;
11546 };
11547
11548 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11549 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11550
11551 let start_before = snapshot.anchor_before(selection.start);
11552 let end_after = snapshot.anchor_after(selection.end);
11553
11554 edits.push((start_before..start_before, open_tag));
11555 edits.push((end_after..end_after, close_tag));
11556
11557 boundaries.push((
11558 start_before,
11559 end_after,
11560 wrap_config.start_prefix.len(),
11561 wrap_config.end_suffix.len(),
11562 ));
11563 }
11564
11565 if edits.is_empty() {
11566 return;
11567 }
11568
11569 self.transact(window, cx, |this, window, cx| {
11570 let buffer = this.buffer.update(cx, |buffer, cx| {
11571 buffer.edit(edits, None, cx);
11572 buffer.snapshot(cx)
11573 });
11574
11575 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11576 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11577 boundaries.into_iter()
11578 {
11579 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11580 let close_offset = end_after
11581 .to_offset(&buffer)
11582 .saturating_sub_usize(end_suffix_len);
11583 new_selections.push(open_offset..open_offset);
11584 new_selections.push(close_offset..close_offset);
11585 }
11586
11587 this.change_selections(Default::default(), window, cx, |s| {
11588 s.select_ranges(new_selections);
11589 });
11590
11591 this.request_autoscroll(Autoscroll::fit(), cx);
11592 });
11593 }
11594
11595 pub fn toggle_read_only(
11596 &mut self,
11597 _: &workspace::ToggleReadOnlyFile,
11598 _: &mut Window,
11599 cx: &mut Context<Self>,
11600 ) {
11601 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11602 buffer.update(cx, |buffer, cx| {
11603 buffer.set_capability(
11604 match buffer.capability() {
11605 Capability::ReadWrite => Capability::Read,
11606 Capability::Read => Capability::ReadWrite,
11607 Capability::ReadOnly => Capability::ReadOnly,
11608 },
11609 cx,
11610 );
11611 })
11612 }
11613 }
11614
11615 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11616 let Some(project) = self.project.clone() else {
11617 return;
11618 };
11619 let task = self.reload(project, window, cx);
11620 self.detach_and_notify_err(task, window, cx);
11621 }
11622
11623 pub fn restore_file(
11624 &mut self,
11625 _: &::git::RestoreFile,
11626 window: &mut Window,
11627 cx: &mut Context<Self>,
11628 ) {
11629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11630 let mut buffer_ids = HashSet::default();
11631 let snapshot = self.buffer().read(cx).snapshot(cx);
11632 for selection in self
11633 .selections
11634 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11635 {
11636 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11637 }
11638
11639 let buffer = self.buffer().read(cx);
11640 let ranges = buffer_ids
11641 .into_iter()
11642 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11643 .collect::<Vec<_>>();
11644
11645 self.restore_hunks_in_ranges(ranges, window, cx);
11646 }
11647
11648 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11650 let selections = self
11651 .selections
11652 .all(&self.display_snapshot(cx))
11653 .into_iter()
11654 .map(|s| s.range())
11655 .collect();
11656 self.restore_hunks_in_ranges(selections, window, cx);
11657 }
11658
11659 /// Restores the diff hunks in the editor's selections and moves the cursor
11660 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11661 /// not all diff hunks are expanded.
11662 pub fn restore_and_next(
11663 &mut self,
11664 _: &::git::RestoreAndNext,
11665 window: &mut Window,
11666 cx: &mut Context<Self>,
11667 ) {
11668 let selections = self
11669 .selections
11670 .all(&self.display_snapshot(cx))
11671 .into_iter()
11672 .map(|selection| selection.range())
11673 .collect();
11674
11675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11676 self.restore_hunks_in_ranges(selections, window, cx);
11677
11678 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11679 let wrap_around = !all_diff_hunks_expanded;
11680 let snapshot = self.snapshot(window, cx);
11681 let position = self
11682 .selections
11683 .newest::<Point>(&snapshot.display_snapshot)
11684 .head();
11685
11686 self.go_to_hunk_before_or_after_position(
11687 &snapshot,
11688 position,
11689 Direction::Next,
11690 wrap_around,
11691 window,
11692 cx,
11693 );
11694 }
11695
11696 pub fn restore_hunks_in_ranges(
11697 &mut self,
11698 ranges: Vec<Range<Point>>,
11699 window: &mut Window,
11700 cx: &mut Context<Editor>,
11701 ) {
11702 if self.delegate_stage_and_restore {
11703 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11704 if !hunks.is_empty() {
11705 cx.emit(EditorEvent::RestoreRequested { hunks });
11706 }
11707 return;
11708 }
11709 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11710 self.transact(window, cx, |editor, window, cx| {
11711 editor.restore_diff_hunks(hunks, cx);
11712 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11713 selections.refresh()
11714 });
11715 });
11716 }
11717
11718 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11719 let mut revert_changes = HashMap::default();
11720 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11721 for (buffer_id, hunks) in &chunk_by {
11722 let hunks = hunks.collect::<Vec<_>>();
11723 for hunk in &hunks {
11724 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11725 }
11726 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11727 }
11728 if !revert_changes.is_empty() {
11729 self.buffer().update(cx, |multi_buffer, cx| {
11730 for (buffer_id, changes) in revert_changes {
11731 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11732 buffer.update(cx, |buffer, cx| {
11733 buffer.edit(
11734 changes
11735 .into_iter()
11736 .map(|(range, text)| (range, text.to_string())),
11737 None,
11738 cx,
11739 );
11740 });
11741 }
11742 }
11743 });
11744 }
11745 }
11746
11747 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11748 if let Some(status) = self
11749 .addons
11750 .iter()
11751 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11752 {
11753 return Some(status);
11754 }
11755 self.project
11756 .as_ref()?
11757 .read(cx)
11758 .status_for_buffer_id(buffer_id, cx)
11759 }
11760
11761 pub fn open_active_item_in_terminal(
11762 &mut self,
11763 _: &OpenInTerminal,
11764 window: &mut Window,
11765 cx: &mut Context<Self>,
11766 ) {
11767 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11768 let project_path = buffer.read(cx).project_path(cx)?;
11769 let project = self.project()?.read(cx);
11770 let entry = project.entry_for_path(&project_path, cx)?;
11771 let parent = match &entry.canonical_path {
11772 Some(canonical_path) => canonical_path.to_path_buf(),
11773 None => project.absolute_path(&project_path, cx)?,
11774 }
11775 .parent()?
11776 .to_path_buf();
11777 Some(parent)
11778 }) {
11779 window.dispatch_action(
11780 OpenTerminal {
11781 working_directory,
11782 local: false,
11783 }
11784 .boxed_clone(),
11785 cx,
11786 );
11787 }
11788 }
11789
11790 fn set_breakpoint_context_menu(
11791 &mut self,
11792 display_row: DisplayRow,
11793 position: Option<Anchor>,
11794 clicked_point: gpui::Point<Pixels>,
11795 window: &mut Window,
11796 cx: &mut Context<Self>,
11797 ) {
11798 let source = self
11799 .buffer
11800 .read(cx)
11801 .snapshot(cx)
11802 .anchor_before(Point::new(display_row.0, 0u32));
11803
11804 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11805
11806 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11807 self,
11808 source,
11809 clicked_point,
11810 context_menu,
11811 window,
11812 cx,
11813 );
11814 }
11815
11816 fn add_edit_breakpoint_block(
11817 &mut self,
11818 anchor: Anchor,
11819 breakpoint: &Breakpoint,
11820 edit_action: BreakpointPromptEditAction,
11821 window: &mut Window,
11822 cx: &mut Context<Self>,
11823 ) {
11824 let weak_editor = cx.weak_entity();
11825 let bp_prompt = cx.new(|cx| {
11826 BreakpointPromptEditor::new(
11827 weak_editor,
11828 anchor,
11829 breakpoint.clone(),
11830 edit_action,
11831 window,
11832 cx,
11833 )
11834 });
11835
11836 let height = bp_prompt.update(cx, |this, cx| {
11837 this.prompt
11838 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11839 });
11840 let cloned_prompt = bp_prompt.clone();
11841 let blocks = vec![BlockProperties {
11842 style: BlockStyle::Sticky,
11843 placement: BlockPlacement::Above(anchor),
11844 height: Some(height),
11845 render: Arc::new(move |cx| {
11846 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11847 cloned_prompt.clone().into_any_element()
11848 }),
11849 priority: 0,
11850 }];
11851
11852 let focus_handle = bp_prompt.focus_handle(cx);
11853 window.focus(&focus_handle, cx);
11854
11855 let block_ids = self.insert_blocks(blocks, None, cx);
11856 bp_prompt.update(cx, |prompt, _| {
11857 prompt.add_block_ids(block_ids);
11858 });
11859 }
11860
11861 pub(crate) fn breakpoint_at_row(
11862 &self,
11863 row: u32,
11864 window: &mut Window,
11865 cx: &mut Context<Self>,
11866 ) -> Option<(Anchor, Breakpoint)> {
11867 let snapshot = self.snapshot(window, cx);
11868 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11869
11870 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11871 }
11872
11873 pub(crate) fn breakpoint_at_anchor(
11874 &self,
11875 breakpoint_position: Anchor,
11876 snapshot: &EditorSnapshot,
11877 cx: &mut Context<Self>,
11878 ) -> Option<(Anchor, Breakpoint)> {
11879 let buffer = self
11880 .buffer
11881 .read(cx)
11882 .buffer_for_anchor(breakpoint_position, cx)?;
11883
11884 let enclosing_excerpt = breakpoint_position.excerpt_id;
11885 let buffer_snapshot = buffer.read(cx).snapshot();
11886
11887 let row = buffer_snapshot
11888 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11889 .row;
11890
11891 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11892 let anchor_end = snapshot
11893 .buffer_snapshot()
11894 .anchor_after(Point::new(row, line_len));
11895
11896 self.breakpoint_store
11897 .as_ref()?
11898 .read_with(cx, |breakpoint_store, cx| {
11899 breakpoint_store
11900 .breakpoints(
11901 &buffer,
11902 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11903 &buffer_snapshot,
11904 cx,
11905 )
11906 .next()
11907 .and_then(|(bp, _)| {
11908 let breakpoint_row = buffer_snapshot
11909 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11910 .row;
11911
11912 if breakpoint_row == row {
11913 snapshot
11914 .buffer_snapshot()
11915 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11916 .map(|position| (position, bp.bp.clone()))
11917 } else {
11918 None
11919 }
11920 })
11921 })
11922 }
11923
11924 pub fn edit_log_breakpoint(
11925 &mut self,
11926 _: &EditLogBreakpoint,
11927 window: &mut Window,
11928 cx: &mut Context<Self>,
11929 ) {
11930 if self.breakpoint_store.is_none() {
11931 return;
11932 }
11933
11934 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11935 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11936 message: None,
11937 state: BreakpointState::Enabled,
11938 condition: None,
11939 hit_condition: None,
11940 });
11941
11942 self.add_edit_breakpoint_block(
11943 anchor,
11944 &breakpoint,
11945 BreakpointPromptEditAction::Log,
11946 window,
11947 cx,
11948 );
11949 }
11950 }
11951
11952 fn breakpoints_at_cursors(
11953 &self,
11954 window: &mut Window,
11955 cx: &mut Context<Self>,
11956 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11957 let snapshot = self.snapshot(window, cx);
11958 let cursors = self
11959 .selections
11960 .disjoint_anchors_arc()
11961 .iter()
11962 .map(|selection| {
11963 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11964
11965 let breakpoint_position = self
11966 .breakpoint_at_row(cursor_position.row, window, cx)
11967 .map(|bp| bp.0)
11968 .unwrap_or_else(|| {
11969 snapshot
11970 .display_snapshot
11971 .buffer_snapshot()
11972 .anchor_after(Point::new(cursor_position.row, 0))
11973 });
11974
11975 let breakpoint = self
11976 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11977 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11978
11979 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11980 })
11981 // 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.
11982 .collect::<HashMap<Anchor, _>>();
11983
11984 cursors.into_iter().collect()
11985 }
11986
11987 pub fn enable_breakpoint(
11988 &mut self,
11989 _: &crate::actions::EnableBreakpoint,
11990 window: &mut Window,
11991 cx: &mut Context<Self>,
11992 ) {
11993 if self.breakpoint_store.is_none() {
11994 return;
11995 }
11996
11997 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11998 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11999 continue;
12000 };
12001 self.edit_breakpoint_at_anchor(
12002 anchor,
12003 breakpoint,
12004 BreakpointEditAction::InvertState,
12005 cx,
12006 );
12007 }
12008 }
12009
12010 pub fn align_selections(
12011 &mut self,
12012 _: &crate::actions::AlignSelections,
12013 window: &mut Window,
12014 cx: &mut Context<Self>,
12015 ) {
12016 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12017
12018 let display_snapshot = self.display_snapshot(cx);
12019
12020 struct CursorData {
12021 anchor: Anchor,
12022 point: Point,
12023 }
12024 let cursor_data: Vec<CursorData> = self
12025 .selections
12026 .disjoint_anchors()
12027 .iter()
12028 .map(|selection| {
12029 let anchor = if selection.reversed {
12030 selection.head()
12031 } else {
12032 selection.tail()
12033 };
12034 CursorData {
12035 anchor: anchor,
12036 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12037 }
12038 })
12039 .collect();
12040
12041 let rows_anchors_count: Vec<usize> = cursor_data
12042 .iter()
12043 .map(|cursor| cursor.point.row)
12044 .chunk_by(|&row| row)
12045 .into_iter()
12046 .map(|(_, group)| group.count())
12047 .collect();
12048 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12049 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12050 let mut edits = Vec::new();
12051
12052 for column_idx in 0..max_columns {
12053 let mut cursor_index = 0;
12054
12055 // Calculate target_column => position that the selections will go
12056 let mut target_column = 0;
12057 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12058 // Skip rows that don't have this column
12059 if column_idx >= *cursor_count {
12060 cursor_index += cursor_count;
12061 continue;
12062 }
12063
12064 let point = &cursor_data[cursor_index + column_idx].point;
12065 let adjusted_column = point.column + rows_column_offset[row_idx];
12066 if adjusted_column > target_column {
12067 target_column = adjusted_column;
12068 }
12069 cursor_index += cursor_count;
12070 }
12071
12072 // Collect edits for this column
12073 cursor_index = 0;
12074 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12075 // Skip rows that don't have this column
12076 if column_idx >= *cursor_count {
12077 cursor_index += *cursor_count;
12078 continue;
12079 }
12080
12081 let point = &cursor_data[cursor_index + column_idx].point;
12082 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12083 if spaces_needed > 0 {
12084 let anchor = cursor_data[cursor_index + column_idx]
12085 .anchor
12086 .bias_left(&display_snapshot);
12087 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12088 }
12089 rows_column_offset[row_idx] += spaces_needed;
12090
12091 cursor_index += *cursor_count;
12092 }
12093 }
12094
12095 if !edits.is_empty() {
12096 self.transact(window, cx, |editor, _window, cx| {
12097 editor.edit(edits, cx);
12098 });
12099 }
12100 }
12101
12102 pub fn disable_breakpoint(
12103 &mut self,
12104 _: &crate::actions::DisableBreakpoint,
12105 window: &mut Window,
12106 cx: &mut Context<Self>,
12107 ) {
12108 if self.breakpoint_store.is_none() {
12109 return;
12110 }
12111
12112 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12113 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12114 continue;
12115 };
12116 self.edit_breakpoint_at_anchor(
12117 anchor,
12118 breakpoint,
12119 BreakpointEditAction::InvertState,
12120 cx,
12121 );
12122 }
12123 }
12124
12125 pub fn toggle_breakpoint(
12126 &mut self,
12127 _: &crate::actions::ToggleBreakpoint,
12128 window: &mut Window,
12129 cx: &mut Context<Self>,
12130 ) {
12131 if self.breakpoint_store.is_none() {
12132 return;
12133 }
12134
12135 let snapshot = self.snapshot(window, cx);
12136 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12137 if self.gutter_breakpoint_indicator.0.is_some() {
12138 let display_row = anchor
12139 .to_point(snapshot.buffer_snapshot())
12140 .to_display_point(&snapshot.display_snapshot)
12141 .row();
12142 self.update_breakpoint_collision_on_toggle(
12143 display_row,
12144 &BreakpointEditAction::Toggle,
12145 );
12146 }
12147
12148 if let Some(breakpoint) = breakpoint {
12149 self.edit_breakpoint_at_anchor(
12150 anchor,
12151 breakpoint,
12152 BreakpointEditAction::Toggle,
12153 cx,
12154 );
12155 } else {
12156 self.edit_breakpoint_at_anchor(
12157 anchor,
12158 Breakpoint::new_standard(),
12159 BreakpointEditAction::Toggle,
12160 cx,
12161 );
12162 }
12163 }
12164 }
12165
12166 fn update_breakpoint_collision_on_toggle(
12167 &mut self,
12168 display_row: DisplayRow,
12169 edit_action: &BreakpointEditAction,
12170 ) {
12171 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12172 if breakpoint_indicator.display_row == display_row
12173 && matches!(edit_action, BreakpointEditAction::Toggle)
12174 {
12175 breakpoint_indicator.collides_with_existing_breakpoint =
12176 !breakpoint_indicator.collides_with_existing_breakpoint;
12177 }
12178 }
12179 }
12180
12181 pub fn edit_breakpoint_at_anchor(
12182 &mut self,
12183 breakpoint_position: Anchor,
12184 breakpoint: Breakpoint,
12185 edit_action: BreakpointEditAction,
12186 cx: &mut Context<Self>,
12187 ) {
12188 let Some(breakpoint_store) = &self.breakpoint_store else {
12189 return;
12190 };
12191
12192 let Some(buffer) = self
12193 .buffer
12194 .read(cx)
12195 .buffer_for_anchor(breakpoint_position, cx)
12196 else {
12197 return;
12198 };
12199
12200 breakpoint_store.update(cx, |breakpoint_store, cx| {
12201 breakpoint_store.toggle_breakpoint(
12202 buffer,
12203 BreakpointWithPosition {
12204 position: breakpoint_position.text_anchor,
12205 bp: breakpoint,
12206 },
12207 edit_action,
12208 cx,
12209 );
12210 });
12211
12212 cx.notify();
12213 }
12214
12215 #[cfg(any(test, feature = "test-support"))]
12216 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12217 self.breakpoint_store.clone()
12218 }
12219
12220 pub fn prepare_restore_change(
12221 &self,
12222 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12223 hunk: &MultiBufferDiffHunk,
12224 cx: &mut App,
12225 ) -> Option<()> {
12226 if hunk.is_created_file() {
12227 return None;
12228 }
12229 let buffer = self.buffer.read(cx);
12230 let diff = buffer.diff_for(hunk.buffer_id)?;
12231 let buffer = buffer.buffer(hunk.buffer_id)?;
12232 let buffer = buffer.read(cx);
12233 let original_text = diff
12234 .read(cx)
12235 .base_text(cx)
12236 .as_rope()
12237 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12238 let buffer_snapshot = buffer.snapshot();
12239 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12240 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12241 probe
12242 .0
12243 .start
12244 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12245 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12246 }) {
12247 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12248 Some(())
12249 } else {
12250 None
12251 }
12252 }
12253
12254 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12255 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12256 }
12257
12258 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12259 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12260 }
12261
12262 pub fn rotate_selections_forward(
12263 &mut self,
12264 _: &RotateSelectionsForward,
12265 window: &mut Window,
12266 cx: &mut Context<Self>,
12267 ) {
12268 self.rotate_selections(window, cx, false)
12269 }
12270
12271 pub fn rotate_selections_backward(
12272 &mut self,
12273 _: &RotateSelectionsBackward,
12274 window: &mut Window,
12275 cx: &mut Context<Self>,
12276 ) {
12277 self.rotate_selections(window, cx, true)
12278 }
12279
12280 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12282 let display_snapshot = self.display_snapshot(cx);
12283 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12284
12285 if selections.len() < 2 {
12286 return;
12287 }
12288
12289 let (edits, new_selections) = {
12290 let buffer = self.buffer.read(cx).read(cx);
12291 let has_selections = selections.iter().any(|s| !s.is_empty());
12292 if has_selections {
12293 let mut selected_texts: Vec<String> = selections
12294 .iter()
12295 .map(|selection| {
12296 buffer
12297 .text_for_range(selection.start..selection.end)
12298 .collect()
12299 })
12300 .collect();
12301
12302 if reverse {
12303 selected_texts.rotate_left(1);
12304 } else {
12305 selected_texts.rotate_right(1);
12306 }
12307
12308 let mut offset_delta: i64 = 0;
12309 let mut new_selections = Vec::new();
12310 let edits: Vec<_> = selections
12311 .iter()
12312 .zip(selected_texts.iter())
12313 .map(|(selection, new_text)| {
12314 let old_len = (selection.end.0 - selection.start.0) as i64;
12315 let new_len = new_text.len() as i64;
12316 let adjusted_start =
12317 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12318 let adjusted_end =
12319 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12320
12321 new_selections.push(Selection {
12322 id: selection.id,
12323 start: adjusted_start,
12324 end: adjusted_end,
12325 reversed: selection.reversed,
12326 goal: selection.goal,
12327 });
12328
12329 offset_delta += new_len - old_len;
12330 (selection.start..selection.end, new_text.clone())
12331 })
12332 .collect();
12333 (edits, new_selections)
12334 } else {
12335 let mut all_rows: Vec<u32> = selections
12336 .iter()
12337 .map(|selection| buffer.offset_to_point(selection.start).row)
12338 .collect();
12339 all_rows.sort_unstable();
12340 all_rows.dedup();
12341
12342 if all_rows.len() < 2 {
12343 return;
12344 }
12345
12346 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12347 .iter()
12348 .map(|&row| {
12349 let start = Point::new(row, 0);
12350 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12351 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12352 })
12353 .collect();
12354
12355 let mut line_texts: Vec<String> = line_ranges
12356 .iter()
12357 .map(|range| buffer.text_for_range(range.clone()).collect())
12358 .collect();
12359
12360 if reverse {
12361 line_texts.rotate_left(1);
12362 } else {
12363 line_texts.rotate_right(1);
12364 }
12365
12366 let edits = line_ranges
12367 .iter()
12368 .zip(line_texts.iter())
12369 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12370 .collect();
12371
12372 let num_rows = all_rows.len();
12373 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12374 .iter()
12375 .enumerate()
12376 .map(|(i, &row)| (row, i))
12377 .collect();
12378
12379 // Compute new line start offsets after rotation (handles CRLF)
12380 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12381 let first_line_start = line_ranges[0].start.0;
12382 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12383 for text in line_texts.iter().take(num_rows - 1) {
12384 let prev_start = *new_line_starts.last().unwrap();
12385 new_line_starts.push(prev_start + text.len() + newline_len);
12386 }
12387
12388 let new_selections = selections
12389 .iter()
12390 .map(|selection| {
12391 let point = buffer.offset_to_point(selection.start);
12392 let old_index = row_to_index[&point.row];
12393 let new_index = if reverse {
12394 (old_index + num_rows - 1) % num_rows
12395 } else {
12396 (old_index + 1) % num_rows
12397 };
12398 let new_offset =
12399 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12400 Selection {
12401 id: selection.id,
12402 start: new_offset,
12403 end: new_offset,
12404 reversed: selection.reversed,
12405 goal: selection.goal,
12406 }
12407 })
12408 .collect();
12409
12410 (edits, new_selections)
12411 }
12412 };
12413
12414 self.transact(window, cx, |this, window, cx| {
12415 this.buffer.update(cx, |buffer, cx| {
12416 buffer.edit(edits, None, cx);
12417 });
12418 this.change_selections(Default::default(), window, cx, |s| {
12419 s.select(new_selections);
12420 });
12421 });
12422 }
12423
12424 fn manipulate_lines<M>(
12425 &mut self,
12426 window: &mut Window,
12427 cx: &mut Context<Self>,
12428 mut manipulate: M,
12429 ) where
12430 M: FnMut(&str) -> LineManipulationResult,
12431 {
12432 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12433
12434 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12435 let buffer = self.buffer.read(cx).snapshot(cx);
12436
12437 let mut edits = Vec::new();
12438
12439 let selections = self.selections.all::<Point>(&display_map);
12440 let mut selections = selections.iter().peekable();
12441 let mut contiguous_row_selections = Vec::new();
12442 let mut new_selections = Vec::new();
12443 let mut added_lines = 0;
12444 let mut removed_lines = 0;
12445
12446 while let Some(selection) = selections.next() {
12447 let (start_row, end_row) = consume_contiguous_rows(
12448 &mut contiguous_row_selections,
12449 selection,
12450 &display_map,
12451 &mut selections,
12452 );
12453
12454 let start_point = Point::new(start_row.0, 0);
12455 let end_point = Point::new(
12456 end_row.previous_row().0,
12457 buffer.line_len(end_row.previous_row()),
12458 );
12459 let text = buffer
12460 .text_for_range(start_point..end_point)
12461 .collect::<String>();
12462
12463 let LineManipulationResult {
12464 new_text,
12465 line_count_before,
12466 line_count_after,
12467 } = manipulate(&text);
12468
12469 edits.push((start_point..end_point, new_text));
12470
12471 // Selections must change based on added and removed line count
12472 let start_row =
12473 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12474 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12475 new_selections.push(Selection {
12476 id: selection.id,
12477 start: start_row,
12478 end: end_row,
12479 goal: SelectionGoal::None,
12480 reversed: selection.reversed,
12481 });
12482
12483 if line_count_after > line_count_before {
12484 added_lines += line_count_after - line_count_before;
12485 } else if line_count_before > line_count_after {
12486 removed_lines += line_count_before - line_count_after;
12487 }
12488 }
12489
12490 self.transact(window, cx, |this, window, cx| {
12491 let buffer = this.buffer.update(cx, |buffer, cx| {
12492 buffer.edit(edits, None, cx);
12493 buffer.snapshot(cx)
12494 });
12495
12496 // Recalculate offsets on newly edited buffer
12497 let new_selections = new_selections
12498 .iter()
12499 .map(|s| {
12500 let start_point = Point::new(s.start.0, 0);
12501 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12502 Selection {
12503 id: s.id,
12504 start: buffer.point_to_offset(start_point),
12505 end: buffer.point_to_offset(end_point),
12506 goal: s.goal,
12507 reversed: s.reversed,
12508 }
12509 })
12510 .collect();
12511
12512 this.change_selections(Default::default(), window, cx, |s| {
12513 s.select(new_selections);
12514 });
12515
12516 this.request_autoscroll(Autoscroll::fit(), cx);
12517 });
12518 }
12519
12520 fn manipulate_immutable_lines<Fn>(
12521 &mut self,
12522 window: &mut Window,
12523 cx: &mut Context<Self>,
12524 mut callback: Fn,
12525 ) where
12526 Fn: FnMut(&mut Vec<&str>),
12527 {
12528 self.manipulate_lines(window, cx, |text| {
12529 let mut lines: Vec<&str> = text.split('\n').collect();
12530 let line_count_before = lines.len();
12531
12532 callback(&mut lines);
12533
12534 LineManipulationResult {
12535 new_text: lines.join("\n"),
12536 line_count_before,
12537 line_count_after: lines.len(),
12538 }
12539 });
12540 }
12541
12542 fn manipulate_mutable_lines<Fn>(
12543 &mut self,
12544 window: &mut Window,
12545 cx: &mut Context<Self>,
12546 mut callback: Fn,
12547 ) where
12548 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12549 {
12550 self.manipulate_lines(window, cx, |text| {
12551 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12552 let line_count_before = lines.len();
12553
12554 callback(&mut lines);
12555
12556 LineManipulationResult {
12557 new_text: lines.join("\n"),
12558 line_count_before,
12559 line_count_after: lines.len(),
12560 }
12561 });
12562 }
12563
12564 pub fn convert_indentation_to_spaces(
12565 &mut self,
12566 _: &ConvertIndentationToSpaces,
12567 window: &mut Window,
12568 cx: &mut Context<Self>,
12569 ) {
12570 let settings = self.buffer.read(cx).language_settings(cx);
12571 let tab_size = settings.tab_size.get() as usize;
12572
12573 self.manipulate_mutable_lines(window, cx, |lines| {
12574 // Allocates a reasonably sized scratch buffer once for the whole loop
12575 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12576 // Avoids recomputing spaces that could be inserted many times
12577 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12578 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12579 .collect();
12580
12581 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12582 let mut chars = line.as_ref().chars();
12583 let mut col = 0;
12584 let mut changed = false;
12585
12586 for ch in chars.by_ref() {
12587 match ch {
12588 ' ' => {
12589 reindented_line.push(' ');
12590 col += 1;
12591 }
12592 '\t' => {
12593 // \t are converted to spaces depending on the current column
12594 let spaces_len = tab_size - (col % tab_size);
12595 reindented_line.extend(&space_cache[spaces_len - 1]);
12596 col += spaces_len;
12597 changed = true;
12598 }
12599 _ => {
12600 // If we dont append before break, the character is consumed
12601 reindented_line.push(ch);
12602 break;
12603 }
12604 }
12605 }
12606
12607 if !changed {
12608 reindented_line.clear();
12609 continue;
12610 }
12611 // Append the rest of the line and replace old reference with new one
12612 reindented_line.extend(chars);
12613 *line = Cow::Owned(reindented_line.clone());
12614 reindented_line.clear();
12615 }
12616 });
12617 }
12618
12619 pub fn convert_indentation_to_tabs(
12620 &mut self,
12621 _: &ConvertIndentationToTabs,
12622 window: &mut Window,
12623 cx: &mut Context<Self>,
12624 ) {
12625 let settings = self.buffer.read(cx).language_settings(cx);
12626 let tab_size = settings.tab_size.get() as usize;
12627
12628 self.manipulate_mutable_lines(window, cx, |lines| {
12629 // Allocates a reasonably sized buffer once for the whole loop
12630 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12631 // Avoids recomputing spaces that could be inserted many times
12632 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12633 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12634 .collect();
12635
12636 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12637 let mut chars = line.chars();
12638 let mut spaces_count = 0;
12639 let mut first_non_indent_char = None;
12640 let mut changed = false;
12641
12642 for ch in chars.by_ref() {
12643 match ch {
12644 ' ' => {
12645 // Keep track of spaces. Append \t when we reach tab_size
12646 spaces_count += 1;
12647 changed = true;
12648 if spaces_count == tab_size {
12649 reindented_line.push('\t');
12650 spaces_count = 0;
12651 }
12652 }
12653 '\t' => {
12654 reindented_line.push('\t');
12655 spaces_count = 0;
12656 }
12657 _ => {
12658 // Dont append it yet, we might have remaining spaces
12659 first_non_indent_char = Some(ch);
12660 break;
12661 }
12662 }
12663 }
12664
12665 if !changed {
12666 reindented_line.clear();
12667 continue;
12668 }
12669 // Remaining spaces that didn't make a full tab stop
12670 if spaces_count > 0 {
12671 reindented_line.extend(&space_cache[spaces_count - 1]);
12672 }
12673 // If we consume an extra character that was not indentation, add it back
12674 if let Some(extra_char) = first_non_indent_char {
12675 reindented_line.push(extra_char);
12676 }
12677 // Append the rest of the line and replace old reference with new one
12678 reindented_line.extend(chars);
12679 *line = Cow::Owned(reindented_line.clone());
12680 reindented_line.clear();
12681 }
12682 });
12683 }
12684
12685 pub fn convert_to_upper_case(
12686 &mut self,
12687 _: &ConvertToUpperCase,
12688 window: &mut Window,
12689 cx: &mut Context<Self>,
12690 ) {
12691 self.manipulate_text(window, cx, |text| text.to_uppercase())
12692 }
12693
12694 pub fn convert_to_lower_case(
12695 &mut self,
12696 _: &ConvertToLowerCase,
12697 window: &mut Window,
12698 cx: &mut Context<Self>,
12699 ) {
12700 self.manipulate_text(window, cx, |text| text.to_lowercase())
12701 }
12702
12703 pub fn convert_to_title_case(
12704 &mut self,
12705 _: &ConvertToTitleCase,
12706 window: &mut Window,
12707 cx: &mut Context<Self>,
12708 ) {
12709 self.manipulate_text(window, cx, |text| {
12710 Self::convert_text_case(text, Case::Title)
12711 })
12712 }
12713
12714 pub fn convert_to_snake_case(
12715 &mut self,
12716 _: &ConvertToSnakeCase,
12717 window: &mut Window,
12718 cx: &mut Context<Self>,
12719 ) {
12720 self.manipulate_text(window, cx, |text| {
12721 Self::convert_text_case(text, Case::Snake)
12722 })
12723 }
12724
12725 pub fn convert_to_kebab_case(
12726 &mut self,
12727 _: &ConvertToKebabCase,
12728 window: &mut Window,
12729 cx: &mut Context<Self>,
12730 ) {
12731 self.manipulate_text(window, cx, |text| {
12732 Self::convert_text_case(text, Case::Kebab)
12733 })
12734 }
12735
12736 pub fn convert_to_upper_camel_case(
12737 &mut self,
12738 _: &ConvertToUpperCamelCase,
12739 window: &mut Window,
12740 cx: &mut Context<Self>,
12741 ) {
12742 self.manipulate_text(window, cx, |text| {
12743 Self::convert_text_case(text, Case::UpperCamel)
12744 })
12745 }
12746
12747 pub fn convert_to_lower_camel_case(
12748 &mut self,
12749 _: &ConvertToLowerCamelCase,
12750 window: &mut Window,
12751 cx: &mut Context<Self>,
12752 ) {
12753 self.manipulate_text(window, cx, |text| {
12754 Self::convert_text_case(text, Case::Camel)
12755 })
12756 }
12757
12758 pub fn convert_to_opposite_case(
12759 &mut self,
12760 _: &ConvertToOppositeCase,
12761 window: &mut Window,
12762 cx: &mut Context<Self>,
12763 ) {
12764 self.manipulate_text(window, cx, |text| {
12765 text.chars()
12766 .fold(String::with_capacity(text.len()), |mut t, c| {
12767 if c.is_uppercase() {
12768 t.extend(c.to_lowercase());
12769 } else {
12770 t.extend(c.to_uppercase());
12771 }
12772 t
12773 })
12774 })
12775 }
12776
12777 pub fn convert_to_sentence_case(
12778 &mut self,
12779 _: &ConvertToSentenceCase,
12780 window: &mut Window,
12781 cx: &mut Context<Self>,
12782 ) {
12783 self.manipulate_text(window, cx, |text| {
12784 Self::convert_text_case(text, Case::Sentence)
12785 })
12786 }
12787
12788 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12789 self.manipulate_text(window, cx, |text| {
12790 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12791 if has_upper_case_characters {
12792 text.to_lowercase()
12793 } else {
12794 text.to_uppercase()
12795 }
12796 })
12797 }
12798
12799 pub fn convert_to_rot13(
12800 &mut self,
12801 _: &ConvertToRot13,
12802 window: &mut Window,
12803 cx: &mut Context<Self>,
12804 ) {
12805 self.manipulate_text(window, cx, |text| {
12806 text.chars()
12807 .map(|c| match c {
12808 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12809 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12810 _ => c,
12811 })
12812 .collect()
12813 })
12814 }
12815
12816 fn convert_text_case(text: &str, case: Case) -> String {
12817 text.lines()
12818 .map(|line| {
12819 let trimmed_start = line.trim_start();
12820 let leading = &line[..line.len() - trimmed_start.len()];
12821 let trimmed = trimmed_start.trim_end();
12822 let trailing = &trimmed_start[trimmed.len()..];
12823 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12824 })
12825 .join("\n")
12826 }
12827
12828 pub fn convert_to_rot47(
12829 &mut self,
12830 _: &ConvertToRot47,
12831 window: &mut Window,
12832 cx: &mut Context<Self>,
12833 ) {
12834 self.manipulate_text(window, cx, |text| {
12835 text.chars()
12836 .map(|c| {
12837 let code_point = c as u32;
12838 if code_point >= 33 && code_point <= 126 {
12839 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12840 }
12841 c
12842 })
12843 .collect()
12844 })
12845 }
12846
12847 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12848 where
12849 Fn: FnMut(&str) -> String,
12850 {
12851 let buffer = self.buffer.read(cx).snapshot(cx);
12852
12853 let mut new_selections = Vec::new();
12854 let mut edits = Vec::new();
12855 let mut selection_adjustment = 0isize;
12856
12857 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12858 let selection_is_empty = selection.is_empty();
12859
12860 let (start, end) = if selection_is_empty {
12861 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12862 (word_range.start, word_range.end)
12863 } else {
12864 (
12865 buffer.point_to_offset(selection.start),
12866 buffer.point_to_offset(selection.end),
12867 )
12868 };
12869
12870 let text = buffer.text_for_range(start..end).collect::<String>();
12871 let old_length = text.len() as isize;
12872 let text = callback(&text);
12873
12874 new_selections.push(Selection {
12875 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12876 end: MultiBufferOffset(
12877 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12878 ),
12879 goal: SelectionGoal::None,
12880 id: selection.id,
12881 reversed: selection.reversed,
12882 });
12883
12884 selection_adjustment += old_length - text.len() as isize;
12885
12886 edits.push((start..end, text));
12887 }
12888
12889 self.transact(window, cx, |this, window, cx| {
12890 this.buffer.update(cx, |buffer, cx| {
12891 buffer.edit(edits, None, cx);
12892 });
12893
12894 this.change_selections(Default::default(), window, cx, |s| {
12895 s.select(new_selections);
12896 });
12897
12898 this.request_autoscroll(Autoscroll::fit(), cx);
12899 });
12900 }
12901
12902 pub fn move_selection_on_drop(
12903 &mut self,
12904 selection: &Selection<Anchor>,
12905 target: DisplayPoint,
12906 is_cut: bool,
12907 window: &mut Window,
12908 cx: &mut Context<Self>,
12909 ) {
12910 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12911 let buffer = display_map.buffer_snapshot();
12912 let mut edits = Vec::new();
12913 let insert_point = display_map
12914 .clip_point(target, Bias::Left)
12915 .to_point(&display_map);
12916 let text = buffer
12917 .text_for_range(selection.start..selection.end)
12918 .collect::<String>();
12919 if is_cut {
12920 edits.push(((selection.start..selection.end), String::new()));
12921 }
12922 let insert_anchor = buffer.anchor_before(insert_point);
12923 edits.push(((insert_anchor..insert_anchor), text));
12924 let last_edit_start = insert_anchor.bias_left(buffer);
12925 let last_edit_end = insert_anchor.bias_right(buffer);
12926 self.transact(window, cx, |this, window, cx| {
12927 this.buffer.update(cx, |buffer, cx| {
12928 buffer.edit(edits, None, cx);
12929 });
12930 this.change_selections(Default::default(), window, cx, |s| {
12931 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12932 });
12933 });
12934 }
12935
12936 pub fn clear_selection_drag_state(&mut self) {
12937 self.selection_drag_state = SelectionDragState::None;
12938 }
12939
12940 pub fn duplicate(
12941 &mut self,
12942 upwards: bool,
12943 whole_lines: bool,
12944 window: &mut Window,
12945 cx: &mut Context<Self>,
12946 ) {
12947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12948
12949 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12950 let buffer = display_map.buffer_snapshot();
12951 let selections = self.selections.all::<Point>(&display_map);
12952
12953 let mut edits = Vec::new();
12954 let mut selections_iter = selections.iter().peekable();
12955 while let Some(selection) = selections_iter.next() {
12956 let mut rows = selection.spanned_rows(false, &display_map);
12957 // duplicate line-wise
12958 if whole_lines || selection.start == selection.end {
12959 // Avoid duplicating the same lines twice.
12960 while let Some(next_selection) = selections_iter.peek() {
12961 let next_rows = next_selection.spanned_rows(false, &display_map);
12962 if next_rows.start < rows.end {
12963 rows.end = next_rows.end;
12964 selections_iter.next().unwrap();
12965 } else {
12966 break;
12967 }
12968 }
12969
12970 // Copy the text from the selected row region and splice it either at the start
12971 // or end of the region.
12972 let start = Point::new(rows.start.0, 0);
12973 let end = Point::new(
12974 rows.end.previous_row().0,
12975 buffer.line_len(rows.end.previous_row()),
12976 );
12977
12978 let mut text = buffer.text_for_range(start..end).collect::<String>();
12979
12980 let insert_location = if upwards {
12981 // When duplicating upward, we need to insert before the current line.
12982 // If we're on the last line and it doesn't end with a newline,
12983 // we need to add a newline before the duplicated content.
12984 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12985 && buffer.max_point().column > 0
12986 && !text.ends_with('\n');
12987
12988 if needs_leading_newline {
12989 text.insert(0, '\n');
12990 end
12991 } else {
12992 text.push('\n');
12993 Point::new(rows.start.0, 0)
12994 }
12995 } else {
12996 text.push('\n');
12997 start
12998 };
12999 edits.push((insert_location..insert_location, text));
13000 } else {
13001 // duplicate character-wise
13002 let start = selection.start;
13003 let end = selection.end;
13004 let text = buffer.text_for_range(start..end).collect::<String>();
13005 edits.push((selection.end..selection.end, text));
13006 }
13007 }
13008
13009 self.transact(window, cx, |this, window, cx| {
13010 this.buffer.update(cx, |buffer, cx| {
13011 buffer.edit(edits, None, cx);
13012 });
13013
13014 // When duplicating upward with whole lines, move the cursor to the duplicated line
13015 if upwards && whole_lines {
13016 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13017
13018 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13019 let mut new_ranges = Vec::new();
13020 let selections = s.all::<Point>(&display_map);
13021 let mut selections_iter = selections.iter().peekable();
13022
13023 while let Some(first_selection) = selections_iter.next() {
13024 // Group contiguous selections together to find the total row span
13025 let mut group_selections = vec![first_selection];
13026 let mut rows = first_selection.spanned_rows(false, &display_map);
13027
13028 while let Some(next_selection) = selections_iter.peek() {
13029 let next_rows = next_selection.spanned_rows(false, &display_map);
13030 if next_rows.start < rows.end {
13031 rows.end = next_rows.end;
13032 group_selections.push(selections_iter.next().unwrap());
13033 } else {
13034 break;
13035 }
13036 }
13037
13038 let row_count = rows.end.0 - rows.start.0;
13039
13040 // Move all selections in this group up by the total number of duplicated rows
13041 for selection in group_selections {
13042 let new_start = Point::new(
13043 selection.start.row.saturating_sub(row_count),
13044 selection.start.column,
13045 );
13046
13047 let new_end = Point::new(
13048 selection.end.row.saturating_sub(row_count),
13049 selection.end.column,
13050 );
13051
13052 new_ranges.push(new_start..new_end);
13053 }
13054 }
13055
13056 s.select_ranges(new_ranges);
13057 });
13058 }
13059
13060 this.request_autoscroll(Autoscroll::fit(), cx);
13061 });
13062 }
13063
13064 pub fn duplicate_line_up(
13065 &mut self,
13066 _: &DuplicateLineUp,
13067 window: &mut Window,
13068 cx: &mut Context<Self>,
13069 ) {
13070 self.duplicate(true, true, window, cx);
13071 }
13072
13073 pub fn duplicate_line_down(
13074 &mut self,
13075 _: &DuplicateLineDown,
13076 window: &mut Window,
13077 cx: &mut Context<Self>,
13078 ) {
13079 self.duplicate(false, true, window, cx);
13080 }
13081
13082 pub fn duplicate_selection(
13083 &mut self,
13084 _: &DuplicateSelection,
13085 window: &mut Window,
13086 cx: &mut Context<Self>,
13087 ) {
13088 self.duplicate(false, false, window, cx);
13089 }
13090
13091 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13092 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13093 if self.mode.is_single_line() {
13094 cx.propagate();
13095 return;
13096 }
13097
13098 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13099 let buffer = self.buffer.read(cx).snapshot(cx);
13100
13101 let mut edits = Vec::new();
13102 let mut unfold_ranges = Vec::new();
13103 let mut refold_creases = Vec::new();
13104
13105 let selections = self.selections.all::<Point>(&display_map);
13106 let mut selections = selections.iter().peekable();
13107 let mut contiguous_row_selections = Vec::new();
13108 let mut new_selections = Vec::new();
13109
13110 while let Some(selection) = selections.next() {
13111 // Find all the selections that span a contiguous row range
13112 let (start_row, end_row) = consume_contiguous_rows(
13113 &mut contiguous_row_selections,
13114 selection,
13115 &display_map,
13116 &mut selections,
13117 );
13118
13119 // Move the text spanned by the row range to be before the line preceding the row range
13120 if start_row.0 > 0 {
13121 let range_to_move = Point::new(
13122 start_row.previous_row().0,
13123 buffer.line_len(start_row.previous_row()),
13124 )
13125 ..Point::new(
13126 end_row.previous_row().0,
13127 buffer.line_len(end_row.previous_row()),
13128 );
13129 let insertion_point = display_map
13130 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13131 .0;
13132
13133 // Don't move lines across excerpts
13134 if buffer
13135 .excerpt_containing(insertion_point..range_to_move.end)
13136 .is_some()
13137 {
13138 let text = buffer
13139 .text_for_range(range_to_move.clone())
13140 .flat_map(|s| s.chars())
13141 .skip(1)
13142 .chain(['\n'])
13143 .collect::<String>();
13144
13145 edits.push((
13146 buffer.anchor_after(range_to_move.start)
13147 ..buffer.anchor_before(range_to_move.end),
13148 String::new(),
13149 ));
13150 let insertion_anchor = buffer.anchor_after(insertion_point);
13151 edits.push((insertion_anchor..insertion_anchor, text));
13152
13153 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13154
13155 // Move selections up
13156 new_selections.extend(contiguous_row_selections.drain(..).map(
13157 |mut selection| {
13158 selection.start.row -= row_delta;
13159 selection.end.row -= row_delta;
13160 selection
13161 },
13162 ));
13163
13164 // Move folds up
13165 unfold_ranges.push(range_to_move.clone());
13166 for fold in display_map.folds_in_range(
13167 buffer.anchor_before(range_to_move.start)
13168 ..buffer.anchor_after(range_to_move.end),
13169 ) {
13170 let mut start = fold.range.start.to_point(&buffer);
13171 let mut end = fold.range.end.to_point(&buffer);
13172 start.row -= row_delta;
13173 end.row -= row_delta;
13174 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13175 }
13176 }
13177 }
13178
13179 // If we didn't move line(s), preserve the existing selections
13180 new_selections.append(&mut contiguous_row_selections);
13181 }
13182
13183 self.transact(window, cx, |this, window, cx| {
13184 this.unfold_ranges(&unfold_ranges, true, true, cx);
13185 this.buffer.update(cx, |buffer, cx| {
13186 for (range, text) in edits {
13187 buffer.edit([(range, text)], None, cx);
13188 }
13189 });
13190 this.fold_creases(refold_creases, true, window, cx);
13191 this.change_selections(Default::default(), window, cx, |s| {
13192 s.select(new_selections);
13193 })
13194 });
13195 }
13196
13197 pub fn move_line_down(
13198 &mut self,
13199 _: &MoveLineDown,
13200 window: &mut Window,
13201 cx: &mut Context<Self>,
13202 ) {
13203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13204 if self.mode.is_single_line() {
13205 cx.propagate();
13206 return;
13207 }
13208
13209 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13210 let buffer = self.buffer.read(cx).snapshot(cx);
13211
13212 let mut edits = Vec::new();
13213 let mut unfold_ranges = Vec::new();
13214 let mut refold_creases = Vec::new();
13215
13216 let selections = self.selections.all::<Point>(&display_map);
13217 let mut selections = selections.iter().peekable();
13218 let mut contiguous_row_selections = Vec::new();
13219 let mut new_selections = Vec::new();
13220
13221 while let Some(selection) = selections.next() {
13222 // Find all the selections that span a contiguous row range
13223 let (start_row, end_row) = consume_contiguous_rows(
13224 &mut contiguous_row_selections,
13225 selection,
13226 &display_map,
13227 &mut selections,
13228 );
13229
13230 // Move the text spanned by the row range to be after the last line of the row range
13231 if end_row.0 <= buffer.max_point().row {
13232 let range_to_move =
13233 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13234 let insertion_point = display_map
13235 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13236 .0;
13237
13238 // Don't move lines across excerpt boundaries
13239 if buffer
13240 .excerpt_containing(range_to_move.start..insertion_point)
13241 .is_some()
13242 {
13243 let mut text = String::from("\n");
13244 text.extend(buffer.text_for_range(range_to_move.clone()));
13245 text.pop(); // Drop trailing newline
13246 edits.push((
13247 buffer.anchor_after(range_to_move.start)
13248 ..buffer.anchor_before(range_to_move.end),
13249 String::new(),
13250 ));
13251 let insertion_anchor = buffer.anchor_after(insertion_point);
13252 edits.push((insertion_anchor..insertion_anchor, text));
13253
13254 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13255
13256 // Move selections down
13257 new_selections.extend(contiguous_row_selections.drain(..).map(
13258 |mut selection| {
13259 selection.start.row += row_delta;
13260 selection.end.row += row_delta;
13261 selection
13262 },
13263 ));
13264
13265 // Move folds down
13266 unfold_ranges.push(range_to_move.clone());
13267 for fold in display_map.folds_in_range(
13268 buffer.anchor_before(range_to_move.start)
13269 ..buffer.anchor_after(range_to_move.end),
13270 ) {
13271 let mut start = fold.range.start.to_point(&buffer);
13272 let mut end = fold.range.end.to_point(&buffer);
13273 start.row += row_delta;
13274 end.row += row_delta;
13275 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13276 }
13277 }
13278 }
13279
13280 // If we didn't move line(s), preserve the existing selections
13281 new_selections.append(&mut contiguous_row_selections);
13282 }
13283
13284 self.transact(window, cx, |this, window, cx| {
13285 this.unfold_ranges(&unfold_ranges, true, true, cx);
13286 this.buffer.update(cx, |buffer, cx| {
13287 for (range, text) in edits {
13288 buffer.edit([(range, text)], None, cx);
13289 }
13290 });
13291 this.fold_creases(refold_creases, true, window, cx);
13292 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13293 });
13294 }
13295
13296 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13297 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13298 let text_layout_details = &self.text_layout_details(window, cx);
13299 self.transact(window, cx, |this, window, cx| {
13300 let edits = this.change_selections(Default::default(), window, cx, |s| {
13301 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13302 s.move_with(&mut |display_map, selection| {
13303 if !selection.is_empty() {
13304 return;
13305 }
13306
13307 let mut head = selection.head();
13308 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13309 if head.column() == display_map.line_len(head.row()) {
13310 transpose_offset = display_map
13311 .buffer_snapshot()
13312 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13313 }
13314
13315 if transpose_offset == MultiBufferOffset(0) {
13316 return;
13317 }
13318
13319 *head.column_mut() += 1;
13320 head = display_map.clip_point(head, Bias::Right);
13321 let goal = SelectionGoal::HorizontalPosition(
13322 display_map
13323 .x_for_display_point(head, text_layout_details)
13324 .into(),
13325 );
13326 selection.collapse_to(head, goal);
13327
13328 let transpose_start = display_map
13329 .buffer_snapshot()
13330 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13331 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13332 let transpose_end = display_map
13333 .buffer_snapshot()
13334 .clip_offset(transpose_offset + 1usize, Bias::Right);
13335 if let Some(ch) = display_map
13336 .buffer_snapshot()
13337 .chars_at(transpose_start)
13338 .next()
13339 {
13340 edits.push((transpose_start..transpose_offset, String::new()));
13341 edits.push((transpose_end..transpose_end, ch.to_string()));
13342 }
13343 }
13344 });
13345 edits
13346 });
13347 this.buffer
13348 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13349 let selections = this
13350 .selections
13351 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13352 this.change_selections(Default::default(), window, cx, |s| {
13353 s.select(selections);
13354 });
13355 });
13356 }
13357
13358 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13359 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13360 if self.mode.is_single_line() {
13361 cx.propagate();
13362 return;
13363 }
13364
13365 self.rewrap_impl(RewrapOptions::default(), cx)
13366 }
13367
13368 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13369 let buffer = self.buffer.read(cx).snapshot(cx);
13370 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13371
13372 #[derive(Clone, Debug, PartialEq)]
13373 enum CommentFormat {
13374 /// single line comment, with prefix for line
13375 Line(String),
13376 /// single line within a block comment, with prefix for line
13377 BlockLine(String),
13378 /// a single line of a block comment that includes the initial delimiter
13379 BlockCommentWithStart(BlockCommentConfig),
13380 /// a single line of a block comment that includes the ending delimiter
13381 BlockCommentWithEnd(BlockCommentConfig),
13382 }
13383
13384 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13385 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13386 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13387 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13388 .peekable();
13389
13390 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13391 row
13392 } else {
13393 return Vec::new();
13394 };
13395
13396 let language_settings = buffer.language_settings_at(selection.head(), cx);
13397 let language_scope = buffer.language_scope_at(selection.head());
13398
13399 let indent_and_prefix_for_row =
13400 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13401 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13402 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13403 &language_scope
13404 {
13405 let indent_end = Point::new(row, indent.len);
13406 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13407 let line_text_after_indent = buffer
13408 .text_for_range(indent_end..line_end)
13409 .collect::<String>();
13410
13411 let is_within_comment_override = buffer
13412 .language_scope_at(indent_end)
13413 .is_some_and(|scope| scope.override_name() == Some("comment"));
13414 let comment_delimiters = if is_within_comment_override {
13415 // we are within a comment syntax node, but we don't
13416 // yet know what kind of comment: block, doc or line
13417 match (
13418 language_scope.documentation_comment(),
13419 language_scope.block_comment(),
13420 ) {
13421 (Some(config), _) | (_, Some(config))
13422 if buffer.contains_str_at(indent_end, &config.start) =>
13423 {
13424 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13425 }
13426 (Some(config), _) | (_, Some(config))
13427 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13428 {
13429 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13430 }
13431 (Some(config), _) | (_, Some(config))
13432 if buffer.contains_str_at(indent_end, &config.prefix) =>
13433 {
13434 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13435 }
13436 (_, _) => language_scope
13437 .line_comment_prefixes()
13438 .iter()
13439 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13440 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13441 }
13442 } else {
13443 // we not in an overridden comment node, but we may
13444 // be within a non-overridden line comment node
13445 language_scope
13446 .line_comment_prefixes()
13447 .iter()
13448 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13449 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13450 };
13451
13452 let rewrap_prefix = language_scope
13453 .rewrap_prefixes()
13454 .iter()
13455 .find_map(|prefix_regex| {
13456 prefix_regex.find(&line_text_after_indent).map(|mat| {
13457 if mat.start() == 0 {
13458 Some(mat.as_str().to_string())
13459 } else {
13460 None
13461 }
13462 })
13463 })
13464 .flatten();
13465 (comment_delimiters, rewrap_prefix)
13466 } else {
13467 (None, None)
13468 };
13469 (indent, comment_prefix, rewrap_prefix)
13470 };
13471
13472 let mut ranges = Vec::new();
13473 let from_empty_selection = selection.is_empty();
13474
13475 let mut current_range_start = first_row;
13476 let mut prev_row = first_row;
13477 let (
13478 mut current_range_indent,
13479 mut current_range_comment_delimiters,
13480 mut current_range_rewrap_prefix,
13481 ) = indent_and_prefix_for_row(first_row);
13482
13483 for row in non_blank_rows_iter.skip(1) {
13484 let has_paragraph_break = row > prev_row + 1;
13485
13486 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13487 indent_and_prefix_for_row(row);
13488
13489 let has_indent_change = row_indent != current_range_indent;
13490 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13491
13492 let has_boundary_change = has_comment_change
13493 || row_rewrap_prefix.is_some()
13494 || (has_indent_change && current_range_comment_delimiters.is_some());
13495
13496 if has_paragraph_break || has_boundary_change {
13497 ranges.push((
13498 language_settings.clone(),
13499 Point::new(current_range_start, 0)
13500 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13501 current_range_indent,
13502 current_range_comment_delimiters.clone(),
13503 current_range_rewrap_prefix.clone(),
13504 from_empty_selection,
13505 ));
13506 current_range_start = row;
13507 current_range_indent = row_indent;
13508 current_range_comment_delimiters = row_comment_delimiters;
13509 current_range_rewrap_prefix = row_rewrap_prefix;
13510 }
13511 prev_row = row;
13512 }
13513
13514 ranges.push((
13515 language_settings.clone(),
13516 Point::new(current_range_start, 0)
13517 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13518 current_range_indent,
13519 current_range_comment_delimiters,
13520 current_range_rewrap_prefix,
13521 from_empty_selection,
13522 ));
13523
13524 ranges
13525 });
13526
13527 let mut edits = Vec::new();
13528 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13529
13530 for (
13531 language_settings,
13532 wrap_range,
13533 mut indent_size,
13534 comment_prefix,
13535 rewrap_prefix,
13536 from_empty_selection,
13537 ) in wrap_ranges
13538 {
13539 let mut start_row = wrap_range.start.row;
13540 let mut end_row = wrap_range.end.row;
13541
13542 // Skip selections that overlap with a range that has already been rewrapped.
13543 let selection_range = start_row..end_row;
13544 if rewrapped_row_ranges
13545 .iter()
13546 .any(|range| range.overlaps(&selection_range))
13547 {
13548 continue;
13549 }
13550
13551 let tab_size = language_settings.tab_size;
13552
13553 let (line_prefix, inside_comment) = match &comment_prefix {
13554 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13555 (Some(prefix.as_str()), true)
13556 }
13557 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13558 (Some(prefix.as_ref()), true)
13559 }
13560 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13561 start: _,
13562 end: _,
13563 prefix,
13564 tab_size,
13565 })) => {
13566 indent_size.len += tab_size;
13567 (Some(prefix.as_ref()), true)
13568 }
13569 None => (None, false),
13570 };
13571 let indent_prefix = indent_size.chars().collect::<String>();
13572 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13573
13574 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13575 RewrapBehavior::InComments => inside_comment,
13576 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13577 RewrapBehavior::Anywhere => true,
13578 };
13579
13580 let should_rewrap = options.override_language_settings
13581 || allow_rewrap_based_on_language
13582 || self.hard_wrap.is_some();
13583 if !should_rewrap {
13584 continue;
13585 }
13586
13587 if from_empty_selection {
13588 'expand_upwards: while start_row > 0 {
13589 let prev_row = start_row - 1;
13590 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13591 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13592 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13593 {
13594 start_row = prev_row;
13595 } else {
13596 break 'expand_upwards;
13597 }
13598 }
13599
13600 'expand_downwards: while end_row < buffer.max_point().row {
13601 let next_row = end_row + 1;
13602 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13603 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13604 && !buffer.is_line_blank(MultiBufferRow(next_row))
13605 {
13606 end_row = next_row;
13607 } else {
13608 break 'expand_downwards;
13609 }
13610 }
13611 }
13612
13613 let start = Point::new(start_row, 0);
13614 let start_offset = ToOffset::to_offset(&start, &buffer);
13615 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13616 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13617 let mut first_line_delimiter = None;
13618 let mut last_line_delimiter = None;
13619 let Some(lines_without_prefixes) = selection_text
13620 .lines()
13621 .enumerate()
13622 .map(|(ix, line)| {
13623 let line_trimmed = line.trim_start();
13624 if rewrap_prefix.is_some() && ix > 0 {
13625 Ok(line_trimmed)
13626 } else if let Some(
13627 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13628 start,
13629 prefix,
13630 end,
13631 tab_size,
13632 })
13633 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13634 start,
13635 prefix,
13636 end,
13637 tab_size,
13638 }),
13639 ) = &comment_prefix
13640 {
13641 let line_trimmed = line_trimmed
13642 .strip_prefix(start.as_ref())
13643 .map(|s| {
13644 let mut indent_size = indent_size;
13645 indent_size.len -= tab_size;
13646 let indent_prefix: String = indent_size.chars().collect();
13647 first_line_delimiter = Some((indent_prefix, start));
13648 s.trim_start()
13649 })
13650 .unwrap_or(line_trimmed);
13651 let line_trimmed = line_trimmed
13652 .strip_suffix(end.as_ref())
13653 .map(|s| {
13654 last_line_delimiter = Some(end);
13655 s.trim_end()
13656 })
13657 .unwrap_or(line_trimmed);
13658 let line_trimmed = line_trimmed
13659 .strip_prefix(prefix.as_ref())
13660 .unwrap_or(line_trimmed);
13661 Ok(line_trimmed)
13662 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13663 line_trimmed.strip_prefix(prefix).with_context(|| {
13664 format!("line did not start with prefix {prefix:?}: {line:?}")
13665 })
13666 } else {
13667 line_trimmed
13668 .strip_prefix(&line_prefix.trim_start())
13669 .with_context(|| {
13670 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13671 })
13672 }
13673 })
13674 .collect::<Result<Vec<_>, _>>()
13675 .log_err()
13676 else {
13677 continue;
13678 };
13679
13680 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13681 buffer
13682 .language_settings_at(Point::new(start_row, 0), cx)
13683 .preferred_line_length as usize
13684 });
13685
13686 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13687 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13688 } else {
13689 line_prefix.clone()
13690 };
13691
13692 let wrapped_text = {
13693 let mut wrapped_text = wrap_with_prefix(
13694 line_prefix,
13695 subsequent_lines_prefix,
13696 lines_without_prefixes.join("\n"),
13697 wrap_column,
13698 tab_size,
13699 options.preserve_existing_whitespace,
13700 );
13701
13702 if let Some((indent, delimiter)) = first_line_delimiter {
13703 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13704 }
13705 if let Some(last_line) = last_line_delimiter {
13706 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13707 }
13708
13709 wrapped_text
13710 };
13711
13712 // TODO: should always use char-based diff while still supporting cursor behavior that
13713 // matches vim.
13714 let mut diff_options = DiffOptions::default();
13715 if options.override_language_settings {
13716 diff_options.max_word_diff_len = 0;
13717 diff_options.max_word_diff_line_count = 0;
13718 } else {
13719 diff_options.max_word_diff_len = usize::MAX;
13720 diff_options.max_word_diff_line_count = usize::MAX;
13721 }
13722
13723 for (old_range, new_text) in
13724 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13725 {
13726 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13727 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13728 edits.push((edit_start..edit_end, new_text));
13729 }
13730
13731 rewrapped_row_ranges.push(start_row..=end_row);
13732 }
13733
13734 self.buffer
13735 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13736 }
13737
13738 pub fn cut_common(
13739 &mut self,
13740 cut_no_selection_line: bool,
13741 window: &mut Window,
13742 cx: &mut Context<Self>,
13743 ) -> ClipboardItem {
13744 let mut text = String::new();
13745 let buffer = self.buffer.read(cx).snapshot(cx);
13746 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13747 let mut clipboard_selections = Vec::with_capacity(selections.len());
13748 {
13749 let max_point = buffer.max_point();
13750 let mut is_first = true;
13751 let mut prev_selection_was_entire_line = false;
13752 for selection in &mut selections {
13753 let is_entire_line =
13754 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13755 if is_entire_line {
13756 selection.start = Point::new(selection.start.row, 0);
13757 if !selection.is_empty() && selection.end.column == 0 {
13758 selection.end = cmp::min(max_point, selection.end);
13759 } else {
13760 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13761 }
13762 selection.goal = SelectionGoal::None;
13763 }
13764 if is_first {
13765 is_first = false;
13766 } else if !prev_selection_was_entire_line {
13767 text += "\n";
13768 }
13769 prev_selection_was_entire_line = is_entire_line;
13770 let mut len = 0;
13771 for chunk in buffer.text_for_range(selection.start..selection.end) {
13772 text.push_str(chunk);
13773 len += chunk.len();
13774 }
13775
13776 clipboard_selections.push(ClipboardSelection::for_buffer(
13777 len,
13778 is_entire_line,
13779 selection.range(),
13780 &buffer,
13781 self.project.as_ref(),
13782 cx,
13783 ));
13784 }
13785 }
13786
13787 self.transact(window, cx, |this, window, cx| {
13788 this.change_selections(Default::default(), window, cx, |s| {
13789 s.select(selections);
13790 });
13791 this.insert("", window, cx);
13792 });
13793 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13794 }
13795
13796 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13797 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13798 let item = self.cut_common(true, window, cx);
13799 cx.write_to_clipboard(item);
13800 }
13801
13802 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13803 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13804 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13805 s.move_with(&mut |snapshot, sel| {
13806 if sel.is_empty() {
13807 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13808 }
13809 if sel.is_empty() {
13810 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13811 }
13812 });
13813 });
13814 let item = self.cut_common(false, window, cx);
13815 cx.set_global(KillRing(item))
13816 }
13817
13818 pub fn kill_ring_yank(
13819 &mut self,
13820 _: &KillRingYank,
13821 window: &mut Window,
13822 cx: &mut Context<Self>,
13823 ) {
13824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13825 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13826 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13827 (kill_ring.text().to_string(), kill_ring.metadata_json())
13828 } else {
13829 return;
13830 }
13831 } else {
13832 return;
13833 };
13834 self.do_paste(&text, metadata, false, window, cx);
13835 }
13836
13837 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13838 self.do_copy(true, cx);
13839 }
13840
13841 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13842 self.do_copy(false, cx);
13843 }
13844
13845 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13846 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13847 let buffer = self.buffer.read(cx).read(cx);
13848 let mut text = String::new();
13849 let mut clipboard_selections = Vec::with_capacity(selections.len());
13850
13851 let max_point = buffer.max_point();
13852 let mut is_first = true;
13853 for selection in &selections {
13854 let mut start = selection.start;
13855 let mut end = selection.end;
13856 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13857 let mut add_trailing_newline = false;
13858 if is_entire_line {
13859 start = Point::new(start.row, 0);
13860 let next_line_start = Point::new(end.row + 1, 0);
13861 if next_line_start <= max_point {
13862 end = next_line_start;
13863 } else {
13864 // We're on the last line without a trailing newline.
13865 // Copy to the end of the line and add a newline afterwards.
13866 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13867 add_trailing_newline = true;
13868 }
13869 }
13870
13871 let mut trimmed_selections = Vec::new();
13872 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13873 let row = MultiBufferRow(start.row);
13874 let first_indent = buffer.indent_size_for_line(row);
13875 if first_indent.len == 0 || start.column > first_indent.len {
13876 trimmed_selections.push(start..end);
13877 } else {
13878 trimmed_selections.push(
13879 Point::new(row.0, first_indent.len)
13880 ..Point::new(row.0, buffer.line_len(row)),
13881 );
13882 for row in start.row + 1..=end.row {
13883 let mut line_len = buffer.line_len(MultiBufferRow(row));
13884 if row == end.row {
13885 line_len = end.column;
13886 }
13887 if line_len == 0 {
13888 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13889 continue;
13890 }
13891 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13892 if row_indent_size.len >= first_indent.len {
13893 trimmed_selections
13894 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13895 } else {
13896 trimmed_selections.clear();
13897 trimmed_selections.push(start..end);
13898 break;
13899 }
13900 }
13901 }
13902 } else {
13903 trimmed_selections.push(start..end);
13904 }
13905
13906 let is_multiline_trim = trimmed_selections.len() > 1;
13907 let mut selection_len: usize = 0;
13908 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13909
13910 for trimmed_range in trimmed_selections {
13911 if is_first {
13912 is_first = false;
13913 } else if is_multiline_trim || !prev_selection_was_entire_line {
13914 text.push('\n');
13915 if is_multiline_trim {
13916 selection_len += 1;
13917 }
13918 }
13919 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13920 text.push_str(chunk);
13921 selection_len += chunk.len();
13922 }
13923 if add_trailing_newline {
13924 text.push('\n');
13925 selection_len += 1;
13926 }
13927 }
13928
13929 clipboard_selections.push(ClipboardSelection::for_buffer(
13930 selection_len,
13931 is_entire_line,
13932 start..end,
13933 &buffer,
13934 self.project.as_ref(),
13935 cx,
13936 ));
13937 }
13938
13939 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13940 text,
13941 clipboard_selections,
13942 ));
13943 }
13944
13945 pub fn do_paste(
13946 &mut self,
13947 text: &String,
13948 clipboard_selections: Option<Vec<ClipboardSelection>>,
13949 handle_entire_lines: bool,
13950 window: &mut Window,
13951 cx: &mut Context<Self>,
13952 ) {
13953 if self.read_only(cx) {
13954 return;
13955 }
13956
13957 let clipboard_text = Cow::Borrowed(text.as_str());
13958
13959 self.transact(window, cx, |this, window, cx| {
13960 let had_active_edit_prediction = this.has_active_edit_prediction();
13961 let display_map = this.display_snapshot(cx);
13962 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13963 let cursor_offset = this
13964 .selections
13965 .last::<MultiBufferOffset>(&display_map)
13966 .head();
13967
13968 if let Some(mut clipboard_selections) = clipboard_selections {
13969 let all_selections_were_entire_line =
13970 clipboard_selections.iter().all(|s| s.is_entire_line);
13971 let first_selection_indent_column =
13972 clipboard_selections.first().map(|s| s.first_line_indent);
13973 if clipboard_selections.len() != old_selections.len() {
13974 clipboard_selections.drain(..);
13975 }
13976 let mut auto_indent_on_paste = true;
13977
13978 this.buffer.update(cx, |buffer, cx| {
13979 let snapshot = buffer.read(cx);
13980 auto_indent_on_paste = snapshot
13981 .language_settings_at(cursor_offset, cx)
13982 .auto_indent_on_paste;
13983
13984 let mut start_offset = 0;
13985 let mut edits = Vec::new();
13986 let mut original_indent_columns = Vec::new();
13987 for (ix, selection) in old_selections.iter().enumerate() {
13988 let to_insert;
13989 let entire_line;
13990 let original_indent_column;
13991 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13992 let end_offset = start_offset + clipboard_selection.len;
13993 to_insert = &clipboard_text[start_offset..end_offset];
13994 entire_line = clipboard_selection.is_entire_line;
13995 start_offset = if entire_line {
13996 end_offset
13997 } else {
13998 end_offset + 1
13999 };
14000 original_indent_column = Some(clipboard_selection.first_line_indent);
14001 } else {
14002 to_insert = &*clipboard_text;
14003 entire_line = all_selections_were_entire_line;
14004 original_indent_column = first_selection_indent_column
14005 }
14006
14007 let (range, to_insert) =
14008 if selection.is_empty() && handle_entire_lines && entire_line {
14009 // If the corresponding selection was empty when this slice of the
14010 // clipboard text was written, then the entire line containing the
14011 // selection was copied. If this selection is also currently empty,
14012 // then paste the line before the current line of the buffer.
14013 let column = selection.start.to_point(&snapshot).column as usize;
14014 let line_start = selection.start - column;
14015 (line_start..line_start, Cow::Borrowed(to_insert))
14016 } else {
14017 let language = snapshot.language_at(selection.head());
14018 let range = selection.range();
14019 if let Some(language) = language
14020 && language.name() == "Markdown"
14021 {
14022 edit_for_markdown_paste(
14023 &snapshot,
14024 range,
14025 to_insert,
14026 url::Url::parse(to_insert).ok(),
14027 )
14028 } else {
14029 (range, Cow::Borrowed(to_insert))
14030 }
14031 };
14032
14033 edits.push((range, to_insert));
14034 original_indent_columns.push(original_indent_column);
14035 }
14036 drop(snapshot);
14037
14038 buffer.edit(
14039 edits,
14040 if auto_indent_on_paste {
14041 Some(AutoindentMode::Block {
14042 original_indent_columns,
14043 })
14044 } else {
14045 None
14046 },
14047 cx,
14048 );
14049 });
14050
14051 let selections = this
14052 .selections
14053 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14054 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14055 } else {
14056 let url = url::Url::parse(&clipboard_text).ok();
14057
14058 let auto_indent_mode = if !clipboard_text.is_empty() {
14059 Some(AutoindentMode::Block {
14060 original_indent_columns: Vec::new(),
14061 })
14062 } else {
14063 None
14064 };
14065
14066 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14067 let snapshot = buffer.snapshot(cx);
14068
14069 let anchors = old_selections
14070 .iter()
14071 .map(|s| {
14072 let anchor = snapshot.anchor_after(s.head());
14073 s.map(|_| anchor)
14074 })
14075 .collect::<Vec<_>>();
14076
14077 let mut edits = Vec::new();
14078
14079 // When pasting text without metadata (e.g. copied from an
14080 // external editor using multiple cursors) and the number of
14081 // lines matches the number of selections, distribute one
14082 // line per cursor instead of pasting the whole text at each.
14083 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14084 let distribute_lines =
14085 old_selections.len() > 1 && lines.len() == old_selections.len();
14086
14087 for (ix, selection) in old_selections.iter().enumerate() {
14088 let language = snapshot.language_at(selection.head());
14089 let range = selection.range();
14090
14091 let text_for_cursor: &str = if distribute_lines {
14092 lines[ix]
14093 } else {
14094 &clipboard_text
14095 };
14096
14097 let (edit_range, edit_text) = if let Some(language) = language
14098 && language.name() == "Markdown"
14099 {
14100 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14101 } else {
14102 (range, Cow::Borrowed(text_for_cursor))
14103 };
14104
14105 edits.push((edit_range, edit_text));
14106 }
14107
14108 drop(snapshot);
14109 buffer.edit(edits, auto_indent_mode, cx);
14110
14111 anchors
14112 });
14113
14114 this.change_selections(Default::default(), window, cx, |s| {
14115 s.select_anchors(selection_anchors);
14116 });
14117 }
14118
14119 // 🤔 | .. | show_in_menu |
14120 // | .. | true true
14121 // | had_edit_prediction | false true
14122
14123 let trigger_in_words =
14124 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14125
14126 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14127 });
14128 }
14129
14130 pub fn diff_clipboard_with_selection(
14131 &mut self,
14132 _: &DiffClipboardWithSelection,
14133 window: &mut Window,
14134 cx: &mut Context<Self>,
14135 ) {
14136 let selections = self
14137 .selections
14138 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14139
14140 if selections.is_empty() {
14141 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14142 return;
14143 };
14144
14145 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14146 item.entries().iter().find_map(|entry| match entry {
14147 ClipboardEntry::String(text) => Some(text.text().to_string()),
14148 _ => None,
14149 })
14150 });
14151
14152 let Some(clipboard_text) = clipboard_text else {
14153 log::warn!("Clipboard doesn't contain text.");
14154 return;
14155 };
14156
14157 window.dispatch_action(
14158 Box::new(DiffClipboardWithSelectionData {
14159 clipboard_text,
14160 editor: cx.entity(),
14161 }),
14162 cx,
14163 );
14164 }
14165
14166 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14167 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14168 if let Some(item) = cx.read_from_clipboard() {
14169 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14170 ClipboardEntry::String(s) => Some(s),
14171 _ => None,
14172 });
14173 match clipboard_string {
14174 Some(clipboard_string) => self.do_paste(
14175 clipboard_string.text(),
14176 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14177 true,
14178 window,
14179 cx,
14180 ),
14181 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14182 }
14183 }
14184 }
14185
14186 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14187 if self.read_only(cx) {
14188 return;
14189 }
14190
14191 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14192
14193 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14194 if let Some((selections, _)) =
14195 self.selection_history.transaction(transaction_id).cloned()
14196 {
14197 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14198 s.select_anchors(selections.to_vec());
14199 });
14200 } else {
14201 log::error!(
14202 "No entry in selection_history found for undo. \
14203 This may correspond to a bug where undo does not update the selection. \
14204 If this is occurring, please add details to \
14205 https://github.com/zed-industries/zed/issues/22692"
14206 );
14207 }
14208 self.request_autoscroll(Autoscroll::fit(), cx);
14209 self.unmark_text(window, cx);
14210 self.refresh_edit_prediction(true, false, window, cx);
14211 cx.emit(EditorEvent::Edited { transaction_id });
14212 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14213 }
14214 }
14215
14216 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14217 if self.read_only(cx) {
14218 return;
14219 }
14220
14221 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14222
14223 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14224 if let Some((_, Some(selections))) =
14225 self.selection_history.transaction(transaction_id).cloned()
14226 {
14227 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14228 s.select_anchors(selections.to_vec());
14229 });
14230 } else {
14231 log::error!(
14232 "No entry in selection_history found for redo. \
14233 This may correspond to a bug where undo does not update the selection. \
14234 If this is occurring, please add details to \
14235 https://github.com/zed-industries/zed/issues/22692"
14236 );
14237 }
14238 self.request_autoscroll(Autoscroll::fit(), cx);
14239 self.unmark_text(window, cx);
14240 self.refresh_edit_prediction(true, false, window, cx);
14241 cx.emit(EditorEvent::Edited { transaction_id });
14242 }
14243 }
14244
14245 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14246 self.buffer
14247 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14248 }
14249
14250 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14251 self.buffer
14252 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14253 }
14254
14255 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14257 self.change_selections(Default::default(), window, cx, |s| {
14258 s.move_with(&mut |map, selection| {
14259 let cursor = if selection.is_empty() {
14260 movement::left(map, selection.start)
14261 } else {
14262 selection.start
14263 };
14264 selection.collapse_to(cursor, SelectionGoal::None);
14265 });
14266 })
14267 }
14268
14269 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14271 self.change_selections(Default::default(), window, cx, |s| {
14272 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14273 })
14274 }
14275
14276 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14278 self.change_selections(Default::default(), window, cx, |s| {
14279 s.move_with(&mut |map, selection| {
14280 let cursor = if selection.is_empty() {
14281 movement::right(map, selection.end)
14282 } else {
14283 selection.end
14284 };
14285 selection.collapse_to(cursor, SelectionGoal::None)
14286 });
14287 })
14288 }
14289
14290 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14292 self.change_selections(Default::default(), window, cx, |s| {
14293 s.move_heads_with(&mut |map, head, _| {
14294 (movement::right(map, head), SelectionGoal::None)
14295 });
14296 });
14297 }
14298
14299 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14300 if self.take_rename(true, window, cx).is_some() {
14301 return;
14302 }
14303
14304 if self.mode.is_single_line() {
14305 cx.propagate();
14306 return;
14307 }
14308
14309 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14310
14311 let text_layout_details = &self.text_layout_details(window, cx);
14312 let selection_count = self.selections.count();
14313 let first_selection = self.selections.first_anchor();
14314
14315 self.change_selections(Default::default(), window, cx, |s| {
14316 s.move_with(&mut |map, selection| {
14317 if !selection.is_empty() {
14318 selection.goal = SelectionGoal::None;
14319 }
14320 let (cursor, goal) = movement::up(
14321 map,
14322 selection.start,
14323 selection.goal,
14324 false,
14325 text_layout_details,
14326 );
14327 selection.collapse_to(cursor, goal);
14328 });
14329 });
14330
14331 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14332 {
14333 cx.propagate();
14334 }
14335 }
14336
14337 pub fn move_up_by_lines(
14338 &mut self,
14339 action: &MoveUpByLines,
14340 window: &mut Window,
14341 cx: &mut Context<Self>,
14342 ) {
14343 if self.take_rename(true, window, cx).is_some() {
14344 return;
14345 }
14346
14347 if self.mode.is_single_line() {
14348 cx.propagate();
14349 return;
14350 }
14351
14352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14353
14354 let text_layout_details = &self.text_layout_details(window, cx);
14355
14356 self.change_selections(Default::default(), window, cx, |s| {
14357 s.move_with(&mut |map, selection| {
14358 if !selection.is_empty() {
14359 selection.goal = SelectionGoal::None;
14360 }
14361 let (cursor, goal) = movement::up_by_rows(
14362 map,
14363 selection.start,
14364 action.lines,
14365 selection.goal,
14366 false,
14367 text_layout_details,
14368 );
14369 selection.collapse_to(cursor, goal);
14370 });
14371 })
14372 }
14373
14374 pub fn move_down_by_lines(
14375 &mut self,
14376 action: &MoveDownByLines,
14377 window: &mut Window,
14378 cx: &mut Context<Self>,
14379 ) {
14380 if self.take_rename(true, window, cx).is_some() {
14381 return;
14382 }
14383
14384 if self.mode.is_single_line() {
14385 cx.propagate();
14386 return;
14387 }
14388
14389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14390
14391 let text_layout_details = &self.text_layout_details(window, cx);
14392
14393 self.change_selections(Default::default(), window, cx, |s| {
14394 s.move_with(&mut |map, selection| {
14395 if !selection.is_empty() {
14396 selection.goal = SelectionGoal::None;
14397 }
14398 let (cursor, goal) = movement::down_by_rows(
14399 map,
14400 selection.start,
14401 action.lines,
14402 selection.goal,
14403 false,
14404 text_layout_details,
14405 );
14406 selection.collapse_to(cursor, goal);
14407 });
14408 })
14409 }
14410
14411 pub fn select_down_by_lines(
14412 &mut self,
14413 action: &SelectDownByLines,
14414 window: &mut Window,
14415 cx: &mut Context<Self>,
14416 ) {
14417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14418 let text_layout_details = &self.text_layout_details(window, cx);
14419 self.change_selections(Default::default(), window, cx, |s| {
14420 s.move_heads_with(&mut |map, head, goal| {
14421 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14422 })
14423 })
14424 }
14425
14426 pub fn select_up_by_lines(
14427 &mut self,
14428 action: &SelectUpByLines,
14429 window: &mut Window,
14430 cx: &mut Context<Self>,
14431 ) {
14432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14433 let text_layout_details = &self.text_layout_details(window, cx);
14434 self.change_selections(Default::default(), window, cx, |s| {
14435 s.move_heads_with(&mut |map, head, goal| {
14436 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14437 })
14438 })
14439 }
14440
14441 pub fn select_page_up(
14442 &mut self,
14443 _: &SelectPageUp,
14444 window: &mut Window,
14445 cx: &mut Context<Self>,
14446 ) {
14447 let Some(row_count) = self.visible_row_count() else {
14448 return;
14449 };
14450
14451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14452
14453 let text_layout_details = &self.text_layout_details(window, cx);
14454
14455 self.change_selections(Default::default(), window, cx, |s| {
14456 s.move_heads_with(&mut |map, head, goal| {
14457 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14458 })
14459 })
14460 }
14461
14462 pub fn move_page_up(
14463 &mut self,
14464 action: &MovePageUp,
14465 window: &mut Window,
14466 cx: &mut Context<Self>,
14467 ) {
14468 if self.take_rename(true, window, cx).is_some() {
14469 return;
14470 }
14471
14472 if self
14473 .context_menu
14474 .borrow_mut()
14475 .as_mut()
14476 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14477 .unwrap_or(false)
14478 {
14479 return;
14480 }
14481
14482 if matches!(self.mode, EditorMode::SingleLine) {
14483 cx.propagate();
14484 return;
14485 }
14486
14487 let Some(row_count) = self.visible_row_count() else {
14488 return;
14489 };
14490
14491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14492
14493 let effects = if action.center_cursor {
14494 SelectionEffects::scroll(Autoscroll::center())
14495 } else {
14496 SelectionEffects::default()
14497 };
14498
14499 let text_layout_details = &self.text_layout_details(window, cx);
14500
14501 self.change_selections(effects, window, cx, |s| {
14502 s.move_with(&mut |map, selection| {
14503 if !selection.is_empty() {
14504 selection.goal = SelectionGoal::None;
14505 }
14506 let (cursor, goal) = movement::up_by_rows(
14507 map,
14508 selection.end,
14509 row_count,
14510 selection.goal,
14511 false,
14512 text_layout_details,
14513 );
14514 selection.collapse_to(cursor, goal);
14515 });
14516 });
14517 }
14518
14519 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14520 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14521 let text_layout_details = &self.text_layout_details(window, cx);
14522 self.change_selections(Default::default(), window, cx, |s| {
14523 s.move_heads_with(&mut |map, head, goal| {
14524 movement::up(map, head, goal, false, text_layout_details)
14525 })
14526 })
14527 }
14528
14529 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14530 self.take_rename(true, window, cx);
14531
14532 if self.mode.is_single_line() {
14533 cx.propagate();
14534 return;
14535 }
14536
14537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14538
14539 let text_layout_details = &self.text_layout_details(window, cx);
14540 let selection_count = self.selections.count();
14541 let first_selection = self.selections.first_anchor();
14542
14543 self.change_selections(Default::default(), window, cx, |s| {
14544 s.move_with(&mut |map, selection| {
14545 if !selection.is_empty() {
14546 selection.goal = SelectionGoal::None;
14547 }
14548 let (cursor, goal) = movement::down(
14549 map,
14550 selection.end,
14551 selection.goal,
14552 false,
14553 text_layout_details,
14554 );
14555 selection.collapse_to(cursor, goal);
14556 });
14557 });
14558
14559 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14560 {
14561 cx.propagate();
14562 }
14563 }
14564
14565 pub fn select_page_down(
14566 &mut self,
14567 _: &SelectPageDown,
14568 window: &mut Window,
14569 cx: &mut Context<Self>,
14570 ) {
14571 let Some(row_count) = self.visible_row_count() else {
14572 return;
14573 };
14574
14575 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14576
14577 let text_layout_details = &self.text_layout_details(window, cx);
14578
14579 self.change_selections(Default::default(), window, cx, |s| {
14580 s.move_heads_with(&mut |map, head, goal| {
14581 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14582 })
14583 })
14584 }
14585
14586 pub fn move_page_down(
14587 &mut self,
14588 action: &MovePageDown,
14589 window: &mut Window,
14590 cx: &mut Context<Self>,
14591 ) {
14592 if self.take_rename(true, window, cx).is_some() {
14593 return;
14594 }
14595
14596 if self
14597 .context_menu
14598 .borrow_mut()
14599 .as_mut()
14600 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14601 .unwrap_or(false)
14602 {
14603 return;
14604 }
14605
14606 if matches!(self.mode, EditorMode::SingleLine) {
14607 cx.propagate();
14608 return;
14609 }
14610
14611 let Some(row_count) = self.visible_row_count() else {
14612 return;
14613 };
14614
14615 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14616
14617 let effects = if action.center_cursor {
14618 SelectionEffects::scroll(Autoscroll::center())
14619 } else {
14620 SelectionEffects::default()
14621 };
14622
14623 let text_layout_details = &self.text_layout_details(window, cx);
14624 self.change_selections(effects, window, cx, |s| {
14625 s.move_with(&mut |map, selection| {
14626 if !selection.is_empty() {
14627 selection.goal = SelectionGoal::None;
14628 }
14629 let (cursor, goal) = movement::down_by_rows(
14630 map,
14631 selection.end,
14632 row_count,
14633 selection.goal,
14634 false,
14635 text_layout_details,
14636 );
14637 selection.collapse_to(cursor, goal);
14638 });
14639 });
14640 }
14641
14642 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14644 let text_layout_details = &self.text_layout_details(window, cx);
14645 self.change_selections(Default::default(), window, cx, |s| {
14646 s.move_heads_with(&mut |map, head, goal| {
14647 movement::down(map, head, goal, false, text_layout_details)
14648 })
14649 });
14650 }
14651
14652 pub fn context_menu_first(
14653 &mut self,
14654 _: &ContextMenuFirst,
14655 window: &mut Window,
14656 cx: &mut Context<Self>,
14657 ) {
14658 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14659 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14660 }
14661 }
14662
14663 pub fn context_menu_prev(
14664 &mut self,
14665 _: &ContextMenuPrevious,
14666 window: &mut Window,
14667 cx: &mut Context<Self>,
14668 ) {
14669 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14670 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14671 }
14672 }
14673
14674 pub fn context_menu_next(
14675 &mut self,
14676 _: &ContextMenuNext,
14677 window: &mut Window,
14678 cx: &mut Context<Self>,
14679 ) {
14680 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14681 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14682 }
14683 }
14684
14685 pub fn context_menu_last(
14686 &mut self,
14687 _: &ContextMenuLast,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 ) {
14691 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14692 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14693 }
14694 }
14695
14696 pub fn signature_help_prev(
14697 &mut self,
14698 _: &SignatureHelpPrevious,
14699 _: &mut Window,
14700 cx: &mut Context<Self>,
14701 ) {
14702 if let Some(popover) = self.signature_help_state.popover_mut() {
14703 if popover.current_signature == 0 {
14704 popover.current_signature = popover.signatures.len() - 1;
14705 } else {
14706 popover.current_signature -= 1;
14707 }
14708 cx.notify();
14709 }
14710 }
14711
14712 pub fn signature_help_next(
14713 &mut self,
14714 _: &SignatureHelpNext,
14715 _: &mut Window,
14716 cx: &mut Context<Self>,
14717 ) {
14718 if let Some(popover) = self.signature_help_state.popover_mut() {
14719 if popover.current_signature + 1 == popover.signatures.len() {
14720 popover.current_signature = 0;
14721 } else {
14722 popover.current_signature += 1;
14723 }
14724 cx.notify();
14725 }
14726 }
14727
14728 pub fn move_to_previous_word_start(
14729 &mut self,
14730 _: &MoveToPreviousWordStart,
14731 window: &mut Window,
14732 cx: &mut Context<Self>,
14733 ) {
14734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14735 self.change_selections(Default::default(), window, cx, |s| {
14736 s.move_cursors_with(&mut |map, head, _| {
14737 (
14738 movement::previous_word_start(map, head),
14739 SelectionGoal::None,
14740 )
14741 });
14742 })
14743 }
14744
14745 pub fn move_to_previous_subword_start(
14746 &mut self,
14747 _: &MoveToPreviousSubwordStart,
14748 window: &mut Window,
14749 cx: &mut Context<Self>,
14750 ) {
14751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14752 self.change_selections(Default::default(), window, cx, |s| {
14753 s.move_cursors_with(&mut |map, head, _| {
14754 (
14755 movement::previous_subword_start(map, head),
14756 SelectionGoal::None,
14757 )
14758 });
14759 })
14760 }
14761
14762 pub fn select_to_previous_word_start(
14763 &mut self,
14764 _: &SelectToPreviousWordStart,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) {
14768 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14769 self.change_selections(Default::default(), window, cx, |s| {
14770 s.move_heads_with(&mut |map, head, _| {
14771 (
14772 movement::previous_word_start(map, head),
14773 SelectionGoal::None,
14774 )
14775 });
14776 })
14777 }
14778
14779 pub fn select_to_previous_subword_start(
14780 &mut self,
14781 _: &SelectToPreviousSubwordStart,
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_heads_with(&mut |map, head, _| {
14788 (
14789 movement::previous_subword_start(map, head),
14790 SelectionGoal::None,
14791 )
14792 });
14793 })
14794 }
14795
14796 pub fn delete_to_previous_word_start(
14797 &mut self,
14798 action: &DeleteToPreviousWordStart,
14799 window: &mut Window,
14800 cx: &mut Context<Self>,
14801 ) {
14802 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14803 self.transact(window, cx, |this, window, cx| {
14804 this.select_autoclose_pair(window, cx);
14805 this.change_selections(Default::default(), window, cx, |s| {
14806 s.move_with(&mut |map, selection| {
14807 if selection.is_empty() {
14808 let mut cursor = if action.ignore_newlines {
14809 movement::previous_word_start(map, selection.head())
14810 } else {
14811 movement::previous_word_start_or_newline(map, selection.head())
14812 };
14813 cursor = movement::adjust_greedy_deletion(
14814 map,
14815 selection.head(),
14816 cursor,
14817 action.ignore_brackets,
14818 );
14819 selection.set_head(cursor, SelectionGoal::None);
14820 }
14821 });
14822 });
14823 this.insert("", window, cx);
14824 });
14825 }
14826
14827 pub fn delete_to_previous_subword_start(
14828 &mut self,
14829 action: &DeleteToPreviousSubwordStart,
14830 window: &mut Window,
14831 cx: &mut Context<Self>,
14832 ) {
14833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14834 self.transact(window, cx, |this, window, cx| {
14835 this.select_autoclose_pair(window, cx);
14836 this.change_selections(Default::default(), window, cx, |s| {
14837 s.move_with(&mut |map, selection| {
14838 if selection.is_empty() {
14839 let mut cursor = if action.ignore_newlines {
14840 movement::previous_subword_start(map, selection.head())
14841 } else {
14842 movement::previous_subword_start_or_newline(map, selection.head())
14843 };
14844 cursor = movement::adjust_greedy_deletion(
14845 map,
14846 selection.head(),
14847 cursor,
14848 action.ignore_brackets,
14849 );
14850 selection.set_head(cursor, SelectionGoal::None);
14851 }
14852 });
14853 });
14854 this.insert("", window, cx);
14855 });
14856 }
14857
14858 pub fn move_to_next_word_end(
14859 &mut self,
14860 _: &MoveToNextWordEnd,
14861 window: &mut Window,
14862 cx: &mut Context<Self>,
14863 ) {
14864 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14865 self.change_selections(Default::default(), window, cx, |s| {
14866 s.move_cursors_with(&mut |map, head, _| {
14867 (movement::next_word_end(map, head), SelectionGoal::None)
14868 });
14869 })
14870 }
14871
14872 pub fn move_to_next_subword_end(
14873 &mut self,
14874 _: &MoveToNextSubwordEnd,
14875 window: &mut Window,
14876 cx: &mut Context<Self>,
14877 ) {
14878 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14879 self.change_selections(Default::default(), window, cx, |s| {
14880 s.move_cursors_with(&mut |map, head, _| {
14881 (movement::next_subword_end(map, head), SelectionGoal::None)
14882 });
14883 })
14884 }
14885
14886 pub fn select_to_next_word_end(
14887 &mut self,
14888 _: &SelectToNextWordEnd,
14889 window: &mut Window,
14890 cx: &mut Context<Self>,
14891 ) {
14892 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14893 self.change_selections(Default::default(), window, cx, |s| {
14894 s.move_heads_with(&mut |map, head, _| {
14895 (movement::next_word_end(map, head), SelectionGoal::None)
14896 });
14897 })
14898 }
14899
14900 pub fn select_to_next_subword_end(
14901 &mut self,
14902 _: &SelectToNextSubwordEnd,
14903 window: &mut Window,
14904 cx: &mut Context<Self>,
14905 ) {
14906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14907 self.change_selections(Default::default(), window, cx, |s| {
14908 s.move_heads_with(&mut |map, head, _| {
14909 (movement::next_subword_end(map, head), SelectionGoal::None)
14910 });
14911 })
14912 }
14913
14914 pub fn delete_to_next_word_end(
14915 &mut self,
14916 action: &DeleteToNextWordEnd,
14917 window: &mut Window,
14918 cx: &mut Context<Self>,
14919 ) {
14920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14921 self.transact(window, cx, |this, window, cx| {
14922 this.change_selections(Default::default(), window, cx, |s| {
14923 s.move_with(&mut |map, selection| {
14924 if selection.is_empty() {
14925 let mut cursor = if action.ignore_newlines {
14926 movement::next_word_end(map, selection.head())
14927 } else {
14928 movement::next_word_end_or_newline(map, selection.head())
14929 };
14930 cursor = movement::adjust_greedy_deletion(
14931 map,
14932 selection.head(),
14933 cursor,
14934 action.ignore_brackets,
14935 );
14936 selection.set_head(cursor, SelectionGoal::None);
14937 }
14938 });
14939 });
14940 this.insert("", window, cx);
14941 });
14942 }
14943
14944 pub fn delete_to_next_subword_end(
14945 &mut self,
14946 action: &DeleteToNextSubwordEnd,
14947 window: &mut Window,
14948 cx: &mut Context<Self>,
14949 ) {
14950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14951 self.transact(window, cx, |this, window, cx| {
14952 this.change_selections(Default::default(), window, cx, |s| {
14953 s.move_with(&mut |map, selection| {
14954 if selection.is_empty() {
14955 let mut cursor = if action.ignore_newlines {
14956 movement::next_subword_end(map, selection.head())
14957 } else {
14958 movement::next_subword_end_or_newline(map, selection.head())
14959 };
14960 cursor = movement::adjust_greedy_deletion(
14961 map,
14962 selection.head(),
14963 cursor,
14964 action.ignore_brackets,
14965 );
14966 selection.set_head(cursor, SelectionGoal::None);
14967 }
14968 });
14969 });
14970 this.insert("", window, cx);
14971 });
14972 }
14973
14974 pub fn move_to_beginning_of_line(
14975 &mut self,
14976 action: &MoveToBeginningOfLine,
14977 window: &mut Window,
14978 cx: &mut Context<Self>,
14979 ) {
14980 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
14981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14982 self.change_selections(Default::default(), window, cx, |s| {
14983 s.move_cursors_with(&mut |map, head, _| {
14984 (
14985 movement::indented_line_beginning(
14986 map,
14987 head,
14988 action.stop_at_soft_wraps,
14989 stop_at_indent,
14990 ),
14991 SelectionGoal::None,
14992 )
14993 });
14994 })
14995 }
14996
14997 pub fn select_to_beginning_of_line(
14998 &mut self,
14999 action: &SelectToBeginningOfLine,
15000 window: &mut Window,
15001 cx: &mut Context<Self>,
15002 ) {
15003 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15005 self.change_selections(Default::default(), window, cx, |s| {
15006 s.move_heads_with(&mut |map, head, _| {
15007 (
15008 movement::indented_line_beginning(
15009 map,
15010 head,
15011 action.stop_at_soft_wraps,
15012 stop_at_indent,
15013 ),
15014 SelectionGoal::None,
15015 )
15016 });
15017 });
15018 }
15019
15020 pub fn delete_to_beginning_of_line(
15021 &mut self,
15022 action: &DeleteToBeginningOfLine,
15023 window: &mut Window,
15024 cx: &mut Context<Self>,
15025 ) {
15026 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15027 self.transact(window, cx, |this, window, cx| {
15028 this.change_selections(Default::default(), window, cx, |s| {
15029 s.move_with(&mut |_, selection| {
15030 selection.reversed = true;
15031 });
15032 });
15033
15034 this.select_to_beginning_of_line(
15035 &SelectToBeginningOfLine {
15036 stop_at_soft_wraps: false,
15037 stop_at_indent: action.stop_at_indent,
15038 },
15039 window,
15040 cx,
15041 );
15042 this.backspace(&Backspace, window, cx);
15043 });
15044 }
15045
15046 pub fn move_to_end_of_line(
15047 &mut self,
15048 action: &MoveToEndOfLine,
15049 window: &mut Window,
15050 cx: &mut Context<Self>,
15051 ) {
15052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15053 self.change_selections(Default::default(), window, cx, |s| {
15054 s.move_cursors_with(&mut |map, head, _| {
15055 (
15056 movement::line_end(map, head, action.stop_at_soft_wraps),
15057 SelectionGoal::None,
15058 )
15059 });
15060 })
15061 }
15062
15063 pub fn select_to_end_of_line(
15064 &mut self,
15065 action: &SelectToEndOfLine,
15066 window: &mut Window,
15067 cx: &mut Context<Self>,
15068 ) {
15069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15070 self.change_selections(Default::default(), window, cx, |s| {
15071 s.move_heads_with(&mut |map, head, _| {
15072 (
15073 movement::line_end(map, head, action.stop_at_soft_wraps),
15074 SelectionGoal::None,
15075 )
15076 });
15077 })
15078 }
15079
15080 pub fn delete_to_end_of_line(
15081 &mut self,
15082 _: &DeleteToEndOfLine,
15083 window: &mut Window,
15084 cx: &mut Context<Self>,
15085 ) {
15086 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15087 self.transact(window, cx, |this, window, cx| {
15088 this.select_to_end_of_line(
15089 &SelectToEndOfLine {
15090 stop_at_soft_wraps: false,
15091 },
15092 window,
15093 cx,
15094 );
15095 this.delete(&Delete, window, cx);
15096 });
15097 }
15098
15099 pub fn cut_to_end_of_line(
15100 &mut self,
15101 action: &CutToEndOfLine,
15102 window: &mut Window,
15103 cx: &mut Context<Self>,
15104 ) {
15105 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15106 self.transact(window, cx, |this, window, cx| {
15107 this.select_to_end_of_line(
15108 &SelectToEndOfLine {
15109 stop_at_soft_wraps: false,
15110 },
15111 window,
15112 cx,
15113 );
15114 if !action.stop_at_newlines {
15115 this.change_selections(Default::default(), window, cx, |s| {
15116 s.move_with(&mut |_, sel| {
15117 if sel.is_empty() {
15118 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15119 }
15120 });
15121 });
15122 }
15123 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15124 let item = this.cut_common(false, window, cx);
15125 cx.write_to_clipboard(item);
15126 });
15127 }
15128
15129 pub fn move_to_start_of_paragraph(
15130 &mut self,
15131 _: &MoveToStartOfParagraph,
15132 window: &mut Window,
15133 cx: &mut Context<Self>,
15134 ) {
15135 if matches!(self.mode, EditorMode::SingleLine) {
15136 cx.propagate();
15137 return;
15138 }
15139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15140 self.change_selections(Default::default(), window, cx, |s| {
15141 s.move_with(&mut |map, selection| {
15142 selection.collapse_to(
15143 movement::start_of_paragraph(map, selection.head(), 1),
15144 SelectionGoal::None,
15145 )
15146 });
15147 })
15148 }
15149
15150 pub fn move_to_end_of_paragraph(
15151 &mut self,
15152 _: &MoveToEndOfParagraph,
15153 window: &mut Window,
15154 cx: &mut Context<Self>,
15155 ) {
15156 if matches!(self.mode, EditorMode::SingleLine) {
15157 cx.propagate();
15158 return;
15159 }
15160 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15161 self.change_selections(Default::default(), window, cx, |s| {
15162 s.move_with(&mut |map, selection| {
15163 selection.collapse_to(
15164 movement::end_of_paragraph(map, selection.head(), 1),
15165 SelectionGoal::None,
15166 )
15167 });
15168 })
15169 }
15170
15171 pub fn select_to_start_of_paragraph(
15172 &mut self,
15173 _: &SelectToStartOfParagraph,
15174 window: &mut Window,
15175 cx: &mut Context<Self>,
15176 ) {
15177 if matches!(self.mode, EditorMode::SingleLine) {
15178 cx.propagate();
15179 return;
15180 }
15181 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15182 self.change_selections(Default::default(), window, cx, |s| {
15183 s.move_heads_with(&mut |map, head, _| {
15184 (
15185 movement::start_of_paragraph(map, head, 1),
15186 SelectionGoal::None,
15187 )
15188 });
15189 })
15190 }
15191
15192 pub fn select_to_end_of_paragraph(
15193 &mut self,
15194 _: &SelectToEndOfParagraph,
15195 window: &mut Window,
15196 cx: &mut Context<Self>,
15197 ) {
15198 if matches!(self.mode, EditorMode::SingleLine) {
15199 cx.propagate();
15200 return;
15201 }
15202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15203 self.change_selections(Default::default(), window, cx, |s| {
15204 s.move_heads_with(&mut |map, head, _| {
15205 (
15206 movement::end_of_paragraph(map, head, 1),
15207 SelectionGoal::None,
15208 )
15209 });
15210 })
15211 }
15212
15213 pub fn move_to_start_of_excerpt(
15214 &mut self,
15215 _: &MoveToStartOfExcerpt,
15216 window: &mut Window,
15217 cx: &mut Context<Self>,
15218 ) {
15219 if matches!(self.mode, EditorMode::SingleLine) {
15220 cx.propagate();
15221 return;
15222 }
15223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15224 self.change_selections(Default::default(), window, cx, |s| {
15225 s.move_with(&mut |map, selection| {
15226 selection.collapse_to(
15227 movement::start_of_excerpt(
15228 map,
15229 selection.head(),
15230 workspace::searchable::Direction::Prev,
15231 ),
15232 SelectionGoal::None,
15233 )
15234 });
15235 })
15236 }
15237
15238 pub fn move_to_start_of_next_excerpt(
15239 &mut self,
15240 _: &MoveToStartOfNextExcerpt,
15241 window: &mut Window,
15242 cx: &mut Context<Self>,
15243 ) {
15244 if matches!(self.mode, EditorMode::SingleLine) {
15245 cx.propagate();
15246 return;
15247 }
15248
15249 self.change_selections(Default::default(), window, cx, |s| {
15250 s.move_with(&mut |map, selection| {
15251 selection.collapse_to(
15252 movement::start_of_excerpt(
15253 map,
15254 selection.head(),
15255 workspace::searchable::Direction::Next,
15256 ),
15257 SelectionGoal::None,
15258 )
15259 });
15260 })
15261 }
15262
15263 pub fn move_to_end_of_excerpt(
15264 &mut self,
15265 _: &MoveToEndOfExcerpt,
15266 window: &mut Window,
15267 cx: &mut Context<Self>,
15268 ) {
15269 if matches!(self.mode, EditorMode::SingleLine) {
15270 cx.propagate();
15271 return;
15272 }
15273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15274 self.change_selections(Default::default(), window, cx, |s| {
15275 s.move_with(&mut |map, selection| {
15276 selection.collapse_to(
15277 movement::end_of_excerpt(
15278 map,
15279 selection.head(),
15280 workspace::searchable::Direction::Next,
15281 ),
15282 SelectionGoal::None,
15283 )
15284 });
15285 })
15286 }
15287
15288 pub fn move_to_end_of_previous_excerpt(
15289 &mut self,
15290 _: &MoveToEndOfPreviousExcerpt,
15291 window: &mut Window,
15292 cx: &mut Context<Self>,
15293 ) {
15294 if matches!(self.mode, EditorMode::SingleLine) {
15295 cx.propagate();
15296 return;
15297 }
15298 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15299 self.change_selections(Default::default(), window, cx, |s| {
15300 s.move_with(&mut |map, selection| {
15301 selection.collapse_to(
15302 movement::end_of_excerpt(
15303 map,
15304 selection.head(),
15305 workspace::searchable::Direction::Prev,
15306 ),
15307 SelectionGoal::None,
15308 )
15309 });
15310 })
15311 }
15312
15313 pub fn select_to_start_of_excerpt(
15314 &mut self,
15315 _: &SelectToStartOfExcerpt,
15316 window: &mut Window,
15317 cx: &mut Context<Self>,
15318 ) {
15319 if matches!(self.mode, EditorMode::SingleLine) {
15320 cx.propagate();
15321 return;
15322 }
15323 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15324 self.change_selections(Default::default(), window, cx, |s| {
15325 s.move_heads_with(&mut |map, head, _| {
15326 (
15327 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15328 SelectionGoal::None,
15329 )
15330 });
15331 })
15332 }
15333
15334 pub fn select_to_start_of_next_excerpt(
15335 &mut self,
15336 _: &SelectToStartOfNextExcerpt,
15337 window: &mut Window,
15338 cx: &mut Context<Self>,
15339 ) {
15340 if matches!(self.mode, EditorMode::SingleLine) {
15341 cx.propagate();
15342 return;
15343 }
15344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15345 self.change_selections(Default::default(), window, cx, |s| {
15346 s.move_heads_with(&mut |map, head, _| {
15347 (
15348 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15349 SelectionGoal::None,
15350 )
15351 });
15352 })
15353 }
15354
15355 pub fn select_to_end_of_excerpt(
15356 &mut self,
15357 _: &SelectToEndOfExcerpt,
15358 window: &mut Window,
15359 cx: &mut Context<Self>,
15360 ) {
15361 if matches!(self.mode, EditorMode::SingleLine) {
15362 cx.propagate();
15363 return;
15364 }
15365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15366 self.change_selections(Default::default(), window, cx, |s| {
15367 s.move_heads_with(&mut |map, head, _| {
15368 (
15369 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15370 SelectionGoal::None,
15371 )
15372 });
15373 })
15374 }
15375
15376 pub fn select_to_end_of_previous_excerpt(
15377 &mut self,
15378 _: &SelectToEndOfPreviousExcerpt,
15379 window: &mut Window,
15380 cx: &mut Context<Self>,
15381 ) {
15382 if matches!(self.mode, EditorMode::SingleLine) {
15383 cx.propagate();
15384 return;
15385 }
15386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15387 self.change_selections(Default::default(), window, cx, |s| {
15388 s.move_heads_with(&mut |map, head, _| {
15389 (
15390 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15391 SelectionGoal::None,
15392 )
15393 });
15394 })
15395 }
15396
15397 pub fn move_to_beginning(
15398 &mut self,
15399 _: &MoveToBeginning,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) {
15403 if matches!(self.mode, EditorMode::SingleLine) {
15404 cx.propagate();
15405 return;
15406 }
15407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15408 self.change_selections(Default::default(), window, cx, |s| {
15409 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15410 });
15411 }
15412
15413 pub fn select_to_beginning(
15414 &mut self,
15415 _: &SelectToBeginning,
15416 window: &mut Window,
15417 cx: &mut Context<Self>,
15418 ) {
15419 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15420 selection.set_head(Point::zero(), SelectionGoal::None);
15421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15422 self.change_selections(Default::default(), window, cx, |s| {
15423 s.select(vec![selection]);
15424 });
15425 }
15426
15427 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15428 if matches!(self.mode, EditorMode::SingleLine) {
15429 cx.propagate();
15430 return;
15431 }
15432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15433 let cursor = self.buffer.read(cx).read(cx).len();
15434 self.change_selections(Default::default(), window, cx, |s| {
15435 s.select_ranges(vec![cursor..cursor])
15436 });
15437 }
15438
15439 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15440 self.nav_history = nav_history;
15441 }
15442
15443 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15444 self.nav_history.as_ref()
15445 }
15446
15447 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15448 self.push_to_nav_history(
15449 self.selections.newest_anchor().head(),
15450 None,
15451 false,
15452 true,
15453 cx,
15454 );
15455 }
15456
15457 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15458 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15459 let buffer = self.buffer.read(cx).read(cx);
15460 let cursor_position = cursor_anchor.to_point(&buffer);
15461 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15462 let scroll_top_row = scroll_anchor.top_row(&buffer);
15463 drop(buffer);
15464
15465 NavigationData {
15466 cursor_anchor,
15467 cursor_position,
15468 scroll_anchor,
15469 scroll_top_row,
15470 }
15471 }
15472
15473 fn navigation_entry(
15474 &self,
15475 cursor_anchor: Anchor,
15476 cx: &mut Context<Self>,
15477 ) -> Option<NavigationEntry> {
15478 let Some(history) = self.nav_history.clone() else {
15479 return None;
15480 };
15481 let data = self.navigation_data(cursor_anchor, cx);
15482 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15483 }
15484
15485 fn push_to_nav_history(
15486 &mut self,
15487 cursor_anchor: Anchor,
15488 new_position: Option<Point>,
15489 is_deactivate: bool,
15490 always: bool,
15491 cx: &mut Context<Self>,
15492 ) {
15493 let data = self.navigation_data(cursor_anchor, cx);
15494 if let Some(nav_history) = self.nav_history.as_mut() {
15495 if let Some(new_position) = new_position {
15496 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15497 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15498 return;
15499 }
15500 }
15501
15502 nav_history.push(Some(data), cx);
15503 cx.emit(EditorEvent::PushedToNavHistory {
15504 anchor: cursor_anchor,
15505 is_deactivate,
15506 })
15507 }
15508 }
15509
15510 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15512 let buffer = self.buffer.read(cx).snapshot(cx);
15513 let mut selection = self
15514 .selections
15515 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15516 selection.set_head(buffer.len(), SelectionGoal::None);
15517 self.change_selections(Default::default(), window, cx, |s| {
15518 s.select(vec![selection]);
15519 });
15520 }
15521
15522 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15524 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15525 s.select_ranges([Anchor::min()..Anchor::max()]);
15526 });
15527 }
15528
15529 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15530 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15532 let mut selections = self.selections.all::<Point>(&display_map);
15533 let max_point = display_map.buffer_snapshot().max_point();
15534 for selection in &mut selections {
15535 let rows = selection.spanned_rows(true, &display_map);
15536 selection.start = Point::new(rows.start.0, 0);
15537 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15538 selection.reversed = false;
15539 }
15540 self.change_selections(Default::default(), window, cx, |s| {
15541 s.select(selections);
15542 });
15543 }
15544
15545 pub fn split_selection_into_lines(
15546 &mut self,
15547 action: &SplitSelectionIntoLines,
15548 window: &mut Window,
15549 cx: &mut Context<Self>,
15550 ) {
15551 let selections = self
15552 .selections
15553 .all::<Point>(&self.display_snapshot(cx))
15554 .into_iter()
15555 .map(|selection| selection.start..selection.end)
15556 .collect::<Vec<_>>();
15557 self.unfold_ranges(&selections, true, false, cx);
15558
15559 let mut new_selection_ranges = Vec::new();
15560 {
15561 let buffer = self.buffer.read(cx).read(cx);
15562 for selection in selections {
15563 for row in selection.start.row..selection.end.row {
15564 let line_start = Point::new(row, 0);
15565 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15566
15567 if action.keep_selections {
15568 // Keep the selection range for each line
15569 let selection_start = if row == selection.start.row {
15570 selection.start
15571 } else {
15572 line_start
15573 };
15574 new_selection_ranges.push(selection_start..line_end);
15575 } else {
15576 // Collapse to cursor at end of line
15577 new_selection_ranges.push(line_end..line_end);
15578 }
15579 }
15580
15581 let is_multiline_selection = selection.start.row != selection.end.row;
15582 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15583 // so this action feels more ergonomic when paired with other selection operations
15584 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15585 if !should_skip_last {
15586 if action.keep_selections {
15587 if is_multiline_selection {
15588 let line_start = Point::new(selection.end.row, 0);
15589 new_selection_ranges.push(line_start..selection.end);
15590 } else {
15591 new_selection_ranges.push(selection.start..selection.end);
15592 }
15593 } else {
15594 new_selection_ranges.push(selection.end..selection.end);
15595 }
15596 }
15597 }
15598 }
15599 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15600 s.select_ranges(new_selection_ranges);
15601 });
15602 }
15603
15604 pub fn add_selection_above(
15605 &mut self,
15606 action: &AddSelectionAbove,
15607 window: &mut Window,
15608 cx: &mut Context<Self>,
15609 ) {
15610 self.add_selection(true, action.skip_soft_wrap, window, cx);
15611 }
15612
15613 pub fn add_selection_below(
15614 &mut self,
15615 action: &AddSelectionBelow,
15616 window: &mut Window,
15617 cx: &mut Context<Self>,
15618 ) {
15619 self.add_selection(false, action.skip_soft_wrap, window, cx);
15620 }
15621
15622 fn add_selection(
15623 &mut self,
15624 above: bool,
15625 skip_soft_wrap: bool,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) {
15629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15630
15631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15632 let all_selections = self.selections.all::<Point>(&display_map);
15633 let text_layout_details = self.text_layout_details(window, cx);
15634
15635 let (mut columnar_selections, new_selections_to_columnarize) = {
15636 if let Some(state) = self.add_selections_state.as_ref() {
15637 let columnar_selection_ids: HashSet<_> = state
15638 .groups
15639 .iter()
15640 .flat_map(|group| group.stack.iter())
15641 .copied()
15642 .collect();
15643
15644 all_selections
15645 .into_iter()
15646 .partition(|s| columnar_selection_ids.contains(&s.id))
15647 } else {
15648 (Vec::new(), all_selections)
15649 }
15650 };
15651
15652 let mut state = self
15653 .add_selections_state
15654 .take()
15655 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15656
15657 for selection in new_selections_to_columnarize {
15658 let range = selection.display_range(&display_map).sorted();
15659 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15660 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15661 let positions = start_x.min(end_x)..start_x.max(end_x);
15662 let mut stack = Vec::new();
15663 for row in range.start.row().0..=range.end.row().0 {
15664 if let Some(selection) = self.selections.build_columnar_selection(
15665 &display_map,
15666 DisplayRow(row),
15667 &positions,
15668 selection.reversed,
15669 &text_layout_details,
15670 ) {
15671 stack.push(selection.id);
15672 columnar_selections.push(selection);
15673 }
15674 }
15675 if !stack.is_empty() {
15676 if above {
15677 stack.reverse();
15678 }
15679 state.groups.push(AddSelectionsGroup { above, stack });
15680 }
15681 }
15682
15683 let mut final_selections = Vec::new();
15684 let end_row = if above {
15685 DisplayRow(0)
15686 } else {
15687 display_map.max_point().row()
15688 };
15689
15690 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15691 // positions to place new selections, so we need to keep track of the
15692 // column range of the oldest selection in each group, because
15693 // intermediate selections may have been clamped to shorter lines.
15694 // selections may have been clamped to shorter lines.
15695 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15696 let mut map = HashMap::default();
15697 for group in state.groups.iter() {
15698 if let Some(oldest_id) = group.stack.first() {
15699 if let Some(oldest_selection) =
15700 columnar_selections.iter().find(|s| s.id == *oldest_id)
15701 {
15702 let start_col = oldest_selection.start.column;
15703 let end_col = oldest_selection.end.column;
15704 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15705 for id in &group.stack {
15706 map.insert(*id, goal_columns.clone());
15707 }
15708 }
15709 }
15710 }
15711 map
15712 } else {
15713 HashMap::default()
15714 };
15715
15716 let mut last_added_item_per_group = HashMap::default();
15717 for group in state.groups.iter_mut() {
15718 if let Some(last_id) = group.stack.last() {
15719 last_added_item_per_group.insert(*last_id, group);
15720 }
15721 }
15722
15723 for selection in columnar_selections {
15724 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15725 if above == group.above {
15726 let range = selection.display_range(&display_map).sorted();
15727 debug_assert_eq!(range.start.row(), range.end.row());
15728 let row = range.start.row();
15729 let positions =
15730 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15731 Pixels::from(start)..Pixels::from(end)
15732 } else {
15733 let start_x =
15734 display_map.x_for_display_point(range.start, &text_layout_details);
15735 let end_x =
15736 display_map.x_for_display_point(range.end, &text_layout_details);
15737 start_x.min(end_x)..start_x.max(end_x)
15738 };
15739
15740 let maybe_new_selection = if skip_soft_wrap {
15741 let goal_columns = goal_columns_by_selection_id
15742 .remove(&selection.id)
15743 .unwrap_or_else(|| {
15744 let start_col = selection.start.column;
15745 let end_col = selection.end.column;
15746 start_col.min(end_col)..start_col.max(end_col)
15747 });
15748 self.selections.find_next_columnar_selection_by_buffer_row(
15749 &display_map,
15750 row,
15751 end_row,
15752 above,
15753 &goal_columns,
15754 selection.reversed,
15755 &text_layout_details,
15756 )
15757 } else {
15758 self.selections.find_next_columnar_selection_by_display_row(
15759 &display_map,
15760 row,
15761 end_row,
15762 above,
15763 &positions,
15764 selection.reversed,
15765 &text_layout_details,
15766 )
15767 };
15768
15769 if let Some(new_selection) = maybe_new_selection {
15770 group.stack.push(new_selection.id);
15771 if above {
15772 final_selections.push(new_selection);
15773 final_selections.push(selection);
15774 } else {
15775 final_selections.push(selection);
15776 final_selections.push(new_selection);
15777 }
15778 } else {
15779 final_selections.push(selection);
15780 }
15781 } else {
15782 group.stack.pop();
15783 }
15784 } else {
15785 final_selections.push(selection);
15786 }
15787 }
15788
15789 self.change_selections(Default::default(), window, cx, |s| {
15790 s.select(final_selections);
15791 });
15792
15793 let final_selection_ids: HashSet<_> = self
15794 .selections
15795 .all::<Point>(&display_map)
15796 .iter()
15797 .map(|s| s.id)
15798 .collect();
15799 state.groups.retain_mut(|group| {
15800 // selections might get merged above so we remove invalid items from stacks
15801 group.stack.retain(|id| final_selection_ids.contains(id));
15802
15803 // single selection in stack can be treated as initial state
15804 group.stack.len() > 1
15805 });
15806
15807 if !state.groups.is_empty() {
15808 self.add_selections_state = Some(state);
15809 }
15810 }
15811
15812 pub fn insert_snippet_at_selections(
15813 &mut self,
15814 action: &InsertSnippet,
15815 window: &mut Window,
15816 cx: &mut Context<Self>,
15817 ) {
15818 self.try_insert_snippet_at_selections(action, window, cx)
15819 .log_err();
15820 }
15821
15822 fn try_insert_snippet_at_selections(
15823 &mut self,
15824 action: &InsertSnippet,
15825 window: &mut Window,
15826 cx: &mut Context<Self>,
15827 ) -> Result<()> {
15828 let insertion_ranges = self
15829 .selections
15830 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15831 .into_iter()
15832 .map(|selection| selection.range())
15833 .collect_vec();
15834
15835 let snippet = if let Some(snippet_body) = &action.snippet {
15836 if action.language.is_none() && action.name.is_none() {
15837 Snippet::parse(snippet_body)?
15838 } else {
15839 bail!("`snippet` is mutually exclusive with `language` and `name`")
15840 }
15841 } else if let Some(name) = &action.name {
15842 let project = self.project().context("no project")?;
15843 let snippet_store = project.read(cx).snippets().read(cx);
15844 let snippet = snippet_store
15845 .snippets_for(action.language.clone(), cx)
15846 .into_iter()
15847 .find(|snippet| snippet.name == *name)
15848 .context("snippet not found")?;
15849 Snippet::parse(&snippet.body)?
15850 } else {
15851 // todo(andrew): open modal to select snippet
15852 bail!("`name` or `snippet` is required")
15853 };
15854
15855 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15856 }
15857
15858 fn select_match_ranges(
15859 &mut self,
15860 range: Range<MultiBufferOffset>,
15861 reversed: bool,
15862 replace_newest: bool,
15863 auto_scroll: Option<Autoscroll>,
15864 window: &mut Window,
15865 cx: &mut Context<Editor>,
15866 ) {
15867 self.unfold_ranges(
15868 std::slice::from_ref(&range),
15869 false,
15870 auto_scroll.is_some(),
15871 cx,
15872 );
15873 let effects = if let Some(scroll) = auto_scroll {
15874 SelectionEffects::scroll(scroll)
15875 } else {
15876 SelectionEffects::no_scroll()
15877 };
15878 self.change_selections(effects, window, cx, |s| {
15879 if replace_newest {
15880 s.delete(s.newest_anchor().id);
15881 }
15882 if reversed {
15883 s.insert_range(range.end..range.start);
15884 } else {
15885 s.insert_range(range);
15886 }
15887 });
15888 }
15889
15890 pub fn select_next_match_internal(
15891 &mut self,
15892 display_map: &DisplaySnapshot,
15893 replace_newest: bool,
15894 autoscroll: Option<Autoscroll>,
15895 window: &mut Window,
15896 cx: &mut Context<Self>,
15897 ) -> Result<()> {
15898 let buffer = display_map.buffer_snapshot();
15899 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15900 if let Some(mut select_next_state) = self.select_next_state.take() {
15901 let query = &select_next_state.query;
15902 if !select_next_state.done {
15903 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15904 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15905 let mut next_selected_range = None;
15906
15907 let bytes_after_last_selection =
15908 buffer.bytes_in_range(last_selection.end..buffer.len());
15909 let bytes_before_first_selection =
15910 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15911 let query_matches = query
15912 .stream_find_iter(bytes_after_last_selection)
15913 .map(|result| (last_selection.end, result))
15914 .chain(
15915 query
15916 .stream_find_iter(bytes_before_first_selection)
15917 .map(|result| (MultiBufferOffset(0), result)),
15918 );
15919
15920 for (start_offset, query_match) in query_matches {
15921 let query_match = query_match.unwrap(); // can only fail due to I/O
15922 let offset_range =
15923 start_offset + query_match.start()..start_offset + query_match.end();
15924
15925 if !select_next_state.wordwise
15926 || (!buffer.is_inside_word(offset_range.start, None)
15927 && !buffer.is_inside_word(offset_range.end, None))
15928 {
15929 let idx = selections
15930 .partition_point(|selection| selection.end <= offset_range.start);
15931 let overlaps = selections
15932 .get(idx)
15933 .map_or(false, |selection| selection.start < offset_range.end);
15934
15935 if !overlaps {
15936 next_selected_range = Some(offset_range);
15937 break;
15938 }
15939 }
15940 }
15941
15942 if let Some(next_selected_range) = next_selected_range {
15943 self.select_match_ranges(
15944 next_selected_range,
15945 last_selection.reversed,
15946 replace_newest,
15947 autoscroll,
15948 window,
15949 cx,
15950 );
15951 } else {
15952 select_next_state.done = true;
15953 }
15954 }
15955
15956 self.select_next_state = Some(select_next_state);
15957 } else {
15958 let mut only_carets = true;
15959 let mut same_text_selected = true;
15960 let mut selected_text = None;
15961
15962 let mut selections_iter = selections.iter().peekable();
15963 while let Some(selection) = selections_iter.next() {
15964 if selection.start != selection.end {
15965 only_carets = false;
15966 }
15967
15968 if same_text_selected {
15969 if selected_text.is_none() {
15970 selected_text =
15971 Some(buffer.text_for_range(selection.range()).collect::<String>());
15972 }
15973
15974 if let Some(next_selection) = selections_iter.peek() {
15975 if next_selection.len() == selection.len() {
15976 let next_selected_text = buffer
15977 .text_for_range(next_selection.range())
15978 .collect::<String>();
15979 if Some(next_selected_text) != selected_text {
15980 same_text_selected = false;
15981 selected_text = None;
15982 }
15983 } else {
15984 same_text_selected = false;
15985 selected_text = None;
15986 }
15987 }
15988 }
15989 }
15990
15991 if only_carets {
15992 for selection in &mut selections {
15993 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15994 selection.start = word_range.start;
15995 selection.end = word_range.end;
15996 selection.goal = SelectionGoal::None;
15997 selection.reversed = false;
15998 self.select_match_ranges(
15999 selection.start..selection.end,
16000 selection.reversed,
16001 replace_newest,
16002 autoscroll,
16003 window,
16004 cx,
16005 );
16006 }
16007
16008 if selections.len() == 1 {
16009 let selection = selections
16010 .last()
16011 .expect("ensured that there's only one selection");
16012 let query = buffer
16013 .text_for_range(selection.start..selection.end)
16014 .collect::<String>();
16015 let is_empty = query.is_empty();
16016 let select_state = SelectNextState {
16017 query: self.build_query(&[query], cx)?,
16018 wordwise: true,
16019 done: is_empty,
16020 };
16021 self.select_next_state = Some(select_state);
16022 } else {
16023 self.select_next_state = None;
16024 }
16025 } else if let Some(selected_text) = selected_text {
16026 self.select_next_state = Some(SelectNextState {
16027 query: self.build_query(&[selected_text], cx)?,
16028 wordwise: false,
16029 done: false,
16030 });
16031 self.select_next_match_internal(
16032 display_map,
16033 replace_newest,
16034 autoscroll,
16035 window,
16036 cx,
16037 )?;
16038 }
16039 }
16040 Ok(())
16041 }
16042
16043 pub fn select_all_matches(
16044 &mut self,
16045 _action: &SelectAllMatches,
16046 window: &mut Window,
16047 cx: &mut Context<Self>,
16048 ) -> Result<()> {
16049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16050
16051 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16052
16053 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16054 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16055 else {
16056 return Ok(());
16057 };
16058
16059 let mut new_selections = Vec::new();
16060
16061 let reversed = self
16062 .selections
16063 .oldest::<MultiBufferOffset>(&display_map)
16064 .reversed;
16065 let buffer = display_map.buffer_snapshot();
16066 let query_matches = select_next_state
16067 .query
16068 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16069
16070 for query_match in query_matches.into_iter() {
16071 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16072 let offset_range = if reversed {
16073 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16074 } else {
16075 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16076 };
16077
16078 if !select_next_state.wordwise
16079 || (!buffer.is_inside_word(offset_range.start, None)
16080 && !buffer.is_inside_word(offset_range.end, None))
16081 {
16082 new_selections.push(offset_range.start..offset_range.end);
16083 }
16084 }
16085
16086 select_next_state.done = true;
16087
16088 if new_selections.is_empty() {
16089 log::error!("bug: new_selections is empty in select_all_matches");
16090 return Ok(());
16091 }
16092
16093 self.unfold_ranges(&new_selections, false, false, cx);
16094 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16095 selections.select_ranges(new_selections)
16096 });
16097
16098 Ok(())
16099 }
16100
16101 pub fn select_next(
16102 &mut self,
16103 action: &SelectNext,
16104 window: &mut Window,
16105 cx: &mut Context<Self>,
16106 ) -> Result<()> {
16107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16108 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16109 self.select_next_match_internal(
16110 &display_map,
16111 action.replace_newest,
16112 Some(Autoscroll::newest()),
16113 window,
16114 cx,
16115 )
16116 }
16117
16118 pub fn select_previous(
16119 &mut self,
16120 action: &SelectPrevious,
16121 window: &mut Window,
16122 cx: &mut Context<Self>,
16123 ) -> Result<()> {
16124 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16125 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16126 let buffer = display_map.buffer_snapshot();
16127 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16128 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16129 let query = &select_prev_state.query;
16130 if !select_prev_state.done {
16131 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16132 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16133 let mut next_selected_range = None;
16134 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16135 let bytes_before_last_selection =
16136 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16137 let bytes_after_first_selection =
16138 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16139 let query_matches = query
16140 .stream_find_iter(bytes_before_last_selection)
16141 .map(|result| (last_selection.start, result))
16142 .chain(
16143 query
16144 .stream_find_iter(bytes_after_first_selection)
16145 .map(|result| (buffer.len(), result)),
16146 );
16147 for (end_offset, query_match) in query_matches {
16148 let query_match = query_match.unwrap(); // can only fail due to I/O
16149 let offset_range =
16150 end_offset - query_match.end()..end_offset - query_match.start();
16151
16152 if !select_prev_state.wordwise
16153 || (!buffer.is_inside_word(offset_range.start, None)
16154 && !buffer.is_inside_word(offset_range.end, None))
16155 {
16156 next_selected_range = Some(offset_range);
16157 break;
16158 }
16159 }
16160
16161 if let Some(next_selected_range) = next_selected_range {
16162 self.select_match_ranges(
16163 next_selected_range,
16164 last_selection.reversed,
16165 action.replace_newest,
16166 Some(Autoscroll::newest()),
16167 window,
16168 cx,
16169 );
16170 } else {
16171 select_prev_state.done = true;
16172 }
16173 }
16174
16175 self.select_prev_state = Some(select_prev_state);
16176 } else {
16177 let mut only_carets = true;
16178 let mut same_text_selected = true;
16179 let mut selected_text = None;
16180
16181 let mut selections_iter = selections.iter().peekable();
16182 while let Some(selection) = selections_iter.next() {
16183 if selection.start != selection.end {
16184 only_carets = false;
16185 }
16186
16187 if same_text_selected {
16188 if selected_text.is_none() {
16189 selected_text =
16190 Some(buffer.text_for_range(selection.range()).collect::<String>());
16191 }
16192
16193 if let Some(next_selection) = selections_iter.peek() {
16194 if next_selection.len() == selection.len() {
16195 let next_selected_text = buffer
16196 .text_for_range(next_selection.range())
16197 .collect::<String>();
16198 if Some(next_selected_text) != selected_text {
16199 same_text_selected = false;
16200 selected_text = None;
16201 }
16202 } else {
16203 same_text_selected = false;
16204 selected_text = None;
16205 }
16206 }
16207 }
16208 }
16209
16210 if only_carets {
16211 for selection in &mut selections {
16212 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16213 selection.start = word_range.start;
16214 selection.end = word_range.end;
16215 selection.goal = SelectionGoal::None;
16216 selection.reversed = false;
16217 self.select_match_ranges(
16218 selection.start..selection.end,
16219 selection.reversed,
16220 action.replace_newest,
16221 Some(Autoscroll::newest()),
16222 window,
16223 cx,
16224 );
16225 }
16226 if selections.len() == 1 {
16227 let selection = selections
16228 .last()
16229 .expect("ensured that there's only one selection");
16230 let query = buffer
16231 .text_for_range(selection.start..selection.end)
16232 .collect::<String>();
16233 let is_empty = query.is_empty();
16234 let select_state = SelectNextState {
16235 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16236 wordwise: true,
16237 done: is_empty,
16238 };
16239 self.select_prev_state = Some(select_state);
16240 } else {
16241 self.select_prev_state = None;
16242 }
16243 } else if let Some(selected_text) = selected_text {
16244 self.select_prev_state = Some(SelectNextState {
16245 query: self
16246 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16247 wordwise: false,
16248 done: false,
16249 });
16250 self.select_previous(action, window, cx)?;
16251 }
16252 }
16253 Ok(())
16254 }
16255
16256 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16257 /// setting the case sensitivity based on the global
16258 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16259 /// editor's settings.
16260 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16261 where
16262 I: IntoIterator<Item = P>,
16263 P: AsRef<[u8]>,
16264 {
16265 let case_sensitive = self
16266 .select_next_is_case_sensitive
16267 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16268
16269 let mut builder = AhoCorasickBuilder::new();
16270 builder.ascii_case_insensitive(!case_sensitive);
16271 builder.build(patterns)
16272 }
16273
16274 pub fn find_next_match(
16275 &mut self,
16276 _: &FindNextMatch,
16277 window: &mut Window,
16278 cx: &mut Context<Self>,
16279 ) -> Result<()> {
16280 let selections = self.selections.disjoint_anchors_arc();
16281 match selections.first() {
16282 Some(first) if selections.len() >= 2 => {
16283 self.change_selections(Default::default(), window, cx, |s| {
16284 s.select_ranges([first.range()]);
16285 });
16286 }
16287 _ => self.select_next(
16288 &SelectNext {
16289 replace_newest: true,
16290 },
16291 window,
16292 cx,
16293 )?,
16294 }
16295 Ok(())
16296 }
16297
16298 pub fn find_previous_match(
16299 &mut self,
16300 _: &FindPreviousMatch,
16301 window: &mut Window,
16302 cx: &mut Context<Self>,
16303 ) -> Result<()> {
16304 let selections = self.selections.disjoint_anchors_arc();
16305 match selections.last() {
16306 Some(last) if selections.len() >= 2 => {
16307 self.change_selections(Default::default(), window, cx, |s| {
16308 s.select_ranges([last.range()]);
16309 });
16310 }
16311 _ => self.select_previous(
16312 &SelectPrevious {
16313 replace_newest: true,
16314 },
16315 window,
16316 cx,
16317 )?,
16318 }
16319 Ok(())
16320 }
16321
16322 pub fn toggle_comments(
16323 &mut self,
16324 action: &ToggleComments,
16325 window: &mut Window,
16326 cx: &mut Context<Self>,
16327 ) {
16328 if self.read_only(cx) {
16329 return;
16330 }
16331 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16332 let text_layout_details = &self.text_layout_details(window, cx);
16333 self.transact(window, cx, |this, window, cx| {
16334 let mut selections = this
16335 .selections
16336 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16337 let mut edits = Vec::new();
16338 let mut selection_edit_ranges = Vec::new();
16339 let mut last_toggled_row = None;
16340 let snapshot = this.buffer.read(cx).read(cx);
16341 let empty_str: Arc<str> = Arc::default();
16342 let mut suffixes_inserted = Vec::new();
16343 let ignore_indent = action.ignore_indent;
16344
16345 fn comment_prefix_range(
16346 snapshot: &MultiBufferSnapshot,
16347 row: MultiBufferRow,
16348 comment_prefix: &str,
16349 comment_prefix_whitespace: &str,
16350 ignore_indent: bool,
16351 ) -> Range<Point> {
16352 let indent_size = if ignore_indent {
16353 0
16354 } else {
16355 snapshot.indent_size_for_line(row).len
16356 };
16357
16358 let start = Point::new(row.0, indent_size);
16359
16360 let mut line_bytes = snapshot
16361 .bytes_in_range(start..snapshot.max_point())
16362 .flatten()
16363 .copied();
16364
16365 // If this line currently begins with the line comment prefix, then record
16366 // the range containing the prefix.
16367 if line_bytes
16368 .by_ref()
16369 .take(comment_prefix.len())
16370 .eq(comment_prefix.bytes())
16371 {
16372 // Include any whitespace that matches the comment prefix.
16373 let matching_whitespace_len = line_bytes
16374 .zip(comment_prefix_whitespace.bytes())
16375 .take_while(|(a, b)| a == b)
16376 .count() as u32;
16377 let end = Point::new(
16378 start.row,
16379 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16380 );
16381 start..end
16382 } else {
16383 start..start
16384 }
16385 }
16386
16387 fn comment_suffix_range(
16388 snapshot: &MultiBufferSnapshot,
16389 row: MultiBufferRow,
16390 comment_suffix: &str,
16391 comment_suffix_has_leading_space: bool,
16392 ) -> Range<Point> {
16393 let end = Point::new(row.0, snapshot.line_len(row));
16394 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16395
16396 let mut line_end_bytes = snapshot
16397 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16398 .flatten()
16399 .copied();
16400
16401 let leading_space_len = if suffix_start_column > 0
16402 && line_end_bytes.next() == Some(b' ')
16403 && comment_suffix_has_leading_space
16404 {
16405 1
16406 } else {
16407 0
16408 };
16409
16410 // If this line currently begins with the line comment prefix, then record
16411 // the range containing the prefix.
16412 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16413 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16414 start..end
16415 } else {
16416 end..end
16417 }
16418 }
16419
16420 // TODO: Handle selections that cross excerpts
16421 for selection in &mut selections {
16422 let start_column = snapshot
16423 .indent_size_for_line(MultiBufferRow(selection.start.row))
16424 .len;
16425 let language = if let Some(language) =
16426 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16427 {
16428 language
16429 } else {
16430 continue;
16431 };
16432
16433 selection_edit_ranges.clear();
16434
16435 // If multiple selections contain a given row, avoid processing that
16436 // row more than once.
16437 let mut start_row = MultiBufferRow(selection.start.row);
16438 if last_toggled_row == Some(start_row) {
16439 start_row = start_row.next_row();
16440 }
16441 let end_row =
16442 if selection.end.row > selection.start.row && selection.end.column == 0 {
16443 MultiBufferRow(selection.end.row - 1)
16444 } else {
16445 MultiBufferRow(selection.end.row)
16446 };
16447 last_toggled_row = Some(end_row);
16448
16449 if start_row > end_row {
16450 continue;
16451 }
16452
16453 // If the language has line comments, toggle those.
16454 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16455
16456 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16457 if ignore_indent {
16458 full_comment_prefixes = full_comment_prefixes
16459 .into_iter()
16460 .map(|s| Arc::from(s.trim_end()))
16461 .collect();
16462 }
16463
16464 if !full_comment_prefixes.is_empty() {
16465 let first_prefix = full_comment_prefixes
16466 .first()
16467 .expect("prefixes is non-empty");
16468 let prefix_trimmed_lengths = full_comment_prefixes
16469 .iter()
16470 .map(|p| p.trim_end_matches(' ').len())
16471 .collect::<SmallVec<[usize; 4]>>();
16472
16473 let mut all_selection_lines_are_comments = true;
16474
16475 for row in start_row.0..=end_row.0 {
16476 let row = MultiBufferRow(row);
16477 if start_row < end_row && snapshot.is_line_blank(row) {
16478 continue;
16479 }
16480
16481 let prefix_range = full_comment_prefixes
16482 .iter()
16483 .zip(prefix_trimmed_lengths.iter().copied())
16484 .map(|(prefix, trimmed_prefix_len)| {
16485 comment_prefix_range(
16486 snapshot.deref(),
16487 row,
16488 &prefix[..trimmed_prefix_len],
16489 &prefix[trimmed_prefix_len..],
16490 ignore_indent,
16491 )
16492 })
16493 .max_by_key(|range| range.end.column - range.start.column)
16494 .expect("prefixes is non-empty");
16495
16496 if prefix_range.is_empty() {
16497 all_selection_lines_are_comments = false;
16498 }
16499
16500 selection_edit_ranges.push(prefix_range);
16501 }
16502
16503 if all_selection_lines_are_comments {
16504 edits.extend(
16505 selection_edit_ranges
16506 .iter()
16507 .cloned()
16508 .map(|range| (range, empty_str.clone())),
16509 );
16510 } else {
16511 let min_column = selection_edit_ranges
16512 .iter()
16513 .map(|range| range.start.column)
16514 .min()
16515 .unwrap_or(0);
16516 edits.extend(selection_edit_ranges.iter().map(|range| {
16517 let position = Point::new(range.start.row, min_column);
16518 (position..position, first_prefix.clone())
16519 }));
16520 }
16521 } else if let Some(BlockCommentConfig {
16522 start: full_comment_prefix,
16523 end: comment_suffix,
16524 ..
16525 }) = language.block_comment()
16526 {
16527 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16528 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16529 let prefix_range = comment_prefix_range(
16530 snapshot.deref(),
16531 start_row,
16532 comment_prefix,
16533 comment_prefix_whitespace,
16534 ignore_indent,
16535 );
16536 let suffix_range = comment_suffix_range(
16537 snapshot.deref(),
16538 end_row,
16539 comment_suffix.trim_start_matches(' '),
16540 comment_suffix.starts_with(' '),
16541 );
16542
16543 if prefix_range.is_empty() || suffix_range.is_empty() {
16544 edits.push((
16545 prefix_range.start..prefix_range.start,
16546 full_comment_prefix.clone(),
16547 ));
16548 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16549 suffixes_inserted.push((end_row, comment_suffix.len()));
16550 } else {
16551 edits.push((prefix_range, empty_str.clone()));
16552 edits.push((suffix_range, empty_str.clone()));
16553 }
16554 } else {
16555 continue;
16556 }
16557 }
16558
16559 drop(snapshot);
16560 this.buffer.update(cx, |buffer, cx| {
16561 buffer.edit(edits, None, cx);
16562 });
16563
16564 // Adjust selections so that they end before any comment suffixes that
16565 // were inserted.
16566 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16567 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16568 let snapshot = this.buffer.read(cx).read(cx);
16569 for selection in &mut selections {
16570 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16571 match row.cmp(&MultiBufferRow(selection.end.row)) {
16572 Ordering::Less => {
16573 suffixes_inserted.next();
16574 continue;
16575 }
16576 Ordering::Greater => break,
16577 Ordering::Equal => {
16578 if selection.end.column == snapshot.line_len(row) {
16579 if selection.is_empty() {
16580 selection.start.column -= suffix_len as u32;
16581 }
16582 selection.end.column -= suffix_len as u32;
16583 }
16584 break;
16585 }
16586 }
16587 }
16588 }
16589
16590 drop(snapshot);
16591 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16592
16593 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16594 let selections_on_single_row = selections.windows(2).all(|selections| {
16595 selections[0].start.row == selections[1].start.row
16596 && selections[0].end.row == selections[1].end.row
16597 && selections[0].start.row == selections[0].end.row
16598 });
16599 let selections_selecting = selections
16600 .iter()
16601 .any(|selection| selection.start != selection.end);
16602 let advance_downwards = action.advance_downwards
16603 && selections_on_single_row
16604 && !selections_selecting
16605 && !matches!(this.mode, EditorMode::SingleLine);
16606
16607 if advance_downwards {
16608 let snapshot = this.buffer.read(cx).snapshot(cx);
16609
16610 this.change_selections(Default::default(), window, cx, |s| {
16611 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16612 let mut point = display_point.to_point(display_snapshot);
16613 point.row += 1;
16614 point = snapshot.clip_point(point, Bias::Left);
16615 let display_point = point.to_display_point(display_snapshot);
16616 let goal = SelectionGoal::HorizontalPosition(
16617 display_snapshot
16618 .x_for_display_point(display_point, text_layout_details)
16619 .into(),
16620 );
16621 (display_point, goal)
16622 })
16623 });
16624 }
16625 });
16626 }
16627
16628 pub fn select_enclosing_symbol(
16629 &mut self,
16630 _: &SelectEnclosingSymbol,
16631 window: &mut Window,
16632 cx: &mut Context<Self>,
16633 ) {
16634 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16635
16636 let buffer = self.buffer.read(cx).snapshot(cx);
16637 let old_selections = self
16638 .selections
16639 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16640 .into_boxed_slice();
16641
16642 fn update_selection(
16643 selection: &Selection<MultiBufferOffset>,
16644 buffer_snap: &MultiBufferSnapshot,
16645 ) -> Option<Selection<MultiBufferOffset>> {
16646 let cursor = selection.head();
16647 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16648 for symbol in symbols.iter().rev() {
16649 let start = symbol.range.start.to_offset(buffer_snap);
16650 let end = symbol.range.end.to_offset(buffer_snap);
16651 let new_range = start..end;
16652 if start < selection.start || end > selection.end {
16653 return Some(Selection {
16654 id: selection.id,
16655 start: new_range.start,
16656 end: new_range.end,
16657 goal: SelectionGoal::None,
16658 reversed: selection.reversed,
16659 });
16660 }
16661 }
16662 None
16663 }
16664
16665 let mut selected_larger_symbol = false;
16666 let new_selections = old_selections
16667 .iter()
16668 .map(|selection| match update_selection(selection, &buffer) {
16669 Some(new_selection) => {
16670 if new_selection.range() != selection.range() {
16671 selected_larger_symbol = true;
16672 }
16673 new_selection
16674 }
16675 None => selection.clone(),
16676 })
16677 .collect::<Vec<_>>();
16678
16679 if selected_larger_symbol {
16680 self.change_selections(Default::default(), window, cx, |s| {
16681 s.select(new_selections);
16682 });
16683 }
16684 }
16685
16686 pub fn select_larger_syntax_node(
16687 &mut self,
16688 _: &SelectLargerSyntaxNode,
16689 window: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) {
16692 let Some(visible_row_count) = self.visible_row_count() else {
16693 return;
16694 };
16695 let old_selections: Box<[_]> = self
16696 .selections
16697 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16698 .into();
16699 if old_selections.is_empty() {
16700 return;
16701 }
16702
16703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16704
16705 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16706 let buffer = self.buffer.read(cx).snapshot(cx);
16707
16708 let mut selected_larger_node = false;
16709 let mut new_selections = old_selections
16710 .iter()
16711 .map(|selection| {
16712 let old_range = selection.start..selection.end;
16713
16714 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16715 // manually select word at selection
16716 if ["string_content", "inline"].contains(&node.kind()) {
16717 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16718 // ignore if word is already selected
16719 if !word_range.is_empty() && old_range != word_range {
16720 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16721 // only select word if start and end point belongs to same word
16722 if word_range == last_word_range {
16723 selected_larger_node = true;
16724 return Selection {
16725 id: selection.id,
16726 start: word_range.start,
16727 end: word_range.end,
16728 goal: SelectionGoal::None,
16729 reversed: selection.reversed,
16730 };
16731 }
16732 }
16733 }
16734 }
16735
16736 let mut new_range = old_range.clone();
16737 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16738 new_range = range;
16739 if !node.is_named() {
16740 continue;
16741 }
16742 if !display_map.intersects_fold(new_range.start)
16743 && !display_map.intersects_fold(new_range.end)
16744 {
16745 break;
16746 }
16747 }
16748
16749 selected_larger_node |= new_range != old_range;
16750 Selection {
16751 id: selection.id,
16752 start: new_range.start,
16753 end: new_range.end,
16754 goal: SelectionGoal::None,
16755 reversed: selection.reversed,
16756 }
16757 })
16758 .collect::<Vec<_>>();
16759
16760 if !selected_larger_node {
16761 return; // don't put this call in the history
16762 }
16763
16764 // scroll based on transformation done to the last selection created by the user
16765 let (last_old, last_new) = old_selections
16766 .last()
16767 .zip(new_selections.last().cloned())
16768 .expect("old_selections isn't empty");
16769
16770 let is_selection_reversed = if new_selections.len() == 1 {
16771 let should_be_reversed = last_old.start != last_new.start;
16772 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16773 should_be_reversed
16774 } else {
16775 last_new.reversed
16776 };
16777
16778 if selected_larger_node {
16779 self.select_syntax_node_history.disable_clearing = true;
16780 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16781 s.select(new_selections.clone());
16782 });
16783 self.select_syntax_node_history.disable_clearing = false;
16784 }
16785
16786 let start_row = last_new.start.to_display_point(&display_map).row().0;
16787 let end_row = last_new.end.to_display_point(&display_map).row().0;
16788 let selection_height = end_row - start_row + 1;
16789 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16790
16791 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16792 let scroll_behavior = if fits_on_the_screen {
16793 self.request_autoscroll(Autoscroll::fit(), cx);
16794 SelectSyntaxNodeScrollBehavior::FitSelection
16795 } else if is_selection_reversed {
16796 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16797 SelectSyntaxNodeScrollBehavior::CursorTop
16798 } else {
16799 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16800 SelectSyntaxNodeScrollBehavior::CursorBottom
16801 };
16802
16803 let old_selections: Box<[Selection<Anchor>]> = old_selections
16804 .iter()
16805 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16806 .collect();
16807 self.select_syntax_node_history.push((
16808 old_selections,
16809 scroll_behavior,
16810 is_selection_reversed,
16811 ));
16812 }
16813
16814 pub fn select_smaller_syntax_node(
16815 &mut self,
16816 _: &SelectSmallerSyntaxNode,
16817 window: &mut Window,
16818 cx: &mut Context<Self>,
16819 ) {
16820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16821
16822 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16823 self.select_syntax_node_history.pop()
16824 {
16825 if let Some(selection) = selections.last_mut() {
16826 selection.reversed = is_selection_reversed;
16827 }
16828
16829 let snapshot = self.buffer.read(cx).snapshot(cx);
16830 let selections: Vec<Selection<MultiBufferOffset>> = selections
16831 .iter()
16832 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16833 .collect();
16834
16835 self.select_syntax_node_history.disable_clearing = true;
16836 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16837 s.select(selections);
16838 });
16839 self.select_syntax_node_history.disable_clearing = false;
16840
16841 match scroll_behavior {
16842 SelectSyntaxNodeScrollBehavior::CursorTop => {
16843 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16844 }
16845 SelectSyntaxNodeScrollBehavior::FitSelection => {
16846 self.request_autoscroll(Autoscroll::fit(), cx);
16847 }
16848 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16849 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16850 }
16851 }
16852 }
16853 }
16854
16855 pub fn unwrap_syntax_node(
16856 &mut self,
16857 _: &UnwrapSyntaxNode,
16858 window: &mut Window,
16859 cx: &mut Context<Self>,
16860 ) {
16861 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16862
16863 let buffer = self.buffer.read(cx).snapshot(cx);
16864 let selections = self
16865 .selections
16866 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16867 .into_iter()
16868 // subtracting the offset requires sorting
16869 .sorted_by_key(|i| i.start);
16870
16871 let full_edits = selections
16872 .into_iter()
16873 .filter_map(|selection| {
16874 let child = if selection.is_empty()
16875 && let Some((_, ancestor_range)) =
16876 buffer.syntax_ancestor(selection.start..selection.end)
16877 {
16878 ancestor_range
16879 } else {
16880 selection.range()
16881 };
16882
16883 let mut parent = child.clone();
16884 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16885 parent = ancestor_range;
16886 if parent.start < child.start || parent.end > child.end {
16887 break;
16888 }
16889 }
16890
16891 if parent == child {
16892 return None;
16893 }
16894 let text = buffer.text_for_range(child).collect::<String>();
16895 Some((selection.id, parent, text))
16896 })
16897 .collect::<Vec<_>>();
16898 if full_edits.is_empty() {
16899 return;
16900 }
16901
16902 self.transact(window, cx, |this, window, cx| {
16903 this.buffer.update(cx, |buffer, cx| {
16904 buffer.edit(
16905 full_edits
16906 .iter()
16907 .map(|(_, p, t)| (p.clone(), t.clone()))
16908 .collect::<Vec<_>>(),
16909 None,
16910 cx,
16911 );
16912 });
16913 this.change_selections(Default::default(), window, cx, |s| {
16914 let mut offset = 0;
16915 let mut selections = vec![];
16916 for (id, parent, text) in full_edits {
16917 let start = parent.start - offset;
16918 offset += (parent.end - parent.start) - text.len();
16919 selections.push(Selection {
16920 id,
16921 start,
16922 end: start + text.len(),
16923 reversed: false,
16924 goal: Default::default(),
16925 });
16926 }
16927 s.select(selections);
16928 });
16929 });
16930 }
16931
16932 pub fn select_next_syntax_node(
16933 &mut self,
16934 _: &SelectNextSyntaxNode,
16935 window: &mut Window,
16936 cx: &mut Context<Self>,
16937 ) {
16938 let old_selections: Box<[_]> = self
16939 .selections
16940 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16941 .into();
16942 if old_selections.is_empty() {
16943 return;
16944 }
16945
16946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16947
16948 let buffer = self.buffer.read(cx).snapshot(cx);
16949 let mut selected_sibling = false;
16950
16951 let new_selections = old_selections
16952 .iter()
16953 .map(|selection| {
16954 let old_range = selection.start..selection.end;
16955
16956 let old_range =
16957 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16958 let excerpt = buffer.excerpt_containing(old_range.clone());
16959
16960 if let Some(mut excerpt) = excerpt
16961 && let Some(node) = excerpt
16962 .buffer()
16963 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16964 {
16965 let new_range = excerpt.map_range_from_buffer(
16966 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16967 );
16968 selected_sibling = true;
16969 Selection {
16970 id: selection.id,
16971 start: new_range.start,
16972 end: new_range.end,
16973 goal: SelectionGoal::None,
16974 reversed: selection.reversed,
16975 }
16976 } else {
16977 selection.clone()
16978 }
16979 })
16980 .collect::<Vec<_>>();
16981
16982 if selected_sibling {
16983 self.change_selections(
16984 SelectionEffects::scroll(Autoscroll::fit()),
16985 window,
16986 cx,
16987 |s| {
16988 s.select(new_selections);
16989 },
16990 );
16991 }
16992 }
16993
16994 pub fn select_prev_syntax_node(
16995 &mut self,
16996 _: &SelectPreviousSyntaxNode,
16997 window: &mut Window,
16998 cx: &mut Context<Self>,
16999 ) {
17000 let old_selections: Box<[_]> = self
17001 .selections
17002 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17003 .into();
17004 if old_selections.is_empty() {
17005 return;
17006 }
17007
17008 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17009
17010 let buffer = self.buffer.read(cx).snapshot(cx);
17011 let mut selected_sibling = false;
17012
17013 let new_selections = old_selections
17014 .iter()
17015 .map(|selection| {
17016 let old_range = selection.start..selection.end;
17017 let old_range =
17018 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17019 let excerpt = buffer.excerpt_containing(old_range.clone());
17020
17021 if let Some(mut excerpt) = excerpt
17022 && let Some(node) = excerpt
17023 .buffer()
17024 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
17025 {
17026 let new_range = excerpt.map_range_from_buffer(
17027 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17028 );
17029 selected_sibling = true;
17030 Selection {
17031 id: selection.id,
17032 start: new_range.start,
17033 end: new_range.end,
17034 goal: SelectionGoal::None,
17035 reversed: selection.reversed,
17036 }
17037 } else {
17038 selection.clone()
17039 }
17040 })
17041 .collect::<Vec<_>>();
17042
17043 if selected_sibling {
17044 self.change_selections(
17045 SelectionEffects::scroll(Autoscroll::fit()),
17046 window,
17047 cx,
17048 |s| {
17049 s.select(new_selections);
17050 },
17051 );
17052 }
17053 }
17054
17055 pub fn move_to_start_of_larger_syntax_node(
17056 &mut self,
17057 _: &MoveToStartOfLargerSyntaxNode,
17058 window: &mut Window,
17059 cx: &mut Context<Self>,
17060 ) {
17061 self.move_cursors_to_syntax_nodes(window, cx, false);
17062 }
17063
17064 pub fn move_to_end_of_larger_syntax_node(
17065 &mut self,
17066 _: &MoveToEndOfLargerSyntaxNode,
17067 window: &mut Window,
17068 cx: &mut Context<Self>,
17069 ) {
17070 self.move_cursors_to_syntax_nodes(window, cx, true);
17071 }
17072
17073 fn find_syntax_node_boundary(
17074 &self,
17075 selection_pos: MultiBufferOffset,
17076 move_to_end: bool,
17077 display_map: &DisplaySnapshot,
17078 buffer: &MultiBufferSnapshot,
17079 ) -> MultiBufferOffset {
17080 let old_range = selection_pos..selection_pos;
17081 let mut new_pos = selection_pos;
17082 let mut search_range = old_range;
17083 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17084 search_range = range.clone();
17085 if !node.is_named()
17086 || display_map.intersects_fold(range.start)
17087 || display_map.intersects_fold(range.end)
17088 // If cursor is already at the end of the syntax node, continue searching
17089 || (move_to_end && range.end == selection_pos)
17090 // If cursor is already at the start of the syntax node, continue searching
17091 || (!move_to_end && range.start == selection_pos)
17092 {
17093 continue;
17094 }
17095
17096 // If we found a string_content node, find the largest parent that is still string_content
17097 // Enables us to skip to the end of strings without taking multiple steps inside the string
17098 let (_, final_range) = if node.kind() == "string_content" {
17099 let mut current_node = node;
17100 let mut current_range = range;
17101 while let Some((parent, parent_range)) =
17102 buffer.syntax_ancestor(current_range.clone())
17103 {
17104 if parent.kind() == "string_content" {
17105 current_node = parent;
17106 current_range = parent_range;
17107 } else {
17108 break;
17109 }
17110 }
17111
17112 (current_node, current_range)
17113 } else {
17114 (node, range)
17115 };
17116
17117 new_pos = if move_to_end {
17118 final_range.end
17119 } else {
17120 final_range.start
17121 };
17122
17123 break;
17124 }
17125
17126 new_pos
17127 }
17128
17129 fn move_cursors_to_syntax_nodes(
17130 &mut self,
17131 window: &mut Window,
17132 cx: &mut Context<Self>,
17133 move_to_end: bool,
17134 ) -> bool {
17135 let old_selections: Box<[_]> = self
17136 .selections
17137 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17138 .into();
17139 if old_selections.is_empty() {
17140 return false;
17141 }
17142
17143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17144
17145 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17146 let buffer = self.buffer.read(cx).snapshot(cx);
17147
17148 let mut any_cursor_moved = false;
17149 let new_selections = old_selections
17150 .iter()
17151 .map(|selection| {
17152 if !selection.is_empty() {
17153 return selection.clone();
17154 }
17155
17156 let selection_pos = selection.head();
17157 let new_pos = self.find_syntax_node_boundary(
17158 selection_pos,
17159 move_to_end,
17160 &display_map,
17161 &buffer,
17162 );
17163
17164 any_cursor_moved |= new_pos != selection_pos;
17165
17166 Selection {
17167 id: selection.id,
17168 start: new_pos,
17169 end: new_pos,
17170 goal: SelectionGoal::None,
17171 reversed: false,
17172 }
17173 })
17174 .collect::<Vec<_>>();
17175
17176 self.change_selections(Default::default(), window, cx, |s| {
17177 s.select(new_selections);
17178 });
17179 self.request_autoscroll(Autoscroll::newest(), cx);
17180
17181 any_cursor_moved
17182 }
17183
17184 pub fn select_to_start_of_larger_syntax_node(
17185 &mut self,
17186 _: &SelectToStartOfLargerSyntaxNode,
17187 window: &mut Window,
17188 cx: &mut Context<Self>,
17189 ) {
17190 self.select_to_syntax_nodes(window, cx, false);
17191 }
17192
17193 pub fn select_to_end_of_larger_syntax_node(
17194 &mut self,
17195 _: &SelectToEndOfLargerSyntaxNode,
17196 window: &mut Window,
17197 cx: &mut Context<Self>,
17198 ) {
17199 self.select_to_syntax_nodes(window, cx, true);
17200 }
17201
17202 fn select_to_syntax_nodes(
17203 &mut self,
17204 window: &mut Window,
17205 cx: &mut Context<Self>,
17206 move_to_end: bool,
17207 ) {
17208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17209
17210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17211 let buffer = self.buffer.read(cx).snapshot(cx);
17212 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17213
17214 let new_selections = old_selections
17215 .iter()
17216 .map(|selection| {
17217 let new_pos = self.find_syntax_node_boundary(
17218 selection.head(),
17219 move_to_end,
17220 &display_map,
17221 &buffer,
17222 );
17223
17224 let mut new_selection = selection.clone();
17225 new_selection.set_head(new_pos, SelectionGoal::None);
17226 new_selection
17227 })
17228 .collect::<Vec<_>>();
17229
17230 self.change_selections(Default::default(), window, cx, |s| {
17231 s.select(new_selections);
17232 });
17233 }
17234
17235 pub fn move_to_enclosing_bracket(
17236 &mut self,
17237 _: &MoveToEnclosingBracket,
17238 window: &mut Window,
17239 cx: &mut Context<Self>,
17240 ) {
17241 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17242 self.change_selections(Default::default(), window, cx, |s| {
17243 s.move_offsets_with(&mut |snapshot, selection| {
17244 let Some(enclosing_bracket_ranges) =
17245 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17246 else {
17247 return;
17248 };
17249
17250 let mut best_length = usize::MAX;
17251 let mut best_inside = false;
17252 let mut best_in_bracket_range = false;
17253 let mut best_destination = None;
17254 for (open, close) in enclosing_bracket_ranges {
17255 let close = close.to_inclusive();
17256 let length = *close.end() - open.start;
17257 let inside = selection.start >= open.end && selection.end <= *close.start();
17258 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17259 || close.contains(&selection.head());
17260
17261 // If best is next to a bracket and current isn't, skip
17262 if !in_bracket_range && best_in_bracket_range {
17263 continue;
17264 }
17265
17266 // Prefer smaller lengths unless best is inside and current isn't
17267 if length > best_length && (best_inside || !inside) {
17268 continue;
17269 }
17270
17271 best_length = length;
17272 best_inside = inside;
17273 best_in_bracket_range = in_bracket_range;
17274 best_destination = Some(
17275 if close.contains(&selection.start) && close.contains(&selection.end) {
17276 if inside { open.end } else { open.start }
17277 } else if inside {
17278 *close.start()
17279 } else {
17280 *close.end()
17281 },
17282 );
17283 }
17284
17285 if let Some(destination) = best_destination {
17286 selection.collapse_to(destination, SelectionGoal::None);
17287 }
17288 })
17289 });
17290 }
17291
17292 pub fn undo_selection(
17293 &mut self,
17294 _: &UndoSelection,
17295 window: &mut Window,
17296 cx: &mut Context<Self>,
17297 ) {
17298 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17299 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17300 self.selection_history.mode = SelectionHistoryMode::Undoing;
17301 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17302 this.end_selection(window, cx);
17303 this.change_selections(
17304 SelectionEffects::scroll(Autoscroll::newest()),
17305 window,
17306 cx,
17307 |s| s.select_anchors(entry.selections.to_vec()),
17308 );
17309 });
17310 self.selection_history.mode = SelectionHistoryMode::Normal;
17311
17312 self.select_next_state = entry.select_next_state;
17313 self.select_prev_state = entry.select_prev_state;
17314 self.add_selections_state = entry.add_selections_state;
17315 }
17316 }
17317
17318 pub fn redo_selection(
17319 &mut self,
17320 _: &RedoSelection,
17321 window: &mut Window,
17322 cx: &mut Context<Self>,
17323 ) {
17324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17325 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17326 self.selection_history.mode = SelectionHistoryMode::Redoing;
17327 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17328 this.end_selection(window, cx);
17329 this.change_selections(
17330 SelectionEffects::scroll(Autoscroll::newest()),
17331 window,
17332 cx,
17333 |s| s.select_anchors(entry.selections.to_vec()),
17334 );
17335 });
17336 self.selection_history.mode = SelectionHistoryMode::Normal;
17337
17338 self.select_next_state = entry.select_next_state;
17339 self.select_prev_state = entry.select_prev_state;
17340 self.add_selections_state = entry.add_selections_state;
17341 }
17342 }
17343
17344 pub fn expand_excerpts(
17345 &mut self,
17346 action: &ExpandExcerpts,
17347 _: &mut Window,
17348 cx: &mut Context<Self>,
17349 ) {
17350 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17351 }
17352
17353 pub fn expand_excerpts_down(
17354 &mut self,
17355 action: &ExpandExcerptsDown,
17356 _: &mut Window,
17357 cx: &mut Context<Self>,
17358 ) {
17359 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17360 }
17361
17362 pub fn expand_excerpts_up(
17363 &mut self,
17364 action: &ExpandExcerptsUp,
17365 _: &mut Window,
17366 cx: &mut Context<Self>,
17367 ) {
17368 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17369 }
17370
17371 pub fn expand_excerpts_for_direction(
17372 &mut self,
17373 lines: u32,
17374 direction: ExpandExcerptDirection,
17375 cx: &mut Context<Self>,
17376 ) {
17377 let selections = self.selections.disjoint_anchors_arc();
17378
17379 let lines = if lines == 0 {
17380 EditorSettings::get_global(cx).expand_excerpt_lines
17381 } else {
17382 lines
17383 };
17384
17385 let snapshot = self.buffer.read(cx).snapshot(cx);
17386 let excerpt_ids = selections
17387 .iter()
17388 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17389 .unique()
17390 .sorted()
17391 .collect::<Vec<_>>();
17392
17393 if self.delegate_expand_excerpts {
17394 cx.emit(EditorEvent::ExpandExcerptsRequested {
17395 excerpt_ids,
17396 lines,
17397 direction,
17398 });
17399 return;
17400 }
17401
17402 self.buffer.update(cx, |buffer, cx| {
17403 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17404 })
17405 }
17406
17407 pub fn expand_excerpt(
17408 &mut self,
17409 excerpt: ExcerptId,
17410 direction: ExpandExcerptDirection,
17411 window: &mut Window,
17412 cx: &mut Context<Self>,
17413 ) {
17414 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17415
17416 if self.delegate_expand_excerpts {
17417 cx.emit(EditorEvent::ExpandExcerptsRequested {
17418 excerpt_ids: vec![excerpt],
17419 lines: lines_to_expand,
17420 direction,
17421 });
17422 return;
17423 }
17424
17425 let current_scroll_position = self.scroll_position(cx);
17426 let mut scroll = None;
17427
17428 if direction == ExpandExcerptDirection::Down {
17429 let multi_buffer = self.buffer.read(cx);
17430 let snapshot = multi_buffer.snapshot(cx);
17431 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17432 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17433 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17434 {
17435 let buffer_snapshot = buffer.read(cx).snapshot();
17436 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17437 let last_row = buffer_snapshot.max_point().row;
17438 let lines_below = last_row.saturating_sub(excerpt_end_row);
17439 if lines_below >= lines_to_expand {
17440 scroll = Some(
17441 current_scroll_position
17442 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17443 );
17444 }
17445 }
17446 }
17447 if direction == ExpandExcerptDirection::Up
17448 && self
17449 .buffer
17450 .read(cx)
17451 .snapshot(cx)
17452 .excerpt_before(excerpt)
17453 .is_none()
17454 {
17455 scroll = Some(current_scroll_position);
17456 }
17457
17458 self.buffer.update(cx, |buffer, cx| {
17459 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17460 });
17461
17462 if let Some(new_scroll_position) = scroll {
17463 self.set_scroll_position(new_scroll_position, window, cx);
17464 }
17465 }
17466
17467 pub fn go_to_singleton_buffer_point(
17468 &mut self,
17469 point: Point,
17470 window: &mut Window,
17471 cx: &mut Context<Self>,
17472 ) {
17473 self.go_to_singleton_buffer_range(point..point, window, cx);
17474 }
17475
17476 pub fn go_to_singleton_buffer_range(
17477 &mut self,
17478 range: Range<Point>,
17479 window: &mut Window,
17480 cx: &mut Context<Self>,
17481 ) {
17482 let multibuffer = self.buffer().read(cx);
17483 let Some(buffer) = multibuffer.as_singleton() else {
17484 return;
17485 };
17486 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17487 return;
17488 };
17489 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17490 return;
17491 };
17492 self.change_selections(
17493 SelectionEffects::default().nav_history(true),
17494 window,
17495 cx,
17496 |s| s.select_anchor_ranges([start..end]),
17497 );
17498 }
17499
17500 pub fn go_to_diagnostic(
17501 &mut self,
17502 action: &GoToDiagnostic,
17503 window: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) {
17506 if !self.diagnostics_enabled() {
17507 return;
17508 }
17509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17510 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17511 }
17512
17513 pub fn go_to_prev_diagnostic(
17514 &mut self,
17515 action: &GoToPreviousDiagnostic,
17516 window: &mut Window,
17517 cx: &mut Context<Self>,
17518 ) {
17519 if !self.diagnostics_enabled() {
17520 return;
17521 }
17522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17523 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17524 }
17525
17526 pub fn go_to_diagnostic_impl(
17527 &mut self,
17528 direction: Direction,
17529 severity: GoToDiagnosticSeverityFilter,
17530 window: &mut Window,
17531 cx: &mut Context<Self>,
17532 ) {
17533 let buffer = self.buffer.read(cx).snapshot(cx);
17534 let selection = self
17535 .selections
17536 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17537
17538 let mut active_group_id = None;
17539 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17540 && active_group.active_range.start.to_offset(&buffer) == selection.start
17541 {
17542 active_group_id = Some(active_group.group_id);
17543 }
17544
17545 fn filtered<'a>(
17546 severity: GoToDiagnosticSeverityFilter,
17547 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17548 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17549 diagnostics
17550 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17551 .filter(|entry| entry.range.start != entry.range.end)
17552 .filter(|entry| !entry.diagnostic.is_unnecessary)
17553 }
17554
17555 let before = filtered(
17556 severity,
17557 buffer
17558 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17559 .filter(|entry| entry.range.start <= selection.start),
17560 );
17561 let after = filtered(
17562 severity,
17563 buffer
17564 .diagnostics_in_range(selection.start..buffer.len())
17565 .filter(|entry| entry.range.start >= selection.start),
17566 );
17567
17568 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17569 if direction == Direction::Prev {
17570 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17571 {
17572 for diagnostic in prev_diagnostics.into_iter().rev() {
17573 if diagnostic.range.start != selection.start
17574 || active_group_id
17575 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17576 {
17577 found = Some(diagnostic);
17578 break 'outer;
17579 }
17580 }
17581 }
17582 } else {
17583 for diagnostic in after.chain(before) {
17584 if diagnostic.range.start != selection.start
17585 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17586 {
17587 found = Some(diagnostic);
17588 break;
17589 }
17590 }
17591 }
17592 let Some(next_diagnostic) = found else {
17593 return;
17594 };
17595
17596 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17597 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17598 return;
17599 };
17600 let snapshot = self.snapshot(window, cx);
17601 if snapshot.intersects_fold(next_diagnostic.range.start) {
17602 self.unfold_ranges(
17603 std::slice::from_ref(&next_diagnostic.range),
17604 true,
17605 false,
17606 cx,
17607 );
17608 }
17609 self.change_selections(Default::default(), window, cx, |s| {
17610 s.select_ranges(vec![
17611 next_diagnostic.range.start..next_diagnostic.range.start,
17612 ])
17613 });
17614 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17615 self.refresh_edit_prediction(false, true, window, cx);
17616 }
17617
17618 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17620 let snapshot = self.snapshot(window, cx);
17621 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17622 self.go_to_hunk_before_or_after_position(
17623 &snapshot,
17624 selection.head(),
17625 Direction::Next,
17626 true,
17627 window,
17628 cx,
17629 );
17630 }
17631
17632 pub fn go_to_hunk_before_or_after_position(
17633 &mut self,
17634 snapshot: &EditorSnapshot,
17635 position: Point,
17636 direction: Direction,
17637 wrap_around: bool,
17638 window: &mut Window,
17639 cx: &mut Context<Editor>,
17640 ) {
17641 let row = if direction == Direction::Next {
17642 self.hunk_after_position(snapshot, position, wrap_around)
17643 .map(|hunk| hunk.row_range.start)
17644 } else {
17645 self.hunk_before_position(snapshot, position, wrap_around)
17646 };
17647
17648 if let Some(row) = row {
17649 let destination = Point::new(row.0, 0);
17650 let autoscroll = Autoscroll::center();
17651
17652 self.unfold_ranges(&[destination..destination], false, false, cx);
17653 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17654 s.select_ranges([destination..destination]);
17655 });
17656 }
17657 }
17658
17659 fn hunk_after_position(
17660 &mut self,
17661 snapshot: &EditorSnapshot,
17662 position: Point,
17663 wrap_around: bool,
17664 ) -> Option<MultiBufferDiffHunk> {
17665 let result = snapshot
17666 .buffer_snapshot()
17667 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17668 .find(|hunk| hunk.row_range.start.0 > position.row);
17669
17670 if wrap_around {
17671 result.or_else(|| {
17672 snapshot
17673 .buffer_snapshot()
17674 .diff_hunks_in_range(Point::zero()..position)
17675 .find(|hunk| hunk.row_range.end.0 < position.row)
17676 })
17677 } else {
17678 result
17679 }
17680 }
17681
17682 fn go_to_prev_hunk(
17683 &mut self,
17684 _: &GoToPreviousHunk,
17685 window: &mut Window,
17686 cx: &mut Context<Self>,
17687 ) {
17688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17689 let snapshot = self.snapshot(window, cx);
17690 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17691 self.go_to_hunk_before_or_after_position(
17692 &snapshot,
17693 selection.head(),
17694 Direction::Prev,
17695 true,
17696 window,
17697 cx,
17698 );
17699 }
17700
17701 fn hunk_before_position(
17702 &mut self,
17703 snapshot: &EditorSnapshot,
17704 position: Point,
17705 wrap_around: bool,
17706 ) -> Option<MultiBufferRow> {
17707 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17708
17709 if wrap_around {
17710 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17711 } else {
17712 result
17713 }
17714 }
17715
17716 fn go_to_next_change(
17717 &mut self,
17718 _: &GoToNextChange,
17719 window: &mut Window,
17720 cx: &mut Context<Self>,
17721 ) {
17722 if let Some(selections) = self
17723 .change_list
17724 .next_change(1, Direction::Next)
17725 .map(|s| s.to_vec())
17726 {
17727 self.change_selections(Default::default(), window, cx, |s| {
17728 let map = s.display_snapshot();
17729 s.select_display_ranges(selections.iter().map(|a| {
17730 let point = a.to_display_point(&map);
17731 point..point
17732 }))
17733 })
17734 }
17735 }
17736
17737 fn go_to_previous_change(
17738 &mut self,
17739 _: &GoToPreviousChange,
17740 window: &mut Window,
17741 cx: &mut Context<Self>,
17742 ) {
17743 if let Some(selections) = self
17744 .change_list
17745 .next_change(1, Direction::Prev)
17746 .map(|s| s.to_vec())
17747 {
17748 self.change_selections(Default::default(), window, cx, |s| {
17749 let map = s.display_snapshot();
17750 s.select_display_ranges(selections.iter().map(|a| {
17751 let point = a.to_display_point(&map);
17752 point..point
17753 }))
17754 })
17755 }
17756 }
17757
17758 pub fn go_to_next_document_highlight(
17759 &mut self,
17760 _: &GoToNextDocumentHighlight,
17761 window: &mut Window,
17762 cx: &mut Context<Self>,
17763 ) {
17764 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17765 }
17766
17767 pub fn go_to_prev_document_highlight(
17768 &mut self,
17769 _: &GoToPreviousDocumentHighlight,
17770 window: &mut Window,
17771 cx: &mut Context<Self>,
17772 ) {
17773 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17774 }
17775
17776 pub fn go_to_document_highlight_before_or_after_position(
17777 &mut self,
17778 direction: Direction,
17779 window: &mut Window,
17780 cx: &mut Context<Editor>,
17781 ) {
17782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17783 let snapshot = self.snapshot(window, cx);
17784 let buffer = &snapshot.buffer_snapshot();
17785 let position = self
17786 .selections
17787 .newest::<Point>(&snapshot.display_snapshot)
17788 .head();
17789 let anchor_position = buffer.anchor_after(position);
17790
17791 // Get all document highlights (both read and write)
17792 let mut all_highlights = Vec::new();
17793
17794 if let Some((_, read_highlights)) = self
17795 .background_highlights
17796 .get(&HighlightKey::DocumentHighlightRead)
17797 {
17798 all_highlights.extend(read_highlights.iter());
17799 }
17800
17801 if let Some((_, write_highlights)) = self
17802 .background_highlights
17803 .get(&HighlightKey::DocumentHighlightWrite)
17804 {
17805 all_highlights.extend(write_highlights.iter());
17806 }
17807
17808 if all_highlights.is_empty() {
17809 return;
17810 }
17811
17812 // Sort highlights by position
17813 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17814
17815 let target_highlight = match direction {
17816 Direction::Next => {
17817 // Find the first highlight after the current position
17818 all_highlights
17819 .iter()
17820 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17821 }
17822 Direction::Prev => {
17823 // Find the last highlight before the current position
17824 all_highlights
17825 .iter()
17826 .rev()
17827 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17828 }
17829 };
17830
17831 if let Some(highlight) = target_highlight {
17832 let destination = highlight.start.to_point(buffer);
17833 let autoscroll = Autoscroll::center();
17834
17835 self.unfold_ranges(&[destination..destination], false, false, cx);
17836 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17837 s.select_ranges([destination..destination]);
17838 });
17839 }
17840 }
17841
17842 fn go_to_line<T: 'static>(
17843 &mut self,
17844 position: Anchor,
17845 highlight_color: Option<Hsla>,
17846 window: &mut Window,
17847 cx: &mut Context<Self>,
17848 ) {
17849 let snapshot = self.snapshot(window, cx).display_snapshot;
17850 let position = position.to_point(&snapshot.buffer_snapshot());
17851 let start = snapshot
17852 .buffer_snapshot()
17853 .clip_point(Point::new(position.row, 0), Bias::Left);
17854 let end = start + Point::new(1, 0);
17855 let start = snapshot.buffer_snapshot().anchor_before(start);
17856 let end = snapshot.buffer_snapshot().anchor_before(end);
17857
17858 self.highlight_rows::<T>(
17859 start..end,
17860 highlight_color
17861 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17862 Default::default(),
17863 cx,
17864 );
17865
17866 if self.buffer.read(cx).is_singleton() {
17867 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17868 }
17869 }
17870
17871 pub fn go_to_definition(
17872 &mut self,
17873 _: &GoToDefinition,
17874 window: &mut Window,
17875 cx: &mut Context<Self>,
17876 ) -> Task<Result<Navigated>> {
17877 let definition =
17878 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17879 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17880 cx.spawn_in(window, async move |editor, cx| {
17881 if definition.await? == Navigated::Yes {
17882 return Ok(Navigated::Yes);
17883 }
17884 match fallback_strategy {
17885 GoToDefinitionFallback::None => Ok(Navigated::No),
17886 GoToDefinitionFallback::FindAllReferences => {
17887 match editor.update_in(cx, |editor, window, cx| {
17888 editor.find_all_references(&FindAllReferences::default(), window, cx)
17889 })? {
17890 Some(references) => references.await,
17891 None => Ok(Navigated::No),
17892 }
17893 }
17894 }
17895 })
17896 }
17897
17898 pub fn go_to_declaration(
17899 &mut self,
17900 _: &GoToDeclaration,
17901 window: &mut Window,
17902 cx: &mut Context<Self>,
17903 ) -> Task<Result<Navigated>> {
17904 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17905 }
17906
17907 pub fn go_to_declaration_split(
17908 &mut self,
17909 _: &GoToDeclaration,
17910 window: &mut Window,
17911 cx: &mut Context<Self>,
17912 ) -> Task<Result<Navigated>> {
17913 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17914 }
17915
17916 pub fn go_to_implementation(
17917 &mut self,
17918 _: &GoToImplementation,
17919 window: &mut Window,
17920 cx: &mut Context<Self>,
17921 ) -> Task<Result<Navigated>> {
17922 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17923 }
17924
17925 pub fn go_to_implementation_split(
17926 &mut self,
17927 _: &GoToImplementationSplit,
17928 window: &mut Window,
17929 cx: &mut Context<Self>,
17930 ) -> Task<Result<Navigated>> {
17931 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17932 }
17933
17934 pub fn go_to_type_definition(
17935 &mut self,
17936 _: &GoToTypeDefinition,
17937 window: &mut Window,
17938 cx: &mut Context<Self>,
17939 ) -> Task<Result<Navigated>> {
17940 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17941 }
17942
17943 pub fn go_to_definition_split(
17944 &mut self,
17945 _: &GoToDefinitionSplit,
17946 window: &mut Window,
17947 cx: &mut Context<Self>,
17948 ) -> Task<Result<Navigated>> {
17949 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17950 }
17951
17952 pub fn go_to_type_definition_split(
17953 &mut self,
17954 _: &GoToTypeDefinitionSplit,
17955 window: &mut Window,
17956 cx: &mut Context<Self>,
17957 ) -> Task<Result<Navigated>> {
17958 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17959 }
17960
17961 fn go_to_definition_of_kind(
17962 &mut self,
17963 kind: GotoDefinitionKind,
17964 split: bool,
17965 window: &mut Window,
17966 cx: &mut Context<Self>,
17967 ) -> Task<Result<Navigated>> {
17968 let Some(provider) = self.semantics_provider.clone() else {
17969 return Task::ready(Ok(Navigated::No));
17970 };
17971 let head = self
17972 .selections
17973 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17974 .head();
17975 let buffer = self.buffer.read(cx);
17976 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17977 return Task::ready(Ok(Navigated::No));
17978 };
17979 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17980 return Task::ready(Ok(Navigated::No));
17981 };
17982
17983 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17984
17985 cx.spawn_in(window, async move |editor, cx| {
17986 let Some(definitions) = definitions.await? else {
17987 return Ok(Navigated::No);
17988 };
17989 let navigated = editor
17990 .update_in(cx, |editor, window, cx| {
17991 editor.navigate_to_hover_links(
17992 Some(kind),
17993 definitions
17994 .into_iter()
17995 .filter(|location| {
17996 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17997 })
17998 .map(HoverLink::Text)
17999 .collect::<Vec<_>>(),
18000 nav_entry,
18001 split,
18002 window,
18003 cx,
18004 )
18005 })?
18006 .await?;
18007 anyhow::Ok(navigated)
18008 })
18009 }
18010
18011 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18012 let selection = self.selections.newest_anchor();
18013 let head = selection.head();
18014 let tail = selection.tail();
18015
18016 let Some((buffer, start_position)) =
18017 self.buffer.read(cx).text_anchor_for_position(head, cx)
18018 else {
18019 return;
18020 };
18021
18022 let end_position = if head != tail {
18023 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18024 return;
18025 };
18026 Some(pos)
18027 } else {
18028 None
18029 };
18030
18031 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18032 let url = if let Some(end_pos) = end_position {
18033 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18034 } else {
18035 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18036 };
18037
18038 if let Some(url) = url {
18039 cx.update(|window, cx| {
18040 if parse_zed_link(&url, cx).is_some() {
18041 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18042 } else {
18043 cx.open_url(&url);
18044 }
18045 })?;
18046 }
18047
18048 anyhow::Ok(())
18049 });
18050
18051 url_finder.detach();
18052 }
18053
18054 pub fn open_selected_filename(
18055 &mut self,
18056 _: &OpenSelectedFilename,
18057 window: &mut Window,
18058 cx: &mut Context<Self>,
18059 ) {
18060 let Some(workspace) = self.workspace() else {
18061 return;
18062 };
18063
18064 let position = self.selections.newest_anchor().head();
18065
18066 let Some((buffer, buffer_position)) =
18067 self.buffer.read(cx).text_anchor_for_position(position, cx)
18068 else {
18069 return;
18070 };
18071
18072 let project = self.project.clone();
18073
18074 cx.spawn_in(window, async move |_, cx| {
18075 let result = find_file(&buffer, project, buffer_position, cx).await;
18076
18077 if let Some((_, path)) = result {
18078 workspace
18079 .update_in(cx, |workspace, window, cx| {
18080 workspace.open_resolved_path(path, window, cx)
18081 })?
18082 .await?;
18083 }
18084 anyhow::Ok(())
18085 })
18086 .detach();
18087 }
18088
18089 pub(crate) fn navigate_to_hover_links(
18090 &mut self,
18091 kind: Option<GotoDefinitionKind>,
18092 definitions: Vec<HoverLink>,
18093 origin: Option<NavigationEntry>,
18094 split: bool,
18095 window: &mut Window,
18096 cx: &mut Context<Editor>,
18097 ) -> Task<Result<Navigated>> {
18098 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18099 let mut first_url_or_file = None;
18100 let definitions: Vec<_> = definitions
18101 .into_iter()
18102 .filter_map(|def| match def {
18103 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18104 HoverLink::InlayHint(lsp_location, server_id) => {
18105 let computation =
18106 self.compute_target_location(lsp_location, server_id, window, cx);
18107 Some(cx.background_spawn(computation))
18108 }
18109 HoverLink::Url(url) => {
18110 first_url_or_file = Some(Either::Left(url));
18111 None
18112 }
18113 HoverLink::File(path) => {
18114 first_url_or_file = Some(Either::Right(path));
18115 None
18116 }
18117 })
18118 .collect();
18119
18120 let workspace = self.workspace();
18121
18122 cx.spawn_in(window, async move |editor, cx| {
18123 let locations: Vec<Location> = future::join_all(definitions)
18124 .await
18125 .into_iter()
18126 .filter_map(|location| location.transpose())
18127 .collect::<Result<_>>()
18128 .context("location tasks")?;
18129 let mut locations = cx.update(|_, cx| {
18130 locations
18131 .into_iter()
18132 .map(|location| {
18133 let buffer = location.buffer.read(cx);
18134 (location.buffer, location.range.to_point(buffer))
18135 })
18136 .into_group_map()
18137 })?;
18138 let mut num_locations = 0;
18139 for ranges in locations.values_mut() {
18140 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18141 ranges.dedup();
18142 num_locations += ranges.len();
18143 }
18144
18145 if num_locations > 1 {
18146 let tab_kind = match kind {
18147 Some(GotoDefinitionKind::Implementation) => "Implementations",
18148 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18149 Some(GotoDefinitionKind::Declaration) => "Declarations",
18150 Some(GotoDefinitionKind::Type) => "Types",
18151 };
18152 let title = editor
18153 .update_in(cx, |_, _, cx| {
18154 let target = locations
18155 .iter()
18156 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18157 .map(|(buffer, location)| {
18158 buffer
18159 .read(cx)
18160 .text_for_range(location.clone())
18161 .collect::<String>()
18162 })
18163 .filter(|text| !text.contains('\n'))
18164 .unique()
18165 .take(3)
18166 .join(", ");
18167 if target.is_empty() {
18168 tab_kind.to_owned()
18169 } else {
18170 format!("{tab_kind} for {target}")
18171 }
18172 })
18173 .context("buffer title")?;
18174
18175 let Some(workspace) = workspace else {
18176 return Ok(Navigated::No);
18177 };
18178
18179 let opened = workspace
18180 .update_in(cx, |workspace, window, cx| {
18181 let allow_preview = PreviewTabsSettings::get_global(cx)
18182 .enable_preview_multibuffer_from_code_navigation;
18183 if let Some((target_editor, target_pane)) =
18184 Self::open_locations_in_multibuffer(
18185 workspace,
18186 locations,
18187 title,
18188 split,
18189 allow_preview,
18190 MultibufferSelectionMode::First,
18191 window,
18192 cx,
18193 )
18194 {
18195 // We create our own nav history instead of using
18196 // `target_editor.nav_history` because `nav_history`
18197 // seems to be populated asynchronously when an item
18198 // is added to a pane
18199 let mut nav_history = target_pane
18200 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18201 target_editor.update(cx, |editor, cx| {
18202 let nav_data = editor
18203 .navigation_data(editor.selections.newest_anchor().head(), cx);
18204 let target =
18205 Some(nav_history.navigation_entry(Some(
18206 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18207 )));
18208 nav_history.push_tag(origin, target);
18209 })
18210 }
18211 })
18212 .is_ok();
18213
18214 anyhow::Ok(Navigated::from_bool(opened))
18215 } else if num_locations == 0 {
18216 // If there is one url or file, open it directly
18217 match first_url_or_file {
18218 Some(Either::Left(url)) => {
18219 cx.update(|window, cx| {
18220 if parse_zed_link(&url, cx).is_some() {
18221 window
18222 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18223 } else {
18224 cx.open_url(&url);
18225 }
18226 })?;
18227 Ok(Navigated::Yes)
18228 }
18229 Some(Either::Right(path)) => {
18230 // TODO(andrew): respect preview tab settings
18231 // `enable_keep_preview_on_code_navigation` and
18232 // `enable_preview_file_from_code_navigation`
18233 let Some(workspace) = workspace else {
18234 return Ok(Navigated::No);
18235 };
18236 workspace
18237 .update_in(cx, |workspace, window, cx| {
18238 workspace.open_resolved_path(path, window, cx)
18239 })?
18240 .await?;
18241 Ok(Navigated::Yes)
18242 }
18243 None => Ok(Navigated::No),
18244 }
18245 } else {
18246 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18247 let target_range = target_ranges.first().unwrap().clone();
18248
18249 editor.update_in(cx, |editor, window, cx| {
18250 let range = editor.range_for_match(&target_range);
18251 let range = collapse_multiline_range(range);
18252
18253 if !split
18254 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18255 {
18256 editor.go_to_singleton_buffer_range(range, window, cx);
18257
18258 let target =
18259 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18260 if let Some(mut nav_history) = editor.nav_history.clone() {
18261 nav_history.push_tag(origin, target);
18262 }
18263 } else {
18264 let Some(workspace) = workspace else {
18265 return Navigated::No;
18266 };
18267 let pane = workspace.read(cx).active_pane().clone();
18268 window.defer(cx, move |window, cx| {
18269 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18270 workspace.update(cx, |workspace, cx| {
18271 let pane = if split {
18272 workspace.adjacent_pane(window, cx)
18273 } else {
18274 workspace.active_pane().clone()
18275 };
18276
18277 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18278 let keep_old_preview = preview_tabs_settings
18279 .enable_keep_preview_on_code_navigation;
18280 let allow_new_preview = preview_tabs_settings
18281 .enable_preview_file_from_code_navigation;
18282
18283 let editor = workspace.open_project_item(
18284 pane.clone(),
18285 target_buffer.clone(),
18286 true,
18287 true,
18288 keep_old_preview,
18289 allow_new_preview,
18290 window,
18291 cx,
18292 );
18293 (editor, pane)
18294 });
18295 // We create our own nav history instead of using
18296 // `target_editor.nav_history` because `nav_history`
18297 // seems to be populated asynchronously when an item
18298 // is added to a pane
18299 let mut nav_history = target_pane
18300 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18301 target_editor.update(cx, |target_editor, cx| {
18302 // When selecting a definition in a different buffer, disable the nav history
18303 // to avoid creating a history entry at the previous cursor location.
18304 pane.update(cx, |pane, _| pane.disable_history());
18305 target_editor.go_to_singleton_buffer_range(range, window, cx);
18306
18307 let nav_data = target_editor.navigation_data(
18308 target_editor.selections.newest_anchor().head(),
18309 cx,
18310 );
18311 let target =
18312 Some(nav_history.navigation_entry(Some(
18313 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18314 )));
18315 nav_history.push_tag(origin, target);
18316 pane.update(cx, |pane, _| pane.enable_history());
18317 });
18318 });
18319 }
18320 Navigated::Yes
18321 })
18322 }
18323 })
18324 }
18325
18326 fn compute_target_location(
18327 &self,
18328 lsp_location: lsp::Location,
18329 server_id: LanguageServerId,
18330 window: &mut Window,
18331 cx: &mut Context<Self>,
18332 ) -> Task<anyhow::Result<Option<Location>>> {
18333 let Some(project) = self.project.clone() else {
18334 return Task::ready(Ok(None));
18335 };
18336
18337 cx.spawn_in(window, async move |editor, cx| {
18338 let location_task = editor.update(cx, |_, cx| {
18339 project.update(cx, |project, cx| {
18340 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18341 })
18342 })?;
18343 let location = Some({
18344 let target_buffer_handle = location_task.await.context("open local buffer")?;
18345 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18346 let target_start = target_buffer
18347 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18348 let target_end = target_buffer
18349 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18350 target_buffer.anchor_after(target_start)
18351 ..target_buffer.anchor_before(target_end)
18352 });
18353 Location {
18354 buffer: target_buffer_handle,
18355 range,
18356 }
18357 });
18358 Ok(location)
18359 })
18360 }
18361
18362 fn go_to_next_reference(
18363 &mut self,
18364 _: &GoToNextReference,
18365 window: &mut Window,
18366 cx: &mut Context<Self>,
18367 ) {
18368 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18369 if let Some(task) = task {
18370 task.detach();
18371 };
18372 }
18373
18374 fn go_to_prev_reference(
18375 &mut self,
18376 _: &GoToPreviousReference,
18377 window: &mut Window,
18378 cx: &mut Context<Self>,
18379 ) {
18380 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18381 if let Some(task) = task {
18382 task.detach();
18383 };
18384 }
18385
18386 fn go_to_symbol_by_offset(
18387 &mut self,
18388 window: &mut Window,
18389 cx: &mut Context<Self>,
18390 offset: i8,
18391 ) -> Task<Result<()>> {
18392 let editor_snapshot = self.snapshot(window, cx);
18393
18394 // We don't care about multi-buffer symbols
18395 let Some((excerpt_id, _, _)) = editor_snapshot.as_singleton() else {
18396 return Task::ready(Ok(()));
18397 };
18398
18399 let cursor_offset = self
18400 .selections
18401 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18402 .head();
18403
18404 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18405 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18406 let buffer = ed.buffer.read(cx).as_singleton()?;
18407 Some(buffer.read(cx).remote_id())
18408 }) else {
18409 return Ok(());
18410 };
18411
18412 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18413 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18414
18415 let multi_snapshot = editor_snapshot.buffer();
18416 let buffer_range = |range: &Range<_>| {
18417 Anchor::range_in_buffer(excerpt_id, range.clone()).to_offset(multi_snapshot)
18418 };
18419
18420 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18421 let current_idx = outline_items
18422 .iter()
18423 .enumerate()
18424 .filter_map(|(idx, item)| {
18425 // Find the closest outline item by distance between outline text and cursor location
18426 let source_range = buffer_range(&item.source_range_for_text);
18427 let distance_to_closest_endpoint = cmp::min(
18428 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18429 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18430 );
18431
18432 let item_towards_offset =
18433 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18434 == (offset as isize).signum();
18435
18436 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18437
18438 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18439 // we should not already be within the outline's source range. We then pick the closest outline
18440 // item.
18441 (item_towards_offset && !source_range_contains_cursor)
18442 .then_some((distance_to_closest_endpoint, idx))
18443 })
18444 .min()
18445 .map(|(_, idx)| idx);
18446
18447 let Some(idx) = current_idx else {
18448 return;
18449 };
18450
18451 let range = buffer_range(&outline_items[idx].source_range_for_text);
18452 let selection = [range.start..range.start];
18453
18454 let _ = editor
18455 .update(acx, |editor, ecx| {
18456 editor.change_selections(
18457 SelectionEffects::scroll(Autoscroll::newest()),
18458 window,
18459 ecx,
18460 |s| s.select_ranges(selection),
18461 );
18462 })
18463 .ok();
18464 })?;
18465
18466 Ok(())
18467 })
18468 }
18469
18470 fn go_to_next_symbol(
18471 &mut self,
18472 _: &GoToNextSymbol,
18473 window: &mut Window,
18474 cx: &mut Context<Self>,
18475 ) {
18476 self.go_to_symbol_by_offset(window, cx, 1).detach();
18477 }
18478
18479 fn go_to_previous_symbol(
18480 &mut self,
18481 _: &GoToPreviousSymbol,
18482 window: &mut Window,
18483 cx: &mut Context<Self>,
18484 ) {
18485 self.go_to_symbol_by_offset(window, cx, -1).detach();
18486 }
18487
18488 pub fn go_to_reference_before_or_after_position(
18489 &mut self,
18490 direction: Direction,
18491 count: usize,
18492 window: &mut Window,
18493 cx: &mut Context<Self>,
18494 ) -> Option<Task<Result<()>>> {
18495 let selection = self.selections.newest_anchor();
18496 let head = selection.head();
18497
18498 let multi_buffer = self.buffer.read(cx);
18499
18500 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18501 let workspace = self.workspace()?;
18502 let project = workspace.read(cx).project().clone();
18503 let references =
18504 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18505 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18506 let Some(locations) = references.await? else {
18507 return Ok(());
18508 };
18509
18510 if locations.is_empty() {
18511 // totally normal - the cursor may be on something which is not
18512 // a symbol (e.g. a keyword)
18513 log::info!("no references found under cursor");
18514 return Ok(());
18515 }
18516
18517 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18518
18519 let (locations, current_location_index) =
18520 multi_buffer.update(cx, |multi_buffer, cx| {
18521 let mut locations = locations
18522 .into_iter()
18523 .filter_map(|loc| {
18524 let start = multi_buffer.buffer_anchor_to_anchor(
18525 &loc.buffer,
18526 loc.range.start,
18527 cx,
18528 )?;
18529 let end = multi_buffer.buffer_anchor_to_anchor(
18530 &loc.buffer,
18531 loc.range.end,
18532 cx,
18533 )?;
18534 Some(start..end)
18535 })
18536 .collect::<Vec<_>>();
18537
18538 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18539 // There is an O(n) implementation, but given this list will be
18540 // small (usually <100 items), the extra O(log(n)) factor isn't
18541 // worth the (surprisingly large amount of) extra complexity.
18542 locations
18543 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18544
18545 let head_offset = head.to_offset(&multi_buffer_snapshot);
18546
18547 let current_location_index = locations.iter().position(|loc| {
18548 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18549 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18550 });
18551
18552 (locations, current_location_index)
18553 });
18554
18555 let Some(current_location_index) = current_location_index else {
18556 // This indicates something has gone wrong, because we already
18557 // handle the "no references" case above
18558 log::error!(
18559 "failed to find current reference under cursor. Total references: {}",
18560 locations.len()
18561 );
18562 return Ok(());
18563 };
18564
18565 let destination_location_index = match direction {
18566 Direction::Next => (current_location_index + count) % locations.len(),
18567 Direction::Prev => {
18568 (current_location_index + locations.len() - count % locations.len())
18569 % locations.len()
18570 }
18571 };
18572
18573 // TODO(cameron): is this needed?
18574 // the thinking is to avoid "jumping to the current location" (avoid
18575 // polluting "jumplist" in vim terms)
18576 if current_location_index == destination_location_index {
18577 return Ok(());
18578 }
18579
18580 let Range { start, end } = locations[destination_location_index];
18581
18582 editor.update_in(cx, |editor, window, cx| {
18583 let effects = SelectionEffects::default();
18584
18585 editor.unfold_ranges(&[start..end], false, false, cx);
18586 editor.change_selections(effects, window, cx, |s| {
18587 s.select_ranges([start..start]);
18588 });
18589 })?;
18590
18591 Ok(())
18592 }))
18593 }
18594
18595 pub fn find_all_references(
18596 &mut self,
18597 action: &FindAllReferences,
18598 window: &mut Window,
18599 cx: &mut Context<Self>,
18600 ) -> Option<Task<Result<Navigated>>> {
18601 let always_open_multibuffer = action.always_open_multibuffer;
18602 let selection = self.selections.newest_anchor();
18603 let multi_buffer = self.buffer.read(cx);
18604 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18605 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18606 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18607 let head = selection_offset.head();
18608
18609 let head_anchor = multi_buffer_snapshot.anchor_at(
18610 head,
18611 if head < selection_offset.tail() {
18612 Bias::Right
18613 } else {
18614 Bias::Left
18615 },
18616 );
18617
18618 match self
18619 .find_all_references_task_sources
18620 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18621 {
18622 Ok(_) => {
18623 log::info!(
18624 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18625 );
18626 return None;
18627 }
18628 Err(i) => {
18629 self.find_all_references_task_sources.insert(i, head_anchor);
18630 }
18631 }
18632
18633 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18634 let workspace = self.workspace()?;
18635 let project = workspace.read(cx).project().clone();
18636 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18637 Some(cx.spawn_in(window, async move |editor, cx| {
18638 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18639 if let Ok(i) = editor
18640 .find_all_references_task_sources
18641 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18642 {
18643 editor.find_all_references_task_sources.remove(i);
18644 }
18645 });
18646
18647 let Some(locations) = references.await? else {
18648 return anyhow::Ok(Navigated::No);
18649 };
18650 let mut locations = cx.update(|_, cx| {
18651 locations
18652 .into_iter()
18653 .map(|location| {
18654 let buffer = location.buffer.read(cx);
18655 (location.buffer, location.range.to_point(buffer))
18656 })
18657 // if special-casing the single-match case, remove ranges
18658 // that intersect current selection
18659 .filter(|(location_buffer, location)| {
18660 if always_open_multibuffer || &buffer != location_buffer {
18661 return true;
18662 }
18663
18664 !location.contains_inclusive(&selection_point.range())
18665 })
18666 .into_group_map()
18667 })?;
18668 if locations.is_empty() {
18669 return anyhow::Ok(Navigated::No);
18670 }
18671 for ranges in locations.values_mut() {
18672 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18673 ranges.dedup();
18674 }
18675 let mut num_locations = 0;
18676 for ranges in locations.values_mut() {
18677 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18678 ranges.dedup();
18679 num_locations += ranges.len();
18680 }
18681
18682 if num_locations == 1 && !always_open_multibuffer {
18683 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18684 let target_range = target_ranges.first().unwrap().clone();
18685
18686 return editor.update_in(cx, |editor, window, cx| {
18687 let range = target_range.to_point(target_buffer.read(cx));
18688 let range = editor.range_for_match(&range);
18689 let range = range.start..range.start;
18690
18691 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18692 editor.go_to_singleton_buffer_range(range, window, cx);
18693 } else {
18694 let pane = workspace.read(cx).active_pane().clone();
18695 window.defer(cx, move |window, cx| {
18696 let target_editor: Entity<Self> =
18697 workspace.update(cx, |workspace, cx| {
18698 let pane = workspace.active_pane().clone();
18699
18700 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18701 let keep_old_preview = preview_tabs_settings
18702 .enable_keep_preview_on_code_navigation;
18703 let allow_new_preview = preview_tabs_settings
18704 .enable_preview_file_from_code_navigation;
18705
18706 workspace.open_project_item(
18707 pane,
18708 target_buffer.clone(),
18709 true,
18710 true,
18711 keep_old_preview,
18712 allow_new_preview,
18713 window,
18714 cx,
18715 )
18716 });
18717 target_editor.update(cx, |target_editor, cx| {
18718 // When selecting a definition in a different buffer, disable the nav history
18719 // to avoid creating a history entry at the previous cursor location.
18720 pane.update(cx, |pane, _| pane.disable_history());
18721 target_editor.go_to_singleton_buffer_range(range, window, cx);
18722 pane.update(cx, |pane, _| pane.enable_history());
18723 });
18724 });
18725 }
18726 Navigated::No
18727 });
18728 }
18729
18730 workspace.update_in(cx, |workspace, window, cx| {
18731 let target = locations
18732 .iter()
18733 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18734 .map(|(buffer, location)| {
18735 buffer
18736 .read(cx)
18737 .text_for_range(location.clone())
18738 .collect::<String>()
18739 })
18740 .filter(|text| !text.contains('\n'))
18741 .unique()
18742 .take(3)
18743 .join(", ");
18744 let title = if target.is_empty() {
18745 "References".to_owned()
18746 } else {
18747 format!("References to {target}")
18748 };
18749 let allow_preview = PreviewTabsSettings::get_global(cx)
18750 .enable_preview_multibuffer_from_code_navigation;
18751 Self::open_locations_in_multibuffer(
18752 workspace,
18753 locations,
18754 title,
18755 false,
18756 allow_preview,
18757 MultibufferSelectionMode::First,
18758 window,
18759 cx,
18760 );
18761 Navigated::Yes
18762 })
18763 }))
18764 }
18765
18766 /// Opens a multibuffer with the given project locations in it.
18767 pub fn open_locations_in_multibuffer(
18768 workspace: &mut Workspace,
18769 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18770 title: String,
18771 split: bool,
18772 allow_preview: bool,
18773 multibuffer_selection_mode: MultibufferSelectionMode,
18774 window: &mut Window,
18775 cx: &mut Context<Workspace>,
18776 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18777 if locations.is_empty() {
18778 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18779 return None;
18780 }
18781
18782 let capability = workspace.project().read(cx).capability();
18783 let mut ranges = <Vec<Range<Anchor>>>::new();
18784
18785 // a key to find existing multibuffer editors with the same set of locations
18786 // to prevent us from opening more and more multibuffer tabs for searches and the like
18787 let mut key = (title.clone(), vec![]);
18788 let excerpt_buffer = cx.new(|cx| {
18789 let key = &mut key.1;
18790 let mut multibuffer = MultiBuffer::new(capability);
18791 for (buffer, mut ranges_for_buffer) in locations {
18792 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18793 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18794 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18795 PathKey::for_buffer(&buffer, cx),
18796 buffer.clone(),
18797 ranges_for_buffer,
18798 multibuffer_context_lines(cx),
18799 cx,
18800 );
18801 ranges.extend(new_ranges)
18802 }
18803
18804 multibuffer.with_title(title)
18805 });
18806 let existing = workspace.active_pane().update(cx, |pane, cx| {
18807 pane.items()
18808 .filter_map(|item| item.downcast::<Editor>())
18809 .find(|editor| {
18810 editor
18811 .read(cx)
18812 .lookup_key
18813 .as_ref()
18814 .and_then(|it| {
18815 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18816 })
18817 .is_some_and(|it| *it == key)
18818 })
18819 });
18820 let was_existing = existing.is_some();
18821 let editor = existing.unwrap_or_else(|| {
18822 cx.new(|cx| {
18823 let mut editor = Editor::for_multibuffer(
18824 excerpt_buffer,
18825 Some(workspace.project().clone()),
18826 window,
18827 cx,
18828 );
18829 editor.lookup_key = Some(Box::new(key));
18830 editor
18831 })
18832 });
18833 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18834 MultibufferSelectionMode::First => {
18835 if let Some(first_range) = ranges.first() {
18836 editor.change_selections(
18837 SelectionEffects::no_scroll(),
18838 window,
18839 cx,
18840 |selections| {
18841 selections.clear_disjoint();
18842 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18843 },
18844 );
18845 }
18846 editor.highlight_background(
18847 HighlightKey::Editor,
18848 &ranges,
18849 |_, theme| theme.colors().editor_highlighted_line_background,
18850 cx,
18851 );
18852 }
18853 MultibufferSelectionMode::All => {
18854 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18855 selections.clear_disjoint();
18856 selections.select_anchor_ranges(ranges);
18857 });
18858 }
18859 });
18860
18861 let item = Box::new(editor.clone());
18862
18863 let pane = if split {
18864 workspace.adjacent_pane(window, cx)
18865 } else {
18866 workspace.active_pane().clone()
18867 };
18868 let activate_pane = split;
18869
18870 let mut destination_index = None;
18871 pane.update(cx, |pane, cx| {
18872 if allow_preview && !was_existing {
18873 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18874 }
18875 if was_existing && !allow_preview {
18876 pane.unpreview_item_if_preview(item.item_id());
18877 }
18878 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18879 });
18880
18881 Some((editor, pane))
18882 }
18883
18884 pub fn rename(
18885 &mut self,
18886 _: &Rename,
18887 window: &mut Window,
18888 cx: &mut Context<Self>,
18889 ) -> Option<Task<Result<()>>> {
18890 use language::ToOffset as _;
18891
18892 let provider = self.semantics_provider.clone()?;
18893 let selection = self.selections.newest_anchor().clone();
18894 let (cursor_buffer, cursor_buffer_position) = self
18895 .buffer
18896 .read(cx)
18897 .text_anchor_for_position(selection.head(), cx)?;
18898 let (tail_buffer, cursor_buffer_position_end) = self
18899 .buffer
18900 .read(cx)
18901 .text_anchor_for_position(selection.tail(), cx)?;
18902 if tail_buffer != cursor_buffer {
18903 return None;
18904 }
18905
18906 let snapshot = cursor_buffer.read(cx).snapshot();
18907 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18908 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18909 let prepare_rename = provider
18910 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18911 .unwrap_or_else(|| Task::ready(Ok(None)));
18912 drop(snapshot);
18913
18914 Some(cx.spawn_in(window, async move |this, cx| {
18915 let rename_range = if let Some(range) = prepare_rename.await? {
18916 Some(range)
18917 } else {
18918 this.update(cx, |this, cx| {
18919 let buffer = this.buffer.read(cx).snapshot(cx);
18920 let mut buffer_highlights = this
18921 .document_highlights_for_position(selection.head(), &buffer)
18922 .filter(|highlight| {
18923 highlight.start.excerpt_id == selection.head().excerpt_id
18924 && highlight.end.excerpt_id == selection.head().excerpt_id
18925 });
18926 buffer_highlights
18927 .next()
18928 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18929 })?
18930 };
18931 if let Some(rename_range) = rename_range {
18932 this.update_in(cx, |this, window, cx| {
18933 let snapshot = cursor_buffer.read(cx).snapshot();
18934 let rename_buffer_range = rename_range.to_offset(&snapshot);
18935 let cursor_offset_in_rename_range =
18936 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18937 let cursor_offset_in_rename_range_end =
18938 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18939
18940 this.take_rename(false, window, cx);
18941 let buffer = this.buffer.read(cx).read(cx);
18942 let cursor_offset = selection.head().to_offset(&buffer);
18943 let rename_start =
18944 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18945 let rename_end = rename_start + rename_buffer_range.len();
18946 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18947 let mut old_highlight_id = None;
18948 let old_name: Arc<str> = buffer
18949 .chunks(rename_start..rename_end, true)
18950 .map(|chunk| {
18951 if old_highlight_id.is_none() {
18952 old_highlight_id = chunk.syntax_highlight_id;
18953 }
18954 chunk.text
18955 })
18956 .collect::<String>()
18957 .into();
18958
18959 drop(buffer);
18960
18961 // Position the selection in the rename editor so that it matches the current selection.
18962 this.show_local_selections = false;
18963 let rename_editor = cx.new(|cx| {
18964 let mut editor = Editor::single_line(window, cx);
18965 editor.buffer.update(cx, |buffer, cx| {
18966 buffer.edit(
18967 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18968 None,
18969 cx,
18970 )
18971 });
18972 let cursor_offset_in_rename_range =
18973 MultiBufferOffset(cursor_offset_in_rename_range);
18974 let cursor_offset_in_rename_range_end =
18975 MultiBufferOffset(cursor_offset_in_rename_range_end);
18976 let rename_selection_range = match cursor_offset_in_rename_range
18977 .cmp(&cursor_offset_in_rename_range_end)
18978 {
18979 Ordering::Equal => {
18980 editor.select_all(&SelectAll, window, cx);
18981 return editor;
18982 }
18983 Ordering::Less => {
18984 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18985 }
18986 Ordering::Greater => {
18987 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18988 }
18989 };
18990 if rename_selection_range.end.0 > old_name.len() {
18991 editor.select_all(&SelectAll, window, cx);
18992 } else {
18993 editor.change_selections(Default::default(), window, cx, |s| {
18994 s.select_ranges([rename_selection_range]);
18995 });
18996 }
18997 editor
18998 });
18999 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19000 if e == &EditorEvent::Focused {
19001 cx.emit(EditorEvent::FocusedIn)
19002 }
19003 })
19004 .detach();
19005
19006 let write_highlights =
19007 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19008 let read_highlights =
19009 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19010 let ranges = write_highlights
19011 .iter()
19012 .flat_map(|(_, ranges)| ranges.iter())
19013 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19014 .cloned()
19015 .collect();
19016
19017 this.highlight_text(
19018 HighlightKey::Rename,
19019 ranges,
19020 HighlightStyle {
19021 fade_out: Some(0.6),
19022 ..Default::default()
19023 },
19024 cx,
19025 );
19026 let rename_focus_handle = rename_editor.focus_handle(cx);
19027 window.focus(&rename_focus_handle, cx);
19028 let block_id = this.insert_blocks(
19029 [BlockProperties {
19030 style: BlockStyle::Flex,
19031 placement: BlockPlacement::Below(range.start),
19032 height: Some(1),
19033 render: Arc::new({
19034 let rename_editor = rename_editor.clone();
19035 move |cx: &mut BlockContext| {
19036 let mut text_style = cx.editor_style.text.clone();
19037 if let Some(highlight_style) = old_highlight_id
19038 .and_then(|h| h.style(&cx.editor_style.syntax))
19039 {
19040 text_style = text_style.highlight(highlight_style);
19041 }
19042 div()
19043 .block_mouse_except_scroll()
19044 .pl(cx.anchor_x)
19045 .child(EditorElement::new(
19046 &rename_editor,
19047 EditorStyle {
19048 background: cx.theme().system().transparent,
19049 local_player: cx.editor_style.local_player,
19050 text: text_style,
19051 scrollbar_width: cx.editor_style.scrollbar_width,
19052 syntax: cx.editor_style.syntax.clone(),
19053 status: cx.editor_style.status.clone(),
19054 inlay_hints_style: HighlightStyle {
19055 font_weight: Some(FontWeight::BOLD),
19056 ..make_inlay_hints_style(cx.app)
19057 },
19058 edit_prediction_styles: make_suggestion_styles(
19059 cx.app,
19060 ),
19061 ..EditorStyle::default()
19062 },
19063 ))
19064 .into_any_element()
19065 }
19066 }),
19067 priority: 0,
19068 }],
19069 Some(Autoscroll::fit()),
19070 cx,
19071 )[0];
19072 this.pending_rename = Some(RenameState {
19073 range,
19074 old_name,
19075 editor: rename_editor,
19076 block_id,
19077 });
19078 })?;
19079 }
19080
19081 Ok(())
19082 }))
19083 }
19084
19085 pub fn confirm_rename(
19086 &mut self,
19087 _: &ConfirmRename,
19088 window: &mut Window,
19089 cx: &mut Context<Self>,
19090 ) -> Option<Task<Result<()>>> {
19091 let rename = self.take_rename(false, window, cx)?;
19092 let workspace = self.workspace()?.downgrade();
19093 let (buffer, start) = self
19094 .buffer
19095 .read(cx)
19096 .text_anchor_for_position(rename.range.start, cx)?;
19097 let (end_buffer, _) = self
19098 .buffer
19099 .read(cx)
19100 .text_anchor_for_position(rename.range.end, cx)?;
19101 if buffer != end_buffer {
19102 return None;
19103 }
19104
19105 let old_name = rename.old_name;
19106 let new_name = rename.editor.read(cx).text(cx);
19107
19108 let rename = self.semantics_provider.as_ref()?.perform_rename(
19109 &buffer,
19110 start,
19111 new_name.clone(),
19112 cx,
19113 )?;
19114
19115 Some(cx.spawn_in(window, async move |editor, cx| {
19116 let project_transaction = rename.await?;
19117 Self::open_project_transaction(
19118 &editor,
19119 workspace,
19120 project_transaction,
19121 format!("Rename: {} → {}", old_name, new_name),
19122 cx,
19123 )
19124 .await?;
19125
19126 editor.update(cx, |editor, cx| {
19127 editor.refresh_document_highlights(cx);
19128 })?;
19129 Ok(())
19130 }))
19131 }
19132
19133 fn take_rename(
19134 &mut self,
19135 moving_cursor: bool,
19136 window: &mut Window,
19137 cx: &mut Context<Self>,
19138 ) -> Option<RenameState> {
19139 let rename = self.pending_rename.take()?;
19140 if rename.editor.focus_handle(cx).is_focused(window) {
19141 window.focus(&self.focus_handle, cx);
19142 }
19143
19144 self.remove_blocks(
19145 [rename.block_id].into_iter().collect(),
19146 Some(Autoscroll::fit()),
19147 cx,
19148 );
19149 self.clear_highlights(HighlightKey::Rename, cx);
19150 self.show_local_selections = true;
19151
19152 if moving_cursor {
19153 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19154 editor
19155 .selections
19156 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19157 .head()
19158 });
19159
19160 // Update the selection to match the position of the selection inside
19161 // the rename editor.
19162 let snapshot = self.buffer.read(cx).read(cx);
19163 let rename_range = rename.range.to_offset(&snapshot);
19164 let cursor_in_editor = snapshot
19165 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19166 .min(rename_range.end);
19167 drop(snapshot);
19168
19169 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19170 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19171 });
19172 } else {
19173 self.refresh_document_highlights(cx);
19174 }
19175
19176 Some(rename)
19177 }
19178
19179 pub fn pending_rename(&self) -> Option<&RenameState> {
19180 self.pending_rename.as_ref()
19181 }
19182
19183 fn format(
19184 &mut self,
19185 _: &Format,
19186 window: &mut Window,
19187 cx: &mut Context<Self>,
19188 ) -> Option<Task<Result<()>>> {
19189 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19190
19191 let project = match &self.project {
19192 Some(project) => project.clone(),
19193 None => return None,
19194 };
19195
19196 Some(self.perform_format(
19197 project,
19198 FormatTrigger::Manual,
19199 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19200 window,
19201 cx,
19202 ))
19203 }
19204
19205 fn format_selections(
19206 &mut self,
19207 _: &FormatSelections,
19208 window: &mut Window,
19209 cx: &mut Context<Self>,
19210 ) -> Option<Task<Result<()>>> {
19211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19212
19213 let project = match &self.project {
19214 Some(project) => project.clone(),
19215 None => return None,
19216 };
19217
19218 let ranges = self
19219 .selections
19220 .all_adjusted(&self.display_snapshot(cx))
19221 .into_iter()
19222 .map(|selection| selection.range())
19223 .collect_vec();
19224
19225 Some(self.perform_format(
19226 project,
19227 FormatTrigger::Manual,
19228 FormatTarget::Ranges(ranges),
19229 window,
19230 cx,
19231 ))
19232 }
19233
19234 fn perform_format(
19235 &mut self,
19236 project: Entity<Project>,
19237 trigger: FormatTrigger,
19238 target: FormatTarget,
19239 window: &mut Window,
19240 cx: &mut Context<Self>,
19241 ) -> Task<Result<()>> {
19242 let buffer = self.buffer.clone();
19243 let (buffers, target) = match target {
19244 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19245 FormatTarget::Ranges(selection_ranges) => {
19246 let multi_buffer = buffer.read(cx);
19247 let snapshot = multi_buffer.read(cx);
19248 let mut buffers = HashSet::default();
19249 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19250 BTreeMap::new();
19251 for selection_range in selection_ranges {
19252 for (buffer, buffer_range, _) in
19253 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19254 {
19255 let buffer_id = buffer.remote_id();
19256 let start = buffer.anchor_before(buffer_range.start);
19257 let end = buffer.anchor_after(buffer_range.end);
19258 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19259 buffer_id_to_ranges
19260 .entry(buffer_id)
19261 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19262 .or_insert_with(|| vec![start..end]);
19263 }
19264 }
19265 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19266 }
19267 };
19268
19269 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19270 let selections_prev = transaction_id_prev
19271 .and_then(|transaction_id_prev| {
19272 // default to selections as they were after the last edit, if we have them,
19273 // instead of how they are now.
19274 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19275 // will take you back to where you made the last edit, instead of staying where you scrolled
19276 self.selection_history
19277 .transaction(transaction_id_prev)
19278 .map(|t| t.0.clone())
19279 })
19280 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19281
19282 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19283 let format = project.update(cx, |project, cx| {
19284 project.format(buffers, target, true, trigger, cx)
19285 });
19286
19287 cx.spawn_in(window, async move |editor, cx| {
19288 let transaction = futures::select_biased! {
19289 transaction = format.log_err().fuse() => transaction,
19290 () = timeout => {
19291 log::warn!("timed out waiting for formatting");
19292 None
19293 }
19294 };
19295
19296 buffer.update(cx, |buffer, cx| {
19297 if let Some(transaction) = transaction
19298 && !buffer.is_singleton()
19299 {
19300 buffer.push_transaction(&transaction.0, cx);
19301 }
19302 cx.notify();
19303 });
19304
19305 if let Some(transaction_id_now) =
19306 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19307 {
19308 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19309 if has_new_transaction {
19310 editor
19311 .update(cx, |editor, _| {
19312 editor
19313 .selection_history
19314 .insert_transaction(transaction_id_now, selections_prev);
19315 })
19316 .ok();
19317 }
19318 }
19319
19320 Ok(())
19321 })
19322 }
19323
19324 fn organize_imports(
19325 &mut self,
19326 _: &OrganizeImports,
19327 window: &mut Window,
19328 cx: &mut Context<Self>,
19329 ) -> Option<Task<Result<()>>> {
19330 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19331 let project = match &self.project {
19332 Some(project) => project.clone(),
19333 None => return None,
19334 };
19335 Some(self.perform_code_action_kind(
19336 project,
19337 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19338 window,
19339 cx,
19340 ))
19341 }
19342
19343 fn perform_code_action_kind(
19344 &mut self,
19345 project: Entity<Project>,
19346 kind: CodeActionKind,
19347 window: &mut Window,
19348 cx: &mut Context<Self>,
19349 ) -> Task<Result<()>> {
19350 let buffer = self.buffer.clone();
19351 let buffers = buffer.read(cx).all_buffers();
19352 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19353 let apply_action = project.update(cx, |project, cx| {
19354 project.apply_code_action_kind(buffers, kind, true, cx)
19355 });
19356 cx.spawn_in(window, async move |_, cx| {
19357 let transaction = futures::select_biased! {
19358 () = timeout => {
19359 log::warn!("timed out waiting for executing code action");
19360 None
19361 }
19362 transaction = apply_action.log_err().fuse() => transaction,
19363 };
19364 buffer.update(cx, |buffer, cx| {
19365 // check if we need this
19366 if let Some(transaction) = transaction
19367 && !buffer.is_singleton()
19368 {
19369 buffer.push_transaction(&transaction.0, cx);
19370 }
19371 cx.notify();
19372 });
19373 Ok(())
19374 })
19375 }
19376
19377 pub fn restart_language_server(
19378 &mut self,
19379 _: &RestartLanguageServer,
19380 _: &mut Window,
19381 cx: &mut Context<Self>,
19382 ) {
19383 if let Some(project) = self.project.clone() {
19384 self.buffer.update(cx, |multi_buffer, cx| {
19385 project.update(cx, |project, cx| {
19386 project.restart_language_servers_for_buffers(
19387 multi_buffer.all_buffers().into_iter().collect(),
19388 HashSet::default(),
19389 cx,
19390 );
19391 });
19392 })
19393 }
19394 }
19395
19396 pub fn stop_language_server(
19397 &mut self,
19398 _: &StopLanguageServer,
19399 _: &mut Window,
19400 cx: &mut Context<Self>,
19401 ) {
19402 if let Some(project) = self.project.clone() {
19403 self.buffer.update(cx, |multi_buffer, cx| {
19404 project.update(cx, |project, cx| {
19405 project.stop_language_servers_for_buffers(
19406 multi_buffer.all_buffers().into_iter().collect(),
19407 HashSet::default(),
19408 cx,
19409 );
19410 });
19411 });
19412 }
19413 }
19414
19415 fn cancel_language_server_work(
19416 workspace: &mut Workspace,
19417 _: &actions::CancelLanguageServerWork,
19418 _: &mut Window,
19419 cx: &mut Context<Workspace>,
19420 ) {
19421 let project = workspace.project();
19422 let buffers = workspace
19423 .active_item(cx)
19424 .and_then(|item| item.act_as::<Editor>(cx))
19425 .map_or(HashSet::default(), |editor| {
19426 editor.read(cx).buffer.read(cx).all_buffers()
19427 });
19428 project.update(cx, |project, cx| {
19429 project.cancel_language_server_work_for_buffers(buffers, cx);
19430 });
19431 }
19432
19433 fn show_character_palette(
19434 &mut self,
19435 _: &ShowCharacterPalette,
19436 window: &mut Window,
19437 _: &mut Context<Self>,
19438 ) {
19439 window.show_character_palette();
19440 }
19441
19442 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19443 if !self.diagnostics_enabled() {
19444 return;
19445 }
19446
19447 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19448 let buffer = self.buffer.read(cx).snapshot(cx);
19449 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19450 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19451 let is_valid = buffer
19452 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19453 .any(|entry| {
19454 entry.diagnostic.is_primary
19455 && !entry.range.is_empty()
19456 && entry.range.start == primary_range_start
19457 && entry.diagnostic.message == active_diagnostics.active_message
19458 });
19459
19460 if !is_valid {
19461 self.dismiss_diagnostics(cx);
19462 }
19463 }
19464 }
19465
19466 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19467 match &self.active_diagnostics {
19468 ActiveDiagnostic::Group(group) => Some(group),
19469 _ => None,
19470 }
19471 }
19472
19473 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19474 if !self.diagnostics_enabled() {
19475 return;
19476 }
19477 self.dismiss_diagnostics(cx);
19478 self.active_diagnostics = ActiveDiagnostic::All;
19479 }
19480
19481 fn activate_diagnostics(
19482 &mut self,
19483 buffer_id: BufferId,
19484 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19485 window: &mut Window,
19486 cx: &mut Context<Self>,
19487 ) {
19488 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19489 return;
19490 }
19491 self.dismiss_diagnostics(cx);
19492 let snapshot = self.snapshot(window, cx);
19493 let buffer = self.buffer.read(cx).snapshot(cx);
19494 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19495 return;
19496 };
19497
19498 let diagnostic_group = buffer
19499 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19500 .collect::<Vec<_>>();
19501
19502 let language_registry = self
19503 .project()
19504 .map(|project| project.read(cx).languages().clone());
19505
19506 let blocks = renderer.render_group(
19507 diagnostic_group,
19508 buffer_id,
19509 snapshot,
19510 cx.weak_entity(),
19511 language_registry,
19512 cx,
19513 );
19514
19515 let blocks = self.display_map.update(cx, |display_map, cx| {
19516 display_map.insert_blocks(blocks, cx).into_iter().collect()
19517 });
19518 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19519 active_range: buffer.anchor_before(diagnostic.range.start)
19520 ..buffer.anchor_after(diagnostic.range.end),
19521 active_message: diagnostic.diagnostic.message.clone(),
19522 group_id: diagnostic.diagnostic.group_id,
19523 blocks,
19524 });
19525 cx.notify();
19526 }
19527
19528 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19529 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19530 return;
19531 };
19532
19533 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19534 if let ActiveDiagnostic::Group(group) = prev {
19535 self.display_map.update(cx, |display_map, cx| {
19536 display_map.remove_blocks(group.blocks, cx);
19537 });
19538 cx.notify();
19539 }
19540 }
19541
19542 /// Disable inline diagnostics rendering for this editor.
19543 pub fn disable_inline_diagnostics(&mut self) {
19544 self.inline_diagnostics_enabled = false;
19545 self.inline_diagnostics_update = Task::ready(());
19546 self.inline_diagnostics.clear();
19547 }
19548
19549 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19550 self.diagnostics_enabled = false;
19551 self.dismiss_diagnostics(cx);
19552 self.inline_diagnostics_update = Task::ready(());
19553 self.inline_diagnostics.clear();
19554 }
19555
19556 pub fn disable_word_completions(&mut self) {
19557 self.word_completions_enabled = false;
19558 }
19559
19560 pub fn diagnostics_enabled(&self) -> bool {
19561 self.diagnostics_enabled && self.lsp_data_enabled()
19562 }
19563
19564 pub fn inline_diagnostics_enabled(&self) -> bool {
19565 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19566 }
19567
19568 pub fn show_inline_diagnostics(&self) -> bool {
19569 self.show_inline_diagnostics
19570 }
19571
19572 pub fn toggle_inline_diagnostics(
19573 &mut self,
19574 _: &ToggleInlineDiagnostics,
19575 window: &mut Window,
19576 cx: &mut Context<Editor>,
19577 ) {
19578 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19579 self.refresh_inline_diagnostics(false, window, cx);
19580 }
19581
19582 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19583 self.diagnostics_max_severity = severity;
19584 self.display_map.update(cx, |display_map, _| {
19585 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19586 });
19587 }
19588
19589 pub fn toggle_diagnostics(
19590 &mut self,
19591 _: &ToggleDiagnostics,
19592 window: &mut Window,
19593 cx: &mut Context<Editor>,
19594 ) {
19595 if !self.diagnostics_enabled() {
19596 return;
19597 }
19598
19599 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19600 EditorSettings::get_global(cx)
19601 .diagnostics_max_severity
19602 .filter(|severity| severity != &DiagnosticSeverity::Off)
19603 .unwrap_or(DiagnosticSeverity::Hint)
19604 } else {
19605 DiagnosticSeverity::Off
19606 };
19607 self.set_max_diagnostics_severity(new_severity, cx);
19608 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19609 self.active_diagnostics = ActiveDiagnostic::None;
19610 self.inline_diagnostics_update = Task::ready(());
19611 self.inline_diagnostics.clear();
19612 } else {
19613 self.refresh_inline_diagnostics(false, window, cx);
19614 }
19615
19616 cx.notify();
19617 }
19618
19619 pub fn toggle_minimap(
19620 &mut self,
19621 _: &ToggleMinimap,
19622 window: &mut Window,
19623 cx: &mut Context<Editor>,
19624 ) {
19625 if self.supports_minimap(cx) {
19626 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19627 }
19628 }
19629
19630 fn refresh_inline_diagnostics(
19631 &mut self,
19632 debounce: bool,
19633 window: &mut Window,
19634 cx: &mut Context<Self>,
19635 ) {
19636 let max_severity = ProjectSettings::get_global(cx)
19637 .diagnostics
19638 .inline
19639 .max_severity
19640 .unwrap_or(self.diagnostics_max_severity);
19641
19642 if !self.inline_diagnostics_enabled()
19643 || !self.diagnostics_enabled()
19644 || !self.show_inline_diagnostics
19645 || max_severity == DiagnosticSeverity::Off
19646 {
19647 self.inline_diagnostics_update = Task::ready(());
19648 self.inline_diagnostics.clear();
19649 return;
19650 }
19651
19652 let debounce_ms = ProjectSettings::get_global(cx)
19653 .diagnostics
19654 .inline
19655 .update_debounce_ms;
19656 let debounce = if debounce && debounce_ms > 0 {
19657 Some(Duration::from_millis(debounce_ms))
19658 } else {
19659 None
19660 };
19661 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19662 if let Some(debounce) = debounce {
19663 cx.background_executor().timer(debounce).await;
19664 }
19665 let Some(snapshot) = editor.upgrade().map(|editor| {
19666 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19667 }) else {
19668 return;
19669 };
19670
19671 let new_inline_diagnostics = cx
19672 .background_spawn(async move {
19673 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19674 for diagnostic_entry in
19675 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19676 {
19677 let message = diagnostic_entry
19678 .diagnostic
19679 .message
19680 .split_once('\n')
19681 .map(|(line, _)| line)
19682 .map(SharedString::new)
19683 .unwrap_or_else(|| {
19684 SharedString::new(&*diagnostic_entry.diagnostic.message)
19685 });
19686 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19687 let (Ok(i) | Err(i)) = inline_diagnostics
19688 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19689 inline_diagnostics.insert(
19690 i,
19691 (
19692 start_anchor,
19693 InlineDiagnostic {
19694 message,
19695 group_id: diagnostic_entry.diagnostic.group_id,
19696 start: diagnostic_entry.range.start.to_point(&snapshot),
19697 is_primary: diagnostic_entry.diagnostic.is_primary,
19698 severity: diagnostic_entry.diagnostic.severity,
19699 },
19700 ),
19701 );
19702 }
19703 inline_diagnostics
19704 })
19705 .await;
19706
19707 editor
19708 .update(cx, |editor, cx| {
19709 editor.inline_diagnostics = new_inline_diagnostics;
19710 cx.notify();
19711 })
19712 .ok();
19713 });
19714 }
19715
19716 fn pull_diagnostics(
19717 &mut self,
19718 buffer_id: BufferId,
19719 _window: &Window,
19720 cx: &mut Context<Self>,
19721 ) -> Option<()> {
19722 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19723 // skip any LSP updates for it.
19724
19725 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19726 return None;
19727 }
19728 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19729 .diagnostics
19730 .lsp_pull_diagnostics;
19731 if !pull_diagnostics_settings.enabled {
19732 return None;
19733 }
19734 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19735 let project = self.project()?.downgrade();
19736 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19737
19738 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19739 cx.background_executor().timer(debounce).await;
19740 if let Ok(task) = project.update(cx, |project, cx| {
19741 project.lsp_store().update(cx, |lsp_store, cx| {
19742 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19743 })
19744 }) {
19745 task.await.log_err();
19746 }
19747 project
19748 .update(cx, |project, cx| {
19749 project.lsp_store().update(cx, |lsp_store, cx| {
19750 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19751 })
19752 })
19753 .log_err();
19754 });
19755
19756 Some(())
19757 }
19758
19759 pub fn set_selections_from_remote(
19760 &mut self,
19761 selections: Vec<Selection<Anchor>>,
19762 pending_selection: Option<Selection<Anchor>>,
19763 window: &mut Window,
19764 cx: &mut Context<Self>,
19765 ) {
19766 let old_cursor_position = self.selections.newest_anchor().head();
19767 self.selections
19768 .change_with(&self.display_snapshot(cx), |s| {
19769 s.select_anchors(selections);
19770 if let Some(pending_selection) = pending_selection {
19771 s.set_pending(pending_selection, SelectMode::Character);
19772 } else {
19773 s.clear_pending();
19774 }
19775 });
19776 self.selections_did_change(
19777 false,
19778 &old_cursor_position,
19779 SelectionEffects::default(),
19780 window,
19781 cx,
19782 );
19783 }
19784
19785 pub fn transact(
19786 &mut self,
19787 window: &mut Window,
19788 cx: &mut Context<Self>,
19789 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19790 ) -> Option<TransactionId> {
19791 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19792 this.start_transaction_at(Instant::now(), window, cx);
19793 update(this, window, cx);
19794 this.end_transaction_at(Instant::now(), cx)
19795 })
19796 }
19797
19798 pub fn start_transaction_at(
19799 &mut self,
19800 now: Instant,
19801 window: &mut Window,
19802 cx: &mut Context<Self>,
19803 ) -> Option<TransactionId> {
19804 self.end_selection(window, cx);
19805 if let Some(tx_id) = self
19806 .buffer
19807 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19808 {
19809 self.selection_history
19810 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19811 cx.emit(EditorEvent::TransactionBegun {
19812 transaction_id: tx_id,
19813 });
19814 Some(tx_id)
19815 } else {
19816 None
19817 }
19818 }
19819
19820 pub fn end_transaction_at(
19821 &mut self,
19822 now: Instant,
19823 cx: &mut Context<Self>,
19824 ) -> Option<TransactionId> {
19825 if let Some(transaction_id) = self
19826 .buffer
19827 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19828 {
19829 if let Some((_, end_selections)) =
19830 self.selection_history.transaction_mut(transaction_id)
19831 {
19832 *end_selections = Some(self.selections.disjoint_anchors_arc());
19833 } else {
19834 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19835 }
19836
19837 cx.emit(EditorEvent::Edited { transaction_id });
19838 Some(transaction_id)
19839 } else {
19840 None
19841 }
19842 }
19843
19844 pub fn modify_transaction_selection_history(
19845 &mut self,
19846 transaction_id: TransactionId,
19847 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19848 ) -> bool {
19849 self.selection_history
19850 .transaction_mut(transaction_id)
19851 .map(modify)
19852 .is_some()
19853 }
19854
19855 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19856 if self.selection_mark_mode {
19857 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19858 s.move_with(&mut |_, sel| {
19859 sel.collapse_to(sel.head(), SelectionGoal::None);
19860 });
19861 })
19862 }
19863 self.selection_mark_mode = true;
19864 cx.notify();
19865 }
19866
19867 pub fn swap_selection_ends(
19868 &mut self,
19869 _: &actions::SwapSelectionEnds,
19870 window: &mut Window,
19871 cx: &mut Context<Self>,
19872 ) {
19873 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19874 s.move_with(&mut |_, sel| {
19875 if sel.start != sel.end {
19876 sel.reversed = !sel.reversed
19877 }
19878 });
19879 });
19880 self.request_autoscroll(Autoscroll::newest(), cx);
19881 cx.notify();
19882 }
19883
19884 pub fn toggle_focus(
19885 workspace: &mut Workspace,
19886 _: &actions::ToggleFocus,
19887 window: &mut Window,
19888 cx: &mut Context<Workspace>,
19889 ) {
19890 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19891 return;
19892 };
19893 workspace.activate_item(&item, true, true, window, cx);
19894 }
19895
19896 pub fn toggle_fold(
19897 &mut self,
19898 _: &actions::ToggleFold,
19899 window: &mut Window,
19900 cx: &mut Context<Self>,
19901 ) {
19902 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19903 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19904 let selection = self.selections.newest::<Point>(&display_map);
19905
19906 let range = if selection.is_empty() {
19907 let point = selection.head().to_display_point(&display_map);
19908 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19909 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19910 .to_point(&display_map);
19911 start..end
19912 } else {
19913 selection.range()
19914 };
19915 if display_map.folds_in_range(range).next().is_some() {
19916 self.unfold_lines(&Default::default(), window, cx)
19917 } else {
19918 self.fold(&Default::default(), window, cx)
19919 }
19920 } else {
19921 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19922 let buffer_ids: HashSet<_> = self
19923 .selections
19924 .disjoint_anchor_ranges()
19925 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19926 .collect();
19927
19928 let should_unfold = buffer_ids
19929 .iter()
19930 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19931
19932 for buffer_id in buffer_ids {
19933 if should_unfold {
19934 self.unfold_buffer(buffer_id, cx);
19935 } else {
19936 self.fold_buffer(buffer_id, cx);
19937 }
19938 }
19939 }
19940 }
19941
19942 pub fn toggle_fold_recursive(
19943 &mut self,
19944 _: &actions::ToggleFoldRecursive,
19945 window: &mut Window,
19946 cx: &mut Context<Self>,
19947 ) {
19948 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19949
19950 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19951 let range = if selection.is_empty() {
19952 let point = selection.head().to_display_point(&display_map);
19953 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19954 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19955 .to_point(&display_map);
19956 start..end
19957 } else {
19958 selection.range()
19959 };
19960 if display_map.folds_in_range(range).next().is_some() {
19961 self.unfold_recursive(&Default::default(), window, cx)
19962 } else {
19963 self.fold_recursive(&Default::default(), window, cx)
19964 }
19965 }
19966
19967 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19968 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19969 let mut to_fold = Vec::new();
19970 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19971 let selections = self.selections.all_adjusted(&display_map);
19972
19973 for selection in selections {
19974 let range = selection.range().sorted();
19975 let buffer_start_row = range.start.row;
19976
19977 if range.start.row != range.end.row {
19978 let mut found = false;
19979 let mut row = range.start.row;
19980 while row <= range.end.row {
19981 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19982 {
19983 found = true;
19984 row = crease.range().end.row + 1;
19985 to_fold.push(crease);
19986 } else {
19987 row += 1
19988 }
19989 }
19990 if found {
19991 continue;
19992 }
19993 }
19994
19995 for row in (0..=range.start.row).rev() {
19996 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19997 && crease.range().end.row >= buffer_start_row
19998 {
19999 to_fold.push(crease);
20000 if row <= range.start.row {
20001 break;
20002 }
20003 }
20004 }
20005 }
20006
20007 self.fold_creases(to_fold, true, window, cx);
20008 } else {
20009 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20010 let buffer_ids = self
20011 .selections
20012 .disjoint_anchor_ranges()
20013 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20014 .collect::<HashSet<_>>();
20015 for buffer_id in buffer_ids {
20016 self.fold_buffer(buffer_id, cx);
20017 }
20018 }
20019 }
20020
20021 pub fn toggle_fold_all(
20022 &mut self,
20023 _: &actions::ToggleFoldAll,
20024 window: &mut Window,
20025 cx: &mut Context<Self>,
20026 ) {
20027 let has_folds = if self.buffer.read(cx).is_singleton() {
20028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20029 let has_folds = display_map
20030 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20031 .next()
20032 .is_some();
20033 has_folds
20034 } else {
20035 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
20036 let has_folds = buffer_ids
20037 .iter()
20038 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20039 has_folds
20040 };
20041
20042 if has_folds {
20043 self.unfold_all(&actions::UnfoldAll, window, cx);
20044 } else {
20045 self.fold_all(&actions::FoldAll, window, cx);
20046 }
20047 }
20048
20049 fn fold_at_level(
20050 &mut self,
20051 fold_at: &FoldAtLevel,
20052 window: &mut Window,
20053 cx: &mut Context<Self>,
20054 ) {
20055 if !self.buffer.read(cx).is_singleton() {
20056 return;
20057 }
20058
20059 let fold_at_level = fold_at.0;
20060 let snapshot = self.buffer.read(cx).snapshot(cx);
20061 let mut to_fold = Vec::new();
20062 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20063
20064 let row_ranges_to_keep: Vec<Range<u32>> = self
20065 .selections
20066 .all::<Point>(&self.display_snapshot(cx))
20067 .into_iter()
20068 .map(|sel| sel.start.row..sel.end.row)
20069 .collect();
20070
20071 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20072 while start_row < end_row {
20073 match self
20074 .snapshot(window, cx)
20075 .crease_for_buffer_row(MultiBufferRow(start_row))
20076 {
20077 Some(crease) => {
20078 let nested_start_row = crease.range().start.row + 1;
20079 let nested_end_row = crease.range().end.row;
20080
20081 if current_level < fold_at_level {
20082 stack.push((nested_start_row, nested_end_row, current_level + 1));
20083 } else if current_level == fold_at_level {
20084 // Fold iff there is no selection completely contained within the fold region
20085 if !row_ranges_to_keep.iter().any(|selection| {
20086 selection.end >= nested_start_row
20087 && selection.start <= nested_end_row
20088 }) {
20089 to_fold.push(crease);
20090 }
20091 }
20092
20093 start_row = nested_end_row + 1;
20094 }
20095 None => start_row += 1,
20096 }
20097 }
20098 }
20099
20100 self.fold_creases(to_fold, true, window, cx);
20101 }
20102
20103 pub fn fold_at_level_1(
20104 &mut self,
20105 _: &actions::FoldAtLevel1,
20106 window: &mut Window,
20107 cx: &mut Context<Self>,
20108 ) {
20109 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20110 }
20111
20112 pub fn fold_at_level_2(
20113 &mut self,
20114 _: &actions::FoldAtLevel2,
20115 window: &mut Window,
20116 cx: &mut Context<Self>,
20117 ) {
20118 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20119 }
20120
20121 pub fn fold_at_level_3(
20122 &mut self,
20123 _: &actions::FoldAtLevel3,
20124 window: &mut Window,
20125 cx: &mut Context<Self>,
20126 ) {
20127 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20128 }
20129
20130 pub fn fold_at_level_4(
20131 &mut self,
20132 _: &actions::FoldAtLevel4,
20133 window: &mut Window,
20134 cx: &mut Context<Self>,
20135 ) {
20136 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20137 }
20138
20139 pub fn fold_at_level_5(
20140 &mut self,
20141 _: &actions::FoldAtLevel5,
20142 window: &mut Window,
20143 cx: &mut Context<Self>,
20144 ) {
20145 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20146 }
20147
20148 pub fn fold_at_level_6(
20149 &mut self,
20150 _: &actions::FoldAtLevel6,
20151 window: &mut Window,
20152 cx: &mut Context<Self>,
20153 ) {
20154 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20155 }
20156
20157 pub fn fold_at_level_7(
20158 &mut self,
20159 _: &actions::FoldAtLevel7,
20160 window: &mut Window,
20161 cx: &mut Context<Self>,
20162 ) {
20163 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20164 }
20165
20166 pub fn fold_at_level_8(
20167 &mut self,
20168 _: &actions::FoldAtLevel8,
20169 window: &mut Window,
20170 cx: &mut Context<Self>,
20171 ) {
20172 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20173 }
20174
20175 pub fn fold_at_level_9(
20176 &mut self,
20177 _: &actions::FoldAtLevel9,
20178 window: &mut Window,
20179 cx: &mut Context<Self>,
20180 ) {
20181 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20182 }
20183
20184 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20185 if self.buffer.read(cx).is_singleton() {
20186 let mut fold_ranges = Vec::new();
20187 let snapshot = self.buffer.read(cx).snapshot(cx);
20188
20189 for row in 0..snapshot.max_row().0 {
20190 if let Some(foldable_range) = self
20191 .snapshot(window, cx)
20192 .crease_for_buffer_row(MultiBufferRow(row))
20193 {
20194 fold_ranges.push(foldable_range);
20195 }
20196 }
20197
20198 self.fold_creases(fold_ranges, true, window, cx);
20199 } else {
20200 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20201 editor
20202 .update_in(cx, |editor, _, cx| {
20203 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20204 editor.fold_buffer(buffer_id, cx);
20205 }
20206 })
20207 .ok();
20208 });
20209 }
20210 }
20211
20212 pub fn fold_function_bodies(
20213 &mut self,
20214 _: &actions::FoldFunctionBodies,
20215 window: &mut Window,
20216 cx: &mut Context<Self>,
20217 ) {
20218 let snapshot = self.buffer.read(cx).snapshot(cx);
20219
20220 let ranges = snapshot
20221 .text_object_ranges(
20222 MultiBufferOffset(0)..snapshot.len(),
20223 TreeSitterOptions::default(),
20224 )
20225 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20226 .collect::<Vec<_>>();
20227
20228 let creases = ranges
20229 .into_iter()
20230 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20231 .collect();
20232
20233 self.fold_creases(creases, true, window, cx);
20234 }
20235
20236 pub fn fold_recursive(
20237 &mut self,
20238 _: &actions::FoldRecursive,
20239 window: &mut Window,
20240 cx: &mut Context<Self>,
20241 ) {
20242 let mut to_fold = Vec::new();
20243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20244 let selections = self.selections.all_adjusted(&display_map);
20245
20246 for selection in selections {
20247 let range = selection.range().sorted();
20248 let buffer_start_row = range.start.row;
20249
20250 if range.start.row != range.end.row {
20251 let mut found = false;
20252 for row in range.start.row..=range.end.row {
20253 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20254 found = true;
20255 to_fold.push(crease);
20256 }
20257 }
20258 if found {
20259 continue;
20260 }
20261 }
20262
20263 for row in (0..=range.start.row).rev() {
20264 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20265 if crease.range().end.row >= buffer_start_row {
20266 to_fold.push(crease);
20267 } else {
20268 break;
20269 }
20270 }
20271 }
20272 }
20273
20274 self.fold_creases(to_fold, true, window, cx);
20275 }
20276
20277 pub fn fold_at(
20278 &mut self,
20279 buffer_row: MultiBufferRow,
20280 window: &mut Window,
20281 cx: &mut Context<Self>,
20282 ) {
20283 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20284
20285 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20286 let autoscroll = self
20287 .selections
20288 .all::<Point>(&display_map)
20289 .iter()
20290 .any(|selection| crease.range().overlaps(&selection.range()));
20291
20292 self.fold_creases(vec![crease], autoscroll, window, cx);
20293 }
20294 }
20295
20296 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20297 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20298 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20299 let buffer = display_map.buffer_snapshot();
20300 let selections = self.selections.all::<Point>(&display_map);
20301 let ranges = selections
20302 .iter()
20303 .map(|s| {
20304 let range = s.display_range(&display_map).sorted();
20305 let mut start = range.start.to_point(&display_map);
20306 let mut end = range.end.to_point(&display_map);
20307 start.column = 0;
20308 end.column = buffer.line_len(MultiBufferRow(end.row));
20309 start..end
20310 })
20311 .collect::<Vec<_>>();
20312
20313 self.unfold_ranges(&ranges, true, true, cx);
20314 } else {
20315 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20316 let buffer_ids = self
20317 .selections
20318 .disjoint_anchor_ranges()
20319 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20320 .collect::<HashSet<_>>();
20321 for buffer_id in buffer_ids {
20322 self.unfold_buffer(buffer_id, cx);
20323 }
20324 }
20325 }
20326
20327 pub fn unfold_recursive(
20328 &mut self,
20329 _: &UnfoldRecursive,
20330 _window: &mut Window,
20331 cx: &mut Context<Self>,
20332 ) {
20333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20334 let selections = self.selections.all::<Point>(&display_map);
20335 let ranges = selections
20336 .iter()
20337 .map(|s| {
20338 let mut range = s.display_range(&display_map).sorted();
20339 *range.start.column_mut() = 0;
20340 *range.end.column_mut() = display_map.line_len(range.end.row());
20341 let start = range.start.to_point(&display_map);
20342 let end = range.end.to_point(&display_map);
20343 start..end
20344 })
20345 .collect::<Vec<_>>();
20346
20347 self.unfold_ranges(&ranges, true, true, cx);
20348 }
20349
20350 pub fn unfold_at(
20351 &mut self,
20352 buffer_row: MultiBufferRow,
20353 _window: &mut Window,
20354 cx: &mut Context<Self>,
20355 ) {
20356 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20357
20358 let intersection_range = Point::new(buffer_row.0, 0)
20359 ..Point::new(
20360 buffer_row.0,
20361 display_map.buffer_snapshot().line_len(buffer_row),
20362 );
20363
20364 let autoscroll = self
20365 .selections
20366 .all::<Point>(&display_map)
20367 .iter()
20368 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20369
20370 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20371 }
20372
20373 pub fn unfold_all(
20374 &mut self,
20375 _: &actions::UnfoldAll,
20376 _window: &mut Window,
20377 cx: &mut Context<Self>,
20378 ) {
20379 if self.buffer.read(cx).is_singleton() {
20380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20381 self.unfold_ranges(
20382 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20383 true,
20384 true,
20385 cx,
20386 );
20387 } else {
20388 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20389 editor
20390 .update(cx, |editor, cx| {
20391 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20392 editor.unfold_buffer(buffer_id, cx);
20393 }
20394 })
20395 .ok();
20396 });
20397 }
20398 }
20399
20400 pub fn fold_selected_ranges(
20401 &mut self,
20402 _: &FoldSelectedRanges,
20403 window: &mut Window,
20404 cx: &mut Context<Self>,
20405 ) {
20406 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20407 let selections = self.selections.all_adjusted(&display_map);
20408 let ranges = selections
20409 .into_iter()
20410 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20411 .collect::<Vec<_>>();
20412 self.fold_creases(ranges, true, window, cx);
20413 }
20414
20415 pub fn fold_ranges<T: ToOffset + Clone>(
20416 &mut self,
20417 ranges: Vec<Range<T>>,
20418 auto_scroll: bool,
20419 window: &mut Window,
20420 cx: &mut Context<Self>,
20421 ) {
20422 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20423 let ranges = ranges
20424 .into_iter()
20425 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20426 .collect::<Vec<_>>();
20427 self.fold_creases(ranges, auto_scroll, window, cx);
20428 }
20429
20430 pub fn fold_creases<T: ToOffset + Clone>(
20431 &mut self,
20432 creases: Vec<Crease<T>>,
20433 auto_scroll: bool,
20434 window: &mut Window,
20435 cx: &mut Context<Self>,
20436 ) {
20437 if creases.is_empty() {
20438 return;
20439 }
20440
20441 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20442
20443 if auto_scroll {
20444 self.request_autoscroll(Autoscroll::fit(), cx);
20445 }
20446
20447 cx.notify();
20448
20449 self.scrollbar_marker_state.dirty = true;
20450 self.update_data_on_scroll(window, cx);
20451 self.folds_did_change(cx);
20452 }
20453
20454 /// Removes any folds whose ranges intersect any of the given ranges.
20455 pub fn unfold_ranges<T: ToOffset + Clone>(
20456 &mut self,
20457 ranges: &[Range<T>],
20458 inclusive: bool,
20459 auto_scroll: bool,
20460 cx: &mut Context<Self>,
20461 ) {
20462 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20463 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20464 });
20465 self.folds_did_change(cx);
20466 }
20467
20468 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20469 self.fold_buffers([buffer_id], cx);
20470 }
20471
20472 pub fn fold_buffers(
20473 &mut self,
20474 buffer_ids: impl IntoIterator<Item = BufferId>,
20475 cx: &mut Context<Self>,
20476 ) {
20477 if self.buffer().read(cx).is_singleton() {
20478 return;
20479 }
20480
20481 let ids_to_fold: Vec<BufferId> = buffer_ids
20482 .into_iter()
20483 .filter(|id| !self.is_buffer_folded(*id, cx))
20484 .collect();
20485
20486 if ids_to_fold.is_empty() {
20487 return;
20488 }
20489
20490 let mut all_folded_excerpt_ids = Vec::new();
20491 for buffer_id in &ids_to_fold {
20492 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20493 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20494 }
20495
20496 self.display_map.update(cx, |display_map, cx| {
20497 display_map.fold_buffers(ids_to_fold.clone(), cx)
20498 });
20499
20500 let snapshot = self.display_snapshot(cx);
20501 self.selections.change_with(&snapshot, |selections| {
20502 for buffer_id in ids_to_fold {
20503 selections.remove_selections_from_buffer(buffer_id);
20504 }
20505 });
20506
20507 cx.emit(EditorEvent::BufferFoldToggled {
20508 ids: all_folded_excerpt_ids,
20509 folded: true,
20510 });
20511 cx.notify();
20512 }
20513
20514 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20515 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20516 return;
20517 }
20518 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20519 self.display_map.update(cx, |display_map, cx| {
20520 display_map.unfold_buffers([buffer_id], cx);
20521 });
20522 cx.emit(EditorEvent::BufferFoldToggled {
20523 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20524 folded: false,
20525 });
20526 cx.notify();
20527 }
20528
20529 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20530 self.display_map.read(cx).is_buffer_folded(buffer)
20531 }
20532
20533 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20534 if self.buffer().read(cx).is_singleton() {
20535 return false;
20536 }
20537 !self.folded_buffers(cx).is_empty()
20538 }
20539
20540 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20541 self.display_map.read(cx).folded_buffers()
20542 }
20543
20544 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20545 self.display_map.update(cx, |display_map, cx| {
20546 display_map.disable_header_for_buffer(buffer_id, cx);
20547 });
20548 cx.notify();
20549 }
20550
20551 /// Removes any folds with the given ranges.
20552 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20553 &mut self,
20554 ranges: &[Range<T>],
20555 type_id: TypeId,
20556 auto_scroll: bool,
20557 cx: &mut Context<Self>,
20558 ) {
20559 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20560 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20561 });
20562 self.folds_did_change(cx);
20563 }
20564
20565 fn remove_folds_with<T: ToOffset + Clone>(
20566 &mut self,
20567 ranges: &[Range<T>],
20568 auto_scroll: bool,
20569 cx: &mut Context<Self>,
20570 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20571 ) {
20572 if ranges.is_empty() {
20573 return;
20574 }
20575
20576 let mut buffers_affected = HashSet::default();
20577 let multi_buffer = self.buffer().read(cx);
20578 for range in ranges {
20579 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20580 buffers_affected.insert(buffer.read(cx).remote_id());
20581 };
20582 }
20583
20584 self.display_map.update(cx, update);
20585
20586 if auto_scroll {
20587 self.request_autoscroll(Autoscroll::fit(), cx);
20588 }
20589
20590 cx.notify();
20591 self.scrollbar_marker_state.dirty = true;
20592 self.active_indent_guides_state.dirty = true;
20593 }
20594
20595 pub fn update_renderer_widths(
20596 &mut self,
20597 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20598 cx: &mut Context<Self>,
20599 ) -> bool {
20600 self.display_map
20601 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20602 }
20603
20604 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20605 self.display_map.read(cx).fold_placeholder.clone()
20606 }
20607
20608 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20609 self.buffer.update(cx, |buffer, cx| {
20610 buffer.set_all_diff_hunks_expanded(cx);
20611 });
20612 }
20613
20614 pub fn expand_all_diff_hunks(
20615 &mut self,
20616 _: &ExpandAllDiffHunks,
20617 _window: &mut Window,
20618 cx: &mut Context<Self>,
20619 ) {
20620 self.buffer.update(cx, |buffer, cx| {
20621 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20622 });
20623 }
20624
20625 pub fn collapse_all_diff_hunks(
20626 &mut self,
20627 _: &CollapseAllDiffHunks,
20628 _window: &mut Window,
20629 cx: &mut Context<Self>,
20630 ) {
20631 self.buffer.update(cx, |buffer, cx| {
20632 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20633 });
20634 }
20635
20636 pub fn toggle_selected_diff_hunks(
20637 &mut self,
20638 _: &ToggleSelectedDiffHunks,
20639 _window: &mut Window,
20640 cx: &mut Context<Self>,
20641 ) {
20642 let ranges: Vec<_> = self
20643 .selections
20644 .disjoint_anchors()
20645 .iter()
20646 .map(|s| s.range())
20647 .collect();
20648 self.toggle_diff_hunks_in_ranges(ranges, cx);
20649 }
20650
20651 pub fn diff_hunks_in_ranges<'a>(
20652 &'a self,
20653 ranges: &'a [Range<Anchor>],
20654 buffer: &'a MultiBufferSnapshot,
20655 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20656 ranges.iter().flat_map(move |range| {
20657 let end_excerpt_id = range.end.excerpt_id;
20658 let range = range.to_point(buffer);
20659 let mut peek_end = range.end;
20660 if range.end.row < buffer.max_row().0 {
20661 peek_end = Point::new(range.end.row + 1, 0);
20662 }
20663 buffer
20664 .diff_hunks_in_range(range.start..peek_end)
20665 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20666 })
20667 }
20668
20669 pub fn has_stageable_diff_hunks_in_ranges(
20670 &self,
20671 ranges: &[Range<Anchor>],
20672 snapshot: &MultiBufferSnapshot,
20673 ) -> bool {
20674 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20675 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20676 }
20677
20678 pub fn toggle_staged_selected_diff_hunks(
20679 &mut self,
20680 _: &::git::ToggleStaged,
20681 _: &mut Window,
20682 cx: &mut Context<Self>,
20683 ) {
20684 let snapshot = self.buffer.read(cx).snapshot(cx);
20685 let ranges: Vec<_> = self
20686 .selections
20687 .disjoint_anchors()
20688 .iter()
20689 .map(|s| s.range())
20690 .collect();
20691 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20692 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20693 }
20694
20695 pub fn set_render_diff_hunk_controls(
20696 &mut self,
20697 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20698 cx: &mut Context<Self>,
20699 ) {
20700 self.render_diff_hunk_controls = render_diff_hunk_controls;
20701 cx.notify();
20702 }
20703
20704 pub fn stage_and_next(
20705 &mut self,
20706 _: &::git::StageAndNext,
20707 window: &mut Window,
20708 cx: &mut Context<Self>,
20709 ) {
20710 self.do_stage_or_unstage_and_next(true, window, cx);
20711 }
20712
20713 pub fn unstage_and_next(
20714 &mut self,
20715 _: &::git::UnstageAndNext,
20716 window: &mut Window,
20717 cx: &mut Context<Self>,
20718 ) {
20719 self.do_stage_or_unstage_and_next(false, window, cx);
20720 }
20721
20722 pub fn stage_or_unstage_diff_hunks(
20723 &mut self,
20724 stage: bool,
20725 ranges: Vec<Range<Anchor>>,
20726 cx: &mut Context<Self>,
20727 ) {
20728 if self.delegate_stage_and_restore {
20729 let snapshot = self.buffer.read(cx).snapshot(cx);
20730 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20731 if !hunks.is_empty() {
20732 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20733 }
20734 return;
20735 }
20736 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20737 cx.spawn(async move |this, cx| {
20738 task.await?;
20739 this.update(cx, |this, cx| {
20740 let snapshot = this.buffer.read(cx).snapshot(cx);
20741 let chunk_by = this
20742 .diff_hunks_in_ranges(&ranges, &snapshot)
20743 .chunk_by(|hunk| hunk.buffer_id);
20744 for (buffer_id, hunks) in &chunk_by {
20745 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20746 }
20747 })
20748 })
20749 .detach_and_log_err(cx);
20750 }
20751
20752 fn save_buffers_for_ranges_if_needed(
20753 &mut self,
20754 ranges: &[Range<Anchor>],
20755 cx: &mut Context<Editor>,
20756 ) -> Task<Result<()>> {
20757 let multibuffer = self.buffer.read(cx);
20758 let snapshot = multibuffer.read(cx);
20759 let buffer_ids: HashSet<_> = ranges
20760 .iter()
20761 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20762 .collect();
20763 drop(snapshot);
20764
20765 let mut buffers = HashSet::default();
20766 for buffer_id in buffer_ids {
20767 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20768 let buffer = buffer_entity.read(cx);
20769 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20770 {
20771 buffers.insert(buffer_entity);
20772 }
20773 }
20774 }
20775
20776 if let Some(project) = &self.project {
20777 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20778 } else {
20779 Task::ready(Ok(()))
20780 }
20781 }
20782
20783 fn do_stage_or_unstage_and_next(
20784 &mut self,
20785 stage: bool,
20786 window: &mut Window,
20787 cx: &mut Context<Self>,
20788 ) {
20789 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20790
20791 if ranges.iter().any(|range| range.start != range.end) {
20792 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20793 return;
20794 }
20795
20796 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20797
20798 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20799 let wrap_around = !all_diff_hunks_expanded;
20800 let snapshot = self.snapshot(window, cx);
20801 let position = self
20802 .selections
20803 .newest::<Point>(&snapshot.display_snapshot)
20804 .head();
20805
20806 self.go_to_hunk_before_or_after_position(
20807 &snapshot,
20808 position,
20809 Direction::Next,
20810 wrap_around,
20811 window,
20812 cx,
20813 );
20814 }
20815
20816 pub(crate) fn do_stage_or_unstage(
20817 &self,
20818 stage: bool,
20819 buffer_id: BufferId,
20820 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20821 cx: &mut App,
20822 ) -> Option<()> {
20823 let project = self.project()?;
20824 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20825 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20826 let buffer_snapshot = buffer.read(cx).snapshot();
20827 let file_exists = buffer_snapshot
20828 .file()
20829 .is_some_and(|file| file.disk_state().exists());
20830 diff.update(cx, |diff, cx| {
20831 diff.stage_or_unstage_hunks(
20832 stage,
20833 &hunks
20834 .map(|hunk| buffer_diff::DiffHunk {
20835 buffer_range: hunk.buffer_range,
20836 // We don't need to pass in word diffs here because they're only used for rendering and
20837 // this function changes internal state
20838 base_word_diffs: Vec::default(),
20839 buffer_word_diffs: Vec::default(),
20840 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20841 ..hunk.diff_base_byte_range.end.0,
20842 secondary_status: hunk.status.secondary,
20843 range: Point::zero()..Point::zero(), // unused
20844 })
20845 .collect::<Vec<_>>(),
20846 &buffer_snapshot,
20847 file_exists,
20848 cx,
20849 )
20850 });
20851 None
20852 }
20853
20854 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20855 let ranges: Vec<_> = self
20856 .selections
20857 .disjoint_anchors()
20858 .iter()
20859 .map(|s| s.range())
20860 .collect();
20861 self.buffer
20862 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20863 }
20864
20865 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20866 self.buffer.update(cx, |buffer, cx| {
20867 let ranges = vec![Anchor::min()..Anchor::max()];
20868 if !buffer.all_diff_hunks_expanded()
20869 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20870 {
20871 buffer.collapse_diff_hunks(ranges, cx);
20872 true
20873 } else {
20874 false
20875 }
20876 })
20877 }
20878
20879 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20880 if self.buffer.read(cx).all_diff_hunks_expanded() {
20881 return true;
20882 }
20883 let ranges = vec![Anchor::min()..Anchor::max()];
20884 self.buffer
20885 .read(cx)
20886 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20887 }
20888
20889 fn toggle_diff_hunks_in_ranges(
20890 &mut self,
20891 ranges: Vec<Range<Anchor>>,
20892 cx: &mut Context<Editor>,
20893 ) {
20894 self.buffer.update(cx, |buffer, cx| {
20895 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20896 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20897 })
20898 }
20899
20900 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20901 self.buffer.update(cx, |buffer, cx| {
20902 buffer.toggle_single_diff_hunk(range, cx);
20903 })
20904 }
20905
20906 pub(crate) fn apply_all_diff_hunks(
20907 &mut self,
20908 _: &ApplyAllDiffHunks,
20909 window: &mut Window,
20910 cx: &mut Context<Self>,
20911 ) {
20912 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20913
20914 let buffers = self.buffer.read(cx).all_buffers();
20915 for branch_buffer in buffers {
20916 branch_buffer.update(cx, |branch_buffer, cx| {
20917 branch_buffer.merge_into_base(Vec::new(), cx);
20918 });
20919 }
20920
20921 if let Some(project) = self.project.clone() {
20922 self.save(
20923 SaveOptions {
20924 format: true,
20925 autosave: false,
20926 },
20927 project,
20928 window,
20929 cx,
20930 )
20931 .detach_and_log_err(cx);
20932 }
20933 }
20934
20935 pub(crate) fn apply_selected_diff_hunks(
20936 &mut self,
20937 _: &ApplyDiffHunk,
20938 window: &mut Window,
20939 cx: &mut Context<Self>,
20940 ) {
20941 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20942 let snapshot = self.snapshot(window, cx);
20943 let hunks = snapshot.hunks_for_ranges(
20944 self.selections
20945 .all(&snapshot.display_snapshot)
20946 .into_iter()
20947 .map(|selection| selection.range()),
20948 );
20949 let mut ranges_by_buffer = HashMap::default();
20950 self.transact(window, cx, |editor, _window, cx| {
20951 for hunk in hunks {
20952 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20953 ranges_by_buffer
20954 .entry(buffer.clone())
20955 .or_insert_with(Vec::new)
20956 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20957 }
20958 }
20959
20960 for (buffer, ranges) in ranges_by_buffer {
20961 buffer.update(cx, |buffer, cx| {
20962 buffer.merge_into_base(ranges, cx);
20963 });
20964 }
20965 });
20966
20967 if let Some(project) = self.project.clone() {
20968 self.save(
20969 SaveOptions {
20970 format: true,
20971 autosave: false,
20972 },
20973 project,
20974 window,
20975 cx,
20976 )
20977 .detach_and_log_err(cx);
20978 }
20979 }
20980
20981 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20982 if hovered != self.gutter_hovered {
20983 self.gutter_hovered = hovered;
20984 cx.notify();
20985 }
20986 }
20987
20988 pub fn insert_blocks(
20989 &mut self,
20990 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20991 autoscroll: Option<Autoscroll>,
20992 cx: &mut Context<Self>,
20993 ) -> Vec<CustomBlockId> {
20994 let blocks = self
20995 .display_map
20996 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20997 if let Some(autoscroll) = autoscroll {
20998 self.request_autoscroll(autoscroll, cx);
20999 }
21000 cx.notify();
21001 blocks
21002 }
21003
21004 pub fn resize_blocks(
21005 &mut self,
21006 heights: HashMap<CustomBlockId, u32>,
21007 autoscroll: Option<Autoscroll>,
21008 cx: &mut Context<Self>,
21009 ) {
21010 self.display_map
21011 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21012 if let Some(autoscroll) = autoscroll {
21013 self.request_autoscroll(autoscroll, cx);
21014 }
21015 cx.notify();
21016 }
21017
21018 pub fn replace_blocks(
21019 &mut self,
21020 renderers: HashMap<CustomBlockId, RenderBlock>,
21021 autoscroll: Option<Autoscroll>,
21022 cx: &mut Context<Self>,
21023 ) {
21024 self.display_map
21025 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21026 if let Some(autoscroll) = autoscroll {
21027 self.request_autoscroll(autoscroll, cx);
21028 }
21029 cx.notify();
21030 }
21031
21032 pub fn remove_blocks(
21033 &mut self,
21034 block_ids: HashSet<CustomBlockId>,
21035 autoscroll: Option<Autoscroll>,
21036 cx: &mut Context<Self>,
21037 ) {
21038 self.display_map.update(cx, |display_map, cx| {
21039 display_map.remove_blocks(block_ids, cx)
21040 });
21041 if let Some(autoscroll) = autoscroll {
21042 self.request_autoscroll(autoscroll, cx);
21043 }
21044 cx.notify();
21045 }
21046
21047 pub fn row_for_block(
21048 &self,
21049 block_id: CustomBlockId,
21050 cx: &mut Context<Self>,
21051 ) -> Option<DisplayRow> {
21052 self.display_map
21053 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21054 }
21055
21056 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21057 self.focused_block = Some(focused_block);
21058 }
21059
21060 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21061 self.focused_block.take()
21062 }
21063
21064 pub fn insert_creases(
21065 &mut self,
21066 creases: impl IntoIterator<Item = Crease<Anchor>>,
21067 cx: &mut Context<Self>,
21068 ) -> Vec<CreaseId> {
21069 self.display_map
21070 .update(cx, |map, cx| map.insert_creases(creases, cx))
21071 }
21072
21073 pub fn remove_creases(
21074 &mut self,
21075 ids: impl IntoIterator<Item = CreaseId>,
21076 cx: &mut Context<Self>,
21077 ) -> Vec<(CreaseId, Range<Anchor>)> {
21078 self.display_map
21079 .update(cx, |map, cx| map.remove_creases(ids, cx))
21080 }
21081
21082 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21083 self.display_map
21084 .update(cx, |map, cx| map.snapshot(cx))
21085 .longest_row()
21086 }
21087
21088 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21089 self.display_map
21090 .update(cx, |map, cx| map.snapshot(cx))
21091 .max_point()
21092 }
21093
21094 pub fn text(&self, cx: &App) -> String {
21095 self.buffer.read(cx).read(cx).text()
21096 }
21097
21098 pub fn is_empty(&self, cx: &App) -> bool {
21099 self.buffer.read(cx).read(cx).is_empty()
21100 }
21101
21102 pub fn text_option(&self, cx: &App) -> Option<String> {
21103 let text = self.text(cx);
21104 let text = text.trim();
21105
21106 if text.is_empty() {
21107 return None;
21108 }
21109
21110 Some(text.to_string())
21111 }
21112
21113 pub fn set_text(
21114 &mut self,
21115 text: impl Into<Arc<str>>,
21116 window: &mut Window,
21117 cx: &mut Context<Self>,
21118 ) {
21119 self.transact(window, cx, |this, _, cx| {
21120 this.buffer
21121 .read(cx)
21122 .as_singleton()
21123 .expect("you can only call set_text on editors for singleton buffers")
21124 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21125 });
21126 }
21127
21128 pub fn display_text(&self, cx: &mut App) -> String {
21129 self.display_map
21130 .update(cx, |map, cx| map.snapshot(cx))
21131 .text()
21132 }
21133
21134 fn create_minimap(
21135 &self,
21136 minimap_settings: MinimapSettings,
21137 window: &mut Window,
21138 cx: &mut Context<Self>,
21139 ) -> Option<Entity<Self>> {
21140 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21141 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21142 }
21143
21144 fn initialize_new_minimap(
21145 &self,
21146 minimap_settings: MinimapSettings,
21147 window: &mut Window,
21148 cx: &mut Context<Self>,
21149 ) -> Entity<Self> {
21150 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21151 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21152
21153 let mut minimap = Editor::new_internal(
21154 EditorMode::Minimap {
21155 parent: cx.weak_entity(),
21156 },
21157 self.buffer.clone(),
21158 None,
21159 Some(self.display_map.clone()),
21160 window,
21161 cx,
21162 );
21163 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21164 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21165 minimap.scroll_manager.clone_state(
21166 &self.scroll_manager,
21167 &my_snapshot,
21168 &minimap_snapshot,
21169 cx,
21170 );
21171 minimap.set_text_style_refinement(TextStyleRefinement {
21172 font_size: Some(MINIMAP_FONT_SIZE),
21173 font_weight: Some(MINIMAP_FONT_WEIGHT),
21174 font_family: Some(MINIMAP_FONT_FAMILY),
21175 ..Default::default()
21176 });
21177 minimap.update_minimap_configuration(minimap_settings, cx);
21178 cx.new(|_| minimap)
21179 }
21180
21181 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21182 let current_line_highlight = minimap_settings
21183 .current_line_highlight
21184 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21185 self.set_current_line_highlight(Some(current_line_highlight));
21186 }
21187
21188 pub fn minimap(&self) -> Option<&Entity<Self>> {
21189 self.minimap
21190 .as_ref()
21191 .filter(|_| self.minimap_visibility.visible())
21192 }
21193
21194 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21195 let mut wrap_guides = smallvec![];
21196
21197 if self.show_wrap_guides == Some(false) {
21198 return wrap_guides;
21199 }
21200
21201 let settings = self.buffer.read(cx).language_settings(cx);
21202 if settings.show_wrap_guides {
21203 match self.soft_wrap_mode(cx) {
21204 SoftWrap::Column(soft_wrap) => {
21205 wrap_guides.push((soft_wrap as usize, true));
21206 }
21207 SoftWrap::Bounded(soft_wrap) => {
21208 wrap_guides.push((soft_wrap as usize, true));
21209 }
21210 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21211 }
21212 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21213 }
21214
21215 wrap_guides
21216 }
21217
21218 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21219 let settings = self.buffer.read(cx).language_settings(cx);
21220 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21221 match mode {
21222 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21223 SoftWrap::None
21224 }
21225 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21226 language_settings::SoftWrap::PreferredLineLength => {
21227 SoftWrap::Column(settings.preferred_line_length)
21228 }
21229 language_settings::SoftWrap::Bounded => {
21230 SoftWrap::Bounded(settings.preferred_line_length)
21231 }
21232 }
21233 }
21234
21235 pub fn set_soft_wrap_mode(
21236 &mut self,
21237 mode: language_settings::SoftWrap,
21238 cx: &mut Context<Self>,
21239 ) {
21240 self.soft_wrap_mode_override = Some(mode);
21241 cx.notify();
21242 }
21243
21244 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21245 self.hard_wrap = hard_wrap;
21246 cx.notify();
21247 }
21248
21249 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21250 self.text_style_refinement = Some(style);
21251 }
21252
21253 /// called by the Element so we know what style we were most recently rendered with.
21254 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21255 // We intentionally do not inform the display map about the minimap style
21256 // so that wrapping is not recalculated and stays consistent for the editor
21257 // and its linked minimap.
21258 if !self.mode.is_minimap() {
21259 let font = style.text.font();
21260 let font_size = style.text.font_size.to_pixels(window.rem_size());
21261 let display_map = self
21262 .placeholder_display_map
21263 .as_ref()
21264 .filter(|_| self.is_empty(cx))
21265 .unwrap_or(&self.display_map);
21266
21267 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21268 }
21269 self.style = Some(style);
21270 }
21271
21272 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21273 if self.style.is_none() {
21274 self.style = Some(self.create_style(cx));
21275 }
21276 self.style.as_ref().unwrap()
21277 }
21278
21279 // Called by the element. This method is not designed to be called outside of the editor
21280 // element's layout code because it does not notify when rewrapping is computed synchronously.
21281 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21282 if self.is_empty(cx) {
21283 self.placeholder_display_map
21284 .as_ref()
21285 .map_or(false, |display_map| {
21286 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21287 })
21288 } else {
21289 self.display_map
21290 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21291 }
21292 }
21293
21294 pub fn set_soft_wrap(&mut self) {
21295 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21296 }
21297
21298 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21299 if self.soft_wrap_mode_override.is_some() {
21300 self.soft_wrap_mode_override.take();
21301 } else {
21302 let soft_wrap = match self.soft_wrap_mode(cx) {
21303 SoftWrap::GitDiff => return,
21304 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21305 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21306 language_settings::SoftWrap::None
21307 }
21308 };
21309 self.soft_wrap_mode_override = Some(soft_wrap);
21310 }
21311 cx.notify();
21312 }
21313
21314 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21315 let Some(workspace) = self.workspace() else {
21316 return;
21317 };
21318 let fs = workspace.read(cx).app_state().fs.clone();
21319 let current_show = TabBarSettings::get_global(cx).show;
21320 update_settings_file(fs, cx, move |setting, _| {
21321 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21322 });
21323 }
21324
21325 pub fn toggle_indent_guides(
21326 &mut self,
21327 _: &ToggleIndentGuides,
21328 _: &mut Window,
21329 cx: &mut Context<Self>,
21330 ) {
21331 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21332 self.buffer
21333 .read(cx)
21334 .language_settings(cx)
21335 .indent_guides
21336 .enabled
21337 });
21338 self.show_indent_guides = Some(!currently_enabled);
21339 cx.notify();
21340 }
21341
21342 fn should_show_indent_guides(&self) -> Option<bool> {
21343 self.show_indent_guides
21344 }
21345
21346 pub fn disable_indent_guides_for_buffer(
21347 &mut self,
21348 buffer_id: BufferId,
21349 cx: &mut Context<Self>,
21350 ) {
21351 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21352 cx.notify();
21353 }
21354
21355 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21356 self.buffers_with_disabled_indent_guides
21357 .contains(&buffer_id)
21358 }
21359
21360 pub fn toggle_line_numbers(
21361 &mut self,
21362 _: &ToggleLineNumbers,
21363 _: &mut Window,
21364 cx: &mut Context<Self>,
21365 ) {
21366 let mut editor_settings = EditorSettings::get_global(cx).clone();
21367 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21368 EditorSettings::override_global(editor_settings, cx);
21369 }
21370
21371 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21372 if let Some(show_line_numbers) = self.show_line_numbers {
21373 return show_line_numbers;
21374 }
21375 EditorSettings::get_global(cx).gutter.line_numbers
21376 }
21377
21378 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21379 match (
21380 self.use_relative_line_numbers,
21381 EditorSettings::get_global(cx).relative_line_numbers,
21382 ) {
21383 (None, setting) => setting,
21384 (Some(false), _) => RelativeLineNumbers::Disabled,
21385 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21386 (Some(true), _) => RelativeLineNumbers::Enabled,
21387 }
21388 }
21389
21390 pub fn toggle_relative_line_numbers(
21391 &mut self,
21392 _: &ToggleRelativeLineNumbers,
21393 _: &mut Window,
21394 cx: &mut Context<Self>,
21395 ) {
21396 let is_relative = self.relative_line_numbers(cx);
21397 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21398 }
21399
21400 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21401 self.use_relative_line_numbers = is_relative;
21402 cx.notify();
21403 }
21404
21405 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21406 self.show_gutter = show_gutter;
21407 cx.notify();
21408 }
21409
21410 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21411 self.show_scrollbars = ScrollbarAxes {
21412 horizontal: show,
21413 vertical: show,
21414 };
21415 cx.notify();
21416 }
21417
21418 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21419 self.show_scrollbars.vertical = show;
21420 cx.notify();
21421 }
21422
21423 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21424 self.show_scrollbars.horizontal = show;
21425 cx.notify();
21426 }
21427
21428 pub fn set_minimap_visibility(
21429 &mut self,
21430 minimap_visibility: MinimapVisibility,
21431 window: &mut Window,
21432 cx: &mut Context<Self>,
21433 ) {
21434 if self.minimap_visibility != minimap_visibility {
21435 if minimap_visibility.visible() && self.minimap.is_none() {
21436 let minimap_settings = EditorSettings::get_global(cx).minimap;
21437 self.minimap =
21438 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21439 }
21440 self.minimap_visibility = minimap_visibility;
21441 cx.notify();
21442 }
21443 }
21444
21445 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21446 self.set_show_scrollbars(false, cx);
21447 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21448 }
21449
21450 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21451 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21452 }
21453
21454 /// Normally the text in full mode and auto height editors is padded on the
21455 /// left side by roughly half a character width for improved hit testing.
21456 ///
21457 /// Use this method to disable this for cases where this is not wanted (e.g.
21458 /// if you want to align the editor text with some other text above or below)
21459 /// or if you want to add this padding to single-line editors.
21460 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21461 self.offset_content = offset_content;
21462 cx.notify();
21463 }
21464
21465 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21466 self.show_line_numbers = Some(show_line_numbers);
21467 cx.notify();
21468 }
21469
21470 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21471 self.disable_expand_excerpt_buttons = true;
21472 cx.notify();
21473 }
21474
21475 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21476 self.number_deleted_lines = number;
21477 cx.notify();
21478 }
21479
21480 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21481 self.delegate_expand_excerpts = delegate;
21482 }
21483
21484 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21485 self.delegate_stage_and_restore = delegate;
21486 }
21487
21488 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21489 self.delegate_open_excerpts = delegate;
21490 }
21491
21492 pub fn set_on_local_selections_changed(
21493 &mut self,
21494 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21495 ) {
21496 self.on_local_selections_changed = callback;
21497 }
21498
21499 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21500 self.suppress_selection_callback = suppress;
21501 }
21502
21503 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21504 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21505 cx.notify();
21506 }
21507
21508 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21509 self.show_code_actions = Some(show_code_actions);
21510 cx.notify();
21511 }
21512
21513 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21514 self.show_runnables = Some(show_runnables);
21515 cx.notify();
21516 }
21517
21518 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21519 self.show_breakpoints = Some(show_breakpoints);
21520 cx.notify();
21521 }
21522
21523 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21524 self.show_diff_review_button = show;
21525 cx.notify();
21526 }
21527
21528 pub fn show_diff_review_button(&self) -> bool {
21529 self.show_diff_review_button
21530 }
21531
21532 pub fn render_diff_review_button(
21533 &self,
21534 display_row: DisplayRow,
21535 width: Pixels,
21536 cx: &mut Context<Self>,
21537 ) -> impl IntoElement {
21538 let text_color = cx.theme().colors().text;
21539 let icon_color = cx.theme().colors().icon_accent;
21540
21541 h_flex()
21542 .id("diff_review_button")
21543 .cursor_pointer()
21544 .w(width - px(1.))
21545 .h(relative(0.9))
21546 .justify_center()
21547 .rounded_sm()
21548 .border_1()
21549 .border_color(text_color.opacity(0.1))
21550 .bg(text_color.opacity(0.15))
21551 .hover(|s| {
21552 s.bg(icon_color.opacity(0.4))
21553 .border_color(icon_color.opacity(0.5))
21554 })
21555 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21556 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21557 .on_mouse_down(
21558 gpui::MouseButton::Left,
21559 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21560 editor.start_diff_review_drag(display_row, window, cx);
21561 }),
21562 )
21563 }
21564
21565 pub fn start_diff_review_drag(
21566 &mut self,
21567 display_row: DisplayRow,
21568 window: &mut Window,
21569 cx: &mut Context<Self>,
21570 ) {
21571 let snapshot = self.snapshot(window, cx);
21572 let point = snapshot
21573 .display_snapshot
21574 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21575 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21576 self.diff_review_drag_state = Some(DiffReviewDragState {
21577 start_anchor: anchor,
21578 current_anchor: anchor,
21579 });
21580 cx.notify();
21581 }
21582
21583 pub fn update_diff_review_drag(
21584 &mut self,
21585 display_row: DisplayRow,
21586 window: &mut Window,
21587 cx: &mut Context<Self>,
21588 ) {
21589 if self.diff_review_drag_state.is_none() {
21590 return;
21591 }
21592 let snapshot = self.snapshot(window, cx);
21593 let point = snapshot
21594 .display_snapshot
21595 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21596 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21597 if let Some(drag_state) = &mut self.diff_review_drag_state {
21598 drag_state.current_anchor = anchor;
21599 cx.notify();
21600 }
21601 }
21602
21603 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21604 if let Some(drag_state) = self.diff_review_drag_state.take() {
21605 let snapshot = self.snapshot(window, cx);
21606 let range = drag_state.row_range(&snapshot.display_snapshot);
21607 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21608 }
21609 cx.notify();
21610 }
21611
21612 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21613 self.diff_review_drag_state = None;
21614 cx.notify();
21615 }
21616
21617 /// Calculates the appropriate block height for the diff review overlay.
21618 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21619 /// and 2 lines per comment when expanded.
21620 fn calculate_overlay_height(
21621 &self,
21622 hunk_key: &DiffHunkKey,
21623 comments_expanded: bool,
21624 snapshot: &MultiBufferSnapshot,
21625 ) -> u32 {
21626 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21627 let base_height: u32 = 2; // Input row with avatar and buttons
21628
21629 if comment_count == 0 {
21630 base_height
21631 } else if comments_expanded {
21632 // Header (1 line) + 2 lines per comment
21633 base_height + 1 + (comment_count as u32 * 2)
21634 } else {
21635 // Just header when collapsed
21636 base_height + 1
21637 }
21638 }
21639
21640 pub fn show_diff_review_overlay(
21641 &mut self,
21642 display_range: Range<DisplayRow>,
21643 window: &mut Window,
21644 cx: &mut Context<Self>,
21645 ) {
21646 let Range { start, end } = display_range.sorted();
21647
21648 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21649 let editor_snapshot = self.snapshot(window, cx);
21650
21651 // Convert display rows to multibuffer points
21652 let start_point = editor_snapshot
21653 .display_snapshot
21654 .display_point_to_point(start.as_display_point(), Bias::Left);
21655 let end_point = editor_snapshot
21656 .display_snapshot
21657 .display_point_to_point(end.as_display_point(), Bias::Left);
21658 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21659
21660 // Create anchor range for the selected lines (start of first line to end of last line)
21661 let line_end = Point::new(
21662 end_point.row,
21663 buffer_snapshot.line_len(end_multi_buffer_row),
21664 );
21665 let anchor_range =
21666 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21667
21668 // Compute the hunk key for this display row
21669 let file_path = buffer_snapshot
21670 .file_at(start_point)
21671 .map(|file: &Arc<dyn language::File>| file.path().clone())
21672 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21673 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21674 let new_hunk_key = DiffHunkKey {
21675 file_path,
21676 hunk_start_anchor,
21677 };
21678
21679 // Check if we already have an overlay for this hunk
21680 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21681 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21682 }) {
21683 // Just focus the existing overlay's prompt editor
21684 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21685 window.focus(&focus_handle, cx);
21686 return;
21687 }
21688
21689 // Dismiss overlays that have no comments for their hunks
21690 self.dismiss_overlays_without_comments(cx);
21691
21692 // Get the current user's avatar URI from the project's user_store
21693 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21694 let user_store = project.read(cx).user_store();
21695 user_store
21696 .read(cx)
21697 .current_user()
21698 .map(|user| user.avatar_uri.clone())
21699 });
21700
21701 // Create anchor at the end of the last row so the block appears immediately below it
21702 // Use multibuffer coordinates for anchor creation
21703 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21704 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21705
21706 // Use the hunk key we already computed
21707 let hunk_key = new_hunk_key;
21708
21709 // Create the prompt editor for the review input
21710 let prompt_editor = cx.new(|cx| {
21711 let mut editor = Editor::single_line(window, cx);
21712 editor.set_placeholder_text("Add a review comment...", window, cx);
21713 editor
21714 });
21715
21716 // Register the Newline action on the prompt editor to submit the review
21717 let parent_editor = cx.entity().downgrade();
21718 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21719 prompt_editor.register_action({
21720 let parent_editor = parent_editor.clone();
21721 move |_: &crate::actions::Newline, window, cx| {
21722 if let Some(editor) = parent_editor.upgrade() {
21723 editor.update(cx, |editor, cx| {
21724 editor.submit_diff_review_comment(window, cx);
21725 });
21726 }
21727 }
21728 })
21729 });
21730
21731 // Calculate initial height based on existing comments for this hunk
21732 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21733
21734 // Create the overlay block
21735 let prompt_editor_for_render = prompt_editor.clone();
21736 let hunk_key_for_render = hunk_key.clone();
21737 let editor_handle = cx.entity().downgrade();
21738 let block = BlockProperties {
21739 style: BlockStyle::Sticky,
21740 placement: BlockPlacement::Below(anchor),
21741 height: Some(initial_height),
21742 render: Arc::new(move |cx| {
21743 Self::render_diff_review_overlay(
21744 &prompt_editor_for_render,
21745 &hunk_key_for_render,
21746 &editor_handle,
21747 cx,
21748 )
21749 }),
21750 priority: 0,
21751 };
21752
21753 let block_ids = self.insert_blocks([block], None, cx);
21754 let Some(block_id) = block_ids.into_iter().next() else {
21755 log::error!("Failed to insert diff review overlay block");
21756 return;
21757 };
21758
21759 self.diff_review_overlays.push(DiffReviewOverlay {
21760 anchor_range,
21761 block_id,
21762 prompt_editor: prompt_editor.clone(),
21763 hunk_key,
21764 comments_expanded: true,
21765 inline_edit_editors: HashMap::default(),
21766 inline_edit_subscriptions: HashMap::default(),
21767 user_avatar_uri,
21768 _subscription: subscription,
21769 });
21770
21771 // Focus the prompt editor
21772 let focus_handle = prompt_editor.focus_handle(cx);
21773 window.focus(&focus_handle, cx);
21774
21775 cx.notify();
21776 }
21777
21778 /// Dismisses all diff review overlays.
21779 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21780 if self.diff_review_overlays.is_empty() {
21781 return;
21782 }
21783 let block_ids: HashSet<_> = self
21784 .diff_review_overlays
21785 .drain(..)
21786 .map(|overlay| overlay.block_id)
21787 .collect();
21788 self.remove_blocks(block_ids, None, cx);
21789 cx.notify();
21790 }
21791
21792 /// Dismisses overlays that have no comments stored for their hunks.
21793 /// Keeps overlays that have at least one comment.
21794 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21795 let snapshot = self.buffer.read(cx).snapshot(cx);
21796
21797 // First, compute which overlays have comments (to avoid borrow issues with retain)
21798 let overlays_with_comments: Vec<bool> = self
21799 .diff_review_overlays
21800 .iter()
21801 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21802 .collect();
21803
21804 // Now collect block IDs to remove and retain overlays
21805 let mut block_ids_to_remove = HashSet::default();
21806 let mut index = 0;
21807 self.diff_review_overlays.retain(|overlay| {
21808 let has_comments = overlays_with_comments[index];
21809 index += 1;
21810 if !has_comments {
21811 block_ids_to_remove.insert(overlay.block_id);
21812 }
21813 has_comments
21814 });
21815
21816 if !block_ids_to_remove.is_empty() {
21817 self.remove_blocks(block_ids_to_remove, None, cx);
21818 cx.notify();
21819 }
21820 }
21821
21822 /// Refreshes the diff review overlay block to update its height and render function.
21823 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21824 fn refresh_diff_review_overlay_height(
21825 &mut self,
21826 hunk_key: &DiffHunkKey,
21827 _window: &mut Window,
21828 cx: &mut Context<Self>,
21829 ) {
21830 // Extract all needed data from overlay first to avoid borrow conflicts
21831 let snapshot = self.buffer.read(cx).snapshot(cx);
21832 let (comments_expanded, block_id, prompt_editor) = {
21833 let Some(overlay) = self
21834 .diff_review_overlays
21835 .iter()
21836 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21837 else {
21838 return;
21839 };
21840
21841 (
21842 overlay.comments_expanded,
21843 overlay.block_id,
21844 overlay.prompt_editor.clone(),
21845 )
21846 };
21847
21848 // Calculate new height
21849 let snapshot = self.buffer.read(cx).snapshot(cx);
21850 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21851
21852 // Update the block height using resize_blocks (avoids flicker)
21853 let mut heights = HashMap::default();
21854 heights.insert(block_id, new_height);
21855 self.resize_blocks(heights, None, cx);
21856
21857 // Update the render function using replace_blocks (avoids flicker)
21858 let hunk_key_for_render = hunk_key.clone();
21859 let editor_handle = cx.entity().downgrade();
21860 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21861 Arc::new(move |cx| {
21862 Self::render_diff_review_overlay(
21863 &prompt_editor,
21864 &hunk_key_for_render,
21865 &editor_handle,
21866 cx,
21867 )
21868 });
21869
21870 let mut renderers = HashMap::default();
21871 renderers.insert(block_id, render);
21872 self.replace_blocks(renderers, None, cx);
21873 }
21874
21875 /// Action handler for SubmitDiffReviewComment.
21876 pub fn submit_diff_review_comment_action(
21877 &mut self,
21878 _: &SubmitDiffReviewComment,
21879 window: &mut Window,
21880 cx: &mut Context<Self>,
21881 ) {
21882 self.submit_diff_review_comment(window, cx);
21883 }
21884
21885 /// Stores the diff review comment locally.
21886 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21887 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21888 // Find the overlay that currently has focus
21889 let overlay_index = self
21890 .diff_review_overlays
21891 .iter()
21892 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21893 let Some(overlay_index) = overlay_index else {
21894 return;
21895 };
21896 let overlay = &self.diff_review_overlays[overlay_index];
21897
21898 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21899 if comment_text.is_empty() {
21900 return;
21901 }
21902
21903 let anchor_range = overlay.anchor_range.clone();
21904 let hunk_key = overlay.hunk_key.clone();
21905
21906 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21907
21908 // Clear the prompt editor but keep the overlay open
21909 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21910 overlay.prompt_editor.update(cx, |editor, cx| {
21911 editor.clear(window, cx);
21912 });
21913 }
21914
21915 // Refresh the overlay to update the block height for the new comment
21916 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21917
21918 cx.notify();
21919 }
21920
21921 /// Returns the prompt editor for the diff review overlay, if one is active.
21922 /// This is primarily used for testing.
21923 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21924 self.diff_review_overlays
21925 .first()
21926 .map(|overlay| &overlay.prompt_editor)
21927 }
21928
21929 /// Returns the line range for the first diff review overlay, if one is active.
21930 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21931 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21932 let overlay = self.diff_review_overlays.first()?;
21933 let snapshot = self.buffer.read(cx).snapshot(cx);
21934 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21935 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21936 let start_row = snapshot
21937 .point_to_buffer_point(start_point)
21938 .map(|(_, p, _)| p.row)
21939 .unwrap_or(start_point.row);
21940 let end_row = snapshot
21941 .point_to_buffer_point(end_point)
21942 .map(|(_, p, _)| p.row)
21943 .unwrap_or(end_point.row);
21944 Some((start_row, end_row))
21945 }
21946
21947 /// Sets whether the comments section is expanded in the diff review overlay.
21948 /// This is primarily used for testing.
21949 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21950 for overlay in &mut self.diff_review_overlays {
21951 overlay.comments_expanded = expanded;
21952 }
21953 cx.notify();
21954 }
21955
21956 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21957 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21958 a.file_path == b.file_path
21959 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21960 }
21961
21962 /// Returns comments for a specific hunk, ordered by creation time.
21963 pub fn comments_for_hunk<'a>(
21964 &'a self,
21965 key: &DiffHunkKey,
21966 snapshot: &MultiBufferSnapshot,
21967 ) -> &'a [StoredReviewComment] {
21968 let key_point = key.hunk_start_anchor.to_point(snapshot);
21969 self.stored_review_comments
21970 .iter()
21971 .find(|(k, _)| {
21972 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21973 })
21974 .map(|(_, comments)| comments.as_slice())
21975 .unwrap_or(&[])
21976 }
21977
21978 /// Returns the total count of stored review comments across all hunks.
21979 pub fn total_review_comment_count(&self) -> usize {
21980 self.stored_review_comments
21981 .iter()
21982 .map(|(_, v)| v.len())
21983 .sum()
21984 }
21985
21986 /// Returns the count of comments for a specific hunk.
21987 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21988 let key_point = key.hunk_start_anchor.to_point(snapshot);
21989 self.stored_review_comments
21990 .iter()
21991 .find(|(k, _)| {
21992 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21993 })
21994 .map(|(_, v)| v.len())
21995 .unwrap_or(0)
21996 }
21997
21998 /// Adds a new review comment to a specific hunk.
21999 pub fn add_review_comment(
22000 &mut self,
22001 hunk_key: DiffHunkKey,
22002 comment: String,
22003 anchor_range: Range<Anchor>,
22004 cx: &mut Context<Self>,
22005 ) -> usize {
22006 let id = self.next_review_comment_id;
22007 self.next_review_comment_id += 1;
22008
22009 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22010
22011 let snapshot = self.buffer.read(cx).snapshot(cx);
22012 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22013
22014 // Find existing entry for this hunk or add a new one
22015 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22016 k.file_path == hunk_key.file_path
22017 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22018 }) {
22019 comments.push(stored_comment);
22020 } else {
22021 self.stored_review_comments
22022 .push((hunk_key, vec![stored_comment]));
22023 }
22024
22025 cx.emit(EditorEvent::ReviewCommentsChanged {
22026 total_count: self.total_review_comment_count(),
22027 });
22028 cx.notify();
22029 id
22030 }
22031
22032 /// Removes a review comment by ID from any hunk.
22033 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22034 for (_, comments) in self.stored_review_comments.iter_mut() {
22035 if let Some(index) = comments.iter().position(|c| c.id == id) {
22036 comments.remove(index);
22037 cx.emit(EditorEvent::ReviewCommentsChanged {
22038 total_count: self.total_review_comment_count(),
22039 });
22040 cx.notify();
22041 return true;
22042 }
22043 }
22044 false
22045 }
22046
22047 /// Updates a review comment's text by ID.
22048 pub fn update_review_comment(
22049 &mut self,
22050 id: usize,
22051 new_comment: String,
22052 cx: &mut Context<Self>,
22053 ) -> bool {
22054 for (_, comments) in self.stored_review_comments.iter_mut() {
22055 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22056 comment.comment = new_comment;
22057 comment.is_editing = false;
22058 cx.emit(EditorEvent::ReviewCommentsChanged {
22059 total_count: self.total_review_comment_count(),
22060 });
22061 cx.notify();
22062 return true;
22063 }
22064 }
22065 false
22066 }
22067
22068 /// Sets a comment's editing state.
22069 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22070 for (_, comments) in self.stored_review_comments.iter_mut() {
22071 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22072 comment.is_editing = is_editing;
22073 cx.notify();
22074 return;
22075 }
22076 }
22077 }
22078
22079 /// Takes all stored comments from all hunks, clearing the storage.
22080 /// Returns a Vec of (hunk_key, comments) pairs.
22081 pub fn take_all_review_comments(
22082 &mut self,
22083 cx: &mut Context<Self>,
22084 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22085 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22086 self.dismiss_all_diff_review_overlays(cx);
22087 let comments = std::mem::take(&mut self.stored_review_comments);
22088 // Reset the ID counter since all comments have been taken
22089 self.next_review_comment_id = 0;
22090 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22091 cx.notify();
22092 comments
22093 }
22094
22095 /// Removes review comments whose anchors are no longer valid or whose
22096 /// associated diff hunks no longer exist.
22097 ///
22098 /// This should be called when the buffer changes to prevent orphaned comments
22099 /// from accumulating.
22100 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22101 let snapshot = self.buffer.read(cx).snapshot(cx);
22102 let original_count = self.total_review_comment_count();
22103
22104 // Remove comments with invalid hunk anchors
22105 self.stored_review_comments
22106 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22107
22108 // Also clean up individual comments with invalid anchor ranges
22109 for (_, comments) in &mut self.stored_review_comments {
22110 comments.retain(|comment| {
22111 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22112 });
22113 }
22114
22115 // Remove empty hunk entries
22116 self.stored_review_comments
22117 .retain(|(_, comments)| !comments.is_empty());
22118
22119 let new_count = self.total_review_comment_count();
22120 if new_count != original_count {
22121 cx.emit(EditorEvent::ReviewCommentsChanged {
22122 total_count: new_count,
22123 });
22124 cx.notify();
22125 }
22126 }
22127
22128 /// Toggles the expanded state of the comments section in the overlay.
22129 pub fn toggle_review_comments_expanded(
22130 &mut self,
22131 _: &ToggleReviewCommentsExpanded,
22132 window: &mut Window,
22133 cx: &mut Context<Self>,
22134 ) {
22135 // Find the overlay that currently has focus, or use the first one
22136 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22137 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22138 overlay.comments_expanded = !overlay.comments_expanded;
22139 Some(overlay.hunk_key.clone())
22140 } else {
22141 None
22142 }
22143 });
22144
22145 // If no focused overlay found, toggle the first one
22146 let hunk_key = overlay_info.or_else(|| {
22147 self.diff_review_overlays.first_mut().map(|overlay| {
22148 overlay.comments_expanded = !overlay.comments_expanded;
22149 overlay.hunk_key.clone()
22150 })
22151 });
22152
22153 if let Some(hunk_key) = hunk_key {
22154 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22155 cx.notify();
22156 }
22157 }
22158
22159 /// Handles the EditReviewComment action - sets a comment into editing mode.
22160 pub fn edit_review_comment(
22161 &mut self,
22162 action: &EditReviewComment,
22163 window: &mut Window,
22164 cx: &mut Context<Self>,
22165 ) {
22166 let comment_id = action.id;
22167
22168 // Set the comment to editing mode
22169 self.set_comment_editing(comment_id, true, cx);
22170
22171 // Find the overlay that contains this comment and create an inline editor if needed
22172 // First, find which hunk this comment belongs to
22173 let hunk_key = self
22174 .stored_review_comments
22175 .iter()
22176 .find_map(|(key, comments)| {
22177 if comments.iter().any(|c| c.id == comment_id) {
22178 Some(key.clone())
22179 } else {
22180 None
22181 }
22182 });
22183
22184 let snapshot = self.buffer.read(cx).snapshot(cx);
22185 if let Some(hunk_key) = hunk_key {
22186 if let Some(overlay) = self
22187 .diff_review_overlays
22188 .iter_mut()
22189 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22190 {
22191 if let std::collections::hash_map::Entry::Vacant(entry) =
22192 overlay.inline_edit_editors.entry(comment_id)
22193 {
22194 // Find the comment text
22195 let comment_text = self
22196 .stored_review_comments
22197 .iter()
22198 .flat_map(|(_, comments)| comments)
22199 .find(|c| c.id == comment_id)
22200 .map(|c| c.comment.clone())
22201 .unwrap_or_default();
22202
22203 // Create inline editor
22204 let parent_editor = cx.entity().downgrade();
22205 let inline_editor = cx.new(|cx| {
22206 let mut editor = Editor::single_line(window, cx);
22207 editor.set_text(&*comment_text, window, cx);
22208 // Select all text for easy replacement
22209 editor.select_all(&crate::actions::SelectAll, window, cx);
22210 editor
22211 });
22212
22213 // Register the Newline action to confirm the edit
22214 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22215 inline_editor.register_action({
22216 let parent_editor = parent_editor.clone();
22217 move |_: &crate::actions::Newline, window, cx| {
22218 if let Some(editor) = parent_editor.upgrade() {
22219 editor.update(cx, |editor, cx| {
22220 editor.confirm_edit_review_comment(comment_id, window, cx);
22221 });
22222 }
22223 }
22224 })
22225 });
22226
22227 // Store the subscription to keep the action handler alive
22228 overlay
22229 .inline_edit_subscriptions
22230 .insert(comment_id, subscription);
22231
22232 // Focus the inline editor
22233 let focus_handle = inline_editor.focus_handle(cx);
22234 window.focus(&focus_handle, cx);
22235
22236 entry.insert(inline_editor);
22237 }
22238 }
22239 }
22240
22241 cx.notify();
22242 }
22243
22244 /// Confirms an inline edit of a review comment.
22245 pub fn confirm_edit_review_comment(
22246 &mut self,
22247 comment_id: usize,
22248 _window: &mut Window,
22249 cx: &mut Context<Self>,
22250 ) {
22251 // Get the new text from the inline editor
22252 // Find the overlay containing this comment's inline editor
22253 let snapshot = self.buffer.read(cx).snapshot(cx);
22254 let hunk_key = self
22255 .stored_review_comments
22256 .iter()
22257 .find_map(|(key, comments)| {
22258 if comments.iter().any(|c| c.id == comment_id) {
22259 Some(key.clone())
22260 } else {
22261 None
22262 }
22263 });
22264
22265 let new_text = hunk_key
22266 .as_ref()
22267 .and_then(|hunk_key| {
22268 self.diff_review_overlays
22269 .iter()
22270 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22271 })
22272 .as_ref()
22273 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22274 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22275
22276 if let Some(new_text) = new_text {
22277 if !new_text.is_empty() {
22278 self.update_review_comment(comment_id, new_text, cx);
22279 }
22280 }
22281
22282 // Remove the inline editor and its subscription
22283 if let Some(hunk_key) = hunk_key {
22284 if let Some(overlay) = self
22285 .diff_review_overlays
22286 .iter_mut()
22287 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22288 {
22289 overlay.inline_edit_editors.remove(&comment_id);
22290 overlay.inline_edit_subscriptions.remove(&comment_id);
22291 }
22292 }
22293
22294 // Clear editing state
22295 self.set_comment_editing(comment_id, false, cx);
22296 }
22297
22298 /// Cancels an inline edit of a review comment.
22299 pub fn cancel_edit_review_comment(
22300 &mut self,
22301 comment_id: usize,
22302 _window: &mut Window,
22303 cx: &mut Context<Self>,
22304 ) {
22305 // Find which hunk this comment belongs to
22306 let hunk_key = self
22307 .stored_review_comments
22308 .iter()
22309 .find_map(|(key, comments)| {
22310 if comments.iter().any(|c| c.id == comment_id) {
22311 Some(key.clone())
22312 } else {
22313 None
22314 }
22315 });
22316
22317 // Remove the inline editor and its subscription
22318 if let Some(hunk_key) = hunk_key {
22319 let snapshot = self.buffer.read(cx).snapshot(cx);
22320 if let Some(overlay) = self
22321 .diff_review_overlays
22322 .iter_mut()
22323 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22324 {
22325 overlay.inline_edit_editors.remove(&comment_id);
22326 overlay.inline_edit_subscriptions.remove(&comment_id);
22327 }
22328 }
22329
22330 // Clear editing state
22331 self.set_comment_editing(comment_id, false, cx);
22332 }
22333
22334 /// Action handler for ConfirmEditReviewComment.
22335 pub fn confirm_edit_review_comment_action(
22336 &mut self,
22337 action: &ConfirmEditReviewComment,
22338 window: &mut Window,
22339 cx: &mut Context<Self>,
22340 ) {
22341 self.confirm_edit_review_comment(action.id, window, cx);
22342 }
22343
22344 /// Action handler for CancelEditReviewComment.
22345 pub fn cancel_edit_review_comment_action(
22346 &mut self,
22347 action: &CancelEditReviewComment,
22348 window: &mut Window,
22349 cx: &mut Context<Self>,
22350 ) {
22351 self.cancel_edit_review_comment(action.id, window, cx);
22352 }
22353
22354 /// Handles the DeleteReviewComment action - removes a comment.
22355 pub fn delete_review_comment(
22356 &mut self,
22357 action: &DeleteReviewComment,
22358 window: &mut Window,
22359 cx: &mut Context<Self>,
22360 ) {
22361 // Get the hunk key before removing the comment
22362 // Find the hunk key from the comment itself
22363 let comment_id = action.id;
22364 let hunk_key = self
22365 .stored_review_comments
22366 .iter()
22367 .find_map(|(key, comments)| {
22368 if comments.iter().any(|c| c.id == comment_id) {
22369 Some(key.clone())
22370 } else {
22371 None
22372 }
22373 });
22374
22375 // Also get it from the overlay for refresh purposes
22376 let overlay_hunk_key = self
22377 .diff_review_overlays
22378 .first()
22379 .map(|o| o.hunk_key.clone());
22380
22381 self.remove_review_comment(action.id, cx);
22382
22383 // Refresh the overlay height after removing a comment
22384 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22385 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22386 }
22387 }
22388
22389 fn render_diff_review_overlay(
22390 prompt_editor: &Entity<Editor>,
22391 hunk_key: &DiffHunkKey,
22392 editor_handle: &WeakEntity<Editor>,
22393 cx: &mut BlockContext,
22394 ) -> AnyElement {
22395 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22396 if ranges.is_empty() {
22397 return None;
22398 }
22399 let formatted: Vec<String> = ranges
22400 .iter()
22401 .map(|(start, end)| {
22402 let start_line = start + 1;
22403 let end_line = end + 1;
22404 if start_line == end_line {
22405 format!("Line {start_line}")
22406 } else {
22407 format!("Lines {start_line}-{end_line}")
22408 }
22409 })
22410 .collect();
22411 // Don't show label for single line in single excerpt
22412 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22413 return None;
22414 }
22415 Some(formatted.join(" ⋯ "))
22416 }
22417
22418 let theme = cx.theme();
22419 let colors = theme.colors();
22420
22421 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22422 editor_handle
22423 .upgrade()
22424 .map(|editor| {
22425 let editor = editor.read(cx);
22426 let snapshot = editor.buffer().read(cx).snapshot(cx);
22427 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22428 let (expanded, editors, avatar_uri, line_ranges) = editor
22429 .diff_review_overlays
22430 .iter()
22431 .find(|overlay| {
22432 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22433 })
22434 .map(|o| {
22435 let start_point = o.anchor_range.start.to_point(&snapshot);
22436 let end_point = o.anchor_range.end.to_point(&snapshot);
22437 // Get line ranges per excerpt to detect discontinuities
22438 let buffer_ranges =
22439 snapshot.range_to_buffer_ranges(start_point..end_point);
22440 let ranges: Vec<(u32, u32)> = buffer_ranges
22441 .iter()
22442 .map(|(buffer, range, _)| {
22443 let start = buffer.offset_to_point(range.start.0).row;
22444 let end = buffer.offset_to_point(range.end.0).row;
22445 (start, end)
22446 })
22447 .collect();
22448 (
22449 o.comments_expanded,
22450 o.inline_edit_editors.clone(),
22451 o.user_avatar_uri.clone(),
22452 if ranges.is_empty() {
22453 None
22454 } else {
22455 Some(ranges)
22456 },
22457 )
22458 })
22459 .unwrap_or((true, HashMap::default(), None, None));
22460 (comments, expanded, editors, avatar_uri, line_ranges)
22461 })
22462 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22463
22464 let comment_count = comments.len();
22465 let avatar_size = px(20.);
22466 let action_icon_size = IconSize::XSmall;
22467
22468 v_flex()
22469 .w_full()
22470 .bg(colors.editor_background)
22471 .border_b_1()
22472 .border_color(colors.border)
22473 .px_2()
22474 .pb_2()
22475 .gap_2()
22476 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22477 .when_some(line_ranges, |el, ranges| {
22478 let label = format_line_ranges(&ranges);
22479 if let Some(label) = label {
22480 el.child(
22481 h_flex()
22482 .w_full()
22483 .px_2()
22484 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22485 )
22486 } else {
22487 el
22488 }
22489 })
22490 // Top row: editable input with user's avatar
22491 .child(
22492 h_flex()
22493 .w_full()
22494 .items_center()
22495 .gap_2()
22496 .px_2()
22497 .py_1p5()
22498 .rounded_md()
22499 .bg(colors.surface_background)
22500 .child(
22501 div()
22502 .size(avatar_size)
22503 .flex_shrink_0()
22504 .rounded_full()
22505 .overflow_hidden()
22506 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22507 Avatar::new(avatar_uri.clone())
22508 .size(avatar_size)
22509 .into_any_element()
22510 } else {
22511 Icon::new(IconName::Person)
22512 .size(IconSize::Small)
22513 .color(ui::Color::Muted)
22514 .into_any_element()
22515 }),
22516 )
22517 .child(
22518 div()
22519 .flex_1()
22520 .border_1()
22521 .border_color(colors.border)
22522 .rounded_md()
22523 .bg(colors.editor_background)
22524 .px_2()
22525 .py_1()
22526 .child(prompt_editor.clone()),
22527 )
22528 .child(
22529 h_flex()
22530 .flex_shrink_0()
22531 .gap_1()
22532 .child(
22533 IconButton::new("diff-review-close", IconName::Close)
22534 .icon_color(ui::Color::Muted)
22535 .icon_size(action_icon_size)
22536 .tooltip(Tooltip::text("Close"))
22537 .on_click(|_, window, cx| {
22538 window
22539 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22540 }),
22541 )
22542 .child(
22543 IconButton::new("diff-review-add", IconName::Return)
22544 .icon_color(ui::Color::Muted)
22545 .icon_size(action_icon_size)
22546 .tooltip(Tooltip::text("Add comment"))
22547 .on_click(|_, window, cx| {
22548 window.dispatch_action(
22549 Box::new(crate::actions::SubmitDiffReviewComment),
22550 cx,
22551 );
22552 }),
22553 ),
22554 ),
22555 )
22556 // Expandable comments section (only shown when there are comments)
22557 .when(comment_count > 0, |el| {
22558 el.child(Self::render_comments_section(
22559 comments,
22560 comments_expanded,
22561 inline_editors,
22562 user_avatar_uri,
22563 avatar_size,
22564 action_icon_size,
22565 colors,
22566 ))
22567 })
22568 .into_any_element()
22569 }
22570
22571 fn render_comments_section(
22572 comments: Vec<StoredReviewComment>,
22573 expanded: bool,
22574 inline_editors: HashMap<usize, Entity<Editor>>,
22575 user_avatar_uri: Option<SharedUri>,
22576 avatar_size: Pixels,
22577 action_icon_size: IconSize,
22578 colors: &theme::ThemeColors,
22579 ) -> impl IntoElement {
22580 let comment_count = comments.len();
22581
22582 v_flex()
22583 .w_full()
22584 .gap_1()
22585 // Header with expand/collapse toggle
22586 .child(
22587 h_flex()
22588 .id("review-comments-header")
22589 .w_full()
22590 .items_center()
22591 .gap_1()
22592 .px_2()
22593 .py_1()
22594 .cursor_pointer()
22595 .rounded_md()
22596 .hover(|style| style.bg(colors.ghost_element_hover))
22597 .on_click(|_, window: &mut Window, cx| {
22598 window.dispatch_action(
22599 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22600 cx,
22601 );
22602 })
22603 .child(
22604 Icon::new(if expanded {
22605 IconName::ChevronDown
22606 } else {
22607 IconName::ChevronRight
22608 })
22609 .size(IconSize::Small)
22610 .color(ui::Color::Muted),
22611 )
22612 .child(
22613 Label::new(format!(
22614 "{} Comment{}",
22615 comment_count,
22616 if comment_count == 1 { "" } else { "s" }
22617 ))
22618 .size(LabelSize::Small)
22619 .color(Color::Muted),
22620 ),
22621 )
22622 // Comments list (when expanded)
22623 .when(expanded, |el| {
22624 el.children(comments.into_iter().map(|comment| {
22625 let inline_editor = inline_editors.get(&comment.id).cloned();
22626 Self::render_comment_row(
22627 comment,
22628 inline_editor,
22629 user_avatar_uri.clone(),
22630 avatar_size,
22631 action_icon_size,
22632 colors,
22633 )
22634 }))
22635 })
22636 }
22637
22638 fn render_comment_row(
22639 comment: StoredReviewComment,
22640 inline_editor: Option<Entity<Editor>>,
22641 user_avatar_uri: Option<SharedUri>,
22642 avatar_size: Pixels,
22643 action_icon_size: IconSize,
22644 colors: &theme::ThemeColors,
22645 ) -> impl IntoElement {
22646 let comment_id = comment.id;
22647 let is_editing = inline_editor.is_some();
22648
22649 h_flex()
22650 .w_full()
22651 .items_center()
22652 .gap_2()
22653 .px_2()
22654 .py_1p5()
22655 .rounded_md()
22656 .bg(colors.surface_background)
22657 .child(
22658 div()
22659 .size(avatar_size)
22660 .flex_shrink_0()
22661 .rounded_full()
22662 .overflow_hidden()
22663 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22664 Avatar::new(avatar_uri.clone())
22665 .size(avatar_size)
22666 .into_any_element()
22667 } else {
22668 Icon::new(IconName::Person)
22669 .size(IconSize::Small)
22670 .color(ui::Color::Muted)
22671 .into_any_element()
22672 }),
22673 )
22674 .child(if let Some(editor) = inline_editor {
22675 // Inline edit mode: show an editable text field
22676 div()
22677 .flex_1()
22678 .border_1()
22679 .border_color(colors.border)
22680 .rounded_md()
22681 .bg(colors.editor_background)
22682 .px_2()
22683 .py_1()
22684 .child(editor)
22685 .into_any_element()
22686 } else {
22687 // Display mode: show the comment text
22688 div()
22689 .flex_1()
22690 .text_sm()
22691 .text_color(colors.text)
22692 .child(comment.comment)
22693 .into_any_element()
22694 })
22695 .child(if is_editing {
22696 // Editing mode: show close and confirm buttons
22697 h_flex()
22698 .gap_1()
22699 .child(
22700 IconButton::new(
22701 format!("diff-review-cancel-edit-{comment_id}"),
22702 IconName::Close,
22703 )
22704 .icon_color(ui::Color::Muted)
22705 .icon_size(action_icon_size)
22706 .tooltip(Tooltip::text("Cancel"))
22707 .on_click(move |_, window, cx| {
22708 window.dispatch_action(
22709 Box::new(crate::actions::CancelEditReviewComment {
22710 id: comment_id,
22711 }),
22712 cx,
22713 );
22714 }),
22715 )
22716 .child(
22717 IconButton::new(
22718 format!("diff-review-confirm-edit-{comment_id}"),
22719 IconName::Return,
22720 )
22721 .icon_color(ui::Color::Muted)
22722 .icon_size(action_icon_size)
22723 .tooltip(Tooltip::text("Confirm"))
22724 .on_click(move |_, window, cx| {
22725 window.dispatch_action(
22726 Box::new(crate::actions::ConfirmEditReviewComment {
22727 id: comment_id,
22728 }),
22729 cx,
22730 );
22731 }),
22732 )
22733 .into_any_element()
22734 } else {
22735 // Display mode: no action buttons for now (edit/delete not yet implemented)
22736 gpui::Empty.into_any_element()
22737 })
22738 }
22739
22740 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22741 if self.display_map.read(cx).masked != masked {
22742 self.display_map.update(cx, |map, _| map.masked = masked);
22743 }
22744 cx.notify()
22745 }
22746
22747 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22748 self.show_wrap_guides = Some(show_wrap_guides);
22749 cx.notify();
22750 }
22751
22752 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22753 self.show_indent_guides = Some(show_indent_guides);
22754 cx.notify();
22755 }
22756
22757 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22758 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22759 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22760 && let Some(dir) = file.abs_path(cx).parent()
22761 {
22762 return Some(dir.to_owned());
22763 }
22764 }
22765
22766 None
22767 }
22768
22769 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22770 self.active_excerpt(cx)?
22771 .1
22772 .read(cx)
22773 .file()
22774 .and_then(|f| f.as_local())
22775 }
22776
22777 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22778 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22779 let buffer = buffer.read(cx);
22780 if let Some(project_path) = buffer.project_path(cx) {
22781 let project = self.project()?.read(cx);
22782 project.absolute_path(&project_path, cx)
22783 } else {
22784 buffer
22785 .file()
22786 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22787 }
22788 })
22789 }
22790
22791 pub fn reveal_in_finder(
22792 &mut self,
22793 _: &RevealInFileManager,
22794 _window: &mut Window,
22795 cx: &mut Context<Self>,
22796 ) {
22797 if let Some(path) = self.target_file_abs_path(cx) {
22798 if let Some(project) = self.project() {
22799 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22800 } else {
22801 cx.reveal_path(&path);
22802 }
22803 }
22804 }
22805
22806 pub fn copy_path(
22807 &mut self,
22808 _: &zed_actions::workspace::CopyPath,
22809 _window: &mut Window,
22810 cx: &mut Context<Self>,
22811 ) {
22812 if let Some(path) = self.target_file_abs_path(cx)
22813 && let Some(path) = path.to_str()
22814 {
22815 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22816 } else {
22817 cx.propagate();
22818 }
22819 }
22820
22821 pub fn copy_relative_path(
22822 &mut self,
22823 _: &zed_actions::workspace::CopyRelativePath,
22824 _window: &mut Window,
22825 cx: &mut Context<Self>,
22826 ) {
22827 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22828 let project = self.project()?.read(cx);
22829 let path = buffer.read(cx).file()?.path();
22830 let path = path.display(project.path_style(cx));
22831 Some(path)
22832 }) {
22833 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22834 } else {
22835 cx.propagate();
22836 }
22837 }
22838
22839 /// Returns the project path for the editor's buffer, if any buffer is
22840 /// opened in the editor.
22841 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22842 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22843 buffer.read(cx).project_path(cx)
22844 } else {
22845 None
22846 }
22847 }
22848
22849 // Returns true if the editor handled a go-to-line request
22850 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22851 maybe!({
22852 let breakpoint_store = self.breakpoint_store.as_ref()?;
22853
22854 let (active_stack_frame, debug_line_pane_id) = {
22855 let store = breakpoint_store.read(cx);
22856 let active_stack_frame = store.active_position().cloned();
22857 let debug_line_pane_id = store.active_debug_line_pane_id();
22858 (active_stack_frame, debug_line_pane_id)
22859 };
22860
22861 let Some(active_stack_frame) = active_stack_frame else {
22862 self.clear_row_highlights::<ActiveDebugLine>();
22863 return None;
22864 };
22865
22866 if let Some(debug_line_pane_id) = debug_line_pane_id {
22867 if let Some(workspace) = self
22868 .workspace
22869 .as_ref()
22870 .and_then(|(workspace, _)| workspace.upgrade())
22871 {
22872 let editor_pane_id = workspace
22873 .read(cx)
22874 .pane_for_item_id(cx.entity_id())
22875 .map(|pane| pane.entity_id());
22876
22877 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22878 self.clear_row_highlights::<ActiveDebugLine>();
22879 return None;
22880 }
22881 }
22882 }
22883
22884 let position = active_stack_frame.position;
22885 let buffer_id = position.buffer_id?;
22886 let snapshot = self
22887 .project
22888 .as_ref()?
22889 .read(cx)
22890 .buffer_for_id(buffer_id, cx)?
22891 .read(cx)
22892 .snapshot();
22893
22894 let mut handled = false;
22895 for (id, _, ExcerptRange { context, .. }) in
22896 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22897 {
22898 if context.start.cmp(&position, &snapshot).is_ge()
22899 || context.end.cmp(&position, &snapshot).is_lt()
22900 {
22901 continue;
22902 }
22903 let snapshot = self.buffer.read(cx).snapshot(cx);
22904 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22905
22906 handled = true;
22907 self.clear_row_highlights::<ActiveDebugLine>();
22908
22909 self.go_to_line::<ActiveDebugLine>(
22910 multibuffer_anchor,
22911 Some(cx.theme().colors().editor_debugger_active_line_background),
22912 window,
22913 cx,
22914 );
22915
22916 cx.notify();
22917 }
22918
22919 handled.then_some(())
22920 })
22921 .is_some()
22922 }
22923
22924 pub fn copy_file_name_without_extension(
22925 &mut self,
22926 _: &CopyFileNameWithoutExtension,
22927 _: &mut Window,
22928 cx: &mut Context<Self>,
22929 ) {
22930 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22931 let file = buffer.read(cx).file()?;
22932 file.path().file_stem()
22933 }) {
22934 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22935 }
22936 }
22937
22938 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22939 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22940 let file = buffer.read(cx).file()?;
22941 Some(file.file_name(cx))
22942 }) {
22943 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22944 }
22945 }
22946
22947 pub fn toggle_git_blame(
22948 &mut self,
22949 _: &::git::Blame,
22950 window: &mut Window,
22951 cx: &mut Context<Self>,
22952 ) {
22953 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22954
22955 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22956 self.start_git_blame(true, window, cx);
22957 }
22958
22959 cx.notify();
22960 }
22961
22962 pub fn toggle_git_blame_inline(
22963 &mut self,
22964 _: &ToggleGitBlameInline,
22965 window: &mut Window,
22966 cx: &mut Context<Self>,
22967 ) {
22968 self.toggle_git_blame_inline_internal(true, window, cx);
22969 cx.notify();
22970 }
22971
22972 pub fn open_git_blame_commit(
22973 &mut self,
22974 _: &OpenGitBlameCommit,
22975 window: &mut Window,
22976 cx: &mut Context<Self>,
22977 ) {
22978 self.open_git_blame_commit_internal(window, cx);
22979 }
22980
22981 fn open_git_blame_commit_internal(
22982 &mut self,
22983 window: &mut Window,
22984 cx: &mut Context<Self>,
22985 ) -> Option<()> {
22986 let blame = self.blame.as_ref()?;
22987 let snapshot = self.snapshot(window, cx);
22988 let cursor = self
22989 .selections
22990 .newest::<Point>(&snapshot.display_snapshot)
22991 .head();
22992 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22993 let (_, blame_entry) = blame
22994 .update(cx, |blame, cx| {
22995 blame
22996 .blame_for_rows(
22997 &[RowInfo {
22998 buffer_id: Some(buffer.remote_id()),
22999 buffer_row: Some(point.row),
23000 ..Default::default()
23001 }],
23002 cx,
23003 )
23004 .next()
23005 })
23006 .flatten()?;
23007 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23008 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23009 let workspace = self.workspace()?.downgrade();
23010 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23011 None
23012 }
23013
23014 pub fn git_blame_inline_enabled(&self) -> bool {
23015 self.git_blame_inline_enabled
23016 }
23017
23018 pub fn toggle_selection_menu(
23019 &mut self,
23020 _: &ToggleSelectionMenu,
23021 _: &mut Window,
23022 cx: &mut Context<Self>,
23023 ) {
23024 self.show_selection_menu = self
23025 .show_selection_menu
23026 .map(|show_selections_menu| !show_selections_menu)
23027 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23028
23029 cx.notify();
23030 }
23031
23032 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23033 self.show_selection_menu
23034 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23035 }
23036
23037 fn start_git_blame(
23038 &mut self,
23039 user_triggered: bool,
23040 window: &mut Window,
23041 cx: &mut Context<Self>,
23042 ) {
23043 if let Some(project) = self.project() {
23044 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23045 && buffer.read(cx).file().is_none()
23046 {
23047 return;
23048 }
23049
23050 let focused = self.focus_handle(cx).contains_focused(window, cx);
23051
23052 let project = project.clone();
23053 let blame = cx
23054 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23055 self.blame_subscription =
23056 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23057 self.blame = Some(blame);
23058 }
23059 }
23060
23061 fn toggle_git_blame_inline_internal(
23062 &mut self,
23063 user_triggered: bool,
23064 window: &mut Window,
23065 cx: &mut Context<Self>,
23066 ) {
23067 if self.git_blame_inline_enabled {
23068 self.git_blame_inline_enabled = false;
23069 self.show_git_blame_inline = false;
23070 self.show_git_blame_inline_delay_task.take();
23071 } else {
23072 self.git_blame_inline_enabled = true;
23073 self.start_git_blame_inline(user_triggered, window, cx);
23074 }
23075
23076 cx.notify();
23077 }
23078
23079 fn start_git_blame_inline(
23080 &mut self,
23081 user_triggered: bool,
23082 window: &mut Window,
23083 cx: &mut Context<Self>,
23084 ) {
23085 self.start_git_blame(user_triggered, window, cx);
23086
23087 if ProjectSettings::get_global(cx)
23088 .git
23089 .inline_blame_delay()
23090 .is_some()
23091 {
23092 self.start_inline_blame_timer(window, cx);
23093 } else {
23094 self.show_git_blame_inline = true
23095 }
23096 }
23097
23098 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23099 self.blame.as_ref()
23100 }
23101
23102 pub fn show_git_blame_gutter(&self) -> bool {
23103 self.show_git_blame_gutter
23104 }
23105
23106 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23107 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23108 }
23109
23110 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23111 self.show_git_blame_inline
23112 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23113 && !self.newest_selection_head_on_empty_line(cx)
23114 && self.has_blame_entries(cx)
23115 }
23116
23117 fn has_blame_entries(&self, cx: &App) -> bool {
23118 self.blame()
23119 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23120 }
23121
23122 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23123 let cursor_anchor = self.selections.newest_anchor().head();
23124
23125 let snapshot = self.buffer.read(cx).snapshot(cx);
23126 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23127
23128 snapshot.line_len(buffer_row) == 0
23129 }
23130
23131 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23132 let buffer_and_selection = maybe!({
23133 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23134 let selection_range = selection.range();
23135
23136 let multi_buffer = self.buffer().read(cx);
23137 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23138 let buffer_ranges = multi_buffer_snapshot
23139 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23140
23141 let (buffer, range, _) = if selection.reversed {
23142 buffer_ranges.first()
23143 } else {
23144 buffer_ranges.last()
23145 }?;
23146
23147 let buffer_range = range.to_point(buffer);
23148
23149 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23150 return Some((
23151 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23152 buffer_range.start.row..buffer_range.end.row,
23153 ));
23154 };
23155
23156 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23157 let start =
23158 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23159 let end =
23160 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23161
23162 Some((
23163 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23164 start.row..end.row,
23165 ))
23166 });
23167
23168 let Some((buffer, selection)) = buffer_and_selection else {
23169 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23170 };
23171
23172 let Some(project) = self.project() else {
23173 return Task::ready(Err(anyhow!("editor does not have project")));
23174 };
23175
23176 project.update(cx, |project, cx| {
23177 project.get_permalink_to_line(&buffer, selection, cx)
23178 })
23179 }
23180
23181 pub fn copy_permalink_to_line(
23182 &mut self,
23183 _: &CopyPermalinkToLine,
23184 window: &mut Window,
23185 cx: &mut Context<Self>,
23186 ) {
23187 let permalink_task = self.get_permalink_to_line(cx);
23188 let workspace = self.workspace();
23189
23190 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23191 Ok(permalink) => {
23192 cx.update(|_, cx| {
23193 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23194 })
23195 .ok();
23196 }
23197 Err(err) => {
23198 let message = format!("Failed to copy permalink: {err}");
23199
23200 anyhow::Result::<()>::Err(err).log_err();
23201
23202 if let Some(workspace) = workspace {
23203 workspace
23204 .update_in(cx, |workspace, _, cx| {
23205 struct CopyPermalinkToLine;
23206
23207 workspace.show_toast(
23208 Toast::new(
23209 NotificationId::unique::<CopyPermalinkToLine>(),
23210 message,
23211 ),
23212 cx,
23213 )
23214 })
23215 .ok();
23216 }
23217 }
23218 })
23219 .detach();
23220 }
23221
23222 pub fn copy_file_location(
23223 &mut self,
23224 _: &CopyFileLocation,
23225 _: &mut Window,
23226 cx: &mut Context<Self>,
23227 ) {
23228 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23229
23230 let start_line = selection.start.row + 1;
23231 let end_line = selection.end.row + 1;
23232
23233 let end_line = if selection.end.column == 0 && end_line > start_line {
23234 end_line - 1
23235 } else {
23236 end_line
23237 };
23238
23239 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23240 let project = self.project()?.read(cx);
23241 let file = buffer.read(cx).file()?;
23242 let path = file.path().display(project.path_style(cx));
23243
23244 let location = if start_line == end_line {
23245 format!("{path}:{start_line}")
23246 } else {
23247 format!("{path}:{start_line}-{end_line}")
23248 };
23249 Some(location)
23250 }) {
23251 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23252 }
23253 }
23254
23255 pub fn open_permalink_to_line(
23256 &mut self,
23257 _: &OpenPermalinkToLine,
23258 window: &mut Window,
23259 cx: &mut Context<Self>,
23260 ) {
23261 let permalink_task = self.get_permalink_to_line(cx);
23262 let workspace = self.workspace();
23263
23264 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23265 Ok(permalink) => {
23266 cx.update(|_, cx| {
23267 cx.open_url(permalink.as_ref());
23268 })
23269 .ok();
23270 }
23271 Err(err) => {
23272 let message = format!("Failed to open permalink: {err}");
23273
23274 anyhow::Result::<()>::Err(err).log_err();
23275
23276 if let Some(workspace) = workspace {
23277 workspace.update(cx, |workspace, cx| {
23278 struct OpenPermalinkToLine;
23279
23280 workspace.show_toast(
23281 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23282 cx,
23283 )
23284 });
23285 }
23286 }
23287 })
23288 .detach();
23289 }
23290
23291 pub fn insert_uuid_v4(
23292 &mut self,
23293 _: &InsertUuidV4,
23294 window: &mut Window,
23295 cx: &mut Context<Self>,
23296 ) {
23297 self.insert_uuid(UuidVersion::V4, window, cx);
23298 }
23299
23300 pub fn insert_uuid_v7(
23301 &mut self,
23302 _: &InsertUuidV7,
23303 window: &mut Window,
23304 cx: &mut Context<Self>,
23305 ) {
23306 self.insert_uuid(UuidVersion::V7, window, cx);
23307 }
23308
23309 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23310 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23311 self.transact(window, cx, |this, window, cx| {
23312 let edits = this
23313 .selections
23314 .all::<Point>(&this.display_snapshot(cx))
23315 .into_iter()
23316 .map(|selection| {
23317 let uuid = match version {
23318 UuidVersion::V4 => uuid::Uuid::new_v4(),
23319 UuidVersion::V7 => uuid::Uuid::now_v7(),
23320 };
23321
23322 (selection.range(), uuid.to_string())
23323 });
23324 this.edit(edits, cx);
23325 this.refresh_edit_prediction(true, false, window, cx);
23326 });
23327 }
23328
23329 pub fn open_selections_in_multibuffer(
23330 &mut self,
23331 _: &OpenSelectionsInMultibuffer,
23332 window: &mut Window,
23333 cx: &mut Context<Self>,
23334 ) {
23335 let multibuffer = self.buffer.read(cx);
23336
23337 let Some(buffer) = multibuffer.as_singleton() else {
23338 return;
23339 };
23340
23341 let Some(workspace) = self.workspace() else {
23342 return;
23343 };
23344
23345 let title = multibuffer.title(cx).to_string();
23346
23347 let locations = self
23348 .selections
23349 .all_anchors(&self.display_snapshot(cx))
23350 .iter()
23351 .map(|selection| {
23352 (
23353 buffer.clone(),
23354 (selection.start.text_anchor..selection.end.text_anchor)
23355 .to_point(buffer.read(cx)),
23356 )
23357 })
23358 .into_group_map();
23359
23360 cx.spawn_in(window, async move |_, cx| {
23361 workspace.update_in(cx, |workspace, window, cx| {
23362 Self::open_locations_in_multibuffer(
23363 workspace,
23364 locations,
23365 format!("Selections for '{title}'"),
23366 false,
23367 false,
23368 MultibufferSelectionMode::All,
23369 window,
23370 cx,
23371 );
23372 })
23373 })
23374 .detach();
23375 }
23376
23377 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23378 /// last highlight added will be used.
23379 ///
23380 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23381 pub fn highlight_rows<T: 'static>(
23382 &mut self,
23383 range: Range<Anchor>,
23384 color: Hsla,
23385 options: RowHighlightOptions,
23386 cx: &mut Context<Self>,
23387 ) {
23388 let snapshot = self.buffer().read(cx).snapshot(cx);
23389 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23390 let ix = row_highlights.binary_search_by(|highlight| {
23391 Ordering::Equal
23392 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23393 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23394 });
23395
23396 if let Err(mut ix) = ix {
23397 let index = post_inc(&mut self.highlight_order);
23398
23399 // If this range intersects with the preceding highlight, then merge it with
23400 // the preceding highlight. Otherwise insert a new highlight.
23401 let mut merged = false;
23402 if ix > 0 {
23403 let prev_highlight = &mut row_highlights[ix - 1];
23404 if prev_highlight
23405 .range
23406 .end
23407 .cmp(&range.start, &snapshot)
23408 .is_ge()
23409 {
23410 ix -= 1;
23411 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23412 prev_highlight.range.end = range.end;
23413 }
23414 merged = true;
23415 prev_highlight.index = index;
23416 prev_highlight.color = color;
23417 prev_highlight.options = options;
23418 }
23419 }
23420
23421 if !merged {
23422 row_highlights.insert(
23423 ix,
23424 RowHighlight {
23425 range,
23426 index,
23427 color,
23428 options,
23429 type_id: TypeId::of::<T>(),
23430 },
23431 );
23432 }
23433
23434 // If any of the following highlights intersect with this one, merge them.
23435 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23436 let highlight = &row_highlights[ix];
23437 if next_highlight
23438 .range
23439 .start
23440 .cmp(&highlight.range.end, &snapshot)
23441 .is_le()
23442 {
23443 if next_highlight
23444 .range
23445 .end
23446 .cmp(&highlight.range.end, &snapshot)
23447 .is_gt()
23448 {
23449 row_highlights[ix].range.end = next_highlight.range.end;
23450 }
23451 row_highlights.remove(ix + 1);
23452 } else {
23453 break;
23454 }
23455 }
23456 }
23457 }
23458
23459 /// Remove any highlighted row ranges of the given type that intersect the
23460 /// given ranges.
23461 pub fn remove_highlighted_rows<T: 'static>(
23462 &mut self,
23463 ranges_to_remove: Vec<Range<Anchor>>,
23464 cx: &mut Context<Self>,
23465 ) {
23466 let snapshot = self.buffer().read(cx).snapshot(cx);
23467 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23468 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23469 row_highlights.retain(|highlight| {
23470 while let Some(range_to_remove) = ranges_to_remove.peek() {
23471 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23472 Ordering::Less | Ordering::Equal => {
23473 ranges_to_remove.next();
23474 }
23475 Ordering::Greater => {
23476 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23477 Ordering::Less | Ordering::Equal => {
23478 return false;
23479 }
23480 Ordering::Greater => break,
23481 }
23482 }
23483 }
23484 }
23485
23486 true
23487 })
23488 }
23489
23490 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23491 pub fn clear_row_highlights<T: 'static>(&mut self) {
23492 self.highlighted_rows.remove(&TypeId::of::<T>());
23493 }
23494
23495 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23496 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23497 self.highlighted_rows
23498 .get(&TypeId::of::<T>())
23499 .map_or(&[] as &[_], |vec| vec.as_slice())
23500 .iter()
23501 .map(|highlight| (highlight.range.clone(), highlight.color))
23502 }
23503
23504 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23505 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23506 /// Allows to ignore certain kinds of highlights.
23507 pub fn highlighted_display_rows(
23508 &self,
23509 window: &mut Window,
23510 cx: &mut App,
23511 ) -> BTreeMap<DisplayRow, LineHighlight> {
23512 let snapshot = self.snapshot(window, cx);
23513 let mut used_highlight_orders = HashMap::default();
23514 self.highlighted_rows
23515 .iter()
23516 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23517 .fold(
23518 BTreeMap::<DisplayRow, LineHighlight>::new(),
23519 |mut unique_rows, highlight| {
23520 let start = highlight.range.start.to_display_point(&snapshot);
23521 let end = highlight.range.end.to_display_point(&snapshot);
23522 let start_row = start.row().0;
23523 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23524 {
23525 end.row().0.saturating_sub(1)
23526 } else {
23527 end.row().0
23528 };
23529 for row in start_row..=end_row {
23530 let used_index =
23531 used_highlight_orders.entry(row).or_insert(highlight.index);
23532 if highlight.index >= *used_index {
23533 *used_index = highlight.index;
23534 unique_rows.insert(
23535 DisplayRow(row),
23536 LineHighlight {
23537 include_gutter: highlight.options.include_gutter,
23538 border: None,
23539 background: highlight.color.into(),
23540 type_id: Some(highlight.type_id),
23541 },
23542 );
23543 }
23544 }
23545 unique_rows
23546 },
23547 )
23548 }
23549
23550 pub fn highlighted_display_row_for_autoscroll(
23551 &self,
23552 snapshot: &DisplaySnapshot,
23553 ) -> Option<DisplayRow> {
23554 self.highlighted_rows
23555 .values()
23556 .flat_map(|highlighted_rows| highlighted_rows.iter())
23557 .filter_map(|highlight| {
23558 if highlight.options.autoscroll {
23559 Some(highlight.range.start.to_display_point(snapshot).row())
23560 } else {
23561 None
23562 }
23563 })
23564 .min()
23565 }
23566
23567 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23568 self.highlight_background(
23569 HighlightKey::SearchWithinRange,
23570 ranges,
23571 |_, colors| colors.colors().editor_document_highlight_read_background,
23572 cx,
23573 )
23574 }
23575
23576 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23577 self.breadcrumb_header = Some(new_header);
23578 }
23579
23580 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23581 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23582 }
23583
23584 pub fn highlight_background(
23585 &mut self,
23586 key: HighlightKey,
23587 ranges: &[Range<Anchor>],
23588 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23589 cx: &mut Context<Self>,
23590 ) {
23591 self.background_highlights
23592 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23593 self.scrollbar_marker_state.dirty = true;
23594 cx.notify();
23595 }
23596
23597 pub fn clear_background_highlights(
23598 &mut self,
23599 key: HighlightKey,
23600 cx: &mut Context<Self>,
23601 ) -> Option<BackgroundHighlight> {
23602 let text_highlights = self.background_highlights.remove(&key)?;
23603 if !text_highlights.1.is_empty() {
23604 self.scrollbar_marker_state.dirty = true;
23605 cx.notify();
23606 }
23607 Some(text_highlights)
23608 }
23609
23610 pub fn highlight_gutter<T: 'static>(
23611 &mut self,
23612 ranges: impl Into<Vec<Range<Anchor>>>,
23613 color_fetcher: fn(&App) -> Hsla,
23614 cx: &mut Context<Self>,
23615 ) {
23616 self.gutter_highlights
23617 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23618 cx.notify();
23619 }
23620
23621 pub fn clear_gutter_highlights<T: 'static>(
23622 &mut self,
23623 cx: &mut Context<Self>,
23624 ) -> Option<GutterHighlight> {
23625 cx.notify();
23626 self.gutter_highlights.remove(&TypeId::of::<T>())
23627 }
23628
23629 pub fn insert_gutter_highlight<T: 'static>(
23630 &mut self,
23631 range: Range<Anchor>,
23632 color_fetcher: fn(&App) -> Hsla,
23633 cx: &mut Context<Self>,
23634 ) {
23635 let snapshot = self.buffer().read(cx).snapshot(cx);
23636 let mut highlights = self
23637 .gutter_highlights
23638 .remove(&TypeId::of::<T>())
23639 .map(|(_, highlights)| highlights)
23640 .unwrap_or_default();
23641 let ix = highlights.binary_search_by(|highlight| {
23642 Ordering::Equal
23643 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23644 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23645 });
23646 if let Err(ix) = ix {
23647 highlights.insert(ix, range);
23648 }
23649 self.gutter_highlights
23650 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23651 }
23652
23653 pub fn remove_gutter_highlights<T: 'static>(
23654 &mut self,
23655 ranges_to_remove: Vec<Range<Anchor>>,
23656 cx: &mut Context<Self>,
23657 ) {
23658 let snapshot = self.buffer().read(cx).snapshot(cx);
23659 let Some((color_fetcher, mut gutter_highlights)) =
23660 self.gutter_highlights.remove(&TypeId::of::<T>())
23661 else {
23662 return;
23663 };
23664 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23665 gutter_highlights.retain(|highlight| {
23666 while let Some(range_to_remove) = ranges_to_remove.peek() {
23667 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23668 Ordering::Less | Ordering::Equal => {
23669 ranges_to_remove.next();
23670 }
23671 Ordering::Greater => {
23672 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23673 Ordering::Less | Ordering::Equal => {
23674 return false;
23675 }
23676 Ordering::Greater => break,
23677 }
23678 }
23679 }
23680 }
23681
23682 true
23683 });
23684 self.gutter_highlights
23685 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23686 }
23687
23688 #[cfg(any(test, feature = "test-support"))]
23689 pub fn all_text_highlights(
23690 &self,
23691 window: &mut Window,
23692 cx: &mut Context<Self>,
23693 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23694 let snapshot = self.snapshot(window, cx);
23695 self.display_map.update(cx, |display_map, _| {
23696 display_map
23697 .all_text_highlights()
23698 .map(|(_, highlight)| {
23699 let (style, ranges) = highlight.as_ref();
23700 (
23701 *style,
23702 ranges
23703 .iter()
23704 .map(|range| range.clone().to_display_points(&snapshot))
23705 .collect(),
23706 )
23707 })
23708 .collect()
23709 })
23710 }
23711
23712 #[cfg(any(test, feature = "test-support"))]
23713 pub fn all_text_background_highlights(
23714 &self,
23715 window: &mut Window,
23716 cx: &mut Context<Self>,
23717 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23718 let snapshot = self.snapshot(window, cx);
23719 let buffer = &snapshot.buffer_snapshot();
23720 let start = buffer.anchor_before(MultiBufferOffset(0));
23721 let end = buffer.anchor_after(buffer.len());
23722 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23723 }
23724
23725 #[cfg(any(test, feature = "test-support"))]
23726 pub fn sorted_background_highlights_in_range(
23727 &self,
23728 search_range: Range<Anchor>,
23729 display_snapshot: &DisplaySnapshot,
23730 theme: &Theme,
23731 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23732 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23733 res.sort_by(|a, b| {
23734 a.0.start
23735 .cmp(&b.0.start)
23736 .then_with(|| a.0.end.cmp(&b.0.end))
23737 .then_with(|| a.1.cmp(&b.1))
23738 });
23739 res
23740 }
23741
23742 #[cfg(any(test, feature = "test-support"))]
23743 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23744 let snapshot = self.buffer().read(cx).snapshot(cx);
23745
23746 let highlights = self
23747 .background_highlights
23748 .get(&HighlightKey::BufferSearchHighlights);
23749
23750 if let Some((_color, ranges)) = highlights {
23751 ranges
23752 .iter()
23753 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23754 .collect_vec()
23755 } else {
23756 vec![]
23757 }
23758 }
23759
23760 fn document_highlights_for_position<'a>(
23761 &'a self,
23762 position: Anchor,
23763 buffer: &'a MultiBufferSnapshot,
23764 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23765 let read_highlights = self
23766 .background_highlights
23767 .get(&HighlightKey::DocumentHighlightRead)
23768 .map(|h| &h.1);
23769 let write_highlights = self
23770 .background_highlights
23771 .get(&HighlightKey::DocumentHighlightWrite)
23772 .map(|h| &h.1);
23773 let left_position = position.bias_left(buffer);
23774 let right_position = position.bias_right(buffer);
23775 read_highlights
23776 .into_iter()
23777 .chain(write_highlights)
23778 .flat_map(move |ranges| {
23779 let start_ix = match ranges.binary_search_by(|probe| {
23780 let cmp = probe.end.cmp(&left_position, buffer);
23781 if cmp.is_ge() {
23782 Ordering::Greater
23783 } else {
23784 Ordering::Less
23785 }
23786 }) {
23787 Ok(i) | Err(i) => i,
23788 };
23789
23790 ranges[start_ix..]
23791 .iter()
23792 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23793 })
23794 }
23795
23796 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23797 self.background_highlights
23798 .get(&key)
23799 .is_some_and(|(_, highlights)| !highlights.is_empty())
23800 }
23801
23802 /// Returns all background highlights for a given range.
23803 ///
23804 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23805 pub fn background_highlights_in_range(
23806 &self,
23807 search_range: Range<Anchor>,
23808 display_snapshot: &DisplaySnapshot,
23809 theme: &Theme,
23810 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23811 let mut results = Vec::new();
23812 for (color_fetcher, ranges) in self.background_highlights.values() {
23813 let start_ix = match ranges.binary_search_by(|probe| {
23814 let cmp = probe
23815 .end
23816 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23817 if cmp.is_gt() {
23818 Ordering::Greater
23819 } else {
23820 Ordering::Less
23821 }
23822 }) {
23823 Ok(i) | Err(i) => i,
23824 };
23825 for (index, range) in ranges[start_ix..].iter().enumerate() {
23826 if range
23827 .start
23828 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23829 .is_ge()
23830 {
23831 break;
23832 }
23833
23834 let color = color_fetcher(&(start_ix + index), theme);
23835 let start = range.start.to_display_point(display_snapshot);
23836 let end = range.end.to_display_point(display_snapshot);
23837 results.push((start..end, color))
23838 }
23839 }
23840 results
23841 }
23842
23843 pub fn gutter_highlights_in_range(
23844 &self,
23845 search_range: Range<Anchor>,
23846 display_snapshot: &DisplaySnapshot,
23847 cx: &App,
23848 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23849 let mut results = Vec::new();
23850 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23851 let color = color_fetcher(cx);
23852 let start_ix = match ranges.binary_search_by(|probe| {
23853 let cmp = probe
23854 .end
23855 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23856 if cmp.is_gt() {
23857 Ordering::Greater
23858 } else {
23859 Ordering::Less
23860 }
23861 }) {
23862 Ok(i) | Err(i) => i,
23863 };
23864 for range in &ranges[start_ix..] {
23865 if range
23866 .start
23867 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23868 .is_ge()
23869 {
23870 break;
23871 }
23872
23873 let start = range.start.to_display_point(display_snapshot);
23874 let end = range.end.to_display_point(display_snapshot);
23875 results.push((start..end, color))
23876 }
23877 }
23878 results
23879 }
23880
23881 /// Get the text ranges corresponding to the redaction query
23882 pub fn redacted_ranges(
23883 &self,
23884 search_range: Range<Anchor>,
23885 display_snapshot: &DisplaySnapshot,
23886 cx: &App,
23887 ) -> Vec<Range<DisplayPoint>> {
23888 display_snapshot
23889 .buffer_snapshot()
23890 .redacted_ranges(search_range, |file| {
23891 if let Some(file) = file {
23892 file.is_private()
23893 && EditorSettings::get(
23894 Some(SettingsLocation {
23895 worktree_id: file.worktree_id(cx),
23896 path: file.path().as_ref(),
23897 }),
23898 cx,
23899 )
23900 .redact_private_values
23901 } else {
23902 false
23903 }
23904 })
23905 .map(|range| {
23906 range.start.to_display_point(display_snapshot)
23907 ..range.end.to_display_point(display_snapshot)
23908 })
23909 .collect()
23910 }
23911
23912 pub fn highlight_text_key(
23913 &mut self,
23914 key: HighlightKey,
23915 ranges: Vec<Range<Anchor>>,
23916 style: HighlightStyle,
23917 merge: bool,
23918 cx: &mut Context<Self>,
23919 ) {
23920 self.display_map.update(cx, |map, cx| {
23921 map.highlight_text(key, ranges, style, merge, cx);
23922 });
23923 cx.notify();
23924 }
23925
23926 pub fn highlight_text(
23927 &mut self,
23928 key: HighlightKey,
23929 ranges: Vec<Range<Anchor>>,
23930 style: HighlightStyle,
23931 cx: &mut Context<Self>,
23932 ) {
23933 self.display_map.update(cx, |map, cx| {
23934 map.highlight_text(key, ranges, style, false, cx)
23935 });
23936 cx.notify();
23937 }
23938
23939 pub fn text_highlights<'a>(
23940 &'a self,
23941 key: HighlightKey,
23942 cx: &'a App,
23943 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23944 self.display_map.read(cx).text_highlights(key)
23945 }
23946
23947 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23948 let cleared = self
23949 .display_map
23950 .update(cx, |map, _| map.clear_highlights(key));
23951 if cleared {
23952 cx.notify();
23953 }
23954 }
23955
23956 pub fn clear_highlights_with(
23957 &mut self,
23958 f: &mut dyn FnMut(&HighlightKey) -> bool,
23959 cx: &mut Context<Self>,
23960 ) {
23961 let cleared = self
23962 .display_map
23963 .update(cx, |map, _| map.clear_highlights_with(f));
23964 if cleared {
23965 cx.notify();
23966 }
23967 }
23968
23969 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23970 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23971 && self.focus_handle.is_focused(window)
23972 }
23973
23974 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23975 self.show_cursor_when_unfocused = is_enabled;
23976 cx.notify();
23977 }
23978
23979 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23980 cx.notify();
23981 }
23982
23983 fn on_debug_session_event(
23984 &mut self,
23985 _session: Entity<Session>,
23986 event: &SessionEvent,
23987 cx: &mut Context<Self>,
23988 ) {
23989 if let SessionEvent::InvalidateInlineValue = event {
23990 self.refresh_inline_values(cx);
23991 }
23992 }
23993
23994 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23995 let Some(semantics) = self.semantics_provider.clone() else {
23996 return;
23997 };
23998
23999 if !self.inline_value_cache.enabled {
24000 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24001 self.splice_inlays(&inlays, Vec::new(), cx);
24002 return;
24003 }
24004
24005 let current_execution_position = self
24006 .highlighted_rows
24007 .get(&TypeId::of::<ActiveDebugLine>())
24008 .and_then(|lines| lines.last().map(|line| line.range.end));
24009
24010 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24011 let inline_values = editor
24012 .update(cx, |editor, cx| {
24013 let Some(current_execution_position) = current_execution_position else {
24014 return Some(Task::ready(Ok(Vec::new())));
24015 };
24016
24017 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
24018 let snapshot = buffer.snapshot(cx);
24019
24020 let excerpt = snapshot.excerpt_containing(
24021 current_execution_position..current_execution_position,
24022 )?;
24023
24024 editor.buffer.read(cx).buffer(excerpt.buffer_id())
24025 })?;
24026
24027 if current_execution_position
24028 .text_anchor
24029 .buffer_id
24030 .is_some_and(|id| id != buffer.read(cx).remote_id())
24031 {
24032 return Some(Task::ready(Ok(Vec::new())));
24033 }
24034
24035 let range =
24036 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
24037
24038 semantics.inline_values(buffer, range, cx)
24039 })
24040 .ok()
24041 .flatten()?
24042 .await
24043 .context("refreshing debugger inlays")
24044 .log_err()?;
24045
24046 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24047
24048 for (buffer_id, inline_value) in inline_values
24049 .into_iter()
24050 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
24051 {
24052 buffer_inline_values
24053 .entry(buffer_id)
24054 .or_default()
24055 .push(inline_value);
24056 }
24057
24058 editor
24059 .update(cx, |editor, cx| {
24060 let snapshot = editor.buffer.read(cx).snapshot(cx);
24061 let mut new_inlays = Vec::default();
24062
24063 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
24064 let buffer_id = buffer_snapshot.remote_id();
24065 buffer_inline_values
24066 .get(&buffer_id)
24067 .into_iter()
24068 .flatten()
24069 .for_each(|hint| {
24070 let inlay = Inlay::debugger(
24071 post_inc(&mut editor.next_inlay_id),
24072 Anchor::in_buffer(excerpt_id, hint.position),
24073 hint.text(),
24074 );
24075 if !inlay.text().chars().contains(&'\n') {
24076 new_inlays.push(inlay);
24077 }
24078 });
24079 }
24080
24081 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24082 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24083
24084 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24085 })
24086 .ok()?;
24087 Some(())
24088 });
24089 }
24090
24091 fn on_buffer_event(
24092 &mut self,
24093 multibuffer: &Entity<MultiBuffer>,
24094 event: &multi_buffer::Event,
24095 window: &mut Window,
24096 cx: &mut Context<Self>,
24097 ) {
24098 match event {
24099 multi_buffer::Event::Edited {
24100 edited_buffer,
24101 is_local,
24102 } => {
24103 self.scrollbar_marker_state.dirty = true;
24104 self.active_indent_guides_state.dirty = true;
24105 self.refresh_active_diagnostics(cx);
24106 self.refresh_code_actions(window, cx);
24107 self.refresh_single_line_folds(window, cx);
24108 let snapshot = self.snapshot(window, cx);
24109 self.refresh_matching_bracket_highlights(&snapshot, cx);
24110 self.refresh_outline_symbols_at_cursor(cx);
24111 self.refresh_sticky_headers(&snapshot, cx);
24112 if *is_local && self.has_active_edit_prediction() {
24113 self.update_visible_edit_prediction(window, cx);
24114 }
24115
24116 // Clean up orphaned review comments after edits
24117 self.cleanup_orphaned_review_comments(cx);
24118
24119 if let Some(buffer) = edited_buffer {
24120 if buffer.read(cx).file().is_none() {
24121 cx.emit(EditorEvent::TitleChanged);
24122 }
24123
24124 if self.project.is_some() {
24125 let buffer_id = buffer.read(cx).remote_id();
24126 self.register_buffer(buffer_id, cx);
24127 self.update_lsp_data(Some(buffer_id), window, cx);
24128 self.refresh_inlay_hints(
24129 InlayHintRefreshReason::BufferEdited(buffer_id),
24130 cx,
24131 );
24132 }
24133 }
24134
24135 cx.emit(EditorEvent::BufferEdited);
24136 cx.emit(SearchEvent::MatchesInvalidated);
24137
24138 let Some(project) = &self.project else { return };
24139 let (telemetry, is_via_ssh) = {
24140 let project = project.read(cx);
24141 let telemetry = project.client().telemetry().clone();
24142 let is_via_ssh = project.is_via_remote_server();
24143 (telemetry, is_via_ssh)
24144 };
24145 telemetry.log_edit_event("editor", is_via_ssh);
24146 }
24147 multi_buffer::Event::ExcerptsAdded {
24148 buffer,
24149 predecessor,
24150 excerpts,
24151 } => {
24152 let buffer_id = buffer.read(cx).remote_id();
24153 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24154 && let Some(project) = &self.project
24155 {
24156 update_uncommitted_diff_for_buffer(
24157 cx.entity(),
24158 project,
24159 [buffer.clone()],
24160 self.buffer.clone(),
24161 cx,
24162 )
24163 .detach();
24164 }
24165 self.semantic_token_state
24166 .invalidate_buffer(&buffer.read(cx).remote_id());
24167 self.update_lsp_data(Some(buffer_id), window, cx);
24168 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24169 self.refresh_runnables(None, window, cx);
24170 self.colorize_brackets(false, cx);
24171 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24172 cx.emit(EditorEvent::ExcerptsAdded {
24173 buffer: buffer.clone(),
24174 predecessor: *predecessor,
24175 excerpts: excerpts.clone(),
24176 });
24177 }
24178 multi_buffer::Event::ExcerptsRemoved {
24179 ids,
24180 removed_buffer_ids,
24181 } => {
24182 if let Some(inlay_hints) = &mut self.inlay_hints {
24183 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24184 }
24185 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24186 for buffer_id in removed_buffer_ids {
24187 self.registered_buffers.remove(buffer_id);
24188 self.clear_runnables(Some(*buffer_id));
24189 self.semantic_token_state.invalidate_buffer(buffer_id);
24190 self.display_map.update(cx, |display_map, cx| {
24191 display_map.invalidate_semantic_highlights(*buffer_id);
24192 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24193 });
24194 }
24195
24196 self.display_map.update(cx, |display_map, cx| {
24197 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24198 });
24199
24200 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24201 cx.emit(EditorEvent::ExcerptsRemoved {
24202 ids: ids.clone(),
24203 removed_buffer_ids: removed_buffer_ids.clone(),
24204 });
24205 }
24206 multi_buffer::Event::ExcerptsEdited {
24207 excerpt_ids,
24208 buffer_ids,
24209 } => {
24210 self.display_map.update(cx, |map, cx| {
24211 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24212 });
24213 cx.emit(EditorEvent::ExcerptsEdited {
24214 ids: excerpt_ids.clone(),
24215 });
24216 }
24217 multi_buffer::Event::ExcerptsExpanded { ids } => {
24218 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24219 self.refresh_document_highlights(cx);
24220 let snapshot = multibuffer.read(cx).snapshot(cx);
24221 for id in ids {
24222 self.bracket_fetched_tree_sitter_chunks.remove(id);
24223 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24224 self.semantic_token_state
24225 .invalidate_buffer(&buffer.remote_id());
24226 }
24227 }
24228 self.colorize_brackets(false, cx);
24229 self.update_lsp_data(None, window, cx);
24230 self.refresh_runnables(None, window, cx);
24231 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24232 }
24233 multi_buffer::Event::Reparsed(buffer_id) => {
24234 self.refresh_runnables(Some(*buffer_id), window, cx);
24235 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24236 self.colorize_brackets(true, cx);
24237 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24238
24239 cx.emit(EditorEvent::Reparsed(*buffer_id));
24240 }
24241 multi_buffer::Event::DiffHunksToggled => {
24242 self.refresh_runnables(None, window, cx);
24243 }
24244 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24245 if !is_fresh_language {
24246 self.registered_buffers.remove(&buffer_id);
24247 }
24248 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24249 cx.emit(EditorEvent::Reparsed(*buffer_id));
24250 self.update_edit_prediction_settings(cx);
24251 cx.notify();
24252 }
24253 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24254 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24255 multi_buffer::Event::FileHandleChanged
24256 | multi_buffer::Event::Reloaded
24257 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24258 multi_buffer::Event::DiagnosticsUpdated => {
24259 self.update_diagnostics_state(window, cx);
24260 }
24261 _ => {}
24262 };
24263 }
24264
24265 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24266 if !self.diagnostics_enabled() {
24267 return;
24268 }
24269 self.refresh_active_diagnostics(cx);
24270 self.refresh_inline_diagnostics(true, window, cx);
24271 self.scrollbar_marker_state.dirty = true;
24272 cx.notify();
24273 }
24274
24275 pub fn start_temporary_diff_override(&mut self) {
24276 self.load_diff_task.take();
24277 self.temporary_diff_override = true;
24278 }
24279
24280 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24281 self.temporary_diff_override = false;
24282 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24283 self.buffer.update(cx, |buffer, cx| {
24284 buffer.set_all_diff_hunks_collapsed(cx);
24285 });
24286
24287 if let Some(project) = self.project.clone() {
24288 self.load_diff_task = Some(
24289 update_uncommitted_diff_for_buffer(
24290 cx.entity(),
24291 &project,
24292 self.buffer.read(cx).all_buffers(),
24293 self.buffer.clone(),
24294 cx,
24295 )
24296 .shared(),
24297 );
24298 }
24299 }
24300
24301 fn on_display_map_changed(
24302 &mut self,
24303 _: Entity<DisplayMap>,
24304 _: &mut Window,
24305 cx: &mut Context<Self>,
24306 ) {
24307 cx.notify();
24308 }
24309
24310 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24311 if !self.mode.is_full() {
24312 return None;
24313 }
24314
24315 let theme_settings = theme::ThemeSettings::get_global(cx);
24316 let theme = cx.theme();
24317 let accent_colors = theme.accents().clone();
24318
24319 let accent_overrides = theme_settings
24320 .theme_overrides
24321 .get(theme.name.as_ref())
24322 .map(|theme_style| &theme_style.accents)
24323 .into_iter()
24324 .flatten()
24325 .chain(
24326 theme_settings
24327 .experimental_theme_overrides
24328 .as_ref()
24329 .map(|overrides| &overrides.accents)
24330 .into_iter()
24331 .flatten(),
24332 )
24333 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24334 .collect();
24335
24336 Some(AccentData {
24337 colors: accent_colors,
24338 overrides: accent_overrides,
24339 })
24340 }
24341
24342 fn fetch_applicable_language_settings(
24343 &self,
24344 cx: &App,
24345 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24346 if !self.mode.is_full() {
24347 return HashMap::default();
24348 }
24349
24350 self.buffer().read(cx).all_buffers().into_iter().fold(
24351 HashMap::default(),
24352 |mut acc, buffer| {
24353 let buffer = buffer.read(cx);
24354 let language = buffer.language().map(|language| language.name());
24355 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24356 let file = buffer.file();
24357 v.insert(language_settings(language, file, cx).into_owned());
24358 }
24359 acc
24360 },
24361 )
24362 }
24363
24364 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24365 let new_language_settings = self.fetch_applicable_language_settings(cx);
24366 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24367 self.applicable_language_settings = new_language_settings;
24368
24369 let new_accents = self.fetch_accent_data(cx);
24370 let accents_changed = new_accents != self.accent_data;
24371 self.accent_data = new_accents;
24372
24373 if self.diagnostics_enabled() {
24374 let new_severity = EditorSettings::get_global(cx)
24375 .diagnostics_max_severity
24376 .unwrap_or(DiagnosticSeverity::Hint);
24377 self.set_max_diagnostics_severity(new_severity, cx);
24378 }
24379 self.refresh_runnables(None, window, cx);
24380 self.update_edit_prediction_settings(cx);
24381 self.refresh_edit_prediction(true, false, window, cx);
24382 self.refresh_inline_values(cx);
24383
24384 let old_cursor_shape = self.cursor_shape;
24385 let old_show_breadcrumbs = self.show_breadcrumbs;
24386
24387 {
24388 let editor_settings = EditorSettings::get_global(cx);
24389 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24390 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24391 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24392 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24393 }
24394
24395 if old_cursor_shape != self.cursor_shape {
24396 cx.emit(EditorEvent::CursorShapeChanged);
24397 }
24398
24399 if old_show_breadcrumbs != self.show_breadcrumbs {
24400 cx.emit(EditorEvent::BreadcrumbsChanged);
24401 }
24402
24403 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24404 let project_settings = ProjectSettings::get_global(cx);
24405 (
24406 project_settings.session.restore_unsaved_buffers,
24407 project_settings.diagnostics.inline.enabled,
24408 project_settings.git.inline_blame.enabled,
24409 )
24410 };
24411 self.buffer_serialization = self
24412 .should_serialize_buffer()
24413 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24414
24415 if self.mode.is_full() {
24416 if self.show_inline_diagnostics != show_inline_diagnostics {
24417 self.show_inline_diagnostics = show_inline_diagnostics;
24418 self.refresh_inline_diagnostics(false, window, cx);
24419 }
24420
24421 if self.git_blame_inline_enabled != inline_blame_enabled {
24422 self.toggle_git_blame_inline_internal(false, window, cx);
24423 }
24424
24425 let minimap_settings = EditorSettings::get_global(cx).minimap;
24426 if self.minimap_visibility != MinimapVisibility::Disabled {
24427 if self.minimap_visibility.settings_visibility()
24428 != minimap_settings.minimap_enabled()
24429 {
24430 self.set_minimap_visibility(
24431 MinimapVisibility::for_mode(self.mode(), cx),
24432 window,
24433 cx,
24434 );
24435 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24436 minimap_entity.update(cx, |minimap_editor, cx| {
24437 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24438 })
24439 }
24440 }
24441
24442 if language_settings_changed || accents_changed {
24443 self.colorize_brackets(true, cx);
24444 }
24445
24446 if language_settings_changed {
24447 self.clear_disabled_lsp_folding_ranges(window, cx);
24448 self.refresh_document_symbols(None, cx);
24449 }
24450
24451 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24452 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24453 }) {
24454 if !inlay_splice.is_empty() {
24455 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24456 }
24457 self.refresh_document_colors(None, window, cx);
24458 }
24459
24460 self.refresh_inlay_hints(
24461 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24462 self.selections.newest_anchor().head(),
24463 &self.buffer.read(cx).snapshot(cx),
24464 cx,
24465 )),
24466 cx,
24467 );
24468
24469 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24470 .global_lsp_settings
24471 .semantic_token_rules
24472 .clone();
24473 let semantic_token_rules_changed = self
24474 .semantic_token_state
24475 .update_rules(new_semantic_token_rules);
24476 if language_settings_changed || semantic_token_rules_changed {
24477 self.invalidate_semantic_tokens(None);
24478 self.refresh_semantic_tokens(None, None, cx);
24479 }
24480 }
24481
24482 cx.notify();
24483 }
24484
24485 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24486 if !self.mode.is_full() {
24487 return;
24488 }
24489
24490 let new_accents = self.fetch_accent_data(cx);
24491 if new_accents != self.accent_data {
24492 self.accent_data = new_accents;
24493 self.colorize_brackets(true, cx);
24494 }
24495
24496 self.invalidate_semantic_tokens(None);
24497 self.refresh_semantic_tokens(None, None, cx);
24498 }
24499
24500 pub fn set_searchable(&mut self, searchable: bool) {
24501 self.searchable = searchable;
24502 }
24503
24504 pub fn searchable(&self) -> bool {
24505 self.searchable
24506 }
24507
24508 pub fn open_excerpts_in_split(
24509 &mut self,
24510 _: &OpenExcerptsSplit,
24511 window: &mut Window,
24512 cx: &mut Context<Self>,
24513 ) {
24514 self.open_excerpts_common(None, true, window, cx)
24515 }
24516
24517 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24518 self.open_excerpts_common(None, false, window, cx)
24519 }
24520
24521 pub(crate) fn open_excerpts_common(
24522 &mut self,
24523 jump_data: Option<JumpData>,
24524 split: bool,
24525 window: &mut Window,
24526 cx: &mut Context<Self>,
24527 ) {
24528 if self.buffer.read(cx).is_singleton() {
24529 cx.propagate();
24530 return;
24531 }
24532
24533 let mut new_selections_by_buffer = HashMap::default();
24534 match &jump_data {
24535 Some(JumpData::MultiBufferPoint {
24536 excerpt_id,
24537 position,
24538 anchor,
24539 line_offset_from_top,
24540 }) => {
24541 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24542 if let Some(buffer) = multi_buffer_snapshot
24543 .buffer_id_for_excerpt(*excerpt_id)
24544 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24545 {
24546 let buffer_snapshot = buffer.read(cx).snapshot();
24547 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24548 language::ToPoint::to_point(anchor, &buffer_snapshot)
24549 } else {
24550 buffer_snapshot.clip_point(*position, Bias::Left)
24551 };
24552 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24553 new_selections_by_buffer.insert(
24554 buffer,
24555 (
24556 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24557 Some(*line_offset_from_top),
24558 ),
24559 );
24560 }
24561 }
24562 Some(JumpData::MultiBufferRow {
24563 row,
24564 line_offset_from_top,
24565 }) => {
24566 let point = MultiBufferPoint::new(row.0, 0);
24567 if let Some((buffer, buffer_point, _)) =
24568 self.buffer.read(cx).point_to_buffer_point(point, cx)
24569 {
24570 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24571 new_selections_by_buffer
24572 .entry(buffer)
24573 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24574 .0
24575 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24576 }
24577 }
24578 None => {
24579 let selections = self
24580 .selections
24581 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24582 let multi_buffer = self.buffer.read(cx);
24583 for selection in selections {
24584 for (snapshot, range, _, anchor) in multi_buffer
24585 .snapshot(cx)
24586 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24587 {
24588 if let Some(anchor) = anchor {
24589 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24590 else {
24591 continue;
24592 };
24593 let offset = text::ToOffset::to_offset(
24594 &anchor.text_anchor,
24595 &buffer_handle.read(cx).snapshot(),
24596 );
24597 let range = BufferOffset(offset)..BufferOffset(offset);
24598 new_selections_by_buffer
24599 .entry(buffer_handle)
24600 .or_insert((Vec::new(), None))
24601 .0
24602 .push(range)
24603 } else {
24604 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24605 else {
24606 continue;
24607 };
24608 new_selections_by_buffer
24609 .entry(buffer_handle)
24610 .or_insert((Vec::new(), None))
24611 .0
24612 .push(range)
24613 }
24614 }
24615 }
24616 }
24617 }
24618
24619 if self.delegate_open_excerpts {
24620 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24621 .into_iter()
24622 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24623 .collect();
24624 if !selections_by_buffer.is_empty() {
24625 cx.emit(EditorEvent::OpenExcerptsRequested {
24626 selections_by_buffer,
24627 split,
24628 });
24629 }
24630 return;
24631 }
24632
24633 let Some(workspace) = self.workspace() else {
24634 cx.propagate();
24635 return;
24636 };
24637
24638 new_selections_by_buffer
24639 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24640
24641 if new_selections_by_buffer.is_empty() {
24642 return;
24643 }
24644
24645 Self::open_buffers_in_workspace(
24646 workspace.downgrade(),
24647 new_selections_by_buffer,
24648 split,
24649 window,
24650 cx,
24651 );
24652 }
24653
24654 pub(crate) fn open_buffers_in_workspace(
24655 workspace: WeakEntity<Workspace>,
24656 new_selections_by_buffer: HashMap<
24657 Entity<language::Buffer>,
24658 (Vec<Range<BufferOffset>>, Option<u32>),
24659 >,
24660 split: bool,
24661 window: &mut Window,
24662 cx: &mut App,
24663 ) {
24664 // We defer the pane interaction because we ourselves are a workspace item
24665 // and activating a new item causes the pane to call a method on us reentrantly,
24666 // which panics if we're on the stack.
24667 window.defer(cx, move |window, cx| {
24668 workspace
24669 .update(cx, |workspace, cx| {
24670 let pane = if split {
24671 workspace.adjacent_pane(window, cx)
24672 } else {
24673 workspace.active_pane().clone()
24674 };
24675
24676 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24677 let buffer_read = buffer.read(cx);
24678 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24679 (true, project::File::from_dyn(Some(file)).is_some())
24680 } else {
24681 (false, false)
24682 };
24683
24684 // If project file is none workspace.open_project_item will fail to open the excerpt
24685 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24686 // so we check if there's a tab match in that case first
24687 let editor = (!has_file || !is_project_file)
24688 .then(|| {
24689 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24690 // so `workspace.open_project_item` will never find them, always opening a new editor.
24691 // Instead, we try to activate the existing editor in the pane first.
24692 let (editor, pane_item_index, pane_item_id) =
24693 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24694 let editor = item.downcast::<Editor>()?;
24695 let singleton_buffer =
24696 editor.read(cx).buffer().read(cx).as_singleton()?;
24697 if singleton_buffer == buffer {
24698 Some((editor, i, item.item_id()))
24699 } else {
24700 None
24701 }
24702 })?;
24703 pane.update(cx, |pane, cx| {
24704 pane.activate_item(pane_item_index, true, true, window, cx);
24705 if !PreviewTabsSettings::get_global(cx)
24706 .enable_preview_from_multibuffer
24707 {
24708 pane.unpreview_item_if_preview(pane_item_id);
24709 }
24710 });
24711 Some(editor)
24712 })
24713 .flatten()
24714 .unwrap_or_else(|| {
24715 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24716 .enable_keep_preview_on_code_navigation;
24717 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24718 .enable_preview_from_multibuffer;
24719 workspace.open_project_item::<Self>(
24720 pane.clone(),
24721 buffer,
24722 true,
24723 true,
24724 keep_old_preview,
24725 allow_new_preview,
24726 window,
24727 cx,
24728 )
24729 });
24730
24731 editor.update(cx, |editor, cx| {
24732 if has_file && !is_project_file {
24733 editor.set_read_only(true);
24734 }
24735 let autoscroll = match scroll_offset {
24736 Some(scroll_offset) => {
24737 Autoscroll::top_relative(scroll_offset as usize)
24738 }
24739 None => Autoscroll::newest(),
24740 };
24741 let nav_history = editor.nav_history.take();
24742 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24743 let Some((excerpt_id, _, buffer_snapshot)) =
24744 multibuffer_snapshot.as_singleton()
24745 else {
24746 return;
24747 };
24748 editor.change_selections(
24749 SelectionEffects::scroll(autoscroll),
24750 window,
24751 cx,
24752 |s| {
24753 s.select_ranges(ranges.into_iter().map(|range| {
24754 let range = buffer_snapshot.anchor_before(range.start)
24755 ..buffer_snapshot.anchor_after(range.end);
24756 multibuffer_snapshot
24757 .anchor_range_in_excerpt(excerpt_id, range)
24758 .unwrap()
24759 }));
24760 },
24761 );
24762 editor.nav_history = nav_history;
24763 });
24764 }
24765 })
24766 .ok();
24767 });
24768 }
24769
24770 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24771 let snapshot = self.buffer.read(cx).read(cx);
24772 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24773 Some(
24774 ranges
24775 .iter()
24776 .map(move |range| {
24777 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24778 })
24779 .collect(),
24780 )
24781 }
24782
24783 fn selection_replacement_ranges(
24784 &self,
24785 range: Range<MultiBufferOffsetUtf16>,
24786 cx: &mut App,
24787 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24788 let selections = self
24789 .selections
24790 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24791 let newest_selection = selections
24792 .iter()
24793 .max_by_key(|selection| selection.id)
24794 .unwrap();
24795 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24796 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24797 let snapshot = self.buffer.read(cx).read(cx);
24798 selections
24799 .into_iter()
24800 .map(|mut selection| {
24801 selection.start.0.0 =
24802 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24803 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24804 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24805 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24806 })
24807 .collect()
24808 }
24809
24810 fn report_editor_event(
24811 &self,
24812 reported_event: ReportEditorEvent,
24813 file_extension: Option<String>,
24814 cx: &App,
24815 ) {
24816 if cfg!(any(test, feature = "test-support")) {
24817 return;
24818 }
24819
24820 let Some(project) = &self.project else { return };
24821
24822 // If None, we are in a file without an extension
24823 let file = self
24824 .buffer
24825 .read(cx)
24826 .as_singleton()
24827 .and_then(|b| b.read(cx).file());
24828 let file_extension = file_extension.or(file
24829 .as_ref()
24830 .and_then(|file| Path::new(file.file_name(cx)).extension())
24831 .and_then(|e| e.to_str())
24832 .map(|a| a.to_string()));
24833
24834 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24835 .map(|vim_mode| vim_mode.0)
24836 .unwrap_or(false);
24837
24838 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24839 let copilot_enabled = edit_predictions_provider
24840 == language::language_settings::EditPredictionProvider::Copilot;
24841 let copilot_enabled_for_language = self
24842 .buffer
24843 .read(cx)
24844 .language_settings(cx)
24845 .show_edit_predictions;
24846
24847 let project = project.read(cx);
24848 let event_type = reported_event.event_type();
24849
24850 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24851 telemetry::event!(
24852 event_type,
24853 type = if auto_saved {"autosave"} else {"manual"},
24854 file_extension,
24855 vim_mode,
24856 copilot_enabled,
24857 copilot_enabled_for_language,
24858 edit_predictions_provider,
24859 is_via_ssh = project.is_via_remote_server(),
24860 );
24861 } else {
24862 telemetry::event!(
24863 event_type,
24864 file_extension,
24865 vim_mode,
24866 copilot_enabled,
24867 copilot_enabled_for_language,
24868 edit_predictions_provider,
24869 is_via_ssh = project.is_via_remote_server(),
24870 );
24871 };
24872 }
24873
24874 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24875 /// with each line being an array of {text, highlight} objects.
24876 fn copy_highlight_json(
24877 &mut self,
24878 _: &CopyHighlightJson,
24879 window: &mut Window,
24880 cx: &mut Context<Self>,
24881 ) {
24882 #[derive(Serialize)]
24883 struct Chunk<'a> {
24884 text: String,
24885 highlight: Option<&'a str>,
24886 }
24887
24888 let snapshot = self.buffer.read(cx).snapshot(cx);
24889 let range = self
24890 .selected_text_range(false, window, cx)
24891 .and_then(|selection| {
24892 if selection.range.is_empty() {
24893 None
24894 } else {
24895 Some(
24896 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24897 selection.range.start,
24898 )))
24899 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24900 selection.range.end,
24901 ))),
24902 )
24903 }
24904 })
24905 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24906
24907 let chunks = snapshot.chunks(range, true);
24908 let mut lines = Vec::new();
24909 let mut line: VecDeque<Chunk> = VecDeque::new();
24910
24911 let Some(style) = self.style.as_ref() else {
24912 return;
24913 };
24914
24915 for chunk in chunks {
24916 let highlight = chunk
24917 .syntax_highlight_id
24918 .and_then(|id| id.name(&style.syntax));
24919 let mut chunk_lines = chunk.text.split('\n').peekable();
24920 while let Some(text) = chunk_lines.next() {
24921 let mut merged_with_last_token = false;
24922 if let Some(last_token) = line.back_mut()
24923 && last_token.highlight == highlight
24924 {
24925 last_token.text.push_str(text);
24926 merged_with_last_token = true;
24927 }
24928
24929 if !merged_with_last_token {
24930 line.push_back(Chunk {
24931 text: text.into(),
24932 highlight,
24933 });
24934 }
24935
24936 if chunk_lines.peek().is_some() {
24937 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24938 line.pop_front();
24939 }
24940 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24941 line.pop_back();
24942 }
24943
24944 lines.push(mem::take(&mut line));
24945 }
24946 }
24947 }
24948
24949 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24950 return;
24951 };
24952 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24953 }
24954
24955 pub fn open_context_menu(
24956 &mut self,
24957 _: &OpenContextMenu,
24958 window: &mut Window,
24959 cx: &mut Context<Self>,
24960 ) {
24961 self.request_autoscroll(Autoscroll::newest(), cx);
24962 let position = self
24963 .selections
24964 .newest_display(&self.display_snapshot(cx))
24965 .start;
24966 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24967 }
24968
24969 pub fn replay_insert_event(
24970 &mut self,
24971 text: &str,
24972 relative_utf16_range: Option<Range<isize>>,
24973 window: &mut Window,
24974 cx: &mut Context<Self>,
24975 ) {
24976 if !self.input_enabled {
24977 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24978 return;
24979 }
24980 if let Some(relative_utf16_range) = relative_utf16_range {
24981 let selections = self
24982 .selections
24983 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24984 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24985 let new_ranges = selections.into_iter().map(|range| {
24986 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24987 range
24988 .head()
24989 .0
24990 .0
24991 .saturating_add_signed(relative_utf16_range.start),
24992 ));
24993 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24994 range
24995 .head()
24996 .0
24997 .0
24998 .saturating_add_signed(relative_utf16_range.end),
24999 ));
25000 start..end
25001 });
25002 s.select_ranges(new_ranges);
25003 });
25004 }
25005
25006 self.handle_input(text, window, cx);
25007 }
25008
25009 pub fn is_focused(&self, window: &Window) -> bool {
25010 self.focus_handle.is_focused(window)
25011 }
25012
25013 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25014 cx.emit(EditorEvent::Focused);
25015
25016 if let Some(descendant) = self
25017 .last_focused_descendant
25018 .take()
25019 .and_then(|descendant| descendant.upgrade())
25020 {
25021 window.focus(&descendant, cx);
25022 } else {
25023 if let Some(blame) = self.blame.as_ref() {
25024 blame.update(cx, GitBlame::focus)
25025 }
25026
25027 self.blink_manager.update(cx, BlinkManager::enable);
25028 self.show_cursor_names(window, cx);
25029 self.buffer.update(cx, |buffer, cx| {
25030 buffer.finalize_last_transaction(cx);
25031 if self.leader_id.is_none() {
25032 buffer.set_active_selections(
25033 &self.selections.disjoint_anchors_arc(),
25034 self.selections.line_mode(),
25035 self.cursor_shape,
25036 cx,
25037 );
25038 }
25039 });
25040
25041 if let Some(position_map) = self.last_position_map.clone()
25042 && !self.mouse_cursor_hidden
25043 {
25044 EditorElement::mouse_moved(
25045 self,
25046 &MouseMoveEvent {
25047 position: window.mouse_position(),
25048 pressed_button: None,
25049 modifiers: window.modifiers(),
25050 },
25051 &position_map,
25052 None,
25053 window,
25054 cx,
25055 );
25056 }
25057 }
25058 }
25059
25060 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25061 cx.emit(EditorEvent::FocusedIn)
25062 }
25063
25064 fn handle_focus_out(
25065 &mut self,
25066 event: FocusOutEvent,
25067 _window: &mut Window,
25068 cx: &mut Context<Self>,
25069 ) {
25070 if event.blurred != self.focus_handle {
25071 self.last_focused_descendant = Some(event.blurred);
25072 }
25073 self.selection_drag_state = SelectionDragState::None;
25074 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25075 }
25076
25077 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25078 self.blink_manager.update(cx, BlinkManager::disable);
25079 self.buffer
25080 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25081
25082 if let Some(blame) = self.blame.as_ref() {
25083 blame.update(cx, GitBlame::blur)
25084 }
25085 if !self.hover_state.focused(window, cx) {
25086 hide_hover(self, cx);
25087 }
25088 if !self
25089 .context_menu
25090 .borrow()
25091 .as_ref()
25092 .is_some_and(|context_menu| context_menu.focused(window, cx))
25093 {
25094 self.hide_context_menu(window, cx);
25095 }
25096 self.take_active_edit_prediction(cx);
25097 cx.emit(EditorEvent::Blurred);
25098 cx.notify();
25099 }
25100
25101 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25102 let mut pending: String = window
25103 .pending_input_keystrokes()
25104 .into_iter()
25105 .flatten()
25106 .filter_map(|keystroke| keystroke.key_char.clone())
25107 .collect();
25108
25109 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25110 pending = "".to_string();
25111 }
25112
25113 let existing_pending = self
25114 .text_highlights(HighlightKey::PendingInput, cx)
25115 .map(|(_, ranges)| ranges.to_vec());
25116 if existing_pending.is_none() && pending.is_empty() {
25117 return;
25118 }
25119 let transaction =
25120 self.transact(window, cx, |this, window, cx| {
25121 let selections = this
25122 .selections
25123 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25124 let edits = selections
25125 .iter()
25126 .map(|selection| (selection.end..selection.end, pending.clone()));
25127 this.edit(edits, cx);
25128 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25129 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25130 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25131 }));
25132 });
25133 if let Some(existing_ranges) = existing_pending {
25134 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25135 this.edit(edits, cx);
25136 }
25137 });
25138
25139 let snapshot = self.snapshot(window, cx);
25140 let ranges = self
25141 .selections
25142 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25143 .into_iter()
25144 .map(|selection| {
25145 snapshot.buffer_snapshot().anchor_after(selection.end)
25146 ..snapshot
25147 .buffer_snapshot()
25148 .anchor_before(selection.end + pending.len())
25149 })
25150 .collect();
25151
25152 if pending.is_empty() {
25153 self.clear_highlights(HighlightKey::PendingInput, cx);
25154 } else {
25155 self.highlight_text(
25156 HighlightKey::PendingInput,
25157 ranges,
25158 HighlightStyle {
25159 underline: Some(UnderlineStyle {
25160 thickness: px(1.),
25161 color: None,
25162 wavy: false,
25163 }),
25164 ..Default::default()
25165 },
25166 cx,
25167 );
25168 }
25169
25170 self.ime_transaction = self.ime_transaction.or(transaction);
25171 if let Some(transaction) = self.ime_transaction {
25172 self.buffer.update(cx, |buffer, cx| {
25173 buffer.group_until_transaction(transaction, cx);
25174 });
25175 }
25176
25177 if self
25178 .text_highlights(HighlightKey::PendingInput, cx)
25179 .is_none()
25180 {
25181 self.ime_transaction.take();
25182 }
25183 }
25184
25185 pub fn register_action_renderer(
25186 &mut self,
25187 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25188 ) -> Subscription {
25189 let id = self.next_editor_action_id.post_inc();
25190 self.editor_actions
25191 .borrow_mut()
25192 .insert(id, Box::new(listener));
25193
25194 let editor_actions = self.editor_actions.clone();
25195 Subscription::new(move || {
25196 editor_actions.borrow_mut().remove(&id);
25197 })
25198 }
25199
25200 pub fn register_action<A: Action>(
25201 &mut self,
25202 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25203 ) -> Subscription {
25204 let id = self.next_editor_action_id.post_inc();
25205 let listener = Arc::new(listener);
25206 self.editor_actions.borrow_mut().insert(
25207 id,
25208 Box::new(move |_, window, _| {
25209 let listener = listener.clone();
25210 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25211 let action = action.downcast_ref().unwrap();
25212 if phase == DispatchPhase::Bubble {
25213 listener(action, window, cx)
25214 }
25215 })
25216 }),
25217 );
25218
25219 let editor_actions = self.editor_actions.clone();
25220 Subscription::new(move || {
25221 editor_actions.borrow_mut().remove(&id);
25222 })
25223 }
25224
25225 pub fn file_header_size(&self) -> u32 {
25226 FILE_HEADER_HEIGHT
25227 }
25228
25229 pub fn restore(
25230 &mut self,
25231 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25232 window: &mut Window,
25233 cx: &mut Context<Self>,
25234 ) {
25235 self.buffer().update(cx, |multi_buffer, cx| {
25236 for (buffer_id, changes) in revert_changes {
25237 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25238 buffer.update(cx, |buffer, cx| {
25239 buffer.edit(
25240 changes
25241 .into_iter()
25242 .map(|(range, text)| (range, text.to_string())),
25243 None,
25244 cx,
25245 );
25246 });
25247 }
25248 }
25249 });
25250 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25251 selections.refresh()
25252 });
25253 }
25254
25255 pub fn to_pixel_point(
25256 &mut self,
25257 source: Anchor,
25258 editor_snapshot: &EditorSnapshot,
25259 window: &mut Window,
25260 cx: &mut App,
25261 ) -> Option<gpui::Point<Pixels>> {
25262 let source_point = source.to_display_point(editor_snapshot);
25263 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25264 }
25265
25266 pub fn display_to_pixel_point(
25267 &mut self,
25268 source: DisplayPoint,
25269 editor_snapshot: &EditorSnapshot,
25270 window: &mut Window,
25271 cx: &mut App,
25272 ) -> Option<gpui::Point<Pixels>> {
25273 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25274 let text_layout_details = self.text_layout_details(window, cx);
25275 let scroll_top = text_layout_details
25276 .scroll_anchor
25277 .scroll_position(editor_snapshot)
25278 .y;
25279
25280 if source.row().as_f64() < scroll_top.floor() {
25281 return None;
25282 }
25283 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25284 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25285 Some(gpui::Point::new(source_x, source_y))
25286 }
25287
25288 pub fn has_visible_completions_menu(&self) -> bool {
25289 !self.edit_prediction_preview_is_active()
25290 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25291 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25292 })
25293 }
25294
25295 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25296 if self.mode.is_minimap() {
25297 return;
25298 }
25299 self.addons
25300 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25301 }
25302
25303 pub fn unregister_addon<T: Addon>(&mut self) {
25304 self.addons.remove(&std::any::TypeId::of::<T>());
25305 }
25306
25307 pub fn addon<T: Addon>(&self) -> Option<&T> {
25308 let type_id = std::any::TypeId::of::<T>();
25309 self.addons
25310 .get(&type_id)
25311 .and_then(|item| item.to_any().downcast_ref::<T>())
25312 }
25313
25314 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25315 let type_id = std::any::TypeId::of::<T>();
25316 self.addons
25317 .get_mut(&type_id)
25318 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25319 }
25320
25321 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25322 let text_layout_details = self.text_layout_details(window, cx);
25323 let style = &text_layout_details.editor_style;
25324 let font_id = window.text_system().resolve_font(&style.text.font());
25325 let font_size = style.text.font_size.to_pixels(window.rem_size());
25326 let line_height = style.text.line_height_in_pixels(window.rem_size());
25327 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25328 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25329
25330 CharacterDimensions {
25331 em_width,
25332 em_advance,
25333 line_height,
25334 }
25335 }
25336
25337 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25338 self.load_diff_task.clone()
25339 }
25340
25341 fn read_metadata_from_db(
25342 &mut self,
25343 item_id: u64,
25344 workspace_id: WorkspaceId,
25345 window: &mut Window,
25346 cx: &mut Context<Editor>,
25347 ) {
25348 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25349 && !self.mode.is_minimap()
25350 && WorkspaceSettings::get(None, cx).restore_on_startup
25351 != RestoreOnStartupBehavior::EmptyTab
25352 {
25353 let buffer_snapshot = OnceCell::new();
25354
25355 // Get file path for path-based fold lookup
25356 let file_path: Option<Arc<Path>> =
25357 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25358 project::File::from_dyn(buffer.read(cx).file())
25359 .map(|file| Arc::from(file.abs_path(cx)))
25360 });
25361
25362 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25363 let db = EditorDb::global(cx);
25364 let (folds, needs_migration) = if let Some(ref path) = file_path {
25365 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25366 && !folds.is_empty()
25367 {
25368 (Some(folds), false)
25369 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25370 && !folds.is_empty()
25371 {
25372 // Found old editor_folds data, will migrate to file_folds
25373 (Some(folds), true)
25374 } else {
25375 (None, false)
25376 }
25377 } else {
25378 // No file path, try editor_folds as fallback
25379 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25380 (folds.filter(|f| !f.is_empty()), false)
25381 };
25382
25383 if let Some(folds) = folds {
25384 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25385 let snapshot_len = snapshot.len().0;
25386
25387 // Helper: search for fingerprint in buffer, return offset if found
25388 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25389 // Ensure we start at a character boundary (defensive)
25390 let search_start = snapshot
25391 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25392 .0;
25393 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25394
25395 let mut byte_offset = search_start;
25396 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25397 if byte_offset > search_end {
25398 break;
25399 }
25400 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25401 return Some(byte_offset);
25402 }
25403 byte_offset += ch.len_utf8();
25404 }
25405 None
25406 };
25407
25408 // Track search position to handle duplicate fingerprints correctly.
25409 // Folds are stored in document order, so we advance after each match.
25410 let mut search_start = 0usize;
25411
25412 // Collect db_folds for migration (only folds with valid fingerprints)
25413 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25414
25415 let valid_folds: Vec<_> = folds
25416 .into_iter()
25417 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25418 // Skip folds without fingerprints (old data before migration)
25419 let sfp = start_fp?;
25420 let efp = end_fp?;
25421 let efp_len = efp.len();
25422
25423 // Fast path: check if fingerprints match at stored offsets
25424 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25425 let start_matches = stored_start < snapshot_len
25426 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25427 let efp_check_pos = stored_end.saturating_sub(efp_len);
25428 let end_matches = efp_check_pos >= stored_start
25429 && stored_end <= snapshot_len
25430 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25431
25432 let (new_start, new_end) = if start_matches && end_matches {
25433 // Offsets unchanged, use stored values
25434 (stored_start, stored_end)
25435 } else if sfp == efp {
25436 // Short fold: identical fingerprints can only match once per search
25437 // Use stored fold length to compute new_end
25438 let new_start = find_fingerprint(&sfp, search_start)?;
25439 let fold_len = stored_end - stored_start;
25440 let new_end = new_start + fold_len;
25441 (new_start, new_end)
25442 } else {
25443 // Slow path: search for fingerprints in buffer
25444 let new_start = find_fingerprint(&sfp, search_start)?;
25445 // Search for end_fp after start, then add efp_len to get actual fold end
25446 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25447 let new_end = efp_pos + efp_len;
25448 (new_start, new_end)
25449 };
25450
25451 // Advance search position for next fold
25452 search_start = new_end;
25453
25454 // Validate fold makes sense (end must be after start)
25455 if new_end <= new_start {
25456 return None;
25457 }
25458
25459 // Collect for migration if needed
25460 if needs_migration {
25461 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25462 }
25463
25464 Some(
25465 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25466 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25467 )
25468 })
25469 .collect();
25470
25471 if !valid_folds.is_empty() {
25472 self.fold_ranges(valid_folds, false, window, cx);
25473
25474 // Migrate from editor_folds to file_folds if we loaded from old table
25475 if needs_migration {
25476 if let Some(ref path) = file_path {
25477 let path = path.clone();
25478 let db = EditorDb::global(cx);
25479 cx.spawn(async move |_, _| {
25480 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25481 .await
25482 .log_err();
25483 })
25484 .detach();
25485 }
25486 }
25487 }
25488 }
25489
25490 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25491 && !selections.is_empty()
25492 {
25493 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25494 // skip adding the initial selection to selection history
25495 self.selection_history.mode = SelectionHistoryMode::Skipping;
25496 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25497 s.select_ranges(selections.into_iter().map(|(start, end)| {
25498 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25499 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25500 }));
25501 });
25502 self.selection_history.mode = SelectionHistoryMode::Normal;
25503 };
25504 }
25505
25506 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25507 }
25508
25509 /// Load folds from the file_folds database table by file path.
25510 /// Used when manually opening a file that was previously closed.
25511 fn load_folds_from_db(
25512 &mut self,
25513 workspace_id: WorkspaceId,
25514 file_path: PathBuf,
25515 window: &mut Window,
25516 cx: &mut Context<Editor>,
25517 ) {
25518 if self.mode.is_minimap()
25519 || WorkspaceSettings::get(None, cx).restore_on_startup
25520 == RestoreOnStartupBehavior::EmptyTab
25521 {
25522 return;
25523 }
25524
25525 let Some(folds) = EditorDb::global(cx)
25526 .get_file_folds(workspace_id, &file_path)
25527 .log_err()
25528 else {
25529 return;
25530 };
25531 if folds.is_empty() {
25532 return;
25533 }
25534
25535 let snapshot = self.buffer.read(cx).snapshot(cx);
25536 let snapshot_len = snapshot.len().0;
25537
25538 // Helper: search for fingerprint in buffer, return offset if found
25539 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25540 let search_start = snapshot
25541 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25542 .0;
25543 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25544
25545 let mut byte_offset = search_start;
25546 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25547 if byte_offset > search_end {
25548 break;
25549 }
25550 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25551 return Some(byte_offset);
25552 }
25553 byte_offset += ch.len_utf8();
25554 }
25555 None
25556 };
25557
25558 let mut search_start = 0usize;
25559
25560 let valid_folds: Vec<_> = folds
25561 .into_iter()
25562 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25563 let sfp = start_fp?;
25564 let efp = end_fp?;
25565 let efp_len = efp.len();
25566
25567 let start_matches = stored_start < snapshot_len
25568 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25569 let efp_check_pos = stored_end.saturating_sub(efp_len);
25570 let end_matches = efp_check_pos >= stored_start
25571 && stored_end <= snapshot_len
25572 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25573
25574 let (new_start, new_end) = if start_matches && end_matches {
25575 (stored_start, stored_end)
25576 } else if sfp == efp {
25577 let new_start = find_fingerprint(&sfp, search_start)?;
25578 let fold_len = stored_end - stored_start;
25579 let new_end = new_start + fold_len;
25580 (new_start, new_end)
25581 } else {
25582 let new_start = find_fingerprint(&sfp, search_start)?;
25583 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25584 let new_end = efp_pos + efp_len;
25585 (new_start, new_end)
25586 };
25587
25588 search_start = new_end;
25589
25590 if new_end <= new_start {
25591 return None;
25592 }
25593
25594 Some(
25595 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25596 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25597 )
25598 })
25599 .collect();
25600
25601 if !valid_folds.is_empty() {
25602 self.fold_ranges(valid_folds, false, window, cx);
25603 }
25604 }
25605
25606 fn lsp_data_enabled(&self) -> bool {
25607 self.enable_lsp_data && self.mode().is_full()
25608 }
25609
25610 fn update_lsp_data(
25611 &mut self,
25612 for_buffer: Option<BufferId>,
25613 window: &mut Window,
25614 cx: &mut Context<'_, Self>,
25615 ) {
25616 if !self.lsp_data_enabled() {
25617 return;
25618 }
25619
25620 if let Some(buffer_id) = for_buffer {
25621 self.pull_diagnostics(buffer_id, window, cx);
25622 }
25623 self.refresh_semantic_tokens(for_buffer, None, cx);
25624 self.refresh_document_colors(for_buffer, window, cx);
25625 self.refresh_folding_ranges(for_buffer, window, cx);
25626 self.refresh_document_symbols(for_buffer, cx);
25627 }
25628
25629 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25630 if !self.lsp_data_enabled() {
25631 return;
25632 }
25633 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25634 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25635 }
25636 }
25637
25638 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25639 if !self.lsp_data_enabled() {
25640 return;
25641 }
25642
25643 if !self.registered_buffers.contains_key(&buffer_id)
25644 && let Some(project) = self.project.as_ref()
25645 {
25646 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25647 project.update(cx, |project, cx| {
25648 self.registered_buffers.insert(
25649 buffer_id,
25650 project.register_buffer_with_language_servers(&buffer, cx),
25651 );
25652 });
25653 } else {
25654 self.registered_buffers.remove(&buffer_id);
25655 }
25656 }
25657 }
25658
25659 fn create_style(&self, cx: &App) -> EditorStyle {
25660 let settings = ThemeSettings::get_global(cx);
25661
25662 let mut text_style = match self.mode {
25663 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25664 color: cx.theme().colors().editor_foreground,
25665 font_family: settings.ui_font.family.clone(),
25666 font_features: settings.ui_font.features.clone(),
25667 font_fallbacks: settings.ui_font.fallbacks.clone(),
25668 font_size: rems(0.875).into(),
25669 font_weight: settings.ui_font.weight,
25670 line_height: relative(settings.buffer_line_height.value()),
25671 ..Default::default()
25672 },
25673 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25674 color: cx.theme().colors().editor_foreground,
25675 font_family: settings.buffer_font.family.clone(),
25676 font_features: settings.buffer_font.features.clone(),
25677 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25678 font_size: settings.buffer_font_size(cx).into(),
25679 font_weight: settings.buffer_font.weight,
25680 line_height: relative(settings.buffer_line_height.value()),
25681 ..Default::default()
25682 },
25683 };
25684 if let Some(text_style_refinement) = &self.text_style_refinement {
25685 text_style.refine(text_style_refinement)
25686 }
25687
25688 let background = match self.mode {
25689 EditorMode::SingleLine => cx.theme().system().transparent,
25690 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25691 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25692 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25693 };
25694
25695 EditorStyle {
25696 background,
25697 border: cx.theme().colors().border,
25698 local_player: cx.theme().players().local(),
25699 text: text_style,
25700 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25701 syntax: cx.theme().syntax().clone(),
25702 status: cx.theme().status().clone(),
25703 inlay_hints_style: make_inlay_hints_style(cx),
25704 edit_prediction_styles: make_suggestion_styles(cx),
25705 unnecessary_code_fade: settings.unnecessary_code_fade,
25706 show_underlines: self.diagnostics_enabled(),
25707 }
25708 }
25709
25710 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25711 let multibuffer = self.buffer().read(cx);
25712 let is_singleton = multibuffer.is_singleton();
25713 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25714 let buffer = multibuffer.buffer(*buffer_id)?;
25715
25716 let buffer = buffer.read(cx);
25717 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25718 let mut breadcrumbs = if is_singleton {
25719 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25720 buffer
25721 .snapshot()
25722 .resolve_file_path(
25723 self.project
25724 .as_ref()
25725 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25726 .unwrap_or_default(),
25727 cx,
25728 )
25729 .unwrap_or_else(|| {
25730 if multibuffer.is_singleton() {
25731 multibuffer.title(cx).to_string()
25732 } else {
25733 "untitled".to_string()
25734 }
25735 })
25736 });
25737 vec![HighlightedText {
25738 text: text.into(),
25739 highlights: vec![],
25740 }]
25741 } else {
25742 vec![]
25743 };
25744
25745 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25746 text: symbol.text.clone().into(),
25747 highlights: symbol.highlight_ranges.clone(),
25748 }));
25749 Some(breadcrumbs)
25750 }
25751
25752 fn disable_lsp_data(&mut self) {
25753 self.enable_lsp_data = false;
25754 }
25755
25756 fn disable_runnables(&mut self) {
25757 self.enable_runnables = false;
25758 }
25759
25760 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25761 self.register_visible_buffers(cx);
25762 self.colorize_brackets(false, cx);
25763 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25764 if !self.buffer().read(cx).is_singleton() {
25765 self.update_lsp_data(None, window, cx);
25766 self.refresh_runnables(None, window, cx);
25767 }
25768 }
25769}
25770
25771fn edit_for_markdown_paste<'a>(
25772 buffer: &MultiBufferSnapshot,
25773 range: Range<MultiBufferOffset>,
25774 to_insert: &'a str,
25775 url: Option<url::Url>,
25776) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25777 if url.is_none() {
25778 return (range, Cow::Borrowed(to_insert));
25779 };
25780
25781 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25782
25783 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25784 Cow::Borrowed(to_insert)
25785 } else {
25786 Cow::Owned(format!("[{old_text}]({to_insert})"))
25787 };
25788 (range, new_text)
25789}
25790
25791fn process_completion_for_edit(
25792 completion: &Completion,
25793 intent: CompletionIntent,
25794 buffer: &Entity<Buffer>,
25795 cursor_position: &text::Anchor,
25796 cx: &mut Context<Editor>,
25797) -> CompletionEdit {
25798 let buffer = buffer.read(cx);
25799 let buffer_snapshot = buffer.snapshot();
25800 let (snippet, new_text) = if completion.is_snippet() {
25801 let mut snippet_source = completion.new_text.clone();
25802 // Workaround for typescript language server issues so that methods don't expand within
25803 // strings and functions with type expressions. The previous point is used because the query
25804 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25805 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25806 let previous_point = if previous_point.column > 0 {
25807 cursor_position.to_previous_offset(&buffer_snapshot)
25808 } else {
25809 cursor_position.to_offset(&buffer_snapshot)
25810 };
25811 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25812 && scope.prefers_label_for_snippet_in_completion()
25813 && let Some(label) = completion.label()
25814 && matches!(
25815 completion.kind(),
25816 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25817 )
25818 {
25819 snippet_source = label;
25820 }
25821 match Snippet::parse(&snippet_source).log_err() {
25822 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25823 None => (None, completion.new_text.clone()),
25824 }
25825 } else {
25826 (None, completion.new_text.clone())
25827 };
25828
25829 let mut range_to_replace = {
25830 let replace_range = &completion.replace_range;
25831 if let CompletionSource::Lsp {
25832 insert_range: Some(insert_range),
25833 ..
25834 } = &completion.source
25835 {
25836 debug_assert_eq!(
25837 insert_range.start, replace_range.start,
25838 "insert_range and replace_range should start at the same position"
25839 );
25840 debug_assert!(
25841 insert_range
25842 .start
25843 .cmp(cursor_position, &buffer_snapshot)
25844 .is_le(),
25845 "insert_range should start before or at cursor position"
25846 );
25847 debug_assert!(
25848 replace_range
25849 .start
25850 .cmp(cursor_position, &buffer_snapshot)
25851 .is_le(),
25852 "replace_range should start before or at cursor position"
25853 );
25854
25855 let should_replace = match intent {
25856 CompletionIntent::CompleteWithInsert => false,
25857 CompletionIntent::CompleteWithReplace => true,
25858 CompletionIntent::Complete | CompletionIntent::Compose => {
25859 let insert_mode =
25860 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25861 .completions
25862 .lsp_insert_mode;
25863 match insert_mode {
25864 LspInsertMode::Insert => false,
25865 LspInsertMode::Replace => true,
25866 LspInsertMode::ReplaceSubsequence => {
25867 let mut text_to_replace = buffer.chars_for_range(
25868 buffer.anchor_before(replace_range.start)
25869 ..buffer.anchor_after(replace_range.end),
25870 );
25871 let mut current_needle = text_to_replace.next();
25872 for haystack_ch in completion.label.text.chars() {
25873 if let Some(needle_ch) = current_needle
25874 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25875 {
25876 current_needle = text_to_replace.next();
25877 }
25878 }
25879 current_needle.is_none()
25880 }
25881 LspInsertMode::ReplaceSuffix => {
25882 if replace_range
25883 .end
25884 .cmp(cursor_position, &buffer_snapshot)
25885 .is_gt()
25886 {
25887 let range_after_cursor = *cursor_position..replace_range.end;
25888 let text_after_cursor = buffer
25889 .text_for_range(
25890 buffer.anchor_before(range_after_cursor.start)
25891 ..buffer.anchor_after(range_after_cursor.end),
25892 )
25893 .collect::<String>()
25894 .to_ascii_lowercase();
25895 completion
25896 .label
25897 .text
25898 .to_ascii_lowercase()
25899 .ends_with(&text_after_cursor)
25900 } else {
25901 true
25902 }
25903 }
25904 }
25905 }
25906 };
25907
25908 if should_replace {
25909 replace_range.clone()
25910 } else {
25911 insert_range.clone()
25912 }
25913 } else {
25914 replace_range.clone()
25915 }
25916 };
25917
25918 if range_to_replace
25919 .end
25920 .cmp(cursor_position, &buffer_snapshot)
25921 .is_lt()
25922 {
25923 range_to_replace.end = *cursor_position;
25924 }
25925
25926 let replace_range = range_to_replace.to_offset(buffer);
25927 CompletionEdit {
25928 new_text,
25929 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25930 snippet,
25931 }
25932}
25933
25934struct CompletionEdit {
25935 new_text: String,
25936 replace_range: Range<BufferOffset>,
25937 snippet: Option<Snippet>,
25938}
25939
25940fn comment_delimiter_for_newline(
25941 start_point: &Point,
25942 buffer: &MultiBufferSnapshot,
25943 language: &LanguageScope,
25944) -> Option<Arc<str>> {
25945 let delimiters = language.line_comment_prefixes();
25946 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25947 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25948
25949 let num_of_whitespaces = snapshot
25950 .chars_for_range(range.clone())
25951 .take_while(|c| c.is_whitespace())
25952 .count();
25953 let comment_candidate = snapshot
25954 .chars_for_range(range.clone())
25955 .skip(num_of_whitespaces)
25956 .take(max_len_of_delimiter + 2)
25957 .collect::<String>();
25958 let (delimiter, trimmed_len, is_repl) = delimiters
25959 .iter()
25960 .filter_map(|delimiter| {
25961 let prefix = delimiter.trim_end();
25962 if comment_candidate.starts_with(prefix) {
25963 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
25964 {
25965 stripped_comment.starts_with(" %%")
25966 } else {
25967 false
25968 };
25969 Some((delimiter, prefix.len(), is_repl))
25970 } else {
25971 None
25972 }
25973 })
25974 .max_by_key(|(_, len, _)| *len)?;
25975
25976 if let Some(BlockCommentConfig {
25977 start: block_start, ..
25978 }) = language.block_comment()
25979 {
25980 let block_start_trimmed = block_start.trim_end();
25981 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25982 let line_content = snapshot
25983 .chars_for_range(range.clone())
25984 .skip(num_of_whitespaces)
25985 .take(block_start_trimmed.len())
25986 .collect::<String>();
25987
25988 if line_content.starts_with(block_start_trimmed) {
25989 return None;
25990 }
25991 }
25992 }
25993
25994 let cursor_is_placed_after_comment_marker =
25995 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25996 if cursor_is_placed_after_comment_marker {
25997 if !is_repl {
25998 return Some(delimiter.clone());
25999 }
26000
26001 let line_content_after_cursor: String = snapshot
26002 .chars_for_range(range)
26003 .skip(start_point.column as usize)
26004 .collect();
26005
26006 if line_content_after_cursor.trim().is_empty() {
26007 return None;
26008 } else {
26009 return Some(delimiter.clone());
26010 }
26011 } else {
26012 None
26013 }
26014}
26015
26016fn documentation_delimiter_for_newline(
26017 start_point: &Point,
26018 buffer: &MultiBufferSnapshot,
26019 language: &LanguageScope,
26020 newline_config: &mut NewlineConfig,
26021) -> Option<Arc<str>> {
26022 let BlockCommentConfig {
26023 start: start_tag,
26024 end: end_tag,
26025 prefix: delimiter,
26026 tab_size: len,
26027 } = language.documentation_comment()?;
26028 let is_within_block_comment = buffer
26029 .language_scope_at(*start_point)
26030 .is_some_and(|scope| scope.override_name() == Some("comment"));
26031 if !is_within_block_comment {
26032 return None;
26033 }
26034
26035 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26036
26037 let num_of_whitespaces = snapshot
26038 .chars_for_range(range.clone())
26039 .take_while(|c| c.is_whitespace())
26040 .count();
26041
26042 // 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.
26043 let column = start_point.column;
26044 let cursor_is_after_start_tag = {
26045 let start_tag_len = start_tag.len();
26046 let start_tag_line = snapshot
26047 .chars_for_range(range.clone())
26048 .skip(num_of_whitespaces)
26049 .take(start_tag_len)
26050 .collect::<String>();
26051 if start_tag_line.starts_with(start_tag.as_ref()) {
26052 num_of_whitespaces + start_tag_len <= column as usize
26053 } else {
26054 false
26055 }
26056 };
26057
26058 let cursor_is_after_delimiter = {
26059 let delimiter_trim = delimiter.trim_end();
26060 let delimiter_line = snapshot
26061 .chars_for_range(range.clone())
26062 .skip(num_of_whitespaces)
26063 .take(delimiter_trim.len())
26064 .collect::<String>();
26065 if delimiter_line.starts_with(delimiter_trim) {
26066 num_of_whitespaces + delimiter_trim.len() <= column as usize
26067 } else {
26068 false
26069 }
26070 };
26071
26072 let mut needs_extra_line = false;
26073 let mut extra_line_additional_indent = IndentSize::spaces(0);
26074
26075 let cursor_is_before_end_tag_if_exists = {
26076 let mut char_position = 0u32;
26077 let mut end_tag_offset = None;
26078
26079 'outer: for chunk in snapshot.text_for_range(range) {
26080 if let Some(byte_pos) = chunk.find(&**end_tag) {
26081 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26082 end_tag_offset = Some(char_position + chars_before_match);
26083 break 'outer;
26084 }
26085 char_position += chunk.chars().count() as u32;
26086 }
26087
26088 if let Some(end_tag_offset) = end_tag_offset {
26089 let cursor_is_before_end_tag = column <= end_tag_offset;
26090 if cursor_is_after_start_tag {
26091 if cursor_is_before_end_tag {
26092 needs_extra_line = true;
26093 }
26094 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26095 if cursor_is_at_start_of_end_tag {
26096 extra_line_additional_indent.len = *len;
26097 }
26098 }
26099 cursor_is_before_end_tag
26100 } else {
26101 true
26102 }
26103 };
26104
26105 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26106 && cursor_is_before_end_tag_if_exists
26107 {
26108 let additional_indent = if cursor_is_after_start_tag {
26109 IndentSize::spaces(*len)
26110 } else {
26111 IndentSize::spaces(0)
26112 };
26113
26114 *newline_config = NewlineConfig::Newline {
26115 additional_indent,
26116 extra_line_additional_indent: if needs_extra_line {
26117 Some(extra_line_additional_indent)
26118 } else {
26119 None
26120 },
26121 prevent_auto_indent: true,
26122 };
26123 Some(delimiter.clone())
26124 } else {
26125 None
26126 }
26127}
26128
26129const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26130
26131fn list_delimiter_for_newline(
26132 start_point: &Point,
26133 buffer: &MultiBufferSnapshot,
26134 language: &LanguageScope,
26135 newline_config: &mut NewlineConfig,
26136) -> Option<Arc<str>> {
26137 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26138
26139 let num_of_whitespaces = snapshot
26140 .chars_for_range(range.clone())
26141 .take_while(|c| c.is_whitespace())
26142 .count();
26143
26144 let task_list_entries: Vec<_> = language
26145 .task_list()
26146 .into_iter()
26147 .flat_map(|config| {
26148 config
26149 .prefixes
26150 .iter()
26151 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26152 })
26153 .collect();
26154 let unordered_list_entries: Vec<_> = language
26155 .unordered_list()
26156 .iter()
26157 .map(|marker| (marker.as_ref(), marker.as_ref()))
26158 .collect();
26159
26160 let all_entries: Vec<_> = task_list_entries
26161 .into_iter()
26162 .chain(unordered_list_entries)
26163 .collect();
26164
26165 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26166 let candidate: String = snapshot
26167 .chars_for_range(range.clone())
26168 .skip(num_of_whitespaces)
26169 .take(max_prefix_len)
26170 .collect();
26171
26172 if let Some((prefix, continuation)) = all_entries
26173 .iter()
26174 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26175 .max_by_key(|(prefix, _)| prefix.len())
26176 {
26177 let end_of_prefix = num_of_whitespaces + prefix.len();
26178 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26179 let has_content_after_marker = snapshot
26180 .chars_for_range(range)
26181 .skip(end_of_prefix)
26182 .any(|c| !c.is_whitespace());
26183
26184 if has_content_after_marker && cursor_is_after_prefix {
26185 return Some((*continuation).into());
26186 }
26187
26188 if start_point.column as usize == end_of_prefix {
26189 if num_of_whitespaces == 0 {
26190 *newline_config = NewlineConfig::ClearCurrentLine;
26191 } else {
26192 *newline_config = NewlineConfig::UnindentCurrentLine {
26193 continuation: (*continuation).into(),
26194 };
26195 }
26196 }
26197
26198 return None;
26199 }
26200 }
26201
26202 let candidate: String = snapshot
26203 .chars_for_range(range.clone())
26204 .skip(num_of_whitespaces)
26205 .take(ORDERED_LIST_MAX_MARKER_LEN)
26206 .collect();
26207
26208 for ordered_config in language.ordered_list() {
26209 let regex = match Regex::new(&ordered_config.pattern) {
26210 Ok(r) => r,
26211 Err(_) => continue,
26212 };
26213
26214 if let Some(captures) = regex.captures(&candidate) {
26215 let full_match = captures.get(0)?;
26216 let marker_len = full_match.len();
26217 let end_of_prefix = num_of_whitespaces + marker_len;
26218 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26219
26220 let has_content_after_marker = snapshot
26221 .chars_for_range(range)
26222 .skip(end_of_prefix)
26223 .any(|c| !c.is_whitespace());
26224
26225 if has_content_after_marker && cursor_is_after_prefix {
26226 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26227 let continuation = ordered_config
26228 .format
26229 .replace("{1}", &(number + 1).to_string());
26230 return Some(continuation.into());
26231 }
26232
26233 if start_point.column as usize == end_of_prefix {
26234 let continuation = ordered_config.format.replace("{1}", "1");
26235 if num_of_whitespaces == 0 {
26236 *newline_config = NewlineConfig::ClearCurrentLine;
26237 } else {
26238 *newline_config = NewlineConfig::UnindentCurrentLine {
26239 continuation: continuation.into(),
26240 };
26241 }
26242 }
26243
26244 return None;
26245 }
26246 }
26247
26248 None
26249}
26250
26251fn is_list_prefix_row(
26252 row: MultiBufferRow,
26253 buffer: &MultiBufferSnapshot,
26254 language: &LanguageScope,
26255) -> bool {
26256 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26257 return false;
26258 };
26259
26260 let num_of_whitespaces = snapshot
26261 .chars_for_range(range.clone())
26262 .take_while(|c| c.is_whitespace())
26263 .count();
26264
26265 let task_list_prefixes: Vec<_> = language
26266 .task_list()
26267 .into_iter()
26268 .flat_map(|config| {
26269 config
26270 .prefixes
26271 .iter()
26272 .map(|p| p.as_ref())
26273 .collect::<Vec<_>>()
26274 })
26275 .collect();
26276 let unordered_list_markers: Vec<_> = language
26277 .unordered_list()
26278 .iter()
26279 .map(|marker| marker.as_ref())
26280 .collect();
26281 let all_prefixes: Vec<_> = task_list_prefixes
26282 .into_iter()
26283 .chain(unordered_list_markers)
26284 .collect();
26285 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26286 let candidate: String = snapshot
26287 .chars_for_range(range.clone())
26288 .skip(num_of_whitespaces)
26289 .take(max_prefix_len)
26290 .collect();
26291 if all_prefixes
26292 .iter()
26293 .any(|prefix| candidate.starts_with(*prefix))
26294 {
26295 return true;
26296 }
26297 }
26298
26299 let ordered_list_candidate: String = snapshot
26300 .chars_for_range(range)
26301 .skip(num_of_whitespaces)
26302 .take(ORDERED_LIST_MAX_MARKER_LEN)
26303 .collect();
26304 for ordered_config in language.ordered_list() {
26305 let regex = match Regex::new(&ordered_config.pattern) {
26306 Ok(r) => r,
26307 Err(_) => continue,
26308 };
26309 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26310 return captures.get(0).is_some();
26311 }
26312 }
26313
26314 false
26315}
26316
26317#[derive(Debug)]
26318enum NewlineConfig {
26319 /// Insert newline with optional additional indent and optional extra blank line
26320 Newline {
26321 additional_indent: IndentSize,
26322 extra_line_additional_indent: Option<IndentSize>,
26323 prevent_auto_indent: bool,
26324 },
26325 /// Clear the current line
26326 ClearCurrentLine,
26327 /// Unindent the current line and add continuation
26328 UnindentCurrentLine { continuation: Arc<str> },
26329}
26330
26331impl NewlineConfig {
26332 fn has_extra_line(&self) -> bool {
26333 matches!(
26334 self,
26335 Self::Newline {
26336 extra_line_additional_indent: Some(_),
26337 ..
26338 }
26339 )
26340 }
26341
26342 fn insert_extra_newline_brackets(
26343 buffer: &MultiBufferSnapshot,
26344 range: Range<MultiBufferOffset>,
26345 language: &language::LanguageScope,
26346 ) -> bool {
26347 let leading_whitespace_len = buffer
26348 .reversed_chars_at(range.start)
26349 .take_while(|c| c.is_whitespace() && *c != '\n')
26350 .map(|c| c.len_utf8())
26351 .sum::<usize>();
26352 let trailing_whitespace_len = buffer
26353 .chars_at(range.end)
26354 .take_while(|c| c.is_whitespace() && *c != '\n')
26355 .map(|c| c.len_utf8())
26356 .sum::<usize>();
26357 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26358
26359 language.brackets().any(|(pair, enabled)| {
26360 let pair_start = pair.start.trim_end();
26361 let pair_end = pair.end.trim_start();
26362
26363 enabled
26364 && pair.newline
26365 && buffer.contains_str_at(range.end, pair_end)
26366 && buffer.contains_str_at(
26367 range.start.saturating_sub_usize(pair_start.len()),
26368 pair_start,
26369 )
26370 })
26371 }
26372
26373 fn insert_extra_newline_tree_sitter(
26374 buffer: &MultiBufferSnapshot,
26375 range: Range<MultiBufferOffset>,
26376 ) -> bool {
26377 let (buffer, range) = match buffer
26378 .range_to_buffer_ranges(range.start..=range.end)
26379 .as_slice()
26380 {
26381 [(buffer, range, _)] => (*buffer, range.clone()),
26382 _ => return false,
26383 };
26384 let pair = {
26385 let mut result: Option<BracketMatch<usize>> = None;
26386
26387 for pair in buffer
26388 .all_bracket_ranges(range.start.0..range.end.0)
26389 .filter(move |pair| {
26390 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26391 })
26392 {
26393 let len = pair.close_range.end - pair.open_range.start;
26394
26395 if let Some(existing) = &result {
26396 let existing_len = existing.close_range.end - existing.open_range.start;
26397 if len > existing_len {
26398 continue;
26399 }
26400 }
26401
26402 result = Some(pair);
26403 }
26404
26405 result
26406 };
26407 let Some(pair) = pair else {
26408 return false;
26409 };
26410 pair.newline_only
26411 && buffer
26412 .chars_for_range(pair.open_range.end..range.start.0)
26413 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26414 .all(|c| c.is_whitespace() && c != '\n')
26415 }
26416}
26417
26418fn update_uncommitted_diff_for_buffer(
26419 editor: Entity<Editor>,
26420 project: &Entity<Project>,
26421 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26422 buffer: Entity<MultiBuffer>,
26423 cx: &mut App,
26424) -> Task<()> {
26425 let mut tasks = Vec::new();
26426 project.update(cx, |project, cx| {
26427 for buffer in buffers {
26428 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26429 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26430 }
26431 }
26432 });
26433 cx.spawn(async move |cx| {
26434 let diffs = future::join_all(tasks).await;
26435 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26436 return;
26437 }
26438
26439 buffer.update(cx, |buffer, cx| {
26440 for diff in diffs.into_iter().flatten() {
26441 buffer.add_diff(diff, cx);
26442 }
26443 });
26444 })
26445}
26446
26447fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26448 let tab_size = tab_size.get() as usize;
26449 let mut width = offset;
26450
26451 for ch in text.chars() {
26452 width += if ch == '\t' {
26453 tab_size - (width % tab_size)
26454 } else {
26455 1
26456 };
26457 }
26458
26459 width - offset
26460}
26461
26462#[cfg(test)]
26463mod tests {
26464 use super::*;
26465
26466 #[test]
26467 fn test_string_size_with_expanded_tabs() {
26468 let nz = |val| NonZeroU32::new(val).unwrap();
26469 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26470 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26471 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26472 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26473 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26474 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26475 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26476 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26477 }
26478}
26479
26480/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26481struct WordBreakingTokenizer<'a> {
26482 input: &'a str,
26483}
26484
26485impl<'a> WordBreakingTokenizer<'a> {
26486 fn new(input: &'a str) -> Self {
26487 Self { input }
26488 }
26489}
26490
26491fn is_char_ideographic(ch: char) -> bool {
26492 use unicode_script::Script::*;
26493 use unicode_script::UnicodeScript;
26494 matches!(ch.script(), Han | Tangut | Yi)
26495}
26496
26497fn is_grapheme_ideographic(text: &str) -> bool {
26498 text.chars().any(is_char_ideographic)
26499}
26500
26501fn is_grapheme_whitespace(text: &str) -> bool {
26502 text.chars().any(|x| x.is_whitespace())
26503}
26504
26505fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26506 text.chars()
26507 .next()
26508 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26509}
26510
26511#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26512enum WordBreakToken<'a> {
26513 Word { token: &'a str, grapheme_len: usize },
26514 InlineWhitespace { token: &'a str, grapheme_len: usize },
26515 Newline,
26516}
26517
26518impl<'a> Iterator for WordBreakingTokenizer<'a> {
26519 /// Yields a span, the count of graphemes in the token, and whether it was
26520 /// whitespace. Note that it also breaks at word boundaries.
26521 type Item = WordBreakToken<'a>;
26522
26523 fn next(&mut self) -> Option<Self::Item> {
26524 use unicode_segmentation::UnicodeSegmentation;
26525 if self.input.is_empty() {
26526 return None;
26527 }
26528
26529 let mut iter = self.input.graphemes(true).peekable();
26530 let mut offset = 0;
26531 let mut grapheme_len = 0;
26532 if let Some(first_grapheme) = iter.next() {
26533 let is_newline = first_grapheme == "\n";
26534 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26535 offset += first_grapheme.len();
26536 grapheme_len += 1;
26537 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26538 if let Some(grapheme) = iter.peek().copied()
26539 && should_stay_with_preceding_ideograph(grapheme)
26540 {
26541 offset += grapheme.len();
26542 grapheme_len += 1;
26543 }
26544 } else {
26545 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26546 let mut next_word_bound = words.peek().copied();
26547 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26548 next_word_bound = words.next();
26549 }
26550 while let Some(grapheme) = iter.peek().copied() {
26551 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26552 break;
26553 };
26554 if is_grapheme_whitespace(grapheme) != is_whitespace
26555 || (grapheme == "\n") != is_newline
26556 {
26557 break;
26558 };
26559 offset += grapheme.len();
26560 grapheme_len += 1;
26561 iter.next();
26562 }
26563 }
26564 let token = &self.input[..offset];
26565 self.input = &self.input[offset..];
26566 if token == "\n" {
26567 Some(WordBreakToken::Newline)
26568 } else if is_whitespace {
26569 Some(WordBreakToken::InlineWhitespace {
26570 token,
26571 grapheme_len,
26572 })
26573 } else {
26574 Some(WordBreakToken::Word {
26575 token,
26576 grapheme_len,
26577 })
26578 }
26579 } else {
26580 None
26581 }
26582 }
26583}
26584
26585#[test]
26586fn test_word_breaking_tokenizer() {
26587 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26588 ("", &[]),
26589 (" ", &[whitespace(" ", 2)]),
26590 ("Ʒ", &[word("Ʒ", 1)]),
26591 ("Ǽ", &[word("Ǽ", 1)]),
26592 ("⋑", &[word("⋑", 1)]),
26593 ("⋑⋑", &[word("⋑⋑", 2)]),
26594 (
26595 "原理,进而",
26596 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26597 ),
26598 (
26599 "hello world",
26600 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26601 ),
26602 (
26603 "hello, world",
26604 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26605 ),
26606 (
26607 " hello world",
26608 &[
26609 whitespace(" ", 2),
26610 word("hello", 5),
26611 whitespace(" ", 1),
26612 word("world", 5),
26613 ],
26614 ),
26615 (
26616 "这是什么 \n 钢笔",
26617 &[
26618 word("这", 1),
26619 word("是", 1),
26620 word("什", 1),
26621 word("么", 1),
26622 whitespace(" ", 1),
26623 newline(),
26624 whitespace(" ", 1),
26625 word("钢", 1),
26626 word("笔", 1),
26627 ],
26628 ),
26629 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26630 ];
26631
26632 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26633 WordBreakToken::Word {
26634 token,
26635 grapheme_len,
26636 }
26637 }
26638
26639 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26640 WordBreakToken::InlineWhitespace {
26641 token,
26642 grapheme_len,
26643 }
26644 }
26645
26646 fn newline() -> WordBreakToken<'static> {
26647 WordBreakToken::Newline
26648 }
26649
26650 for (input, result) in tests {
26651 assert_eq!(
26652 WordBreakingTokenizer::new(input)
26653 .collect::<Vec<_>>()
26654 .as_slice(),
26655 *result,
26656 );
26657 }
26658}
26659
26660fn wrap_with_prefix(
26661 first_line_prefix: String,
26662 subsequent_lines_prefix: String,
26663 unwrapped_text: String,
26664 wrap_column: usize,
26665 tab_size: NonZeroU32,
26666 preserve_existing_whitespace: bool,
26667) -> String {
26668 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26669 let subsequent_lines_prefix_len =
26670 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26671 let mut wrapped_text = String::new();
26672 let mut current_line = first_line_prefix;
26673 let mut is_first_line = true;
26674
26675 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26676 let mut current_line_len = first_line_prefix_len;
26677 let mut in_whitespace = false;
26678 for token in tokenizer {
26679 let have_preceding_whitespace = in_whitespace;
26680 match token {
26681 WordBreakToken::Word {
26682 token,
26683 grapheme_len,
26684 } => {
26685 in_whitespace = false;
26686 let current_prefix_len = if is_first_line {
26687 first_line_prefix_len
26688 } else {
26689 subsequent_lines_prefix_len
26690 };
26691 if current_line_len + grapheme_len > wrap_column
26692 && current_line_len != current_prefix_len
26693 {
26694 wrapped_text.push_str(current_line.trim_end());
26695 wrapped_text.push('\n');
26696 is_first_line = false;
26697 current_line = subsequent_lines_prefix.clone();
26698 current_line_len = subsequent_lines_prefix_len;
26699 }
26700 current_line.push_str(token);
26701 current_line_len += grapheme_len;
26702 }
26703 WordBreakToken::InlineWhitespace {
26704 mut token,
26705 mut grapheme_len,
26706 } => {
26707 in_whitespace = true;
26708 if have_preceding_whitespace && !preserve_existing_whitespace {
26709 continue;
26710 }
26711 if !preserve_existing_whitespace {
26712 // Keep a single whitespace grapheme as-is
26713 if let Some(first) =
26714 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26715 {
26716 token = first;
26717 } else {
26718 token = " ";
26719 }
26720 grapheme_len = 1;
26721 }
26722 let current_prefix_len = if is_first_line {
26723 first_line_prefix_len
26724 } else {
26725 subsequent_lines_prefix_len
26726 };
26727 if current_line_len + grapheme_len > wrap_column {
26728 wrapped_text.push_str(current_line.trim_end());
26729 wrapped_text.push('\n');
26730 is_first_line = false;
26731 current_line = subsequent_lines_prefix.clone();
26732 current_line_len = subsequent_lines_prefix_len;
26733 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26734 current_line.push_str(token);
26735 current_line_len += grapheme_len;
26736 }
26737 }
26738 WordBreakToken::Newline => {
26739 in_whitespace = true;
26740 let current_prefix_len = if is_first_line {
26741 first_line_prefix_len
26742 } else {
26743 subsequent_lines_prefix_len
26744 };
26745 if preserve_existing_whitespace {
26746 wrapped_text.push_str(current_line.trim_end());
26747 wrapped_text.push('\n');
26748 is_first_line = false;
26749 current_line = subsequent_lines_prefix.clone();
26750 current_line_len = subsequent_lines_prefix_len;
26751 } else if have_preceding_whitespace {
26752 continue;
26753 } else if current_line_len + 1 > wrap_column
26754 && current_line_len != current_prefix_len
26755 {
26756 wrapped_text.push_str(current_line.trim_end());
26757 wrapped_text.push('\n');
26758 is_first_line = false;
26759 current_line = subsequent_lines_prefix.clone();
26760 current_line_len = subsequent_lines_prefix_len;
26761 } else if current_line_len != current_prefix_len {
26762 current_line.push(' ');
26763 current_line_len += 1;
26764 }
26765 }
26766 }
26767 }
26768
26769 if !current_line.is_empty() {
26770 wrapped_text.push_str(¤t_line);
26771 }
26772 wrapped_text
26773}
26774
26775#[test]
26776fn test_wrap_with_prefix() {
26777 assert_eq!(
26778 wrap_with_prefix(
26779 "# ".to_string(),
26780 "# ".to_string(),
26781 "abcdefg".to_string(),
26782 4,
26783 NonZeroU32::new(4).unwrap(),
26784 false,
26785 ),
26786 "# abcdefg"
26787 );
26788 assert_eq!(
26789 wrap_with_prefix(
26790 "".to_string(),
26791 "".to_string(),
26792 "\thello world".to_string(),
26793 8,
26794 NonZeroU32::new(4).unwrap(),
26795 false,
26796 ),
26797 "hello\nworld"
26798 );
26799 assert_eq!(
26800 wrap_with_prefix(
26801 "// ".to_string(),
26802 "// ".to_string(),
26803 "xx \nyy zz aa bb cc".to_string(),
26804 12,
26805 NonZeroU32::new(4).unwrap(),
26806 false,
26807 ),
26808 "// xx yy zz\n// aa bb cc"
26809 );
26810 assert_eq!(
26811 wrap_with_prefix(
26812 String::new(),
26813 String::new(),
26814 "这是什么 \n 钢笔".to_string(),
26815 3,
26816 NonZeroU32::new(4).unwrap(),
26817 false,
26818 ),
26819 "这是什\n么 钢\n笔"
26820 );
26821 assert_eq!(
26822 wrap_with_prefix(
26823 String::new(),
26824 String::new(),
26825 format!("foo{}bar", '\u{2009}'), // thin space
26826 80,
26827 NonZeroU32::new(4).unwrap(),
26828 false,
26829 ),
26830 format!("foo{}bar", '\u{2009}')
26831 );
26832}
26833
26834pub trait CollaborationHub {
26835 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26836 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26837 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26838}
26839
26840impl CollaborationHub for Entity<Project> {
26841 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26842 self.read(cx).collaborators()
26843 }
26844
26845 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26846 self.read(cx).user_store().read(cx).participant_indices()
26847 }
26848
26849 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26850 let this = self.read(cx);
26851 let user_ids = this.collaborators().values().map(|c| c.user_id);
26852 this.user_store().read(cx).participant_names(user_ids, cx)
26853 }
26854}
26855
26856pub trait SemanticsProvider {
26857 fn hover(
26858 &self,
26859 buffer: &Entity<Buffer>,
26860 position: text::Anchor,
26861 cx: &mut App,
26862 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26863
26864 fn inline_values(
26865 &self,
26866 buffer_handle: Entity<Buffer>,
26867 range: Range<text::Anchor>,
26868 cx: &mut App,
26869 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26870
26871 fn applicable_inlay_chunks(
26872 &self,
26873 buffer: &Entity<Buffer>,
26874 ranges: &[Range<text::Anchor>],
26875 cx: &mut App,
26876 ) -> Vec<Range<BufferRow>>;
26877
26878 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26879
26880 fn inlay_hints(
26881 &self,
26882 invalidate: InvalidationStrategy,
26883 buffer: Entity<Buffer>,
26884 ranges: Vec<Range<text::Anchor>>,
26885 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26886 cx: &mut App,
26887 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26888
26889 fn semantic_tokens(
26890 &self,
26891 buffer: Entity<Buffer>,
26892 refresh: Option<RefreshForServer>,
26893 cx: &mut App,
26894 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
26895
26896 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26897
26898 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26899
26900 fn document_highlights(
26901 &self,
26902 buffer: &Entity<Buffer>,
26903 position: text::Anchor,
26904 cx: &mut App,
26905 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26906
26907 fn definitions(
26908 &self,
26909 buffer: &Entity<Buffer>,
26910 position: text::Anchor,
26911 kind: GotoDefinitionKind,
26912 cx: &mut App,
26913 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26914
26915 fn range_for_rename(
26916 &self,
26917 buffer: &Entity<Buffer>,
26918 position: text::Anchor,
26919 cx: &mut App,
26920 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26921
26922 fn perform_rename(
26923 &self,
26924 buffer: &Entity<Buffer>,
26925 position: text::Anchor,
26926 new_name: String,
26927 cx: &mut App,
26928 ) -> Option<Task<Result<ProjectTransaction>>>;
26929}
26930
26931pub trait CompletionProvider {
26932 fn completions(
26933 &self,
26934 excerpt_id: ExcerptId,
26935 buffer: &Entity<Buffer>,
26936 buffer_position: text::Anchor,
26937 trigger: CompletionContext,
26938 window: &mut Window,
26939 cx: &mut Context<Editor>,
26940 ) -> Task<Result<Vec<CompletionResponse>>>;
26941
26942 fn resolve_completions(
26943 &self,
26944 _buffer: Entity<Buffer>,
26945 _completion_indices: Vec<usize>,
26946 _completions: Rc<RefCell<Box<[Completion]>>>,
26947 _cx: &mut Context<Editor>,
26948 ) -> Task<Result<bool>> {
26949 Task::ready(Ok(false))
26950 }
26951
26952 fn apply_additional_edits_for_completion(
26953 &self,
26954 _buffer: Entity<Buffer>,
26955 _completions: Rc<RefCell<Box<[Completion]>>>,
26956 _completion_index: usize,
26957 _push_to_history: bool,
26958 _all_commit_ranges: Vec<Range<language::Anchor>>,
26959 _cx: &mut Context<Editor>,
26960 ) -> Task<Result<Option<language::Transaction>>> {
26961 Task::ready(Ok(None))
26962 }
26963
26964 fn is_completion_trigger(
26965 &self,
26966 buffer: &Entity<Buffer>,
26967 position: language::Anchor,
26968 text: &str,
26969 trigger_in_words: bool,
26970 cx: &mut Context<Editor>,
26971 ) -> bool;
26972
26973 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26974
26975 fn sort_completions(&self) -> bool {
26976 true
26977 }
26978
26979 fn filter_completions(&self) -> bool {
26980 true
26981 }
26982
26983 fn show_snippets(&self) -> bool {
26984 false
26985 }
26986}
26987
26988pub trait CodeActionProvider {
26989 fn id(&self) -> Arc<str>;
26990
26991 fn code_actions(
26992 &self,
26993 buffer: &Entity<Buffer>,
26994 range: Range<text::Anchor>,
26995 window: &mut Window,
26996 cx: &mut App,
26997 ) -> Task<Result<Vec<CodeAction>>>;
26998
26999 fn apply_code_action(
27000 &self,
27001 buffer_handle: Entity<Buffer>,
27002 action: CodeAction,
27003 excerpt_id: ExcerptId,
27004 push_to_history: bool,
27005 window: &mut Window,
27006 cx: &mut App,
27007 ) -> Task<Result<ProjectTransaction>>;
27008}
27009
27010impl CodeActionProvider for Entity<Project> {
27011 fn id(&self) -> Arc<str> {
27012 "project".into()
27013 }
27014
27015 fn code_actions(
27016 &self,
27017 buffer: &Entity<Buffer>,
27018 range: Range<text::Anchor>,
27019 _window: &mut Window,
27020 cx: &mut App,
27021 ) -> Task<Result<Vec<CodeAction>>> {
27022 self.update(cx, |project, cx| {
27023 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27024 let code_actions = project.code_actions(buffer, range, None, cx);
27025 cx.background_spawn(async move {
27026 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27027 Ok(code_lens_actions
27028 .context("code lens fetch")?
27029 .into_iter()
27030 .flatten()
27031 .chain(
27032 code_actions
27033 .context("code action fetch")?
27034 .into_iter()
27035 .flatten(),
27036 )
27037 .collect())
27038 })
27039 })
27040 }
27041
27042 fn apply_code_action(
27043 &self,
27044 buffer_handle: Entity<Buffer>,
27045 action: CodeAction,
27046 _excerpt_id: ExcerptId,
27047 push_to_history: bool,
27048 _window: &mut Window,
27049 cx: &mut App,
27050 ) -> Task<Result<ProjectTransaction>> {
27051 self.update(cx, |project, cx| {
27052 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27053 })
27054 }
27055}
27056
27057fn snippet_completions(
27058 project: &Project,
27059 buffer: &Entity<Buffer>,
27060 buffer_anchor: text::Anchor,
27061 classifier: CharClassifier,
27062 cx: &mut App,
27063) -> Task<Result<CompletionResponse>> {
27064 let languages = buffer.read(cx).languages_at(buffer_anchor);
27065 let snippet_store = project.snippets().read(cx);
27066
27067 let scopes: Vec<_> = languages
27068 .iter()
27069 .filter_map(|language| {
27070 let language_name = language.lsp_id();
27071 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27072
27073 if snippets.is_empty() {
27074 None
27075 } else {
27076 Some((language.default_scope(), snippets))
27077 }
27078 })
27079 .collect();
27080
27081 if scopes.is_empty() {
27082 return Task::ready(Ok(CompletionResponse {
27083 completions: vec![],
27084 display_options: CompletionDisplayOptions::default(),
27085 is_incomplete: false,
27086 }));
27087 }
27088
27089 let snapshot = buffer.read(cx).text_snapshot();
27090 let executor = cx.background_executor().clone();
27091
27092 cx.background_spawn(async move {
27093 let is_word_char = |c| classifier.is_word(c);
27094
27095 let mut is_incomplete = false;
27096 let mut completions: Vec<Completion> = Vec::new();
27097
27098 const MAX_PREFIX_LEN: usize = 128;
27099 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27100 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27101 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27102
27103 let max_buffer_window: String = snapshot
27104 .text_for_range(window_start..buffer_offset)
27105 .collect();
27106
27107 if max_buffer_window.is_empty() {
27108 return Ok(CompletionResponse {
27109 completions: vec![],
27110 display_options: CompletionDisplayOptions::default(),
27111 is_incomplete: true,
27112 });
27113 }
27114
27115 for (_scope, snippets) in scopes.into_iter() {
27116 // Sort snippets by word count to match longer snippet prefixes first.
27117 let mut sorted_snippet_candidates = snippets
27118 .iter()
27119 .enumerate()
27120 .flat_map(|(snippet_ix, snippet)| {
27121 snippet
27122 .prefix
27123 .iter()
27124 .enumerate()
27125 .map(move |(prefix_ix, prefix)| {
27126 let word_count =
27127 snippet_candidate_suffixes(prefix, &is_word_char).count();
27128 ((snippet_ix, prefix_ix), prefix, word_count)
27129 })
27130 })
27131 .collect_vec();
27132 sorted_snippet_candidates
27133 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27134
27135 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27136
27137 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27138 .take(
27139 sorted_snippet_candidates
27140 .first()
27141 .map(|(_, _, word_count)| *word_count)
27142 .unwrap_or_default(),
27143 )
27144 .collect_vec();
27145
27146 const MAX_RESULTS: usize = 100;
27147 // Each match also remembers how many characters from the buffer it consumed
27148 let mut matches: Vec<(StringMatch, usize)> = vec![];
27149
27150 let mut snippet_list_cutoff_index = 0;
27151 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27152 let word_count = buffer_index + 1;
27153 // Increase `snippet_list_cutoff_index` until we have all of the
27154 // snippets with sufficiently many words.
27155 while sorted_snippet_candidates
27156 .get(snippet_list_cutoff_index)
27157 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27158 *snippet_word_count >= word_count
27159 })
27160 {
27161 snippet_list_cutoff_index += 1;
27162 }
27163
27164 // Take only the candidates with at least `word_count` many words
27165 let snippet_candidates_at_word_len =
27166 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27167
27168 let candidates = snippet_candidates_at_word_len
27169 .iter()
27170 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27171 .enumerate() // index in `sorted_snippet_candidates`
27172 // First char must match
27173 .filter(|(_ix, prefix)| {
27174 itertools::equal(
27175 prefix
27176 .chars()
27177 .next()
27178 .into_iter()
27179 .flat_map(|c| c.to_lowercase()),
27180 buffer_window
27181 .chars()
27182 .next()
27183 .into_iter()
27184 .flat_map(|c| c.to_lowercase()),
27185 )
27186 })
27187 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27188 .collect::<Vec<StringMatchCandidate>>();
27189
27190 matches.extend(
27191 fuzzy::match_strings(
27192 &candidates,
27193 &buffer_window,
27194 buffer_window.chars().any(|c| c.is_uppercase()),
27195 true,
27196 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27197 &Default::default(),
27198 executor.clone(),
27199 )
27200 .await
27201 .into_iter()
27202 .map(|string_match| (string_match, buffer_window.len())),
27203 );
27204
27205 if matches.len() >= MAX_RESULTS {
27206 break;
27207 }
27208 }
27209
27210 let to_lsp = |point: &text::Anchor| {
27211 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27212 point_to_lsp(end)
27213 };
27214 let lsp_end = to_lsp(&buffer_anchor);
27215
27216 if matches.len() >= MAX_RESULTS {
27217 is_incomplete = true;
27218 }
27219
27220 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27221 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27222 sorted_snippet_candidates[string_match.candidate_id];
27223 let snippet = &snippets[snippet_index];
27224 let start = buffer_offset - buffer_window_len;
27225 let start = snapshot.anchor_before(start);
27226 let range = start..buffer_anchor;
27227 let lsp_start = to_lsp(&start);
27228 let lsp_range = lsp::Range {
27229 start: lsp_start,
27230 end: lsp_end,
27231 };
27232 Completion {
27233 replace_range: range,
27234 new_text: snippet.body.clone(),
27235 source: CompletionSource::Lsp {
27236 insert_range: None,
27237 server_id: LanguageServerId(usize::MAX),
27238 resolved: true,
27239 lsp_completion: Box::new(lsp::CompletionItem {
27240 label: snippet.prefix.first().unwrap().clone(),
27241 kind: Some(CompletionItemKind::SNIPPET),
27242 label_details: snippet.description.as_ref().map(|description| {
27243 lsp::CompletionItemLabelDetails {
27244 detail: Some(description.clone()),
27245 description: None,
27246 }
27247 }),
27248 insert_text_format: Some(InsertTextFormat::SNIPPET),
27249 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27250 lsp::InsertReplaceEdit {
27251 new_text: snippet.body.clone(),
27252 insert: lsp_range,
27253 replace: lsp_range,
27254 },
27255 )),
27256 filter_text: Some(snippet.body.clone()),
27257 sort_text: Some(char::MAX.to_string()),
27258 ..lsp::CompletionItem::default()
27259 }),
27260 lsp_defaults: None,
27261 },
27262 label: CodeLabel {
27263 text: matching_prefix.clone(),
27264 runs: Vec::new(),
27265 filter_range: 0..matching_prefix.len(),
27266 },
27267 icon_path: None,
27268 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27269 single_line: snippet.name.clone().into(),
27270 plain_text: snippet
27271 .description
27272 .clone()
27273 .map(|description| description.into()),
27274 }),
27275 insert_text_mode: None,
27276 confirm: None,
27277 match_start: Some(start),
27278 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27279 }
27280 }));
27281 }
27282
27283 Ok(CompletionResponse {
27284 completions,
27285 display_options: CompletionDisplayOptions::default(),
27286 is_incomplete,
27287 })
27288 })
27289}
27290
27291impl CompletionProvider for Entity<Project> {
27292 fn completions(
27293 &self,
27294 _excerpt_id: ExcerptId,
27295 buffer: &Entity<Buffer>,
27296 buffer_position: text::Anchor,
27297 options: CompletionContext,
27298 _window: &mut Window,
27299 cx: &mut Context<Editor>,
27300 ) -> Task<Result<Vec<CompletionResponse>>> {
27301 self.update(cx, |project, cx| {
27302 let task = project.completions(buffer, buffer_position, options, cx);
27303 cx.background_spawn(task)
27304 })
27305 }
27306
27307 fn resolve_completions(
27308 &self,
27309 buffer: Entity<Buffer>,
27310 completion_indices: Vec<usize>,
27311 completions: Rc<RefCell<Box<[Completion]>>>,
27312 cx: &mut Context<Editor>,
27313 ) -> Task<Result<bool>> {
27314 self.update(cx, |project, cx| {
27315 project.lsp_store().update(cx, |lsp_store, cx| {
27316 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27317 })
27318 })
27319 }
27320
27321 fn apply_additional_edits_for_completion(
27322 &self,
27323 buffer: Entity<Buffer>,
27324 completions: Rc<RefCell<Box<[Completion]>>>,
27325 completion_index: usize,
27326 push_to_history: bool,
27327 all_commit_ranges: Vec<Range<language::Anchor>>,
27328 cx: &mut Context<Editor>,
27329 ) -> Task<Result<Option<language::Transaction>>> {
27330 self.update(cx, |project, cx| {
27331 project.lsp_store().update(cx, |lsp_store, cx| {
27332 lsp_store.apply_additional_edits_for_completion(
27333 buffer,
27334 completions,
27335 completion_index,
27336 push_to_history,
27337 all_commit_ranges,
27338 cx,
27339 )
27340 })
27341 })
27342 }
27343
27344 fn is_completion_trigger(
27345 &self,
27346 buffer: &Entity<Buffer>,
27347 position: language::Anchor,
27348 text: &str,
27349 trigger_in_words: bool,
27350 cx: &mut Context<Editor>,
27351 ) -> bool {
27352 let mut chars = text.chars();
27353 let char = if let Some(char) = chars.next() {
27354 char
27355 } else {
27356 return false;
27357 };
27358 if chars.next().is_some() {
27359 return false;
27360 }
27361
27362 let buffer = buffer.read(cx);
27363 let snapshot = buffer.snapshot();
27364 let classifier = snapshot
27365 .char_classifier_at(position)
27366 .scope_context(Some(CharScopeContext::Completion));
27367 if trigger_in_words && classifier.is_word(char) {
27368 return true;
27369 }
27370
27371 buffer.completion_triggers().contains(text)
27372 }
27373
27374 fn show_snippets(&self) -> bool {
27375 true
27376 }
27377}
27378
27379impl SemanticsProvider for WeakEntity<Project> {
27380 fn hover(
27381 &self,
27382 buffer: &Entity<Buffer>,
27383 position: text::Anchor,
27384 cx: &mut App,
27385 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27386 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27387 .ok()
27388 }
27389
27390 fn document_highlights(
27391 &self,
27392 buffer: &Entity<Buffer>,
27393 position: text::Anchor,
27394 cx: &mut App,
27395 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27396 self.update(cx, |project, cx| {
27397 project.document_highlights(buffer, position, cx)
27398 })
27399 .ok()
27400 }
27401
27402 fn definitions(
27403 &self,
27404 buffer: &Entity<Buffer>,
27405 position: text::Anchor,
27406 kind: GotoDefinitionKind,
27407 cx: &mut App,
27408 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27409 self.update(cx, |project, cx| match kind {
27410 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27411 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27412 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27413 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27414 })
27415 .ok()
27416 }
27417
27418 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27419 self.update(cx, |project, cx| {
27420 if project
27421 .active_debug_session(cx)
27422 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27423 {
27424 return true;
27425 }
27426
27427 buffer.update(cx, |buffer, cx| {
27428 project.any_language_server_supports_inlay_hints(buffer, cx)
27429 })
27430 })
27431 .unwrap_or(false)
27432 }
27433
27434 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27435 self.update(cx, |project, cx| {
27436 buffer.update(cx, |buffer, cx| {
27437 project.any_language_server_supports_semantic_tokens(buffer, cx)
27438 })
27439 })
27440 .unwrap_or(false)
27441 }
27442
27443 fn inline_values(
27444 &self,
27445 buffer_handle: Entity<Buffer>,
27446 range: Range<text::Anchor>,
27447 cx: &mut App,
27448 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27449 self.update(cx, |project, cx| {
27450 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27451
27452 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27453 })
27454 .ok()
27455 .flatten()
27456 }
27457
27458 fn applicable_inlay_chunks(
27459 &self,
27460 buffer: &Entity<Buffer>,
27461 ranges: &[Range<text::Anchor>],
27462 cx: &mut App,
27463 ) -> Vec<Range<BufferRow>> {
27464 self.update(cx, |project, cx| {
27465 project.lsp_store().update(cx, |lsp_store, cx| {
27466 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27467 })
27468 })
27469 .unwrap_or_default()
27470 }
27471
27472 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27473 self.update(cx, |project, cx| {
27474 project.lsp_store().update(cx, |lsp_store, _| {
27475 lsp_store.invalidate_inlay_hints(for_buffers)
27476 })
27477 })
27478 .ok();
27479 }
27480
27481 fn inlay_hints(
27482 &self,
27483 invalidate: InvalidationStrategy,
27484 buffer: Entity<Buffer>,
27485 ranges: Vec<Range<text::Anchor>>,
27486 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27487 cx: &mut App,
27488 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27489 self.update(cx, |project, cx| {
27490 project.lsp_store().update(cx, |lsp_store, cx| {
27491 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27492 })
27493 })
27494 .ok()
27495 }
27496
27497 fn semantic_tokens(
27498 &self,
27499 buffer: Entity<Buffer>,
27500 refresh: Option<RefreshForServer>,
27501 cx: &mut App,
27502 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27503 self.update(cx, |this, cx| {
27504 this.lsp_store().update(cx, |lsp_store, cx| {
27505 lsp_store.semantic_tokens(buffer, refresh, cx)
27506 })
27507 })
27508 .ok()
27509 }
27510
27511 fn range_for_rename(
27512 &self,
27513 buffer: &Entity<Buffer>,
27514 position: text::Anchor,
27515 cx: &mut App,
27516 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27517 self.update(cx, |project, cx| {
27518 let buffer = buffer.clone();
27519 let task = project.prepare_rename(buffer.clone(), position, cx);
27520 cx.spawn(async move |_, cx| {
27521 Ok(match task.await? {
27522 PrepareRenameResponse::Success(range) => Some(range),
27523 PrepareRenameResponse::InvalidPosition => None,
27524 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27525 // Fallback on using TreeSitter info to determine identifier range
27526 buffer.read_with(cx, |buffer, _| {
27527 let snapshot = buffer.snapshot();
27528 let (range, kind) = snapshot.surrounding_word(position, None);
27529 if kind != Some(CharKind::Word) {
27530 return None;
27531 }
27532 Some(
27533 snapshot.anchor_before(range.start)
27534 ..snapshot.anchor_after(range.end),
27535 )
27536 })
27537 }
27538 })
27539 })
27540 })
27541 .ok()
27542 }
27543
27544 fn perform_rename(
27545 &self,
27546 buffer: &Entity<Buffer>,
27547 position: text::Anchor,
27548 new_name: String,
27549 cx: &mut App,
27550 ) -> Option<Task<Result<ProjectTransaction>>> {
27551 self.update(cx, |project, cx| {
27552 project.perform_rename(buffer.clone(), position, new_name, cx)
27553 })
27554 .ok()
27555 }
27556}
27557
27558fn consume_contiguous_rows(
27559 contiguous_row_selections: &mut Vec<Selection<Point>>,
27560 selection: &Selection<Point>,
27561 display_map: &DisplaySnapshot,
27562 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27563) -> (MultiBufferRow, MultiBufferRow) {
27564 contiguous_row_selections.push(selection.clone());
27565 let start_row = starting_row(selection, display_map);
27566 let mut end_row = ending_row(selection, display_map);
27567
27568 while let Some(next_selection) = selections.peek() {
27569 if next_selection.start.row <= end_row.0 {
27570 end_row = ending_row(next_selection, display_map);
27571 contiguous_row_selections.push(selections.next().unwrap().clone());
27572 } else {
27573 break;
27574 }
27575 }
27576 (start_row, end_row)
27577}
27578
27579fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27580 if selection.start.column > 0 {
27581 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27582 } else {
27583 MultiBufferRow(selection.start.row)
27584 }
27585}
27586
27587fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27588 if next_selection.end.column > 0 || next_selection.is_empty() {
27589 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27590 } else {
27591 MultiBufferRow(next_selection.end.row)
27592 }
27593}
27594
27595impl EditorSnapshot {
27596 pub fn remote_selections_in_range<'a>(
27597 &'a self,
27598 range: &'a Range<Anchor>,
27599 collaboration_hub: &dyn CollaborationHub,
27600 cx: &'a App,
27601 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27602 let participant_names = collaboration_hub.user_names(cx);
27603 let participant_indices = collaboration_hub.user_participant_indices(cx);
27604 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27605 let collaborators_by_replica_id = collaborators_by_peer_id
27606 .values()
27607 .map(|collaborator| (collaborator.replica_id, collaborator))
27608 .collect::<HashMap<_, _>>();
27609 self.buffer_snapshot()
27610 .selections_in_range(range, false)
27611 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27612 if replica_id == ReplicaId::AGENT {
27613 Some(RemoteSelection {
27614 replica_id,
27615 selection,
27616 cursor_shape,
27617 line_mode,
27618 collaborator_id: CollaboratorId::Agent,
27619 user_name: Some("Agent".into()),
27620 color: cx.theme().players().agent(),
27621 })
27622 } else {
27623 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27624 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27625 let user_name = participant_names.get(&collaborator.user_id).cloned();
27626 Some(RemoteSelection {
27627 replica_id,
27628 selection,
27629 cursor_shape,
27630 line_mode,
27631 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27632 user_name,
27633 color: if let Some(index) = participant_index {
27634 cx.theme().players().color_for_participant(index.0)
27635 } else {
27636 cx.theme().players().absent()
27637 },
27638 })
27639 }
27640 })
27641 }
27642
27643 pub fn hunks_for_ranges(
27644 &self,
27645 ranges: impl IntoIterator<Item = Range<Point>>,
27646 ) -> Vec<MultiBufferDiffHunk> {
27647 let mut hunks = Vec::new();
27648 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27649 HashMap::default();
27650 for query_range in ranges {
27651 let query_rows =
27652 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27653 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27654 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27655 ) {
27656 // Include deleted hunks that are adjacent to the query range, because
27657 // otherwise they would be missed.
27658 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27659 if hunk.status().is_deleted() {
27660 intersects_range |= hunk.row_range.start == query_rows.end;
27661 intersects_range |= hunk.row_range.end == query_rows.start;
27662 }
27663 if intersects_range {
27664 if !processed_buffer_rows
27665 .entry(hunk.buffer_id)
27666 .or_default()
27667 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27668 {
27669 continue;
27670 }
27671 hunks.push(hunk);
27672 }
27673 }
27674 }
27675
27676 hunks
27677 }
27678
27679 fn display_diff_hunks_for_rows<'a>(
27680 &'a self,
27681 display_rows: Range<DisplayRow>,
27682 folded_buffers: &'a HashSet<BufferId>,
27683 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27684 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27685 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27686
27687 self.buffer_snapshot()
27688 .diff_hunks_in_range(buffer_start..buffer_end)
27689 .filter_map(|hunk| {
27690 if folded_buffers.contains(&hunk.buffer_id)
27691 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27692 {
27693 return None;
27694 }
27695
27696 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27697 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27698 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27699 let line_len = self.buffer_snapshot().line_len(last_row);
27700 Point::new(last_row.0, line_len)
27701 } else {
27702 Point::new(hunk.row_range.end.0, 0)
27703 };
27704
27705 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27706 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27707
27708 let display_hunk = if hunk_display_start.column() != 0 {
27709 DisplayDiffHunk::Folded {
27710 display_row: hunk_display_start.row(),
27711 }
27712 } else {
27713 let mut end_row = hunk_display_end.row();
27714 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27715 end_row.0 += 1;
27716 }
27717 let is_created_file = hunk.is_created_file();
27718
27719 DisplayDiffHunk::Unfolded {
27720 status: hunk.status(),
27721 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27722 ..hunk.diff_base_byte_range.end.0,
27723 word_diffs: hunk.word_diffs,
27724 display_row_range: hunk_display_start.row()..end_row,
27725 multi_buffer_range: Anchor::range_in_buffer(
27726 hunk.excerpt_id,
27727 hunk.buffer_range,
27728 ),
27729 is_created_file,
27730 }
27731 };
27732
27733 Some(display_hunk)
27734 })
27735 }
27736
27737 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27738 self.display_snapshot
27739 .buffer_snapshot()
27740 .language_at(position)
27741 }
27742
27743 pub fn is_focused(&self) -> bool {
27744 self.is_focused
27745 }
27746
27747 pub fn placeholder_text(&self) -> Option<String> {
27748 self.placeholder_display_snapshot
27749 .as_ref()
27750 .map(|display_map| display_map.text())
27751 }
27752
27753 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27754 self.scroll_anchor.scroll_position(&self.display_snapshot)
27755 }
27756
27757 pub fn gutter_dimensions(
27758 &self,
27759 font_id: FontId,
27760 font_size: Pixels,
27761 style: &EditorStyle,
27762 window: &mut Window,
27763 cx: &App,
27764 ) -> GutterDimensions {
27765 if self.show_gutter
27766 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27767 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27768 {
27769 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27770 matches!(
27771 ProjectSettings::get_global(cx).git.git_gutter,
27772 GitGutterSetting::TrackedFiles
27773 )
27774 });
27775 let gutter_settings = EditorSettings::get_global(cx).gutter;
27776 let show_line_numbers = self
27777 .show_line_numbers
27778 .unwrap_or(gutter_settings.line_numbers);
27779 let line_gutter_width = if show_line_numbers {
27780 // Avoid flicker-like gutter resizes when the line number gains another digit by
27781 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27782 let min_width_for_number_on_gutter =
27783 ch_advance * gutter_settings.min_line_number_digits as f32;
27784 self.max_line_number_width(style, window)
27785 .max(min_width_for_number_on_gutter)
27786 } else {
27787 0.0.into()
27788 };
27789
27790 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27791 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27792
27793 let git_blame_entries_width =
27794 self.git_blame_gutter_max_author_length
27795 .map(|max_author_length| {
27796 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27797 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27798
27799 /// The number of characters to dedicate to gaps and margins.
27800 const SPACING_WIDTH: usize = 4;
27801
27802 let max_char_count = max_author_length.min(renderer.max_author_length())
27803 + ::git::SHORT_SHA_LENGTH
27804 + MAX_RELATIVE_TIMESTAMP.len()
27805 + SPACING_WIDTH;
27806
27807 ch_advance * max_char_count
27808 });
27809
27810 let is_singleton = self.buffer_snapshot().is_singleton();
27811
27812 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27813 left_padding += if !is_singleton {
27814 ch_width * 4.0
27815 } else if show_runnables || show_breakpoints {
27816 ch_width * 3.0
27817 } else if show_git_gutter && show_line_numbers {
27818 ch_width * 2.0
27819 } else if show_git_gutter || show_line_numbers {
27820 ch_width
27821 } else {
27822 px(0.)
27823 };
27824
27825 let shows_folds = is_singleton && gutter_settings.folds;
27826
27827 let right_padding = if shows_folds && show_line_numbers {
27828 ch_width * 4.0
27829 } else if shows_folds || (!is_singleton && show_line_numbers) {
27830 ch_width * 3.0
27831 } else if show_line_numbers {
27832 ch_width
27833 } else {
27834 px(0.)
27835 };
27836
27837 GutterDimensions {
27838 left_padding,
27839 right_padding,
27840 width: line_gutter_width + left_padding + right_padding,
27841 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27842 git_blame_entries_width,
27843 }
27844 } else if self.offset_content {
27845 GutterDimensions::default_with_margin(font_id, font_size, cx)
27846 } else {
27847 GutterDimensions::default()
27848 }
27849 }
27850
27851 pub fn render_crease_toggle(
27852 &self,
27853 buffer_row: MultiBufferRow,
27854 row_contains_cursor: bool,
27855 editor: Entity<Editor>,
27856 window: &mut Window,
27857 cx: &mut App,
27858 ) -> Option<AnyElement> {
27859 let folded = self.is_line_folded(buffer_row);
27860 let mut is_foldable = false;
27861
27862 if let Some(crease) = self
27863 .crease_snapshot
27864 .query_row(buffer_row, self.buffer_snapshot())
27865 {
27866 is_foldable = true;
27867 match crease {
27868 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27869 if let Some(render_toggle) = render_toggle {
27870 let toggle_callback =
27871 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27872 if folded {
27873 editor.update(cx, |editor, cx| {
27874 editor.fold_at(buffer_row, window, cx)
27875 });
27876 } else {
27877 editor.update(cx, |editor, cx| {
27878 editor.unfold_at(buffer_row, window, cx)
27879 });
27880 }
27881 });
27882 return Some((render_toggle)(
27883 buffer_row,
27884 folded,
27885 toggle_callback,
27886 window,
27887 cx,
27888 ));
27889 }
27890 }
27891 }
27892 }
27893
27894 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27895
27896 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27897 Some(
27898 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27899 .toggle_state(folded)
27900 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27901 if folded {
27902 this.unfold_at(buffer_row, window, cx);
27903 } else {
27904 this.fold_at(buffer_row, window, cx);
27905 }
27906 }))
27907 .into_any_element(),
27908 )
27909 } else {
27910 None
27911 }
27912 }
27913
27914 pub fn render_crease_trailer(
27915 &self,
27916 buffer_row: MultiBufferRow,
27917 window: &mut Window,
27918 cx: &mut App,
27919 ) -> Option<AnyElement> {
27920 let folded = self.is_line_folded(buffer_row);
27921 if let Crease::Inline { render_trailer, .. } = self
27922 .crease_snapshot
27923 .query_row(buffer_row, self.buffer_snapshot())?
27924 {
27925 let render_trailer = render_trailer.as_ref()?;
27926 Some(render_trailer(buffer_row, folded, window, cx))
27927 } else {
27928 None
27929 }
27930 }
27931
27932 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27933 let digit_count = self.widest_line_number().ilog10() + 1;
27934 column_pixels(style, digit_count as usize, window)
27935 }
27936
27937 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27938 ///
27939 /// This is positive if `base` is before `line`.
27940 fn relative_line_delta(
27941 &self,
27942 current_selection_head: DisplayRow,
27943 first_visible_row: DisplayRow,
27944 consider_wrapped_lines: bool,
27945 ) -> i64 {
27946 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27947 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27948
27949 if consider_wrapped_lines {
27950 let wrap_snapshot = self.wrap_snapshot();
27951 let base_wrap_row = wrap_snapshot
27952 .make_wrap_point(current_selection_head, Bias::Left)
27953 .row();
27954 let wrap_row = wrap_snapshot
27955 .make_wrap_point(first_visible_row, Bias::Left)
27956 .row();
27957
27958 wrap_row.0 as i64 - base_wrap_row.0 as i64
27959 } else {
27960 let fold_snapshot = self.fold_snapshot();
27961 let base_fold_row = fold_snapshot
27962 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27963 .row();
27964 let fold_row = fold_snapshot
27965 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27966 .row();
27967
27968 fold_row as i64 - base_fold_row as i64
27969 }
27970 }
27971
27972 /// Returns the unsigned relative line number to display for each row in `rows`.
27973 ///
27974 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27975 pub fn calculate_relative_line_numbers(
27976 &self,
27977 rows: &Range<DisplayRow>,
27978 current_selection_head: DisplayRow,
27979 count_wrapped_lines: bool,
27980 ) -> HashMap<DisplayRow, u32> {
27981 let initial_offset =
27982 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27983
27984 self.row_infos(rows.start)
27985 .take(rows.len())
27986 .enumerate()
27987 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27988 .filter(|(_row, row_info)| {
27989 row_info.buffer_row.is_some()
27990 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27991 })
27992 .enumerate()
27993 .filter_map(|(i, (row, row_info))| {
27994 // We want to ensure here that the current line has absolute
27995 // numbering, even if we are in a soft-wrapped line. With the
27996 // exception that if we are in a deleted line, we should number this
27997 // relative with 0, as otherwise it would have no line number at all
27998 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27999
28000 (relative_line_number != 0
28001 || row_info
28002 .diff_status
28003 .is_some_and(|status| status.is_deleted()))
28004 .then_some((row, relative_line_number))
28005 })
28006 .collect()
28007 }
28008}
28009
28010pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28011 let font_size = style.text.font_size.to_pixels(window.rem_size());
28012 let layout = window.text_system().shape_line(
28013 SharedString::from(" ".repeat(column)),
28014 font_size,
28015 &[TextRun {
28016 len: column,
28017 font: style.text.font(),
28018 color: Hsla::default(),
28019 ..Default::default()
28020 }],
28021 None,
28022 );
28023
28024 layout.width
28025}
28026
28027impl Deref for EditorSnapshot {
28028 type Target = DisplaySnapshot;
28029
28030 fn deref(&self) -> &Self::Target {
28031 &self.display_snapshot
28032 }
28033}
28034
28035#[derive(Clone, Debug, PartialEq, Eq)]
28036pub enum EditorEvent {
28037 /// Emitted when the stored review comments change (added, removed, or updated).
28038 ReviewCommentsChanged {
28039 /// The new total count of review comments.
28040 total_count: usize,
28041 },
28042 InputIgnored {
28043 text: Arc<str>,
28044 },
28045 InputHandled {
28046 utf16_range_to_replace: Option<Range<isize>>,
28047 text: Arc<str>,
28048 },
28049 ExcerptsAdded {
28050 buffer: Entity<Buffer>,
28051 predecessor: ExcerptId,
28052 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
28053 },
28054 ExcerptsRemoved {
28055 ids: Vec<ExcerptId>,
28056 removed_buffer_ids: Vec<BufferId>,
28057 },
28058 BufferFoldToggled {
28059 ids: Vec<ExcerptId>,
28060 folded: bool,
28061 },
28062 ExcerptsEdited {
28063 ids: Vec<ExcerptId>,
28064 },
28065 ExcerptsExpanded {
28066 ids: Vec<ExcerptId>,
28067 },
28068 ExpandExcerptsRequested {
28069 excerpt_ids: Vec<ExcerptId>,
28070 lines: u32,
28071 direction: ExpandExcerptDirection,
28072 },
28073 StageOrUnstageRequested {
28074 stage: bool,
28075 hunks: Vec<MultiBufferDiffHunk>,
28076 },
28077 OpenExcerptsRequested {
28078 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28079 split: bool,
28080 },
28081 RestoreRequested {
28082 hunks: Vec<MultiBufferDiffHunk>,
28083 },
28084 BufferEdited,
28085 Edited {
28086 transaction_id: clock::Lamport,
28087 },
28088 Reparsed(BufferId),
28089 Focused,
28090 FocusedIn,
28091 Blurred,
28092 DirtyChanged,
28093 Saved,
28094 TitleChanged,
28095 SelectionsChanged {
28096 local: bool,
28097 },
28098 ScrollPositionChanged {
28099 local: bool,
28100 autoscroll: bool,
28101 },
28102 TransactionUndone {
28103 transaction_id: clock::Lamport,
28104 },
28105 TransactionBegun {
28106 transaction_id: clock::Lamport,
28107 },
28108 CursorShapeChanged,
28109 BreadcrumbsChanged,
28110 OutlineSymbolsChanged,
28111 PushedToNavHistory {
28112 anchor: Anchor,
28113 is_deactivate: bool,
28114 },
28115}
28116
28117impl EventEmitter<EditorEvent> for Editor {}
28118
28119impl Focusable for Editor {
28120 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28121 self.focus_handle.clone()
28122 }
28123}
28124
28125impl Render for Editor {
28126 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28127 EditorElement::new(&cx.entity(), self.create_style(cx))
28128 }
28129}
28130
28131impl EntityInputHandler for Editor {
28132 fn text_for_range(
28133 &mut self,
28134 range_utf16: Range<usize>,
28135 adjusted_range: &mut Option<Range<usize>>,
28136 _: &mut Window,
28137 cx: &mut Context<Self>,
28138 ) -> Option<String> {
28139 let snapshot = self.buffer.read(cx).read(cx);
28140 let start = snapshot.clip_offset_utf16(
28141 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28142 Bias::Left,
28143 );
28144 let end = snapshot.clip_offset_utf16(
28145 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28146 Bias::Right,
28147 );
28148 if (start.0.0..end.0.0) != range_utf16 {
28149 adjusted_range.replace(start.0.0..end.0.0);
28150 }
28151 Some(snapshot.text_for_range(start..end).collect())
28152 }
28153
28154 fn selected_text_range(
28155 &mut self,
28156 ignore_disabled_input: bool,
28157 _: &mut Window,
28158 cx: &mut Context<Self>,
28159 ) -> Option<UTF16Selection> {
28160 // Prevent the IME menu from appearing when holding down an alphabetic key
28161 // while input is disabled.
28162 if !ignore_disabled_input && !self.input_enabled {
28163 return None;
28164 }
28165
28166 let selection = self
28167 .selections
28168 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28169 let range = selection.range();
28170
28171 Some(UTF16Selection {
28172 range: range.start.0.0..range.end.0.0,
28173 reversed: selection.reversed,
28174 })
28175 }
28176
28177 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28178 let snapshot = self.buffer.read(cx).read(cx);
28179 let range = self
28180 .text_highlights(HighlightKey::InputComposition, cx)?
28181 .1
28182 .first()?;
28183 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28184 }
28185
28186 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28187 self.clear_highlights(HighlightKey::InputComposition, cx);
28188 self.ime_transaction.take();
28189 }
28190
28191 fn replace_text_in_range(
28192 &mut self,
28193 range_utf16: Option<Range<usize>>,
28194 text: &str,
28195 window: &mut Window,
28196 cx: &mut Context<Self>,
28197 ) {
28198 if !self.input_enabled {
28199 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28200 return;
28201 }
28202
28203 self.transact(window, cx, |this, window, cx| {
28204 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28205 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28206 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28207 Some(this.selection_replacement_ranges(range_utf16, cx))
28208 } else {
28209 this.marked_text_ranges(cx)
28210 };
28211
28212 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28213 let newest_selection_id = this.selections.newest_anchor().id;
28214 this.selections
28215 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28216 .iter()
28217 .zip(ranges_to_replace.iter())
28218 .find_map(|(selection, range)| {
28219 if selection.id == newest_selection_id {
28220 Some(
28221 (range.start.0.0 as isize - selection.head().0.0 as isize)
28222 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28223 )
28224 } else {
28225 None
28226 }
28227 })
28228 });
28229
28230 cx.emit(EditorEvent::InputHandled {
28231 utf16_range_to_replace: range_to_replace,
28232 text: text.into(),
28233 });
28234
28235 if let Some(new_selected_ranges) = new_selected_ranges {
28236 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28237 selections.select_ranges(new_selected_ranges)
28238 });
28239 this.backspace(&Default::default(), window, cx);
28240 }
28241
28242 this.handle_input(text, window, cx);
28243 });
28244
28245 if let Some(transaction) = self.ime_transaction {
28246 self.buffer.update(cx, |buffer, cx| {
28247 buffer.group_until_transaction(transaction, cx);
28248 });
28249 }
28250
28251 self.unmark_text(window, cx);
28252 }
28253
28254 fn replace_and_mark_text_in_range(
28255 &mut self,
28256 range_utf16: Option<Range<usize>>,
28257 text: &str,
28258 new_selected_range_utf16: Option<Range<usize>>,
28259 window: &mut Window,
28260 cx: &mut Context<Self>,
28261 ) {
28262 if !self.input_enabled {
28263 return;
28264 }
28265
28266 let transaction = self.transact(window, cx, |this, window, cx| {
28267 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28268 let snapshot = this.buffer.read(cx).read(cx);
28269 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28270 for marked_range in &mut marked_ranges {
28271 marked_range.end = marked_range.start + relative_range_utf16.end;
28272 marked_range.start += relative_range_utf16.start;
28273 marked_range.start =
28274 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28275 marked_range.end =
28276 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28277 }
28278 }
28279 Some(marked_ranges)
28280 } else if let Some(range_utf16) = range_utf16 {
28281 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28282 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28283 Some(this.selection_replacement_ranges(range_utf16, cx))
28284 } else {
28285 None
28286 };
28287
28288 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28289 let newest_selection_id = this.selections.newest_anchor().id;
28290 this.selections
28291 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28292 .iter()
28293 .zip(ranges_to_replace.iter())
28294 .find_map(|(selection, range)| {
28295 if selection.id == newest_selection_id {
28296 Some(
28297 (range.start.0.0 as isize - selection.head().0.0 as isize)
28298 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28299 )
28300 } else {
28301 None
28302 }
28303 })
28304 });
28305
28306 cx.emit(EditorEvent::InputHandled {
28307 utf16_range_to_replace: range_to_replace,
28308 text: text.into(),
28309 });
28310
28311 if let Some(ranges) = ranges_to_replace {
28312 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28313 s.select_ranges(ranges)
28314 });
28315 }
28316
28317 let marked_ranges = {
28318 let snapshot = this.buffer.read(cx).read(cx);
28319 this.selections
28320 .disjoint_anchors_arc()
28321 .iter()
28322 .map(|selection| {
28323 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28324 })
28325 .collect::<Vec<_>>()
28326 };
28327
28328 if text.is_empty() {
28329 this.unmark_text(window, cx);
28330 } else {
28331 this.highlight_text(
28332 HighlightKey::InputComposition,
28333 marked_ranges.clone(),
28334 HighlightStyle {
28335 underline: Some(UnderlineStyle {
28336 thickness: px(1.),
28337 color: None,
28338 wavy: false,
28339 }),
28340 ..Default::default()
28341 },
28342 cx,
28343 );
28344 }
28345
28346 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28347 let use_autoclose = this.use_autoclose;
28348 let use_auto_surround = this.use_auto_surround;
28349 this.set_use_autoclose(false);
28350 this.set_use_auto_surround(false);
28351 this.handle_input(text, window, cx);
28352 this.set_use_autoclose(use_autoclose);
28353 this.set_use_auto_surround(use_auto_surround);
28354
28355 if let Some(new_selected_range) = new_selected_range_utf16 {
28356 let snapshot = this.buffer.read(cx).read(cx);
28357 let new_selected_ranges = marked_ranges
28358 .into_iter()
28359 .map(|marked_range| {
28360 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28361 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28362 insertion_start.0 + new_selected_range.start,
28363 ));
28364 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28365 insertion_start.0 + new_selected_range.end,
28366 ));
28367 snapshot.clip_offset_utf16(new_start, Bias::Left)
28368 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28369 })
28370 .collect::<Vec<_>>();
28371
28372 drop(snapshot);
28373 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28374 selections.select_ranges(new_selected_ranges)
28375 });
28376 }
28377 });
28378
28379 self.ime_transaction = self.ime_transaction.or(transaction);
28380 if let Some(transaction) = self.ime_transaction {
28381 self.buffer.update(cx, |buffer, cx| {
28382 buffer.group_until_transaction(transaction, cx);
28383 });
28384 }
28385
28386 if self
28387 .text_highlights(HighlightKey::InputComposition, cx)
28388 .is_none()
28389 {
28390 self.ime_transaction.take();
28391 }
28392 }
28393
28394 fn bounds_for_range(
28395 &mut self,
28396 range_utf16: Range<usize>,
28397 element_bounds: gpui::Bounds<Pixels>,
28398 window: &mut Window,
28399 cx: &mut Context<Self>,
28400 ) -> Option<gpui::Bounds<Pixels>> {
28401 let text_layout_details = self.text_layout_details(window, cx);
28402 let CharacterDimensions {
28403 em_width,
28404 em_advance,
28405 line_height,
28406 } = self.character_dimensions(window, cx);
28407
28408 let snapshot = self.snapshot(window, cx);
28409 let scroll_position = snapshot.scroll_position();
28410 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28411
28412 let start =
28413 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28414 let x = Pixels::from(
28415 ScrollOffset::from(
28416 snapshot.x_for_display_point(start, &text_layout_details)
28417 + self.gutter_dimensions.full_width(),
28418 ) - scroll_left,
28419 );
28420 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28421
28422 Some(Bounds {
28423 origin: element_bounds.origin + point(x, y),
28424 size: size(em_width, line_height),
28425 })
28426 }
28427
28428 fn character_index_for_point(
28429 &mut self,
28430 point: gpui::Point<Pixels>,
28431 _window: &mut Window,
28432 _cx: &mut Context<Self>,
28433 ) -> Option<usize> {
28434 let position_map = self.last_position_map.as_ref()?;
28435 if !position_map.text_hitbox.contains(&point) {
28436 return None;
28437 }
28438 let display_point = position_map.point_for_position(point).previous_valid;
28439 let anchor = position_map
28440 .snapshot
28441 .display_point_to_anchor(display_point, Bias::Left);
28442 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28443 Some(utf16_offset.0.0)
28444 }
28445
28446 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28447 self.expects_character_input
28448 }
28449}
28450
28451trait SelectionExt {
28452 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28453 fn spanned_rows(
28454 &self,
28455 include_end_if_at_line_start: bool,
28456 map: &DisplaySnapshot,
28457 ) -> Range<MultiBufferRow>;
28458}
28459
28460impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28461 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28462 let start = self
28463 .start
28464 .to_point(map.buffer_snapshot())
28465 .to_display_point(map);
28466 let end = self
28467 .end
28468 .to_point(map.buffer_snapshot())
28469 .to_display_point(map);
28470 if self.reversed {
28471 end..start
28472 } else {
28473 start..end
28474 }
28475 }
28476
28477 fn spanned_rows(
28478 &self,
28479 include_end_if_at_line_start: bool,
28480 map: &DisplaySnapshot,
28481 ) -> Range<MultiBufferRow> {
28482 let start = self.start.to_point(map.buffer_snapshot());
28483 let mut end = self.end.to_point(map.buffer_snapshot());
28484 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28485 end.row -= 1;
28486 }
28487
28488 let buffer_start = map.prev_line_boundary(start).0;
28489 let buffer_end = map.next_line_boundary(end).0;
28490 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28491 }
28492}
28493
28494impl<T: InvalidationRegion> InvalidationStack<T> {
28495 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28496 where
28497 S: Clone + ToOffset,
28498 {
28499 while let Some(region) = self.last() {
28500 let all_selections_inside_invalidation_ranges =
28501 if selections.len() == region.ranges().len() {
28502 selections
28503 .iter()
28504 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28505 .all(|(selection, invalidation_range)| {
28506 let head = selection.head().to_offset(buffer);
28507 invalidation_range.start <= head && invalidation_range.end >= head
28508 })
28509 } else {
28510 false
28511 };
28512
28513 if all_selections_inside_invalidation_ranges {
28514 break;
28515 } else {
28516 self.pop();
28517 }
28518 }
28519 }
28520}
28521
28522#[derive(Clone)]
28523struct ErasedEditorImpl(Entity<Editor>);
28524
28525impl ui_input::ErasedEditor for ErasedEditorImpl {
28526 fn text(&self, cx: &App) -> String {
28527 self.0.read(cx).text(cx)
28528 }
28529
28530 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28531 self.0.update(cx, |this, cx| {
28532 this.set_text(text, window, cx);
28533 })
28534 }
28535
28536 fn clear(&self, window: &mut Window, cx: &mut App) {
28537 self.0.update(cx, |this, cx| this.clear(window, cx));
28538 }
28539
28540 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28541 self.0.update(cx, |this, cx| {
28542 this.set_placeholder_text(text, window, cx);
28543 });
28544 }
28545
28546 fn focus_handle(&self, cx: &App) -> FocusHandle {
28547 self.0.read(cx).focus_handle(cx)
28548 }
28549
28550 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28551 let settings = ThemeSettings::get_global(cx);
28552 let theme_color = cx.theme().colors();
28553
28554 let text_style = TextStyle {
28555 font_family: settings.ui_font.family.clone(),
28556 font_features: settings.ui_font.features.clone(),
28557 font_size: rems(0.875).into(),
28558 font_weight: settings.ui_font.weight,
28559 font_style: FontStyle::Normal,
28560 line_height: relative(1.2),
28561 color: theme_color.text,
28562 ..Default::default()
28563 };
28564 let editor_style = EditorStyle {
28565 background: theme_color.ghost_element_background,
28566 local_player: cx.theme().players().local(),
28567 syntax: cx.theme().syntax().clone(),
28568 text: text_style,
28569 ..Default::default()
28570 };
28571 EditorElement::new(&self.0, editor_style).into_any()
28572 }
28573
28574 fn as_any(&self) -> &dyn Any {
28575 &self.0
28576 }
28577
28578 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28579 self.0.update(cx, |editor, cx| {
28580 let editor_offset = editor.buffer().read(cx).len(cx);
28581 editor.change_selections(
28582 SelectionEffects::scroll(Autoscroll::Next),
28583 window,
28584 cx,
28585 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28586 );
28587 });
28588 }
28589
28590 fn subscribe(
28591 &self,
28592 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28593 window: &mut Window,
28594 cx: &mut App,
28595 ) -> Subscription {
28596 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28597 let event = match event {
28598 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28599 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28600 _ => return,
28601 };
28602 (callback)(event, window, cx);
28603 })
28604 }
28605
28606 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28607 self.0.update(cx, |editor, cx| {
28608 editor.set_masked(masked, cx);
28609 });
28610 }
28611}
28612impl<T> Default for InvalidationStack<T> {
28613 fn default() -> Self {
28614 Self(Default::default())
28615 }
28616}
28617
28618impl<T> Deref for InvalidationStack<T> {
28619 type Target = Vec<T>;
28620
28621 fn deref(&self) -> &Self::Target {
28622 &self.0
28623 }
28624}
28625
28626impl<T> DerefMut for InvalidationStack<T> {
28627 fn deref_mut(&mut self) -> &mut Self::Target {
28628 &mut self.0
28629 }
28630}
28631
28632impl InvalidationRegion for SnippetState {
28633 fn ranges(&self) -> &[Range<Anchor>] {
28634 &self.ranges[self.active_index]
28635 }
28636}
28637
28638fn edit_prediction_edit_text(
28639 current_snapshot: &BufferSnapshot,
28640 edits: &[(Range<Anchor>, impl AsRef<str>)],
28641 edit_preview: &EditPreview,
28642 include_deletions: bool,
28643 cx: &App,
28644) -> HighlightedText {
28645 let edits = edits
28646 .iter()
28647 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28648 .collect::<Vec<_>>();
28649
28650 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28651}
28652
28653fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28654 // Fallback for providers that don't provide edit_preview (like Copilot)
28655 // Just show the raw edit text with basic styling
28656 let mut text = String::new();
28657 let mut highlights = Vec::new();
28658
28659 let insertion_highlight_style = HighlightStyle {
28660 color: Some(cx.theme().colors().text),
28661 ..Default::default()
28662 };
28663
28664 for (_, edit_text) in edits {
28665 let start_offset = text.len();
28666 text.push_str(edit_text);
28667 let end_offset = text.len();
28668
28669 if start_offset < end_offset {
28670 highlights.push((start_offset..end_offset, insertion_highlight_style));
28671 }
28672 }
28673
28674 HighlightedText {
28675 text: text.into(),
28676 highlights,
28677 }
28678}
28679
28680pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28681 match severity {
28682 lsp::DiagnosticSeverity::ERROR => colors.error,
28683 lsp::DiagnosticSeverity::WARNING => colors.warning,
28684 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28685 lsp::DiagnosticSeverity::HINT => colors.info,
28686 _ => colors.ignored,
28687 }
28688}
28689
28690pub fn styled_runs_for_code_label<'a>(
28691 label: &'a CodeLabel,
28692 syntax_theme: &'a theme::SyntaxTheme,
28693 local_player: &'a theme::PlayerColor,
28694) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28695 let fade_out = HighlightStyle {
28696 fade_out: Some(0.35),
28697 ..Default::default()
28698 };
28699
28700 let mut prev_end = label.filter_range.end;
28701 label
28702 .runs
28703 .iter()
28704 .enumerate()
28705 .flat_map(move |(ix, (range, highlight_id))| {
28706 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28707 HighlightStyle {
28708 color: Some(local_player.cursor),
28709 ..Default::default()
28710 }
28711 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28712 HighlightStyle {
28713 background_color: Some(local_player.selection),
28714 ..Default::default()
28715 }
28716 } else if let Some(style) = highlight_id.style(syntax_theme) {
28717 style
28718 } else {
28719 return Default::default();
28720 };
28721 let muted_style = style.highlight(fade_out);
28722
28723 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28724 if range.start >= label.filter_range.end {
28725 if range.start > prev_end {
28726 runs.push((prev_end..range.start, fade_out));
28727 }
28728 runs.push((range.clone(), muted_style));
28729 } else if range.end <= label.filter_range.end {
28730 runs.push((range.clone(), style));
28731 } else {
28732 runs.push((range.start..label.filter_range.end, style));
28733 runs.push((label.filter_range.end..range.end, muted_style));
28734 }
28735 prev_end = cmp::max(prev_end, range.end);
28736
28737 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28738 runs.push((prev_end..label.text.len(), fade_out));
28739 }
28740
28741 runs
28742 })
28743}
28744
28745pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28746 let mut prev_index = 0;
28747 let mut prev_codepoint: Option<char> = None;
28748 text.char_indices()
28749 .chain([(text.len(), '\0')])
28750 .filter_map(move |(index, codepoint)| {
28751 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28752 let is_boundary = index == text.len()
28753 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28754 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28755 if is_boundary {
28756 let chunk = &text[prev_index..index];
28757 prev_index = index;
28758 Some(chunk)
28759 } else {
28760 None
28761 }
28762 })
28763}
28764
28765/// Given a string of text immediately before the cursor, iterates over possible
28766/// strings a snippet could match to. More precisely: returns an iterator over
28767/// suffixes of `text` created by splitting at word boundaries (before & after
28768/// every non-word character).
28769///
28770/// Shorter suffixes are returned first.
28771pub(crate) fn snippet_candidate_suffixes<'a>(
28772 text: &'a str,
28773 is_word_char: &'a dyn Fn(char) -> bool,
28774) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28775 let mut prev_index = text.len();
28776 let mut prev_codepoint = None;
28777 text.char_indices()
28778 .rev()
28779 .chain([(0, '\0')])
28780 .filter_map(move |(index, codepoint)| {
28781 let prev_index = std::mem::replace(&mut prev_index, index);
28782 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28783 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28784 None
28785 } else {
28786 let chunk = &text[prev_index..]; // go to end of string
28787 Some(chunk)
28788 }
28789 })
28790}
28791
28792pub trait RangeToAnchorExt: Sized {
28793 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28794
28795 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28796 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28797 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28798 }
28799}
28800
28801impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28802 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28803 let start_offset = self.start.to_offset(snapshot);
28804 let end_offset = self.end.to_offset(snapshot);
28805 if start_offset == end_offset {
28806 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28807 } else {
28808 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28809 }
28810 }
28811}
28812
28813pub trait RowExt {
28814 fn as_f64(&self) -> f64;
28815
28816 fn next_row(&self) -> Self;
28817
28818 fn previous_row(&self) -> Self;
28819
28820 fn minus(&self, other: Self) -> u32;
28821}
28822
28823impl RowExt for DisplayRow {
28824 fn as_f64(&self) -> f64 {
28825 self.0 as _
28826 }
28827
28828 fn next_row(&self) -> Self {
28829 Self(self.0 + 1)
28830 }
28831
28832 fn previous_row(&self) -> Self {
28833 Self(self.0.saturating_sub(1))
28834 }
28835
28836 fn minus(&self, other: Self) -> u32 {
28837 self.0 - other.0
28838 }
28839}
28840
28841impl RowExt for MultiBufferRow {
28842 fn as_f64(&self) -> f64 {
28843 self.0 as _
28844 }
28845
28846 fn next_row(&self) -> Self {
28847 Self(self.0 + 1)
28848 }
28849
28850 fn previous_row(&self) -> Self {
28851 Self(self.0.saturating_sub(1))
28852 }
28853
28854 fn minus(&self, other: Self) -> u32 {
28855 self.0 - other.0
28856 }
28857}
28858
28859trait RowRangeExt {
28860 type Row;
28861
28862 fn len(&self) -> usize;
28863
28864 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28865}
28866
28867impl RowRangeExt for Range<MultiBufferRow> {
28868 type Row = MultiBufferRow;
28869
28870 fn len(&self) -> usize {
28871 (self.end.0 - self.start.0) as usize
28872 }
28873
28874 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28875 (self.start.0..self.end.0).map(MultiBufferRow)
28876 }
28877}
28878
28879impl RowRangeExt for Range<DisplayRow> {
28880 type Row = DisplayRow;
28881
28882 fn len(&self) -> usize {
28883 (self.end.0 - self.start.0) as usize
28884 }
28885
28886 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28887 (self.start.0..self.end.0).map(DisplayRow)
28888 }
28889}
28890
28891/// If select range has more than one line, we
28892/// just point the cursor to range.start.
28893fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28894 if range.start.row == range.end.row {
28895 range
28896 } else {
28897 range.start..range.start
28898 }
28899}
28900pub struct KillRing(ClipboardItem);
28901impl Global for KillRing {}
28902
28903const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28904
28905enum BreakpointPromptEditAction {
28906 Log,
28907 Condition,
28908 HitCondition,
28909}
28910
28911struct BreakpointPromptEditor {
28912 pub(crate) prompt: Entity<Editor>,
28913 editor: WeakEntity<Editor>,
28914 breakpoint_anchor: Anchor,
28915 breakpoint: Breakpoint,
28916 edit_action: BreakpointPromptEditAction,
28917 block_ids: HashSet<CustomBlockId>,
28918 editor_margins: Arc<Mutex<EditorMargins>>,
28919 _subscriptions: Vec<Subscription>,
28920}
28921
28922impl BreakpointPromptEditor {
28923 const MAX_LINES: u8 = 4;
28924
28925 fn new(
28926 editor: WeakEntity<Editor>,
28927 breakpoint_anchor: Anchor,
28928 breakpoint: Breakpoint,
28929 edit_action: BreakpointPromptEditAction,
28930 window: &mut Window,
28931 cx: &mut Context<Self>,
28932 ) -> Self {
28933 let base_text = match edit_action {
28934 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28935 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28936 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28937 }
28938 .map(|msg| msg.to_string())
28939 .unwrap_or_default();
28940
28941 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28942 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28943
28944 let prompt = cx.new(|cx| {
28945 let mut prompt = Editor::new(
28946 EditorMode::AutoHeight {
28947 min_lines: 1,
28948 max_lines: Some(Self::MAX_LINES as usize),
28949 },
28950 buffer,
28951 None,
28952 window,
28953 cx,
28954 );
28955 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28956 prompt.set_show_cursor_when_unfocused(false, cx);
28957 prompt.set_placeholder_text(
28958 match edit_action {
28959 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28960 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28961 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28962 },
28963 window,
28964 cx,
28965 );
28966
28967 prompt
28968 });
28969
28970 Self {
28971 prompt,
28972 editor,
28973 breakpoint_anchor,
28974 breakpoint,
28975 edit_action,
28976 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28977 block_ids: Default::default(),
28978 _subscriptions: vec![],
28979 }
28980 }
28981
28982 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28983 self.block_ids.extend(block_ids)
28984 }
28985
28986 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28987 if let Some(editor) = self.editor.upgrade() {
28988 let message = self
28989 .prompt
28990 .read(cx)
28991 .buffer
28992 .read(cx)
28993 .as_singleton()
28994 .expect("A multi buffer in breakpoint prompt isn't possible")
28995 .read(cx)
28996 .as_rope()
28997 .to_string();
28998
28999 editor.update(cx, |editor, cx| {
29000 editor.edit_breakpoint_at_anchor(
29001 self.breakpoint_anchor,
29002 self.breakpoint.clone(),
29003 match self.edit_action {
29004 BreakpointPromptEditAction::Log => {
29005 BreakpointEditAction::EditLogMessage(message.into())
29006 }
29007 BreakpointPromptEditAction::Condition => {
29008 BreakpointEditAction::EditCondition(message.into())
29009 }
29010 BreakpointPromptEditAction::HitCondition => {
29011 BreakpointEditAction::EditHitCondition(message.into())
29012 }
29013 },
29014 cx,
29015 );
29016
29017 editor.remove_blocks(self.block_ids.clone(), None, cx);
29018 cx.focus_self(window);
29019 });
29020 }
29021 }
29022
29023 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29024 self.editor
29025 .update(cx, |editor, cx| {
29026 editor.remove_blocks(self.block_ids.clone(), None, cx);
29027 window.focus(&editor.focus_handle, cx);
29028 })
29029 .log_err();
29030 }
29031
29032 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29033 let settings = ThemeSettings::get_global(cx);
29034 let text_style = TextStyle {
29035 color: if self.prompt.read(cx).read_only(cx) {
29036 cx.theme().colors().text_disabled
29037 } else {
29038 cx.theme().colors().text
29039 },
29040 font_family: settings.buffer_font.family.clone(),
29041 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29042 font_size: settings.buffer_font_size(cx).into(),
29043 font_weight: settings.buffer_font.weight,
29044 line_height: relative(settings.buffer_line_height.value()),
29045 ..Default::default()
29046 };
29047 EditorElement::new(
29048 &self.prompt,
29049 EditorStyle {
29050 background: cx.theme().colors().editor_background,
29051 local_player: cx.theme().players().local(),
29052 text: text_style,
29053 ..Default::default()
29054 },
29055 )
29056 }
29057
29058 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29059 let focus_handle = self.prompt.focus_handle(cx);
29060 IconButton::new("cancel", IconName::Close)
29061 .icon_color(Color::Muted)
29062 .shape(IconButtonShape::Square)
29063 .tooltip(move |_window, cx| {
29064 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29065 })
29066 .on_click(cx.listener(|this, _, window, cx| {
29067 this.cancel(&menu::Cancel, window, cx);
29068 }))
29069 }
29070
29071 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29072 let focus_handle = self.prompt.focus_handle(cx);
29073 IconButton::new("confirm", IconName::Return)
29074 .icon_color(Color::Muted)
29075 .shape(IconButtonShape::Square)
29076 .tooltip(move |_window, cx| {
29077 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29078 })
29079 .on_click(cx.listener(|this, _, window, cx| {
29080 this.confirm(&menu::Confirm, window, cx);
29081 }))
29082 }
29083}
29084
29085impl Render for BreakpointPromptEditor {
29086 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29087 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29088 let editor_margins = *self.editor_margins.lock();
29089 let gutter_dimensions = editor_margins.gutter;
29090 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29091 let right_padding = editor_margins.right + px(9.);
29092 h_flex()
29093 .key_context("Editor")
29094 .bg(cx.theme().colors().editor_background)
29095 .border_y_1()
29096 .border_color(cx.theme().status().info_border)
29097 .size_full()
29098 .py(window.line_height() / 2.5)
29099 .pr(right_padding)
29100 .on_action(cx.listener(Self::confirm))
29101 .on_action(cx.listener(Self::cancel))
29102 .child(
29103 WithRemSize::new(ui_font_size)
29104 .h_full()
29105 .w(left_gutter_width)
29106 .flex()
29107 .flex_row()
29108 .flex_shrink_0()
29109 .items_center()
29110 .justify_center()
29111 .gap_1()
29112 .child(self.render_close_button(cx)),
29113 )
29114 .child(
29115 h_flex()
29116 .w_full()
29117 .justify_between()
29118 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29119 .child(
29120 WithRemSize::new(ui_font_size)
29121 .flex()
29122 .flex_row()
29123 .items_center()
29124 .child(self.render_confirm_button(cx)),
29125 ),
29126 )
29127 }
29128}
29129
29130impl Focusable for BreakpointPromptEditor {
29131 fn focus_handle(&self, cx: &App) -> FocusHandle {
29132 self.prompt.focus_handle(cx)
29133 }
29134}
29135
29136fn all_edits_insertions_or_deletions(
29137 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29138 snapshot: &MultiBufferSnapshot,
29139) -> bool {
29140 let mut all_insertions = true;
29141 let mut all_deletions = true;
29142
29143 for (range, new_text) in edits.iter() {
29144 let range_is_empty = range.to_offset(snapshot).is_empty();
29145 let text_is_empty = new_text.is_empty();
29146
29147 if range_is_empty != text_is_empty {
29148 if range_is_empty {
29149 all_deletions = false;
29150 } else {
29151 all_insertions = false;
29152 }
29153 } else {
29154 return false;
29155 }
29156
29157 if !all_insertions && !all_deletions {
29158 return false;
29159 }
29160 }
29161 all_insertions || all_deletions
29162}
29163
29164struct MissingEditPredictionKeybindingTooltip;
29165
29166impl Render for MissingEditPredictionKeybindingTooltip {
29167 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29168 ui::tooltip_container(cx, |container, cx| {
29169 container
29170 .flex_shrink_0()
29171 .max_w_80()
29172 .min_h(rems_from_px(124.))
29173 .justify_between()
29174 .child(
29175 v_flex()
29176 .flex_1()
29177 .text_ui_sm(cx)
29178 .child(Label::new("Conflict with Accept Keybinding"))
29179 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29180 )
29181 .child(
29182 h_flex()
29183 .pb_1()
29184 .gap_1()
29185 .items_end()
29186 .w_full()
29187 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29188 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29189 }))
29190 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29191 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29192 })),
29193 )
29194 })
29195 }
29196}
29197
29198#[derive(Debug, Clone, Copy, PartialEq)]
29199pub struct LineHighlight {
29200 pub background: Background,
29201 pub border: Option<gpui::Hsla>,
29202 pub include_gutter: bool,
29203 pub type_id: Option<TypeId>,
29204}
29205
29206struct LineManipulationResult {
29207 pub new_text: String,
29208 pub line_count_before: usize,
29209 pub line_count_after: usize,
29210}
29211
29212fn render_diff_hunk_controls(
29213 row: u32,
29214 status: &DiffHunkStatus,
29215 hunk_range: Range<Anchor>,
29216 is_created_file: bool,
29217 line_height: Pixels,
29218 editor: &Entity<Editor>,
29219 _window: &mut Window,
29220 cx: &mut App,
29221) -> AnyElement {
29222 h_flex()
29223 .h(line_height)
29224 .mr_1()
29225 .gap_1()
29226 .px_0p5()
29227 .pb_1()
29228 .border_x_1()
29229 .border_b_1()
29230 .border_color(cx.theme().colors().border_variant)
29231 .rounded_b_lg()
29232 .bg(cx.theme().colors().editor_background)
29233 .gap_1()
29234 .block_mouse_except_scroll()
29235 .shadow_md()
29236 .child(if status.has_secondary_hunk() {
29237 Button::new(("stage", row as u64), "Stage")
29238 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29239 .tooltip({
29240 let focus_handle = editor.focus_handle(cx);
29241 move |_window, cx| {
29242 Tooltip::for_action_in(
29243 "Stage Hunk",
29244 &::git::ToggleStaged,
29245 &focus_handle,
29246 cx,
29247 )
29248 }
29249 })
29250 .on_click({
29251 let editor = editor.clone();
29252 move |_event, _window, cx| {
29253 editor.update(cx, |editor, cx| {
29254 editor.stage_or_unstage_diff_hunks(
29255 true,
29256 vec![hunk_range.start..hunk_range.start],
29257 cx,
29258 );
29259 });
29260 }
29261 })
29262 } else {
29263 Button::new(("unstage", row as u64), "Unstage")
29264 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29265 .tooltip({
29266 let focus_handle = editor.focus_handle(cx);
29267 move |_window, cx| {
29268 Tooltip::for_action_in(
29269 "Unstage Hunk",
29270 &::git::ToggleStaged,
29271 &focus_handle,
29272 cx,
29273 )
29274 }
29275 })
29276 .on_click({
29277 let editor = editor.clone();
29278 move |_event, _window, cx| {
29279 editor.update(cx, |editor, cx| {
29280 editor.stage_or_unstage_diff_hunks(
29281 false,
29282 vec![hunk_range.start..hunk_range.start],
29283 cx,
29284 );
29285 });
29286 }
29287 })
29288 })
29289 .child(
29290 Button::new(("restore", row as u64), "Restore")
29291 .tooltip({
29292 let focus_handle = editor.focus_handle(cx);
29293 move |_window, cx| {
29294 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29295 }
29296 })
29297 .on_click({
29298 let editor = editor.clone();
29299 move |_event, window, cx| {
29300 editor.update(cx, |editor, cx| {
29301 let snapshot = editor.snapshot(window, cx);
29302 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29303 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29304 });
29305 }
29306 })
29307 .disabled(is_created_file),
29308 )
29309 .when(
29310 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29311 |el| {
29312 el.child(
29313 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29314 .shape(IconButtonShape::Square)
29315 .icon_size(IconSize::Small)
29316 // .disabled(!has_multiple_hunks)
29317 .tooltip({
29318 let focus_handle = editor.focus_handle(cx);
29319 move |_window, cx| {
29320 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29321 }
29322 })
29323 .on_click({
29324 let editor = editor.clone();
29325 move |_event, window, cx| {
29326 editor.update(cx, |editor, cx| {
29327 let snapshot = editor.snapshot(window, cx);
29328 let position =
29329 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29330 editor.go_to_hunk_before_or_after_position(
29331 &snapshot,
29332 position,
29333 Direction::Next,
29334 true,
29335 window,
29336 cx,
29337 );
29338 editor.expand_selected_diff_hunks(cx);
29339 });
29340 }
29341 }),
29342 )
29343 .child(
29344 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29345 .shape(IconButtonShape::Square)
29346 .icon_size(IconSize::Small)
29347 // .disabled(!has_multiple_hunks)
29348 .tooltip({
29349 let focus_handle = editor.focus_handle(cx);
29350 move |_window, cx| {
29351 Tooltip::for_action_in(
29352 "Previous Hunk",
29353 &GoToPreviousHunk,
29354 &focus_handle,
29355 cx,
29356 )
29357 }
29358 })
29359 .on_click({
29360 let editor = editor.clone();
29361 move |_event, window, cx| {
29362 editor.update(cx, |editor, cx| {
29363 let snapshot = editor.snapshot(window, cx);
29364 let point =
29365 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29366 editor.go_to_hunk_before_or_after_position(
29367 &snapshot,
29368 point,
29369 Direction::Prev,
29370 true,
29371 window,
29372 cx,
29373 );
29374 editor.expand_selected_diff_hunks(cx);
29375 });
29376 }
29377 }),
29378 )
29379 },
29380 )
29381 .into_any_element()
29382}
29383
29384pub fn multibuffer_context_lines(cx: &App) -> u32 {
29385 EditorSettings::try_get(cx)
29386 .map(|settings| settings.excerpt_context_lines)
29387 .unwrap_or(2)
29388 .min(32)
29389}