1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod semantic_tokens;
42mod split;
43pub mod split_editor_view;
44pub mod tasks;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
65 ShowMinimap,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
80 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
81 ToPoint,
82};
83pub use split::{SplittableEditor, ToggleSplitDiff};
84pub use split_editor_view::SplitEditorView;
85pub use text::Bias;
86
87use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
88use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
89use anyhow::{Context as _, Result, anyhow, bail};
90use blink_manager::BlinkManager;
91use buffer_diff::DiffHunkStatus;
92use client::{Collaborator, ParticipantIndex, parse_zed_link};
93use clock::ReplicaId;
94use code_context_menus::{
95 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
96 CompletionsMenu, ContextMenuOrigin,
97};
98use collections::{BTreeMap, HashMap, HashSet, VecDeque};
99use convert_case::{Case, Casing};
100use dap::TelemetrySpawnLocation;
101use display_map::*;
102use document_colors::LspColorData;
103use edit_prediction_types::{
104 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
105 EditPredictionGranularity, SuggestionDisplayType,
106};
107use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
108use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
109use futures::{
110 FutureExt,
111 future::{self, Shared, join},
112};
113use fuzzy::{StringMatch, StringMatchCandidate};
114use git::blame::{GitBlame, GlobalBlameRenderer};
115use gpui::{
116 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
117 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
118 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
119 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
120 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
121 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
122 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
123 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
124 pulsating_between, px, relative, size,
125};
126use hover_links::{HoverLink, HoveredLinkState, find_file};
127use hover_popover::{HoverState, hide_hover};
128use indent_guides::ActiveIndentGuidesState;
129use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
130use itertools::{Either, Itertools};
131use language::{
132 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
133 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
134 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
135 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, LocalFile, OffsetRangeExt,
136 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
137 TreeSitterOptions, WordsQuery,
138 language_settings::{
139 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
140 all_language_settings, language_settings,
141 },
142 point_from_lsp, point_to_lsp, text_diff_with_options,
143};
144use linked_editing_ranges::refresh_linked_ranges;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId,
148};
149use markdown::Markdown;
150use mouse_context_menu::MouseContextMenu;
151use movement::TextLayoutDetails;
152use multi_buffer::{
153 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
154};
155use parking_lot::Mutex;
156use persistence::DB;
157use project::{
158 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
159 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
160 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
161 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
162 debugger::{
163 breakpoint_store::{
164 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
165 BreakpointStore, BreakpointStoreEvent,
166 },
167 session::{Session, SessionEvent},
168 },
169 git_store::GitStoreEvent,
170 lsp_store::{
171 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
172 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
173 },
174 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
175};
176use rand::seq::SliceRandom;
177use regex::Regex;
178use rpc::{ErrorCode, ErrorExt, proto::PeerId};
179use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
180use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
181use serde::{Deserialize, Serialize};
182use settings::{
183 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
184 update_settings_file,
185};
186use smallvec::{SmallVec, smallvec};
187use snippet::Snippet;
188use std::{
189 any::{Any, TypeId},
190 borrow::Cow,
191 cell::{OnceCell, RefCell},
192 cmp::{self, Ordering, Reverse},
193 collections::hash_map,
194 iter::{self, Peekable},
195 mem,
196 num::NonZeroU32,
197 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
198 path::{Path, PathBuf},
199 rc::Rc,
200 sync::Arc,
201 time::{Duration, Instant},
202};
203use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
204use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
205use theme::{
206 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
207 ThemeSettings, observe_buffer_font_size_adjustment,
208};
209use ui::{
210 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
211 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
212 utils::WithRemSize,
213};
214use ui_input::ErasedEditor;
215use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
216use workspace::{
217 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
218 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
219 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
220 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
221 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
222 searchable::SearchEvent,
223};
224use zed_actions::editor::{MoveDown, MoveUp};
225
226use crate::{
227 code_context_menus::CompletionsMenuSource,
228 editor_settings::MultiCursorModifier,
229 hover_links::{find_url, find_url_from_range},
230 inlays::{
231 InlineValueCache,
232 inlay_hints::{LspInlayHintData, inlay_hint_settings},
233 },
234 scroll::{ScrollOffset, ScrollPixelOffset},
235 selections_collection::resolve_selections_wrapping_blocks,
236 semantic_tokens::SemanticTokenState,
237 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
238};
239
240pub const FILE_HEADER_HEIGHT: u32 = 2;
241pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
242pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
243const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
244const MAX_LINE_LEN: usize = 1024;
245const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
246const MAX_SELECTION_HISTORY_LEN: usize = 1024;
247pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
248#[doc(hidden)]
249pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
250pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
251
252pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
253pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
254pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
255pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
256
257pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
258pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
259pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
260
261pub type RenderDiffHunkControlsFn = Arc<
262 dyn Fn(
263 u32,
264 &DiffHunkStatus,
265 Range<Anchor>,
266 bool,
267 Pixels,
268 &Entity<Editor>,
269 &mut Window,
270 &mut App,
271 ) -> AnyElement,
272>;
273
274enum ReportEditorEvent {
275 Saved { auto_saved: bool },
276 EditorOpened,
277 Closed,
278}
279
280impl ReportEditorEvent {
281 pub fn event_type(&self) -> &'static str {
282 match self {
283 Self::Saved { .. } => "Editor Saved",
284 Self::EditorOpened => "Editor Opened",
285 Self::Closed => "Editor Closed",
286 }
287 }
288}
289
290pub enum ActiveDebugLine {}
291pub enum DebugStackFrameLine {}
292
293pub enum ConflictsOuter {}
294pub enum ConflictsOurs {}
295pub enum ConflictsTheirs {}
296pub enum ConflictsOursMarker {}
297pub enum ConflictsTheirsMarker {}
298
299pub struct HunkAddedColor;
300pub struct HunkRemovedColor;
301
302#[derive(Debug, Copy, Clone, PartialEq, Eq)]
303pub enum Navigated {
304 Yes,
305 No,
306}
307
308impl Navigated {
309 pub fn from_bool(yes: bool) -> Navigated {
310 if yes { Navigated::Yes } else { Navigated::No }
311 }
312}
313
314#[derive(Debug, Clone, PartialEq, Eq)]
315enum DisplayDiffHunk {
316 Folded {
317 display_row: DisplayRow,
318 },
319 Unfolded {
320 is_created_file: bool,
321 diff_base_byte_range: Range<usize>,
322 display_row_range: Range<DisplayRow>,
323 multi_buffer_range: Range<Anchor>,
324 status: DiffHunkStatus,
325 word_diffs: Vec<Range<MultiBufferOffset>>,
326 },
327}
328
329pub enum HideMouseCursorOrigin {
330 TypingAction,
331 MovementAction,
332}
333
334pub fn init(cx: &mut App) {
335 cx.set_global(GlobalBlameRenderer(Arc::new(())));
336 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
337
338 workspace::register_project_item::<Editor>(cx);
339 workspace::FollowableViewRegistry::register::<Editor>(cx);
340 workspace::register_serializable_item::<Editor>(cx);
341
342 cx.observe_new(
343 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
344 workspace.register_action(Editor::new_file);
345 workspace.register_action(Editor::new_file_split);
346 workspace.register_action(Editor::new_file_vertical);
347 workspace.register_action(Editor::new_file_horizontal);
348 workspace.register_action(Editor::cancel_language_server_work);
349 workspace.register_action(Editor::toggle_focus);
350 },
351 )
352 .detach();
353
354 cx.on_action(move |_: &workspace::NewFile, cx| {
355 let app_state = workspace::AppState::global(cx);
356 if let Some(app_state) = app_state.upgrade() {
357 workspace::open_new(
358 Default::default(),
359 app_state,
360 cx,
361 |workspace, window, cx| {
362 Editor::new_file(workspace, &Default::default(), window, cx)
363 },
364 )
365 .detach_and_log_err(cx);
366 }
367 })
368 .on_action(move |_: &workspace::NewWindow, cx| {
369 let app_state = workspace::AppState::global(cx);
370 if let Some(app_state) = app_state.upgrade() {
371 workspace::open_new(
372 Default::default(),
373 app_state,
374 cx,
375 |workspace, window, cx| {
376 cx.activate(true);
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach_and_log_err(cx);
381 }
382 });
383 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
384 Arc::new(ErasedEditorImpl(
385 cx.new(|cx| Editor::single_line(window, cx)),
386 )) as Arc<dyn ErasedEditor>
387 });
388 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
389}
390
391pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
392 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
393}
394
395pub trait DiagnosticRenderer {
396 fn render_group(
397 &self,
398 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
399 buffer_id: BufferId,
400 snapshot: EditorSnapshot,
401 editor: WeakEntity<Editor>,
402 language_registry: Option<Arc<LanguageRegistry>>,
403 cx: &mut App,
404 ) -> Vec<BlockProperties<Anchor>>;
405
406 fn render_hover(
407 &self,
408 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
409 range: Range<Point>,
410 buffer_id: BufferId,
411 language_registry: Option<Arc<LanguageRegistry>>,
412 cx: &mut App,
413 ) -> Option<Entity<markdown::Markdown>>;
414
415 fn open_link(
416 &self,
417 editor: &mut Editor,
418 link: SharedString,
419 window: &mut Window,
420 cx: &mut Context<Editor>,
421 );
422}
423
424pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
425
426impl GlobalDiagnosticRenderer {
427 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
428 cx.try_global::<Self>().map(|g| g.0.clone())
429 }
430}
431
432impl gpui::Global for GlobalDiagnosticRenderer {}
433pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
434 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
435}
436
437pub struct SearchWithinRange;
438
439trait InvalidationRegion {
440 fn ranges(&self) -> &[Range<Anchor>];
441}
442
443#[derive(Clone, Debug, PartialEq)]
444pub enum SelectPhase {
445 Begin {
446 position: DisplayPoint,
447 add: bool,
448 click_count: usize,
449 },
450 BeginColumnar {
451 position: DisplayPoint,
452 reset: bool,
453 mode: ColumnarMode,
454 goal_column: u32,
455 },
456 Extend {
457 position: DisplayPoint,
458 click_count: usize,
459 },
460 Update {
461 position: DisplayPoint,
462 goal_column: u32,
463 scroll_delta: gpui::Point<f32>,
464 },
465 End,
466}
467
468#[derive(Clone, Debug, PartialEq)]
469pub enum ColumnarMode {
470 FromMouse,
471 FromSelection,
472}
473
474#[derive(Clone, Debug)]
475pub enum SelectMode {
476 Character,
477 Word(Range<Anchor>),
478 Line(Range<Anchor>),
479 All,
480}
481
482#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
483pub enum SizingBehavior {
484 /// The editor will layout itself using `size_full` and will include the vertical
485 /// scroll margin as requested by user settings.
486 #[default]
487 Default,
488 /// The editor will layout itself using `size_full`, but will not have any
489 /// vertical overscroll.
490 ExcludeOverscrollMargin,
491 /// The editor will request a vertical size according to its content and will be
492 /// layouted without a vertical scroll margin.
493 SizeByContent,
494}
495
496#[derive(Clone, PartialEq, Eq, Debug)]
497pub enum EditorMode {
498 SingleLine,
499 AutoHeight {
500 min_lines: usize,
501 max_lines: Option<usize>,
502 },
503 Full {
504 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
505 scale_ui_elements_with_buffer_font_size: bool,
506 /// When set to `true`, the editor will render a background for the active line.
507 show_active_line_background: bool,
508 /// Determines the sizing behavior for this editor
509 sizing_behavior: SizingBehavior,
510 },
511 Minimap {
512 parent: WeakEntity<Editor>,
513 },
514}
515
516impl EditorMode {
517 pub fn full() -> Self {
518 Self::Full {
519 scale_ui_elements_with_buffer_font_size: true,
520 show_active_line_background: true,
521 sizing_behavior: SizingBehavior::Default,
522 }
523 }
524
525 #[inline]
526 pub fn is_full(&self) -> bool {
527 matches!(self, Self::Full { .. })
528 }
529
530 #[inline]
531 pub fn is_single_line(&self) -> bool {
532 matches!(self, Self::SingleLine { .. })
533 }
534
535 #[inline]
536 fn is_minimap(&self) -> bool {
537 matches!(self, Self::Minimap { .. })
538 }
539}
540
541#[derive(Copy, Clone, Debug)]
542pub enum SoftWrap {
543 /// Prefer not to wrap at all.
544 ///
545 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
546 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
547 GitDiff,
548 /// Prefer a single line generally, unless an overly long line is encountered.
549 None,
550 /// Soft wrap lines that exceed the editor width.
551 EditorWidth,
552 /// Soft wrap lines at the preferred line length.
553 Column(u32),
554 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
555 Bounded(u32),
556}
557
558#[derive(Clone)]
559pub struct EditorStyle {
560 pub background: Hsla,
561 pub border: Hsla,
562 pub local_player: PlayerColor,
563 pub text: TextStyle,
564 pub scrollbar_width: Pixels,
565 pub syntax: Arc<SyntaxTheme>,
566 pub status: StatusColors,
567 pub inlay_hints_style: HighlightStyle,
568 pub edit_prediction_styles: EditPredictionStyles,
569 pub unnecessary_code_fade: f32,
570 pub show_underlines: bool,
571}
572
573impl Default for EditorStyle {
574 fn default() -> Self {
575 Self {
576 background: Hsla::default(),
577 border: Hsla::default(),
578 local_player: PlayerColor::default(),
579 text: TextStyle::default(),
580 scrollbar_width: Pixels::default(),
581 syntax: Default::default(),
582 // HACK: Status colors don't have a real default.
583 // We should look into removing the status colors from the editor
584 // style and retrieve them directly from the theme.
585 status: StatusColors::dark(),
586 inlay_hints_style: HighlightStyle::default(),
587 edit_prediction_styles: EditPredictionStyles {
588 insertion: HighlightStyle::default(),
589 whitespace: HighlightStyle::default(),
590 },
591 unnecessary_code_fade: Default::default(),
592 show_underlines: true,
593 }
594 }
595}
596
597pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
598 let show_background = language_settings::language_settings(None, None, cx)
599 .inlay_hints
600 .show_background;
601
602 let mut style = cx.theme().syntax().get("hint");
603
604 if style.color.is_none() {
605 style.color = Some(cx.theme().status().hint);
606 }
607
608 if !show_background {
609 style.background_color = None;
610 return style;
611 }
612
613 if style.background_color.is_none() {
614 style.background_color = Some(cx.theme().status().hint_background);
615 }
616
617 style
618}
619
620pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
621 EditPredictionStyles {
622 insertion: HighlightStyle {
623 color: Some(cx.theme().status().predictive),
624 ..HighlightStyle::default()
625 },
626 whitespace: HighlightStyle {
627 background_color: Some(cx.theme().status().created_background),
628 ..HighlightStyle::default()
629 },
630 }
631}
632
633type CompletionId = usize;
634
635pub(crate) enum EditDisplayMode {
636 TabAccept,
637 DiffPopover,
638 Inline,
639}
640
641enum EditPrediction {
642 Edit {
643 edits: Vec<(Range<Anchor>, Arc<str>)>,
644 /// Predicted cursor position as (anchor, offset_from_anchor).
645 /// The anchor is in multibuffer coordinates; after applying edits,
646 /// resolve the anchor and add the offset to get the final cursor position.
647 cursor_position: Option<(Anchor, usize)>,
648 edit_preview: Option<EditPreview>,
649 display_mode: EditDisplayMode,
650 snapshot: BufferSnapshot,
651 },
652 /// Move to a specific location in the active editor
653 MoveWithin {
654 target: Anchor,
655 snapshot: BufferSnapshot,
656 },
657 /// Move to a specific location in a different editor (not the active one)
658 MoveOutside {
659 target: language::Anchor,
660 snapshot: BufferSnapshot,
661 },
662}
663
664struct EditPredictionState {
665 inlay_ids: Vec<InlayId>,
666 completion: EditPrediction,
667 completion_id: Option<SharedString>,
668 invalidation_range: Option<Range<Anchor>>,
669}
670
671enum EditPredictionSettings {
672 Disabled,
673 Enabled {
674 show_in_menu: bool,
675 preview_requires_modifier: bool,
676 },
677}
678
679#[derive(Debug, Clone)]
680struct InlineDiagnostic {
681 message: SharedString,
682 group_id: usize,
683 is_primary: bool,
684 start: Point,
685 severity: lsp::DiagnosticSeverity,
686}
687
688pub enum MenuEditPredictionsPolicy {
689 Never,
690 ByProvider,
691}
692
693pub enum EditPredictionPreview {
694 /// Modifier is not pressed
695 Inactive { released_too_fast: bool },
696 /// Modifier pressed
697 Active {
698 since: Instant,
699 previous_scroll_position: Option<SharedScrollAnchor>,
700 },
701}
702
703impl EditPredictionPreview {
704 pub fn released_too_fast(&self) -> bool {
705 match self {
706 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
707 EditPredictionPreview::Active { .. } => false,
708 }
709 }
710
711 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
712 if let EditPredictionPreview::Active {
713 previous_scroll_position,
714 ..
715 } = self
716 {
717 *previous_scroll_position = scroll_position;
718 }
719 }
720}
721
722pub struct ContextMenuOptions {
723 pub min_entries_visible: usize,
724 pub max_entries_visible: usize,
725 pub placement: Option<ContextMenuPlacement>,
726}
727
728#[derive(Debug, Clone, PartialEq, Eq)]
729pub enum ContextMenuPlacement {
730 Above,
731 Below,
732}
733
734#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
735struct EditorActionId(usize);
736
737impl EditorActionId {
738 pub fn post_inc(&mut self) -> Self {
739 let answer = self.0;
740
741 *self = Self(answer + 1);
742
743 Self(answer)
744 }
745}
746
747// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
748// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
749
750type BackgroundHighlight = (
751 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
752 Arc<[Range<Anchor>]>,
753);
754type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
755
756#[derive(Default)]
757struct ScrollbarMarkerState {
758 scrollbar_size: Size<Pixels>,
759 dirty: bool,
760 markers: Arc<[PaintQuad]>,
761 pending_refresh: Option<Task<Result<()>>>,
762}
763
764impl ScrollbarMarkerState {
765 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
766 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
767 }
768}
769
770#[derive(Clone, Copy, PartialEq, Eq)]
771pub enum MinimapVisibility {
772 Disabled,
773 Enabled {
774 /// The configuration currently present in the users settings.
775 setting_configuration: bool,
776 /// Whether to override the currently set visibility from the users setting.
777 toggle_override: bool,
778 },
779}
780
781impl MinimapVisibility {
782 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
783 if mode.is_full() {
784 Self::Enabled {
785 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
786 toggle_override: false,
787 }
788 } else {
789 Self::Disabled
790 }
791 }
792
793 fn hidden(&self) -> Self {
794 match *self {
795 Self::Enabled {
796 setting_configuration,
797 ..
798 } => Self::Enabled {
799 setting_configuration,
800 toggle_override: setting_configuration,
801 },
802 Self::Disabled => Self::Disabled,
803 }
804 }
805
806 fn disabled(&self) -> bool {
807 matches!(*self, Self::Disabled)
808 }
809
810 fn settings_visibility(&self) -> bool {
811 match *self {
812 Self::Enabled {
813 setting_configuration,
814 ..
815 } => setting_configuration,
816 _ => false,
817 }
818 }
819
820 fn visible(&self) -> bool {
821 match *self {
822 Self::Enabled {
823 setting_configuration,
824 toggle_override,
825 } => setting_configuration ^ toggle_override,
826 _ => false,
827 }
828 }
829
830 fn toggle_visibility(&self) -> Self {
831 match *self {
832 Self::Enabled {
833 toggle_override,
834 setting_configuration,
835 } => Self::Enabled {
836 setting_configuration,
837 toggle_override: !toggle_override,
838 },
839 Self::Disabled => Self::Disabled,
840 }
841 }
842}
843
844#[derive(Debug, Clone, Copy, PartialEq, Eq)]
845pub enum BufferSerialization {
846 All,
847 NonDirtyBuffers,
848}
849
850impl BufferSerialization {
851 fn new(restore_unsaved_buffers: bool) -> Self {
852 if restore_unsaved_buffers {
853 Self::All
854 } else {
855 Self::NonDirtyBuffers
856 }
857 }
858}
859
860#[derive(Clone, Debug)]
861struct RunnableTasks {
862 templates: Vec<(TaskSourceKind, TaskTemplate)>,
863 offset: multi_buffer::Anchor,
864 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
865 column: u32,
866 // Values of all named captures, including those starting with '_'
867 extra_variables: HashMap<String, String>,
868 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
869 context_range: Range<BufferOffset>,
870}
871
872impl RunnableTasks {
873 fn resolve<'a>(
874 &'a self,
875 cx: &'a task::TaskContext,
876 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
877 self.templates.iter().filter_map(|(kind, template)| {
878 template
879 .resolve_task(&kind.to_id_base(), cx)
880 .map(|task| (kind.clone(), task))
881 })
882 }
883}
884
885#[derive(Clone)]
886pub struct ResolvedTasks {
887 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
888 position: Anchor,
889}
890
891/// Addons allow storing per-editor state in other crates (e.g. Vim)
892pub trait Addon: 'static {
893 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
894
895 fn render_buffer_header_controls(
896 &self,
897 _: &ExcerptInfo,
898 _: &Window,
899 _: &App,
900 ) -> Option<AnyElement> {
901 None
902 }
903
904 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
905 None
906 }
907
908 fn to_any(&self) -> &dyn std::any::Any;
909
910 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
911 None
912 }
913}
914
915struct ChangeLocation {
916 current: Option<Vec<Anchor>>,
917 original: Vec<Anchor>,
918}
919impl ChangeLocation {
920 fn locations(&self) -> &[Anchor] {
921 self.current.as_ref().unwrap_or(&self.original)
922 }
923}
924
925/// A set of caret positions, registered when the editor was edited.
926pub struct ChangeList {
927 changes: Vec<ChangeLocation>,
928 /// Currently "selected" change.
929 position: Option<usize>,
930}
931
932impl ChangeList {
933 pub fn new() -> Self {
934 Self {
935 changes: Vec::new(),
936 position: None,
937 }
938 }
939
940 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
941 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
942 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
943 if self.changes.is_empty() {
944 return None;
945 }
946
947 let prev = self.position.unwrap_or(self.changes.len());
948 let next = if direction == Direction::Prev {
949 prev.saturating_sub(count)
950 } else {
951 (prev + count).min(self.changes.len() - 1)
952 };
953 self.position = Some(next);
954 self.changes.get(next).map(|change| change.locations())
955 }
956
957 /// Adds a new change to the list, resetting the change list position.
958 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
959 self.position.take();
960 if let Some(last) = self.changes.last_mut()
961 && group
962 {
963 last.current = Some(new_positions)
964 } else {
965 self.changes.push(ChangeLocation {
966 original: new_positions,
967 current: None,
968 });
969 }
970 }
971
972 pub fn last(&self) -> Option<&[Anchor]> {
973 self.changes.last().map(|change| change.locations())
974 }
975
976 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
977 self.changes.last().map(|change| change.original.as_slice())
978 }
979
980 pub fn invert_last_group(&mut self) {
981 if let Some(last) = self.changes.last_mut()
982 && let Some(current) = last.current.as_mut()
983 {
984 mem::swap(&mut last.original, current);
985 }
986 }
987}
988
989#[derive(Clone)]
990struct InlineBlamePopoverState {
991 scroll_handle: ScrollHandle,
992 commit_message: Option<ParsedCommitMessage>,
993 markdown: Entity<Markdown>,
994}
995
996struct InlineBlamePopover {
997 position: gpui::Point<Pixels>,
998 hide_task: Option<Task<()>>,
999 popover_bounds: Option<Bounds<Pixels>>,
1000 popover_state: InlineBlamePopoverState,
1001 keyboard_grace: bool,
1002}
1003
1004enum SelectionDragState {
1005 /// State when no drag related activity is detected.
1006 None,
1007 /// State when the mouse is down on a selection that is about to be dragged.
1008 ReadyToDrag {
1009 selection: Selection<Anchor>,
1010 click_position: gpui::Point<Pixels>,
1011 mouse_down_time: Instant,
1012 },
1013 /// State when the mouse is dragging the selection in the editor.
1014 Dragging {
1015 selection: Selection<Anchor>,
1016 drop_cursor: Selection<Anchor>,
1017 hide_drop_cursor: bool,
1018 },
1019}
1020
1021enum ColumnarSelectionState {
1022 FromMouse {
1023 selection_tail: Anchor,
1024 display_point: Option<DisplayPoint>,
1025 },
1026 FromSelection {
1027 selection_tail: Anchor,
1028 },
1029}
1030
1031/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1032/// a breakpoint on them.
1033#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1034struct PhantomBreakpointIndicator {
1035 display_row: DisplayRow,
1036 /// There's a small debounce between hovering over the line and showing the indicator.
1037 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1038 is_active: bool,
1039 collides_with_existing_breakpoint: bool,
1040}
1041
1042/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1043/// in diff view mode.
1044#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1045pub(crate) struct PhantomDiffReviewIndicator {
1046 /// The starting anchor of the selection (or the only row if not dragging).
1047 pub start: Anchor,
1048 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1049 pub end: Anchor,
1050 /// There's a small debounce between hovering over the line and showing the indicator.
1051 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1052 pub is_active: bool,
1053}
1054
1055#[derive(Clone, Debug)]
1056pub(crate) struct DiffReviewDragState {
1057 pub start_anchor: Anchor,
1058 pub current_anchor: Anchor,
1059}
1060
1061impl DiffReviewDragState {
1062 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1063 let start = self.start_anchor.to_display_point(snapshot).row();
1064 let current = self.current_anchor.to_display_point(snapshot).row();
1065
1066 (start..=current).sorted()
1067 }
1068}
1069
1070/// Identifies a specific hunk in the diff buffer.
1071/// Used as a key to group comments by their location.
1072#[derive(Clone, Debug)]
1073pub struct DiffHunkKey {
1074 /// The file path (relative to worktree) this hunk belongs to.
1075 pub file_path: Arc<util::rel_path::RelPath>,
1076 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1077 pub hunk_start_anchor: Anchor,
1078}
1079
1080/// A review comment stored locally before being sent to the Agent panel.
1081#[derive(Clone)]
1082pub struct StoredReviewComment {
1083 /// Unique identifier for this comment (for edit/delete operations).
1084 pub id: usize,
1085 /// The comment text entered by the user.
1086 pub comment: String,
1087 /// Anchors for the code range being reviewed.
1088 pub range: Range<Anchor>,
1089 /// Timestamp when the comment was created (for chronological ordering).
1090 pub created_at: Instant,
1091 /// Whether this comment is currently being edited inline.
1092 pub is_editing: bool,
1093}
1094
1095impl StoredReviewComment {
1096 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1097 Self {
1098 id,
1099 comment,
1100 range: anchor_range,
1101 created_at: Instant::now(),
1102 is_editing: false,
1103 }
1104 }
1105}
1106
1107/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1108pub(crate) struct DiffReviewOverlay {
1109 pub anchor_range: Range<Anchor>,
1110 /// The block ID for the overlay.
1111 pub block_id: CustomBlockId,
1112 /// The editor entity for the review input.
1113 pub prompt_editor: Entity<Editor>,
1114 /// The hunk key this overlay belongs to.
1115 pub hunk_key: DiffHunkKey,
1116 /// Whether the comments section is expanded.
1117 pub comments_expanded: bool,
1118 /// Editors for comments currently being edited inline.
1119 /// Key: comment ID, Value: Editor entity for inline editing.
1120 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1121 /// Subscriptions for inline edit editors' action handlers.
1122 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1123 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1124 /// The current user's avatar URI for display in comment rows.
1125 pub user_avatar_uri: Option<SharedUri>,
1126 /// Subscription to keep the action handler alive.
1127 _subscription: Subscription,
1128}
1129
1130/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1131///
1132/// See the [module level documentation](self) for more information.
1133pub struct Editor {
1134 focus_handle: FocusHandle,
1135 last_focused_descendant: Option<WeakFocusHandle>,
1136 /// The text buffer being edited
1137 buffer: Entity<MultiBuffer>,
1138 /// Map of how text in the buffer should be displayed.
1139 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1140 pub display_map: Entity<DisplayMap>,
1141 placeholder_display_map: Option<Entity<DisplayMap>>,
1142 pub selections: SelectionsCollection,
1143 pub scroll_manager: ScrollManager,
1144 /// When inline assist editors are linked, they all render cursors because
1145 /// typing enters text into each of them, even the ones that aren't focused.
1146 pub(crate) show_cursor_when_unfocused: bool,
1147 columnar_selection_state: Option<ColumnarSelectionState>,
1148 add_selections_state: Option<AddSelectionsState>,
1149 select_next_state: Option<SelectNextState>,
1150 select_prev_state: Option<SelectNextState>,
1151 selection_history: SelectionHistory,
1152 defer_selection_effects: bool,
1153 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1154 autoclose_regions: Vec<AutocloseRegion>,
1155 snippet_stack: InvalidationStack<SnippetState>,
1156 select_syntax_node_history: SelectSyntaxNodeHistory,
1157 ime_transaction: Option<TransactionId>,
1158 pub diagnostics_max_severity: DiagnosticSeverity,
1159 active_diagnostics: ActiveDiagnostic,
1160 show_inline_diagnostics: bool,
1161 inline_diagnostics_update: Task<()>,
1162 inline_diagnostics_enabled: bool,
1163 diagnostics_enabled: bool,
1164 word_completions_enabled: bool,
1165 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1166 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1167 hard_wrap: Option<usize>,
1168 project: Option<Entity<Project>>,
1169 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1170 completion_provider: Option<Rc<dyn CompletionProvider>>,
1171 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1172 blink_manager: Entity<BlinkManager>,
1173 show_cursor_names: bool,
1174 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1175 pub show_local_selections: bool,
1176 mode: EditorMode,
1177 show_breadcrumbs: bool,
1178 show_gutter: bool,
1179 show_scrollbars: ScrollbarAxes,
1180 minimap_visibility: MinimapVisibility,
1181 offset_content: bool,
1182 disable_expand_excerpt_buttons: bool,
1183 delegate_expand_excerpts: bool,
1184 delegate_stage_and_restore: bool,
1185 delegate_open_excerpts: bool,
1186 enable_lsp_data: bool,
1187 enable_runnables: bool,
1188 show_line_numbers: Option<bool>,
1189 use_relative_line_numbers: Option<bool>,
1190 show_git_diff_gutter: Option<bool>,
1191 show_code_actions: Option<bool>,
1192 show_runnables: Option<bool>,
1193 show_breakpoints: Option<bool>,
1194 show_diff_review_button: bool,
1195 show_wrap_guides: Option<bool>,
1196 show_indent_guides: Option<bool>,
1197 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1198 highlight_order: usize,
1199 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1200 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1201 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1202 scrollbar_marker_state: ScrollbarMarkerState,
1203 active_indent_guides_state: ActiveIndentGuidesState,
1204 nav_history: Option<ItemNavHistory>,
1205 context_menu: RefCell<Option<CodeContextMenu>>,
1206 context_menu_options: Option<ContextMenuOptions>,
1207 mouse_context_menu: Option<MouseContextMenu>,
1208 completion_tasks: Vec<(CompletionId, Task<()>)>,
1209 inline_blame_popover: Option<InlineBlamePopover>,
1210 inline_blame_popover_show_task: Option<Task<()>>,
1211 signature_help_state: SignatureHelpState,
1212 auto_signature_help: Option<bool>,
1213 find_all_references_task_sources: Vec<Anchor>,
1214 next_completion_id: CompletionId,
1215 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1216 code_actions_task: Option<Task<Result<()>>>,
1217 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1218 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1219 debounced_selection_highlight_complete: bool,
1220 document_highlights_task: Option<Task<()>>,
1221 linked_editing_range_task: Option<Task<Option<()>>>,
1222 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1223 pending_rename: Option<RenameState>,
1224 searchable: bool,
1225 cursor_shape: CursorShape,
1226 /// Whether the cursor is offset one character to the left when something is
1227 /// selected (needed for vim visual mode)
1228 cursor_offset_on_selection: bool,
1229 current_line_highlight: Option<CurrentLineHighlight>,
1230 /// Whether to collapse search match ranges to just their start position.
1231 /// When true, navigating to a match positions the cursor at the match
1232 /// without selecting the matched text.
1233 collapse_matches: bool,
1234 autoindent_mode: Option<AutoindentMode>,
1235 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1236 input_enabled: bool,
1237 expects_character_input: bool,
1238 use_modal_editing: bool,
1239 read_only: bool,
1240 leader_id: Option<CollaboratorId>,
1241 remote_id: Option<ViewId>,
1242 pub hover_state: HoverState,
1243 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1244 prev_pressure_stage: Option<PressureStage>,
1245 gutter_hovered: bool,
1246 hovered_link_state: Option<HoveredLinkState>,
1247 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1248 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1249 active_edit_prediction: Option<EditPredictionState>,
1250 /// Used to prevent flickering as the user types while the menu is open
1251 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1252 edit_prediction_settings: EditPredictionSettings,
1253 edit_predictions_hidden_for_vim_mode: bool,
1254 show_edit_predictions_override: Option<bool>,
1255 show_completions_on_input_override: Option<bool>,
1256 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1257 edit_prediction_preview: EditPredictionPreview,
1258 edit_prediction_indent_conflict: bool,
1259 edit_prediction_requires_modifier_in_indent_conflict: bool,
1260 next_inlay_id: usize,
1261 next_color_inlay_id: usize,
1262 _subscriptions: Vec<Subscription>,
1263 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1264 gutter_dimensions: GutterDimensions,
1265 style: Option<EditorStyle>,
1266 text_style_refinement: Option<TextStyleRefinement>,
1267 next_editor_action_id: EditorActionId,
1268 editor_actions: Rc<
1269 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1270 >,
1271 use_autoclose: bool,
1272 use_auto_surround: bool,
1273 auto_replace_emoji_shortcode: bool,
1274 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1275 show_git_blame_gutter: bool,
1276 show_git_blame_inline: bool,
1277 show_git_blame_inline_delay_task: Option<Task<()>>,
1278 git_blame_inline_enabled: bool,
1279 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1280 buffer_serialization: Option<BufferSerialization>,
1281 show_selection_menu: Option<bool>,
1282 blame: Option<Entity<GitBlame>>,
1283 blame_subscription: Option<Subscription>,
1284 custom_context_menu: Option<
1285 Box<
1286 dyn 'static
1287 + Fn(
1288 &mut Self,
1289 DisplayPoint,
1290 &mut Window,
1291 &mut Context<Self>,
1292 ) -> Option<Entity<ui::ContextMenu>>,
1293 >,
1294 >,
1295 last_bounds: Option<Bounds<Pixels>>,
1296 last_position_map: Option<Rc<PositionMap>>,
1297 expect_bounds_change: Option<Bounds<Pixels>>,
1298 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1299 tasks_update_task: Option<Task<()>>,
1300 breakpoint_store: Option<Entity<BreakpointStore>>,
1301 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1302 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1303 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1304 /// Active diff review overlays. Multiple overlays can be open simultaneously
1305 /// when hunks have comments stored.
1306 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1307 /// Stored review comments grouped by hunk.
1308 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1309 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1310 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1311 /// Counter for generating unique comment IDs.
1312 next_review_comment_id: usize,
1313 hovered_diff_hunk_row: Option<DisplayRow>,
1314 pull_diagnostics_task: Task<()>,
1315 in_project_search: bool,
1316 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1317 breadcrumb_header: Option<String>,
1318 focused_block: Option<FocusedBlock>,
1319 next_scroll_position: NextScrollCursorCenterTopBottom,
1320 addons: HashMap<TypeId, Box<dyn Addon>>,
1321 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1322 load_diff_task: Option<Shared<Task<()>>>,
1323 /// Whether we are temporarily displaying a diff other than git's
1324 temporary_diff_override: bool,
1325 selection_mark_mode: bool,
1326 toggle_fold_multiple_buffers: Task<()>,
1327 _scroll_cursor_center_top_bottom_task: Task<()>,
1328 serialize_selections: Task<()>,
1329 serialize_folds: Task<()>,
1330 mouse_cursor_hidden: bool,
1331 minimap: Option<Entity<Self>>,
1332 hide_mouse_mode: HideMouseMode,
1333 pub change_list: ChangeList,
1334 inline_value_cache: InlineValueCache,
1335 number_deleted_lines: bool,
1336
1337 selection_drag_state: SelectionDragState,
1338 colors: Option<LspColorData>,
1339 post_scroll_update: Task<()>,
1340 refresh_colors_task: Task<()>,
1341 use_document_folding_ranges: bool,
1342 refresh_folding_ranges_task: Task<()>,
1343 inlay_hints: Option<LspInlayHintData>,
1344 folding_newlines: Task<()>,
1345 select_next_is_case_sensitive: Option<bool>,
1346 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1347 on_local_selections_changed:
1348 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1349 suppress_selection_callback: bool,
1350 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1351 accent_data: Option<AccentData>,
1352 bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1353 semantic_token_state: SemanticTokenState,
1354 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1355 refresh_document_symbols_task: Shared<Task<()>>,
1356 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1357 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1358 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1359 sticky_headers_task: Task<()>,
1360 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1361 pub(crate) colorize_brackets_task: Task<()>,
1362}
1363
1364#[derive(Debug, PartialEq)]
1365struct AccentData {
1366 colors: AccentColors,
1367 overrides: Vec<SharedString>,
1368}
1369
1370fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1371 if debounce_ms > 0 {
1372 Some(Duration::from_millis(debounce_ms))
1373 } else {
1374 None
1375 }
1376}
1377
1378#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1379enum NextScrollCursorCenterTopBottom {
1380 #[default]
1381 Center,
1382 Top,
1383 Bottom,
1384}
1385
1386impl NextScrollCursorCenterTopBottom {
1387 fn next(&self) -> Self {
1388 match self {
1389 Self::Center => Self::Top,
1390 Self::Top => Self::Bottom,
1391 Self::Bottom => Self::Center,
1392 }
1393 }
1394}
1395
1396#[derive(Clone)]
1397pub struct EditorSnapshot {
1398 pub mode: EditorMode,
1399 show_gutter: bool,
1400 offset_content: bool,
1401 show_line_numbers: Option<bool>,
1402 number_deleted_lines: bool,
1403 show_git_diff_gutter: Option<bool>,
1404 show_code_actions: Option<bool>,
1405 show_runnables: Option<bool>,
1406 show_breakpoints: Option<bool>,
1407 git_blame_gutter_max_author_length: Option<usize>,
1408 pub display_snapshot: DisplaySnapshot,
1409 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1410 is_focused: bool,
1411 scroll_anchor: SharedScrollAnchor,
1412 ongoing_scroll: OngoingScroll,
1413 current_line_highlight: CurrentLineHighlight,
1414 gutter_hovered: bool,
1415 semantic_tokens_enabled: bool,
1416}
1417
1418#[derive(Default, Debug, Clone, Copy)]
1419pub struct GutterDimensions {
1420 pub left_padding: Pixels,
1421 pub right_padding: Pixels,
1422 pub width: Pixels,
1423 pub margin: Pixels,
1424 pub git_blame_entries_width: Option<Pixels>,
1425}
1426
1427impl GutterDimensions {
1428 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1429 Self {
1430 margin: Self::default_gutter_margin(font_id, font_size, cx),
1431 ..Default::default()
1432 }
1433 }
1434
1435 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1436 -cx.text_system().descent(font_id, font_size)
1437 }
1438 /// The full width of the space taken up by the gutter.
1439 pub fn full_width(&self) -> Pixels {
1440 self.margin + self.width
1441 }
1442
1443 /// The width of the space reserved for the fold indicators,
1444 /// use alongside 'justify_end' and `gutter_width` to
1445 /// right align content with the line numbers
1446 pub fn fold_area_width(&self) -> Pixels {
1447 self.margin + self.right_padding
1448 }
1449}
1450
1451struct CharacterDimensions {
1452 em_width: Pixels,
1453 em_advance: Pixels,
1454 line_height: Pixels,
1455}
1456
1457#[derive(Debug)]
1458pub struct RemoteSelection {
1459 pub replica_id: ReplicaId,
1460 pub selection: Selection<Anchor>,
1461 pub cursor_shape: CursorShape,
1462 pub collaborator_id: CollaboratorId,
1463 pub line_mode: bool,
1464 pub user_name: Option<SharedString>,
1465 pub color: PlayerColor,
1466}
1467
1468#[derive(Clone, Debug)]
1469struct SelectionHistoryEntry {
1470 selections: Arc<[Selection<Anchor>]>,
1471 select_next_state: Option<SelectNextState>,
1472 select_prev_state: Option<SelectNextState>,
1473 add_selections_state: Option<AddSelectionsState>,
1474}
1475
1476#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1477enum SelectionHistoryMode {
1478 #[default]
1479 Normal,
1480 Undoing,
1481 Redoing,
1482 Skipping,
1483}
1484
1485#[derive(Clone, PartialEq, Eq, Hash)]
1486struct HoveredCursor {
1487 replica_id: ReplicaId,
1488 selection_id: usize,
1489}
1490
1491#[derive(Debug)]
1492/// SelectionEffects controls the side-effects of updating the selection.
1493///
1494/// The default behaviour does "what you mostly want":
1495/// - it pushes to the nav history if the cursor moved by >10 lines
1496/// - it re-triggers completion requests
1497/// - it scrolls to fit
1498///
1499/// You might want to modify these behaviours. For example when doing a "jump"
1500/// like go to definition, we always want to add to nav history; but when scrolling
1501/// in vim mode we never do.
1502///
1503/// Similarly, you might want to disable scrolling if you don't want the viewport to
1504/// move.
1505#[derive(Clone)]
1506pub struct SelectionEffects {
1507 nav_history: Option<bool>,
1508 completions: bool,
1509 scroll: Option<Autoscroll>,
1510}
1511
1512impl Default for SelectionEffects {
1513 fn default() -> Self {
1514 Self {
1515 nav_history: None,
1516 completions: true,
1517 scroll: Some(Autoscroll::fit()),
1518 }
1519 }
1520}
1521impl SelectionEffects {
1522 pub fn scroll(scroll: Autoscroll) -> Self {
1523 Self {
1524 scroll: Some(scroll),
1525 ..Default::default()
1526 }
1527 }
1528
1529 pub fn no_scroll() -> Self {
1530 Self {
1531 scroll: None,
1532 ..Default::default()
1533 }
1534 }
1535
1536 pub fn completions(self, completions: bool) -> Self {
1537 Self {
1538 completions,
1539 ..self
1540 }
1541 }
1542
1543 pub fn nav_history(self, nav_history: bool) -> Self {
1544 Self {
1545 nav_history: Some(nav_history),
1546 ..self
1547 }
1548 }
1549}
1550
1551struct DeferredSelectionEffectsState {
1552 changed: bool,
1553 effects: SelectionEffects,
1554 old_cursor_position: Anchor,
1555 history_entry: SelectionHistoryEntry,
1556}
1557
1558#[derive(Default)]
1559struct SelectionHistory {
1560 #[allow(clippy::type_complexity)]
1561 selections_by_transaction:
1562 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1563 mode: SelectionHistoryMode,
1564 undo_stack: VecDeque<SelectionHistoryEntry>,
1565 redo_stack: VecDeque<SelectionHistoryEntry>,
1566}
1567
1568impl SelectionHistory {
1569 #[track_caller]
1570 fn insert_transaction(
1571 &mut self,
1572 transaction_id: TransactionId,
1573 selections: Arc<[Selection<Anchor>]>,
1574 ) {
1575 if selections.is_empty() {
1576 log::error!(
1577 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1578 std::panic::Location::caller()
1579 );
1580 return;
1581 }
1582 self.selections_by_transaction
1583 .insert(transaction_id, (selections, None));
1584 }
1585
1586 #[allow(clippy::type_complexity)]
1587 fn transaction(
1588 &self,
1589 transaction_id: TransactionId,
1590 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1591 self.selections_by_transaction.get(&transaction_id)
1592 }
1593
1594 #[allow(clippy::type_complexity)]
1595 fn transaction_mut(
1596 &mut self,
1597 transaction_id: TransactionId,
1598 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1599 self.selections_by_transaction.get_mut(&transaction_id)
1600 }
1601
1602 fn push(&mut self, entry: SelectionHistoryEntry) {
1603 if !entry.selections.is_empty() {
1604 match self.mode {
1605 SelectionHistoryMode::Normal => {
1606 self.push_undo(entry);
1607 self.redo_stack.clear();
1608 }
1609 SelectionHistoryMode::Undoing => self.push_redo(entry),
1610 SelectionHistoryMode::Redoing => self.push_undo(entry),
1611 SelectionHistoryMode::Skipping => {}
1612 }
1613 }
1614 }
1615
1616 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1617 if self
1618 .undo_stack
1619 .back()
1620 .is_none_or(|e| e.selections != entry.selections)
1621 {
1622 self.undo_stack.push_back(entry);
1623 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1624 self.undo_stack.pop_front();
1625 }
1626 }
1627 }
1628
1629 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1630 if self
1631 .redo_stack
1632 .back()
1633 .is_none_or(|e| e.selections != entry.selections)
1634 {
1635 self.redo_stack.push_back(entry);
1636 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1637 self.redo_stack.pop_front();
1638 }
1639 }
1640 }
1641}
1642
1643#[derive(Clone, Copy)]
1644pub struct RowHighlightOptions {
1645 pub autoscroll: bool,
1646 pub include_gutter: bool,
1647}
1648
1649impl Default for RowHighlightOptions {
1650 fn default() -> Self {
1651 Self {
1652 autoscroll: Default::default(),
1653 include_gutter: true,
1654 }
1655 }
1656}
1657
1658struct RowHighlight {
1659 index: usize,
1660 range: Range<Anchor>,
1661 color: Hsla,
1662 options: RowHighlightOptions,
1663 type_id: TypeId,
1664}
1665
1666#[derive(Clone, Debug)]
1667struct AddSelectionsState {
1668 groups: Vec<AddSelectionsGroup>,
1669}
1670
1671#[derive(Clone, Debug)]
1672struct AddSelectionsGroup {
1673 above: bool,
1674 stack: Vec<usize>,
1675}
1676
1677#[derive(Clone)]
1678struct SelectNextState {
1679 query: AhoCorasick,
1680 wordwise: bool,
1681 done: bool,
1682}
1683
1684impl std::fmt::Debug for SelectNextState {
1685 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1686 f.debug_struct(std::any::type_name::<Self>())
1687 .field("wordwise", &self.wordwise)
1688 .field("done", &self.done)
1689 .finish()
1690 }
1691}
1692
1693#[derive(Debug)]
1694struct AutocloseRegion {
1695 selection_id: usize,
1696 range: Range<Anchor>,
1697 pair: BracketPair,
1698}
1699
1700#[derive(Debug)]
1701struct SnippetState {
1702 ranges: Vec<Vec<Range<Anchor>>>,
1703 active_index: usize,
1704 choices: Vec<Option<Vec<String>>>,
1705}
1706
1707#[doc(hidden)]
1708pub struct RenameState {
1709 pub range: Range<Anchor>,
1710 pub old_name: Arc<str>,
1711 pub editor: Entity<Editor>,
1712 block_id: CustomBlockId,
1713}
1714
1715struct InvalidationStack<T>(Vec<T>);
1716
1717struct RegisteredEditPredictionDelegate {
1718 provider: Arc<dyn EditPredictionDelegateHandle>,
1719 _subscription: Subscription,
1720}
1721
1722#[derive(Debug, PartialEq, Eq)]
1723pub struct ActiveDiagnosticGroup {
1724 pub active_range: Range<Anchor>,
1725 pub active_message: String,
1726 pub group_id: usize,
1727 pub blocks: HashSet<CustomBlockId>,
1728}
1729
1730#[derive(Debug, PartialEq, Eq)]
1731
1732pub(crate) enum ActiveDiagnostic {
1733 None,
1734 All,
1735 Group(ActiveDiagnosticGroup),
1736}
1737
1738#[derive(Serialize, Deserialize, Clone, Debug)]
1739pub struct ClipboardSelection {
1740 /// The number of bytes in this selection.
1741 pub len: usize,
1742 /// Whether this was a full-line selection.
1743 pub is_entire_line: bool,
1744 /// The indentation of the first line when this content was originally copied.
1745 pub first_line_indent: u32,
1746 #[serde(default)]
1747 pub file_path: Option<PathBuf>,
1748 #[serde(default)]
1749 pub line_range: Option<RangeInclusive<u32>>,
1750}
1751
1752impl ClipboardSelection {
1753 pub fn for_buffer(
1754 len: usize,
1755 is_entire_line: bool,
1756 range: Range<Point>,
1757 buffer: &MultiBufferSnapshot,
1758 project: Option<&Entity<Project>>,
1759 cx: &App,
1760 ) -> Self {
1761 let first_line_indent = buffer
1762 .indent_size_for_line(MultiBufferRow(range.start.row))
1763 .len;
1764
1765 let file_path = util::maybe!({
1766 let project = project?.read(cx);
1767 let file = buffer.file_at(range.start)?;
1768 let project_path = ProjectPath {
1769 worktree_id: file.worktree_id(cx),
1770 path: file.path().clone(),
1771 };
1772 project.absolute_path(&project_path, cx)
1773 });
1774
1775 let line_range = file_path.as_ref().and_then(|_| {
1776 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1777 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1778 if start_excerpt_id == end_excerpt_id {
1779 Some(start_point.row..=end_point.row)
1780 } else {
1781 None
1782 }
1783 });
1784
1785 Self {
1786 len,
1787 is_entire_line,
1788 first_line_indent,
1789 file_path,
1790 line_range,
1791 }
1792 }
1793}
1794
1795// selections, scroll behavior, was newest selection reversed
1796type SelectSyntaxNodeHistoryState = (
1797 Box<[Selection<Anchor>]>,
1798 SelectSyntaxNodeScrollBehavior,
1799 bool,
1800);
1801
1802#[derive(Default)]
1803struct SelectSyntaxNodeHistory {
1804 stack: Vec<SelectSyntaxNodeHistoryState>,
1805 // disable temporarily to allow changing selections without losing the stack
1806 pub disable_clearing: bool,
1807}
1808
1809impl SelectSyntaxNodeHistory {
1810 pub fn try_clear(&mut self) {
1811 if !self.disable_clearing {
1812 self.stack.clear();
1813 }
1814 }
1815
1816 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1817 self.stack.push(selection);
1818 }
1819
1820 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1821 self.stack.pop()
1822 }
1823}
1824
1825enum SelectSyntaxNodeScrollBehavior {
1826 CursorTop,
1827 FitSelection,
1828 CursorBottom,
1829}
1830
1831#[derive(Debug, Clone, Copy)]
1832pub(crate) struct NavigationData {
1833 cursor_anchor: Anchor,
1834 cursor_position: Point,
1835 scroll_anchor: ScrollAnchor,
1836 scroll_top_row: u32,
1837}
1838
1839#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1840pub enum GotoDefinitionKind {
1841 Symbol,
1842 Declaration,
1843 Type,
1844 Implementation,
1845}
1846
1847pub enum FormatTarget {
1848 Buffers(HashSet<Entity<Buffer>>),
1849 Ranges(Vec<Range<MultiBufferPoint>>),
1850}
1851
1852pub(crate) struct FocusedBlock {
1853 id: BlockId,
1854 focus_handle: WeakFocusHandle,
1855}
1856
1857#[derive(Clone, Debug)]
1858pub enum JumpData {
1859 MultiBufferRow {
1860 row: MultiBufferRow,
1861 line_offset_from_top: u32,
1862 },
1863 MultiBufferPoint {
1864 excerpt_id: ExcerptId,
1865 position: Point,
1866 anchor: text::Anchor,
1867 line_offset_from_top: u32,
1868 },
1869}
1870
1871pub enum MultibufferSelectionMode {
1872 First,
1873 All,
1874}
1875
1876#[derive(Clone, Copy, Debug, Default)]
1877pub struct RewrapOptions {
1878 pub override_language_settings: bool,
1879 pub preserve_existing_whitespace: bool,
1880}
1881
1882impl Editor {
1883 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1884 let buffer = cx.new(|cx| Buffer::local("", cx));
1885 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1886 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1887 }
1888
1889 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1890 let buffer = cx.new(|cx| Buffer::local("", cx));
1891 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1892 Self::new(EditorMode::full(), buffer, None, window, cx)
1893 }
1894
1895 pub fn auto_height(
1896 min_lines: usize,
1897 max_lines: usize,
1898 window: &mut Window,
1899 cx: &mut Context<Self>,
1900 ) -> Self {
1901 let buffer = cx.new(|cx| Buffer::local("", cx));
1902 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1903 Self::new(
1904 EditorMode::AutoHeight {
1905 min_lines,
1906 max_lines: Some(max_lines),
1907 },
1908 buffer,
1909 None,
1910 window,
1911 cx,
1912 )
1913 }
1914
1915 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1916 /// The editor grows as tall as needed to fit its content.
1917 pub fn auto_height_unbounded(
1918 min_lines: usize,
1919 window: &mut Window,
1920 cx: &mut Context<Self>,
1921 ) -> Self {
1922 let buffer = cx.new(|cx| Buffer::local("", cx));
1923 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1924 Self::new(
1925 EditorMode::AutoHeight {
1926 min_lines,
1927 max_lines: None,
1928 },
1929 buffer,
1930 None,
1931 window,
1932 cx,
1933 )
1934 }
1935
1936 pub fn for_buffer(
1937 buffer: Entity<Buffer>,
1938 project: Option<Entity<Project>>,
1939 window: &mut Window,
1940 cx: &mut Context<Self>,
1941 ) -> Self {
1942 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1943 Self::new(EditorMode::full(), buffer, project, window, cx)
1944 }
1945
1946 pub fn for_multibuffer(
1947 buffer: Entity<MultiBuffer>,
1948 project: Option<Entity<Project>>,
1949 window: &mut Window,
1950 cx: &mut Context<Self>,
1951 ) -> Self {
1952 Self::new(EditorMode::full(), buffer, project, window, cx)
1953 }
1954
1955 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1956 let mut clone = Self::new(
1957 self.mode.clone(),
1958 self.buffer.clone(),
1959 self.project.clone(),
1960 window,
1961 cx,
1962 );
1963 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1964 let snapshot = display_map.snapshot(cx);
1965 clone.display_map.update(cx, |display_map, cx| {
1966 display_map.set_state(&snapshot, cx);
1967 });
1968 snapshot
1969 });
1970 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1971 clone.folds_did_change(cx);
1972 clone.selections.clone_state(&self.selections);
1973 clone
1974 .scroll_manager
1975 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1976 clone.searchable = self.searchable;
1977 clone.read_only = self.read_only;
1978 clone.buffers_with_disabled_indent_guides =
1979 self.buffers_with_disabled_indent_guides.clone();
1980 clone
1981 }
1982
1983 pub fn new(
1984 mode: EditorMode,
1985 buffer: Entity<MultiBuffer>,
1986 project: Option<Entity<Project>>,
1987 window: &mut Window,
1988 cx: &mut Context<Self>,
1989 ) -> Self {
1990 Editor::new_internal(mode, buffer, project, None, window, cx)
1991 }
1992
1993 pub fn refresh_sticky_headers(
1994 &mut self,
1995 display_snapshot: &DisplaySnapshot,
1996 cx: &mut Context<Editor>,
1997 ) {
1998 if !self.mode.is_full() {
1999 return;
2000 }
2001 let multi_buffer = display_snapshot.buffer_snapshot();
2002 let scroll_anchor = self
2003 .scroll_manager
2004 .native_anchor(display_snapshot, cx)
2005 .anchor;
2006 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2007 return;
2008 };
2009 let buffer = buffer.clone();
2010
2011 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2012 let max_row = buffer.max_point().row;
2013 let start_row = buffer_visible_start.row.min(max_row);
2014 let end_row = (buffer_visible_start.row + 10).min(max_row);
2015
2016 let syntax = self.style(cx).syntax.clone();
2017 let background_task = cx.background_spawn(async move {
2018 buffer
2019 .outline_items_containing(
2020 Point::new(start_row, 0)..Point::new(end_row, 0),
2021 true,
2022 Some(syntax.as_ref()),
2023 )
2024 .into_iter()
2025 .map(|outline_item| OutlineItem {
2026 depth: outline_item.depth,
2027 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2028 source_range_for_text: Anchor::range_in_buffer(
2029 excerpt_id,
2030 outline_item.source_range_for_text,
2031 ),
2032 text: outline_item.text,
2033 highlight_ranges: outline_item.highlight_ranges,
2034 name_ranges: outline_item.name_ranges,
2035 body_range: outline_item
2036 .body_range
2037 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2038 annotation_range: outline_item
2039 .annotation_range
2040 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2041 })
2042 .collect()
2043 });
2044 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2045 let sticky_headers = background_task.await;
2046 this.update(cx, |this, cx| {
2047 this.sticky_headers = Some(sticky_headers);
2048 cx.notify();
2049 })
2050 .ok();
2051 });
2052 }
2053
2054 fn new_internal(
2055 mode: EditorMode,
2056 multi_buffer: Entity<MultiBuffer>,
2057 project: Option<Entity<Project>>,
2058 display_map: Option<Entity<DisplayMap>>,
2059 window: &mut Window,
2060 cx: &mut Context<Self>,
2061 ) -> Self {
2062 debug_assert!(
2063 display_map.is_none() || mode.is_minimap(),
2064 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2065 );
2066
2067 let full_mode = mode.is_full();
2068 let is_minimap = mode.is_minimap();
2069 let diagnostics_max_severity = if full_mode {
2070 EditorSettings::get_global(cx)
2071 .diagnostics_max_severity
2072 .unwrap_or(DiagnosticSeverity::Hint)
2073 } else {
2074 DiagnosticSeverity::Off
2075 };
2076 let style = window.text_style();
2077 let font_size = style.font_size.to_pixels(window.rem_size());
2078 let editor = cx.entity().downgrade();
2079 let fold_placeholder = FoldPlaceholder {
2080 constrain_width: false,
2081 render: Arc::new(move |fold_id, fold_range, cx| {
2082 let editor = editor.clone();
2083 FoldPlaceholder::fold_element(fold_id, cx)
2084 .cursor_pointer()
2085 .child("⋯")
2086 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2087 .on_click(move |_, _window, cx| {
2088 editor
2089 .update(cx, |editor, cx| {
2090 editor.unfold_ranges(
2091 &[fold_range.start..fold_range.end],
2092 true,
2093 false,
2094 cx,
2095 );
2096 cx.stop_propagation();
2097 })
2098 .ok();
2099 })
2100 .into_any()
2101 }),
2102 merge_adjacent: true,
2103 ..FoldPlaceholder::default()
2104 };
2105 let display_map = display_map.unwrap_or_else(|| {
2106 cx.new(|cx| {
2107 DisplayMap::new(
2108 multi_buffer.clone(),
2109 style.font(),
2110 font_size,
2111 None,
2112 FILE_HEADER_HEIGHT,
2113 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2114 fold_placeholder,
2115 diagnostics_max_severity,
2116 cx,
2117 )
2118 })
2119 });
2120
2121 let selections = SelectionsCollection::new();
2122
2123 let blink_manager = cx.new(|cx| {
2124 let mut blink_manager = BlinkManager::new(
2125 CURSOR_BLINK_INTERVAL,
2126 |cx| EditorSettings::get_global(cx).cursor_blink,
2127 cx,
2128 );
2129 if is_minimap {
2130 blink_manager.disable(cx);
2131 }
2132 blink_manager
2133 });
2134
2135 let soft_wrap_mode_override =
2136 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2137
2138 let mut project_subscriptions = Vec::new();
2139 if full_mode && let Some(project) = project.as_ref() {
2140 project_subscriptions.push(cx.subscribe_in(
2141 project,
2142 window,
2143 |editor, _, event, window, cx| match event {
2144 project::Event::RefreshCodeLens => {
2145 // we always query lens with actions, without storing them, always refreshing them
2146 }
2147 project::Event::RefreshInlayHints {
2148 server_id,
2149 request_id,
2150 } => {
2151 editor.refresh_inlay_hints(
2152 InlayHintRefreshReason::RefreshRequested {
2153 server_id: *server_id,
2154 request_id: *request_id,
2155 },
2156 cx,
2157 );
2158 }
2159 project::Event::RefreshSemanticTokens {
2160 server_id,
2161 request_id,
2162 } => {
2163 editor.refresh_semantic_tokens(
2164 None,
2165 Some(RefreshForServer {
2166 server_id: *server_id,
2167 request_id: *request_id,
2168 }),
2169 cx,
2170 );
2171 }
2172 project::Event::LanguageServerRemoved(_) => {
2173 editor.registered_buffers.clear();
2174 editor.register_visible_buffers(cx);
2175 editor.invalidate_semantic_tokens(None);
2176 editor.update_lsp_data(None, window, cx);
2177 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2178 if editor.tasks_update_task.is_none() {
2179 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2180 }
2181 }
2182 project::Event::LanguageServerAdded(..) => {
2183 if editor.tasks_update_task.is_none() {
2184 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2185 }
2186 }
2187 project::Event::SnippetEdit(id, snippet_edits) => {
2188 // todo(lw): Non singletons
2189 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2190 let snapshot = buffer.read(cx).snapshot();
2191 let focus_handle = editor.focus_handle(cx);
2192 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2193 for (range, snippet) in snippet_edits {
2194 let buffer_range =
2195 language::range_from_lsp(*range).to_offset(&snapshot);
2196 editor
2197 .insert_snippet(
2198 &[MultiBufferOffset(buffer_range.start)
2199 ..MultiBufferOffset(buffer_range.end)],
2200 snippet.clone(),
2201 window,
2202 cx,
2203 )
2204 .ok();
2205 }
2206 }
2207 }
2208 }
2209 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2210 let buffer_id = *buffer_id;
2211 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2212 editor.register_buffer(buffer_id, cx);
2213 editor.update_lsp_data(Some(buffer_id), window, cx);
2214 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2215 refresh_linked_ranges(editor, window, cx);
2216 editor.refresh_code_actions(window, cx);
2217 editor.refresh_document_highlights(cx);
2218 }
2219 }
2220
2221 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2222 let Some(workspace) = editor.workspace() else {
2223 return;
2224 };
2225 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2226 else {
2227 return;
2228 };
2229
2230 if active_editor.entity_id() == cx.entity_id() {
2231 let entity_id = cx.entity_id();
2232 workspace.update(cx, |this, cx| {
2233 this.panes_mut()
2234 .iter_mut()
2235 .filter(|pane| pane.entity_id() != entity_id)
2236 .for_each(|p| {
2237 p.update(cx, |pane, _| {
2238 pane.nav_history_mut().rename_item(
2239 entity_id,
2240 project_path.clone(),
2241 abs_path.clone().into(),
2242 );
2243 })
2244 });
2245 });
2246
2247 Self::open_transaction_for_hidden_buffers(
2248 workspace,
2249 transaction.clone(),
2250 "Rename".to_string(),
2251 window,
2252 cx,
2253 );
2254 }
2255 }
2256
2257 project::Event::WorkspaceEditApplied(transaction) => {
2258 let Some(workspace) = editor.workspace() else {
2259 return;
2260 };
2261 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2262 else {
2263 return;
2264 };
2265
2266 if active_editor.entity_id() == cx.entity_id() {
2267 Self::open_transaction_for_hidden_buffers(
2268 workspace,
2269 transaction.clone(),
2270 "LSP Edit".to_string(),
2271 window,
2272 cx,
2273 );
2274 }
2275 }
2276
2277 _ => {}
2278 },
2279 ));
2280 if let Some(task_inventory) = project
2281 .read(cx)
2282 .task_store()
2283 .read(cx)
2284 .task_inventory()
2285 .cloned()
2286 {
2287 project_subscriptions.push(cx.observe_in(
2288 &task_inventory,
2289 window,
2290 |editor, _, window, cx| {
2291 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2292 },
2293 ));
2294 };
2295
2296 project_subscriptions.push(cx.subscribe_in(
2297 &project.read(cx).breakpoint_store(),
2298 window,
2299 |editor, _, event, window, cx| match event {
2300 BreakpointStoreEvent::ClearDebugLines => {
2301 editor.clear_row_highlights::<ActiveDebugLine>();
2302 editor.refresh_inline_values(cx);
2303 }
2304 BreakpointStoreEvent::SetDebugLine => {
2305 if editor.go_to_active_debug_line(window, cx) {
2306 cx.stop_propagation();
2307 }
2308
2309 editor.refresh_inline_values(cx);
2310 }
2311 _ => {}
2312 },
2313 ));
2314 let git_store = project.read(cx).git_store().clone();
2315 let project = project.clone();
2316 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2317 if let GitStoreEvent::RepositoryAdded = event {
2318 this.load_diff_task = Some(
2319 update_uncommitted_diff_for_buffer(
2320 cx.entity(),
2321 &project,
2322 this.buffer.read(cx).all_buffers(),
2323 this.buffer.clone(),
2324 cx,
2325 )
2326 .shared(),
2327 );
2328 }
2329 }));
2330 }
2331
2332 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2333
2334 let inlay_hint_settings =
2335 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2336 let focus_handle = cx.focus_handle();
2337 if !is_minimap {
2338 cx.on_focus(&focus_handle, window, Self::handle_focus)
2339 .detach();
2340 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2341 .detach();
2342 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2343 .detach();
2344 cx.on_blur(&focus_handle, window, Self::handle_blur)
2345 .detach();
2346 cx.observe_pending_input(window, Self::observe_pending_input)
2347 .detach();
2348 }
2349
2350 let show_indent_guides =
2351 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2352 Some(false)
2353 } else {
2354 None
2355 };
2356
2357 let breakpoint_store = match (&mode, project.as_ref()) {
2358 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2359 _ => None,
2360 };
2361
2362 let mut code_action_providers = Vec::new();
2363 let mut load_uncommitted_diff = None;
2364 if let Some(project) = project.clone() {
2365 load_uncommitted_diff = Some(
2366 update_uncommitted_diff_for_buffer(
2367 cx.entity(),
2368 &project,
2369 multi_buffer.read(cx).all_buffers(),
2370 multi_buffer.clone(),
2371 cx,
2372 )
2373 .shared(),
2374 );
2375 code_action_providers.push(Rc::new(project) as Rc<_>);
2376 }
2377
2378 let mut editor = Self {
2379 focus_handle,
2380 show_cursor_when_unfocused: false,
2381 last_focused_descendant: None,
2382 buffer: multi_buffer.clone(),
2383 display_map: display_map.clone(),
2384 placeholder_display_map: None,
2385 selections,
2386 scroll_manager: ScrollManager::new(cx),
2387 columnar_selection_state: None,
2388 add_selections_state: None,
2389 select_next_state: None,
2390 select_prev_state: None,
2391 selection_history: SelectionHistory::default(),
2392 defer_selection_effects: false,
2393 deferred_selection_effects_state: None,
2394 autoclose_regions: Vec::new(),
2395 snippet_stack: InvalidationStack::default(),
2396 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2397 ime_transaction: None,
2398 active_diagnostics: ActiveDiagnostic::None,
2399 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2400 inline_diagnostics_update: Task::ready(()),
2401 inline_diagnostics: Vec::new(),
2402 soft_wrap_mode_override,
2403 diagnostics_max_severity,
2404 hard_wrap: None,
2405 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2406 semantics_provider: project
2407 .as_ref()
2408 .map(|project| Rc::new(project.downgrade()) as _),
2409 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2410 project,
2411 blink_manager: blink_manager.clone(),
2412 show_local_selections: true,
2413 show_scrollbars: ScrollbarAxes {
2414 horizontal: full_mode,
2415 vertical: full_mode,
2416 },
2417 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2418 offset_content: !matches!(mode, EditorMode::SingleLine),
2419 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2420 show_gutter: full_mode,
2421 show_line_numbers: (!full_mode).then_some(false),
2422 use_relative_line_numbers: None,
2423 disable_expand_excerpt_buttons: !full_mode,
2424 delegate_expand_excerpts: false,
2425 delegate_stage_and_restore: false,
2426 delegate_open_excerpts: false,
2427 enable_lsp_data: true,
2428 enable_runnables: true,
2429 show_git_diff_gutter: None,
2430 show_code_actions: None,
2431 show_runnables: None,
2432 show_breakpoints: None,
2433 show_diff_review_button: false,
2434 show_wrap_guides: None,
2435 show_indent_guides,
2436 buffers_with_disabled_indent_guides: HashSet::default(),
2437 highlight_order: 0,
2438 highlighted_rows: HashMap::default(),
2439 background_highlights: HashMap::default(),
2440 gutter_highlights: HashMap::default(),
2441 scrollbar_marker_state: ScrollbarMarkerState::default(),
2442 active_indent_guides_state: ActiveIndentGuidesState::default(),
2443 nav_history: None,
2444 context_menu: RefCell::new(None),
2445 context_menu_options: None,
2446 mouse_context_menu: None,
2447 completion_tasks: Vec::new(),
2448 inline_blame_popover: None,
2449 inline_blame_popover_show_task: None,
2450 signature_help_state: SignatureHelpState::default(),
2451 auto_signature_help: None,
2452 find_all_references_task_sources: Vec::new(),
2453 next_completion_id: 0,
2454 next_inlay_id: 0,
2455 code_action_providers,
2456 available_code_actions: None,
2457 code_actions_task: None,
2458 quick_selection_highlight_task: None,
2459 debounced_selection_highlight_task: None,
2460 debounced_selection_highlight_complete: false,
2461 document_highlights_task: None,
2462 linked_editing_range_task: None,
2463 pending_rename: None,
2464 searchable: !is_minimap,
2465 cursor_shape: EditorSettings::get_global(cx)
2466 .cursor_shape
2467 .unwrap_or_default(),
2468 cursor_offset_on_selection: false,
2469 current_line_highlight: None,
2470 autoindent_mode: Some(AutoindentMode::EachLine),
2471 collapse_matches: false,
2472 workspace: None,
2473 input_enabled: !is_minimap,
2474 expects_character_input: !is_minimap,
2475 use_modal_editing: full_mode,
2476 read_only: is_minimap,
2477 use_autoclose: true,
2478 use_auto_surround: true,
2479 auto_replace_emoji_shortcode: false,
2480 jsx_tag_auto_close_enabled_in_any_buffer: false,
2481 leader_id: None,
2482 remote_id: None,
2483 hover_state: HoverState::default(),
2484 pending_mouse_down: None,
2485 prev_pressure_stage: None,
2486 hovered_link_state: None,
2487 edit_prediction_provider: None,
2488 active_edit_prediction: None,
2489 stale_edit_prediction_in_menu: None,
2490 edit_prediction_preview: EditPredictionPreview::Inactive {
2491 released_too_fast: false,
2492 },
2493 inline_diagnostics_enabled: full_mode,
2494 diagnostics_enabled: full_mode,
2495 word_completions_enabled: full_mode,
2496 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2497 gutter_hovered: false,
2498 pixel_position_of_newest_cursor: None,
2499 last_bounds: None,
2500 last_position_map: None,
2501 expect_bounds_change: None,
2502 gutter_dimensions: GutterDimensions::default(),
2503 style: None,
2504 show_cursor_names: false,
2505 hovered_cursors: HashMap::default(),
2506 next_editor_action_id: EditorActionId::default(),
2507 editor_actions: Rc::default(),
2508 edit_predictions_hidden_for_vim_mode: false,
2509 show_edit_predictions_override: None,
2510 show_completions_on_input_override: None,
2511 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2512 edit_prediction_settings: EditPredictionSettings::Disabled,
2513 edit_prediction_indent_conflict: false,
2514 edit_prediction_requires_modifier_in_indent_conflict: true,
2515 custom_context_menu: None,
2516 show_git_blame_gutter: false,
2517 show_git_blame_inline: false,
2518 show_selection_menu: None,
2519 show_git_blame_inline_delay_task: None,
2520 git_blame_inline_enabled: full_mode
2521 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2522 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2523 buffer_serialization: is_minimap.not().then(|| {
2524 BufferSerialization::new(
2525 ProjectSettings::get_global(cx)
2526 .session
2527 .restore_unsaved_buffers,
2528 )
2529 }),
2530 blame: None,
2531 blame_subscription: None,
2532 tasks: BTreeMap::default(),
2533
2534 breakpoint_store,
2535 gutter_breakpoint_indicator: (None, None),
2536 gutter_diff_review_indicator: (None, None),
2537 diff_review_drag_state: None,
2538 diff_review_overlays: Vec::new(),
2539 stored_review_comments: Vec::new(),
2540 next_review_comment_id: 0,
2541 hovered_diff_hunk_row: None,
2542 _subscriptions: (!is_minimap)
2543 .then(|| {
2544 vec![
2545 cx.observe(&multi_buffer, Self::on_buffer_changed),
2546 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2547 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2548 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2549 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2550 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2551 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2552 cx.observe_window_activation(window, |editor, window, cx| {
2553 let active = window.is_window_active();
2554 editor.blink_manager.update(cx, |blink_manager, cx| {
2555 if active {
2556 blink_manager.enable(cx);
2557 } else {
2558 blink_manager.disable(cx);
2559 }
2560 });
2561 if active {
2562 editor.show_mouse_cursor(cx);
2563 }
2564 }),
2565 ]
2566 })
2567 .unwrap_or_default(),
2568 tasks_update_task: None,
2569 pull_diagnostics_task: Task::ready(()),
2570 colors: None,
2571 refresh_colors_task: Task::ready(()),
2572 use_document_folding_ranges: false,
2573 refresh_folding_ranges_task: Task::ready(()),
2574 inlay_hints: None,
2575 next_color_inlay_id: 0,
2576 post_scroll_update: Task::ready(()),
2577 linked_edit_ranges: Default::default(),
2578 in_project_search: false,
2579 previous_search_ranges: None,
2580 breadcrumb_header: None,
2581 focused_block: None,
2582 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2583 addons: HashMap::default(),
2584 registered_buffers: HashMap::default(),
2585 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2586 selection_mark_mode: false,
2587 toggle_fold_multiple_buffers: Task::ready(()),
2588 serialize_selections: Task::ready(()),
2589 serialize_folds: Task::ready(()),
2590 text_style_refinement: None,
2591 load_diff_task: load_uncommitted_diff,
2592 temporary_diff_override: false,
2593 mouse_cursor_hidden: false,
2594 minimap: None,
2595 hide_mouse_mode: EditorSettings::get_global(cx)
2596 .hide_mouse
2597 .unwrap_or_default(),
2598 change_list: ChangeList::new(),
2599 mode,
2600 selection_drag_state: SelectionDragState::None,
2601 folding_newlines: Task::ready(()),
2602 lookup_key: None,
2603 select_next_is_case_sensitive: None,
2604 on_local_selections_changed: None,
2605 suppress_selection_callback: false,
2606 applicable_language_settings: HashMap::default(),
2607 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2608 accent_data: None,
2609 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2610 number_deleted_lines: false,
2611 refresh_matching_bracket_highlights_task: Task::ready(()),
2612 refresh_document_symbols_task: Task::ready(()).shared(),
2613 lsp_document_symbols: HashMap::default(),
2614 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2615 outline_symbols_at_cursor: None,
2616 sticky_headers_task: Task::ready(()),
2617 sticky_headers: None,
2618 colorize_brackets_task: Task::ready(()),
2619 };
2620
2621 if is_minimap {
2622 return editor;
2623 }
2624
2625 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2626 editor.accent_data = editor.fetch_accent_data(cx);
2627
2628 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2629 editor
2630 ._subscriptions
2631 .push(cx.observe(breakpoints, |_, _, cx| {
2632 cx.notify();
2633 }));
2634 }
2635 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2636 editor._subscriptions.extend(project_subscriptions);
2637
2638 editor._subscriptions.push(cx.subscribe_in(
2639 &cx.entity(),
2640 window,
2641 |editor, _, e: &EditorEvent, window, cx| match e {
2642 EditorEvent::ScrollPositionChanged { local, .. } => {
2643 if *local {
2644 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2645 editor.inline_blame_popover.take();
2646 let snapshot = editor.snapshot(window, cx);
2647 let new_anchor = editor
2648 .scroll_manager
2649 .native_anchor(&snapshot.display_snapshot, cx);
2650 editor.update_restoration_data(cx, move |data| {
2651 data.scroll_position = (
2652 new_anchor.top_row(snapshot.buffer_snapshot()),
2653 new_anchor.offset,
2654 );
2655 });
2656
2657 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2658 cx.background_executor()
2659 .timer(Duration::from_millis(50))
2660 .await;
2661 editor
2662 .update_in(cx, |editor, window, cx| {
2663 editor.register_visible_buffers(cx);
2664 editor.colorize_brackets(false, cx);
2665 editor.refresh_inlay_hints(
2666 InlayHintRefreshReason::NewLinesShown,
2667 cx,
2668 );
2669 if !editor.buffer().read(cx).is_singleton() {
2670 editor.update_lsp_data(None, window, cx);
2671 }
2672 })
2673 .ok();
2674 });
2675 }
2676 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2677 }
2678 EditorEvent::Edited { .. } => {
2679 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2680 .map(|vim_mode| vim_mode.0)
2681 .unwrap_or(false);
2682 if !vim_mode {
2683 let display_map = editor.display_snapshot(cx);
2684 let selections = editor.selections.all_adjusted_display(&display_map);
2685 let pop_state = editor
2686 .change_list
2687 .last()
2688 .map(|previous| {
2689 previous.len() == selections.len()
2690 && previous.iter().enumerate().all(|(ix, p)| {
2691 p.to_display_point(&display_map).row()
2692 == selections[ix].head().row()
2693 })
2694 })
2695 .unwrap_or(false);
2696 let new_positions = selections
2697 .into_iter()
2698 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2699 .collect();
2700 editor
2701 .change_list
2702 .push_to_change_list(pop_state, new_positions);
2703 }
2704 }
2705 _ => (),
2706 },
2707 ));
2708
2709 if let Some(dap_store) = editor
2710 .project
2711 .as_ref()
2712 .map(|project| project.read(cx).dap_store())
2713 {
2714 let weak_editor = cx.weak_entity();
2715
2716 editor
2717 ._subscriptions
2718 .push(
2719 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2720 let session_entity = cx.entity();
2721 weak_editor
2722 .update(cx, |editor, cx| {
2723 editor._subscriptions.push(
2724 cx.subscribe(&session_entity, Self::on_debug_session_event),
2725 );
2726 })
2727 .ok();
2728 }),
2729 );
2730
2731 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2732 editor
2733 ._subscriptions
2734 .push(cx.subscribe(&session, Self::on_debug_session_event));
2735 }
2736 }
2737
2738 // skip adding the initial selection to selection history
2739 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2740 editor.end_selection(window, cx);
2741 editor.selection_history.mode = SelectionHistoryMode::Normal;
2742
2743 editor.scroll_manager.show_scrollbars(window, cx);
2744 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2745
2746 if full_mode {
2747 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2748 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2749
2750 if editor.git_blame_inline_enabled {
2751 editor.start_git_blame_inline(false, window, cx);
2752 }
2753
2754 editor.go_to_active_debug_line(window, cx);
2755
2756 editor.minimap =
2757 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2758 editor.colors = Some(LspColorData::new(cx));
2759 editor.use_document_folding_ranges = true;
2760 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2761
2762 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2763 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2764 }
2765 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2766 }
2767
2768 editor
2769 }
2770
2771 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2772 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2773 }
2774
2775 pub fn deploy_mouse_context_menu(
2776 &mut self,
2777 position: gpui::Point<Pixels>,
2778 context_menu: Entity<ContextMenu>,
2779 window: &mut Window,
2780 cx: &mut Context<Self>,
2781 ) {
2782 self.mouse_context_menu = Some(MouseContextMenu::new(
2783 self,
2784 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2785 context_menu,
2786 window,
2787 cx,
2788 ));
2789 }
2790
2791 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2792 self.mouse_context_menu
2793 .as_ref()
2794 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2795 }
2796
2797 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2798 if self
2799 .selections
2800 .pending_anchor()
2801 .is_some_and(|pending_selection| {
2802 let snapshot = self.buffer().read(cx).snapshot(cx);
2803 pending_selection.range().includes(range, &snapshot)
2804 })
2805 {
2806 return true;
2807 }
2808
2809 self.selections
2810 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2811 .into_iter()
2812 .any(|selection| {
2813 // This is needed to cover a corner case, if we just check for an existing
2814 // selection in the fold range, having a cursor at the start of the fold
2815 // marks it as selected. Non-empty selections don't cause this.
2816 let length = selection.end - selection.start;
2817 length > 0
2818 })
2819 }
2820
2821 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2822 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2823 }
2824
2825 fn key_context_internal(
2826 &self,
2827 has_active_edit_prediction: bool,
2828 window: &mut Window,
2829 cx: &mut App,
2830 ) -> KeyContext {
2831 let mut key_context = KeyContext::new_with_defaults();
2832 key_context.add("Editor");
2833 let mode = match self.mode {
2834 EditorMode::SingleLine => "single_line",
2835 EditorMode::AutoHeight { .. } => "auto_height",
2836 EditorMode::Minimap { .. } => "minimap",
2837 EditorMode::Full { .. } => "full",
2838 };
2839
2840 if EditorSettings::jupyter_enabled(cx) {
2841 key_context.add("jupyter");
2842 }
2843
2844 key_context.set("mode", mode);
2845 if self.pending_rename.is_some() {
2846 key_context.add("renaming");
2847 }
2848
2849 if let Some(snippet_stack) = self.snippet_stack.last() {
2850 key_context.add("in_snippet");
2851
2852 if snippet_stack.active_index > 0 {
2853 key_context.add("has_previous_tabstop");
2854 }
2855
2856 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2857 key_context.add("has_next_tabstop");
2858 }
2859 }
2860
2861 match self.context_menu.borrow().as_ref() {
2862 Some(CodeContextMenu::Completions(menu)) => {
2863 if menu.visible() {
2864 key_context.add("menu");
2865 key_context.add("showing_completions");
2866 }
2867 }
2868 Some(CodeContextMenu::CodeActions(menu)) => {
2869 if menu.visible() {
2870 key_context.add("menu");
2871 key_context.add("showing_code_actions")
2872 }
2873 }
2874 None => {}
2875 }
2876
2877 if self.signature_help_state.has_multiple_signatures() {
2878 key_context.add("showing_signature_help");
2879 }
2880
2881 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2882 if !self.focus_handle(cx).contains_focused(window, cx)
2883 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2884 {
2885 for addon in self.addons.values() {
2886 addon.extend_key_context(&mut key_context, cx)
2887 }
2888 }
2889
2890 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2891 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2892 Some(
2893 file.full_path(cx)
2894 .extension()?
2895 .to_string_lossy()
2896 .to_lowercase(),
2897 )
2898 }) {
2899 key_context.set("extension", extension);
2900 }
2901 } else {
2902 key_context.add("multibuffer");
2903 }
2904
2905 if has_active_edit_prediction {
2906 if self.edit_prediction_in_conflict() {
2907 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2908 } else {
2909 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2910 key_context.add("copilot_suggestion");
2911 }
2912 }
2913
2914 if self.selection_mark_mode {
2915 key_context.add("selection_mode");
2916 }
2917
2918 let disjoint = self.selections.disjoint_anchors();
2919 let snapshot = self.snapshot(window, cx);
2920 let snapshot = snapshot.buffer_snapshot();
2921 if self.mode == EditorMode::SingleLine
2922 && let [selection] = disjoint
2923 && selection.start == selection.end
2924 && selection.end.to_offset(snapshot) == snapshot.len()
2925 {
2926 key_context.add("end_of_input");
2927 }
2928
2929 if self.has_any_expanded_diff_hunks(cx) {
2930 key_context.add("diffs_expanded");
2931 }
2932
2933 key_context
2934 }
2935
2936 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2937 self.last_bounds.as_ref()
2938 }
2939
2940 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2941 if self.mouse_cursor_hidden {
2942 self.mouse_cursor_hidden = false;
2943 cx.notify();
2944 }
2945 }
2946
2947 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2948 let hide_mouse_cursor = match origin {
2949 HideMouseCursorOrigin::TypingAction => {
2950 matches!(
2951 self.hide_mouse_mode,
2952 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2953 )
2954 }
2955 HideMouseCursorOrigin::MovementAction => {
2956 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2957 }
2958 };
2959 if self.mouse_cursor_hidden != hide_mouse_cursor {
2960 self.mouse_cursor_hidden = hide_mouse_cursor;
2961 cx.notify();
2962 }
2963 }
2964
2965 pub fn edit_prediction_in_conflict(&self) -> bool {
2966 if !self.show_edit_predictions_in_menu() {
2967 return false;
2968 }
2969
2970 let showing_completions = self
2971 .context_menu
2972 .borrow()
2973 .as_ref()
2974 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2975
2976 showing_completions
2977 || self.edit_prediction_requires_modifier()
2978 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2979 // bindings to insert tab characters.
2980 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2981 }
2982
2983 pub fn accept_edit_prediction_keybind(
2984 &self,
2985 granularity: EditPredictionGranularity,
2986 window: &mut Window,
2987 cx: &mut App,
2988 ) -> AcceptEditPredictionBinding {
2989 let key_context = self.key_context_internal(true, window, cx);
2990 let in_conflict = self.edit_prediction_in_conflict();
2991
2992 let bindings =
2993 match granularity {
2994 EditPredictionGranularity::Word => window
2995 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2996 EditPredictionGranularity::Line => window
2997 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2998 EditPredictionGranularity::Full => {
2999 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
3000 }
3001 };
3002
3003 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
3004 !in_conflict
3005 || binding
3006 .keystrokes()
3007 .first()
3008 .is_some_and(|keystroke| keystroke.modifiers().modified())
3009 }))
3010 }
3011
3012 pub fn new_file(
3013 workspace: &mut Workspace,
3014 _: &workspace::NewFile,
3015 window: &mut Window,
3016 cx: &mut Context<Workspace>,
3017 ) {
3018 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3019 "Failed to create buffer",
3020 window,
3021 cx,
3022 |e, _, _| match e.error_code() {
3023 ErrorCode::RemoteUpgradeRequired => Some(format!(
3024 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3025 e.error_tag("required").unwrap_or("the latest version")
3026 )),
3027 _ => None,
3028 },
3029 );
3030 }
3031
3032 pub fn new_in_workspace(
3033 workspace: &mut Workspace,
3034 window: &mut Window,
3035 cx: &mut Context<Workspace>,
3036 ) -> Task<Result<Entity<Editor>>> {
3037 let project = workspace.project().clone();
3038 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3039
3040 cx.spawn_in(window, async move |workspace, cx| {
3041 let buffer = create.await?;
3042 workspace.update_in(cx, |workspace, window, cx| {
3043 let editor =
3044 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3045 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3046 editor
3047 })
3048 })
3049 }
3050
3051 fn new_file_vertical(
3052 workspace: &mut Workspace,
3053 _: &workspace::NewFileSplitVertical,
3054 window: &mut Window,
3055 cx: &mut Context<Workspace>,
3056 ) {
3057 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3058 }
3059
3060 fn new_file_horizontal(
3061 workspace: &mut Workspace,
3062 _: &workspace::NewFileSplitHorizontal,
3063 window: &mut Window,
3064 cx: &mut Context<Workspace>,
3065 ) {
3066 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3067 }
3068
3069 fn new_file_split(
3070 workspace: &mut Workspace,
3071 action: &workspace::NewFileSplit,
3072 window: &mut Window,
3073 cx: &mut Context<Workspace>,
3074 ) {
3075 Self::new_file_in_direction(workspace, action.0, window, cx)
3076 }
3077
3078 fn new_file_in_direction(
3079 workspace: &mut Workspace,
3080 direction: SplitDirection,
3081 window: &mut Window,
3082 cx: &mut Context<Workspace>,
3083 ) {
3084 let project = workspace.project().clone();
3085 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3086
3087 cx.spawn_in(window, async move |workspace, cx| {
3088 let buffer = create.await?;
3089 workspace.update_in(cx, move |workspace, window, cx| {
3090 workspace.split_item(
3091 direction,
3092 Box::new(
3093 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3094 ),
3095 window,
3096 cx,
3097 )
3098 })?;
3099 anyhow::Ok(())
3100 })
3101 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3102 match e.error_code() {
3103 ErrorCode::RemoteUpgradeRequired => Some(format!(
3104 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3105 e.error_tag("required").unwrap_or("the latest version")
3106 )),
3107 _ => None,
3108 }
3109 });
3110 }
3111
3112 pub fn leader_id(&self) -> Option<CollaboratorId> {
3113 self.leader_id
3114 }
3115
3116 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3117 &self.buffer
3118 }
3119
3120 pub fn project(&self) -> Option<&Entity<Project>> {
3121 self.project.as_ref()
3122 }
3123
3124 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3125 self.workspace.as_ref()?.0.upgrade()
3126 }
3127
3128 /// Detaches a task and shows an error notification in the workspace if available,
3129 /// otherwise just logs the error.
3130 pub fn detach_and_notify_err<R, E>(
3131 &self,
3132 task: Task<Result<R, E>>,
3133 window: &mut Window,
3134 cx: &mut App,
3135 ) where
3136 E: std::fmt::Debug + std::fmt::Display + 'static,
3137 R: 'static,
3138 {
3139 if let Some(workspace) = self.workspace() {
3140 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3141 } else {
3142 task.detach_and_log_err(cx);
3143 }
3144 }
3145
3146 /// Returns the workspace serialization ID if this editor should be serialized.
3147 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3148 self.workspace
3149 .as_ref()
3150 .filter(|_| self.should_serialize_buffer())
3151 .and_then(|workspace| workspace.1)
3152 }
3153
3154 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3155 self.buffer().read(cx).title(cx)
3156 }
3157
3158 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3159 let git_blame_gutter_max_author_length = self
3160 .render_git_blame_gutter(cx)
3161 .then(|| {
3162 if let Some(blame) = self.blame.as_ref() {
3163 let max_author_length =
3164 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3165 Some(max_author_length)
3166 } else {
3167 None
3168 }
3169 })
3170 .flatten();
3171
3172 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3173
3174 EditorSnapshot {
3175 mode: self.mode.clone(),
3176 show_gutter: self.show_gutter,
3177 offset_content: self.offset_content,
3178 show_line_numbers: self.show_line_numbers,
3179 number_deleted_lines: self.number_deleted_lines,
3180 show_git_diff_gutter: self.show_git_diff_gutter,
3181 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3182 show_code_actions: self.show_code_actions,
3183 show_runnables: self.show_runnables,
3184 show_breakpoints: self.show_breakpoints,
3185 git_blame_gutter_max_author_length,
3186 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3187 display_snapshot,
3188 placeholder_display_snapshot: self
3189 .placeholder_display_map
3190 .as_ref()
3191 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3192 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3193 is_focused: self.focus_handle.is_focused(window),
3194 current_line_highlight: self
3195 .current_line_highlight
3196 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3197 gutter_hovered: self.gutter_hovered,
3198 }
3199 }
3200
3201 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3202 self.buffer.read(cx).language_at(point, cx)
3203 }
3204
3205 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3206 self.buffer.read(cx).read(cx).file_at(point).cloned()
3207 }
3208
3209 pub fn active_excerpt(
3210 &self,
3211 cx: &App,
3212 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3213 self.buffer
3214 .read(cx)
3215 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3216 }
3217
3218 pub fn mode(&self) -> &EditorMode {
3219 &self.mode
3220 }
3221
3222 pub fn set_mode(&mut self, mode: EditorMode) {
3223 self.mode = mode;
3224 }
3225
3226 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3227 self.collaboration_hub.as_deref()
3228 }
3229
3230 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3231 self.collaboration_hub = Some(hub);
3232 }
3233
3234 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3235 self.in_project_search = in_project_search;
3236 }
3237
3238 pub fn set_custom_context_menu(
3239 &mut self,
3240 f: impl 'static
3241 + Fn(
3242 &mut Self,
3243 DisplayPoint,
3244 &mut Window,
3245 &mut Context<Self>,
3246 ) -> Option<Entity<ui::ContextMenu>>,
3247 ) {
3248 self.custom_context_menu = Some(Box::new(f))
3249 }
3250
3251 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3252 self.completion_provider = provider;
3253 }
3254
3255 #[cfg(any(test, feature = "test-support"))]
3256 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3257 self.completion_provider.clone()
3258 }
3259
3260 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3261 self.semantics_provider.clone()
3262 }
3263
3264 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3265 self.semantics_provider = provider;
3266 }
3267
3268 pub fn set_edit_prediction_provider<T>(
3269 &mut self,
3270 provider: Option<Entity<T>>,
3271 window: &mut Window,
3272 cx: &mut Context<Self>,
3273 ) where
3274 T: EditPredictionDelegate,
3275 {
3276 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3277 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3278 if this.focus_handle.is_focused(window) {
3279 this.update_visible_edit_prediction(window, cx);
3280 }
3281 }),
3282 provider: Arc::new(provider),
3283 });
3284 self.update_edit_prediction_settings(cx);
3285 self.refresh_edit_prediction(false, false, window, cx);
3286 }
3287
3288 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3289 self.placeholder_display_map
3290 .as_ref()
3291 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3292 }
3293
3294 pub fn set_placeholder_text(
3295 &mut self,
3296 placeholder_text: &str,
3297 window: &mut Window,
3298 cx: &mut Context<Self>,
3299 ) {
3300 let multibuffer = cx
3301 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3302
3303 let style = window.text_style();
3304
3305 self.placeholder_display_map = Some(cx.new(|cx| {
3306 DisplayMap::new(
3307 multibuffer,
3308 style.font(),
3309 style.font_size.to_pixels(window.rem_size()),
3310 None,
3311 FILE_HEADER_HEIGHT,
3312 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3313 Default::default(),
3314 DiagnosticSeverity::Off,
3315 cx,
3316 )
3317 }));
3318 cx.notify();
3319 }
3320
3321 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3322 self.cursor_shape = cursor_shape;
3323
3324 // Disrupt blink for immediate user feedback that the cursor shape has changed
3325 self.blink_manager.update(cx, BlinkManager::show_cursor);
3326
3327 cx.notify();
3328 }
3329
3330 pub fn cursor_shape(&self) -> CursorShape {
3331 self.cursor_shape
3332 }
3333
3334 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3335 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3336 }
3337
3338 pub fn set_current_line_highlight(
3339 &mut self,
3340 current_line_highlight: Option<CurrentLineHighlight>,
3341 ) {
3342 self.current_line_highlight = current_line_highlight;
3343 }
3344
3345 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3346 self.collapse_matches = collapse_matches;
3347 }
3348
3349 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3350 if self.collapse_matches {
3351 return range.start..range.start;
3352 }
3353 range.clone()
3354 }
3355
3356 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3357 self.display_map.read(cx).clip_at_line_ends
3358 }
3359
3360 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3361 if self.display_map.read(cx).clip_at_line_ends != clip {
3362 self.display_map
3363 .update(cx, |map, _| map.clip_at_line_ends = clip);
3364 }
3365 }
3366
3367 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3368 self.input_enabled = input_enabled;
3369 }
3370
3371 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3372 self.expects_character_input = expects_character_input;
3373 }
3374
3375 pub fn set_edit_predictions_hidden_for_vim_mode(
3376 &mut self,
3377 hidden: bool,
3378 window: &mut Window,
3379 cx: &mut Context<Self>,
3380 ) {
3381 if hidden != self.edit_predictions_hidden_for_vim_mode {
3382 self.edit_predictions_hidden_for_vim_mode = hidden;
3383 if hidden {
3384 self.update_visible_edit_prediction(window, cx);
3385 } else {
3386 self.refresh_edit_prediction(true, false, window, cx);
3387 }
3388 }
3389 }
3390
3391 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3392 self.menu_edit_predictions_policy = value;
3393 }
3394
3395 pub fn set_autoindent(&mut self, autoindent: bool) {
3396 if autoindent {
3397 self.autoindent_mode = Some(AutoindentMode::EachLine);
3398 } else {
3399 self.autoindent_mode = None;
3400 }
3401 }
3402
3403 pub fn capability(&self, cx: &App) -> Capability {
3404 if self.read_only {
3405 Capability::ReadOnly
3406 } else {
3407 self.buffer.read(cx).capability()
3408 }
3409 }
3410
3411 pub fn read_only(&self, cx: &App) -> bool {
3412 self.read_only || self.buffer.read(cx).read_only()
3413 }
3414
3415 pub fn set_read_only(&mut self, read_only: bool) {
3416 self.read_only = read_only;
3417 }
3418
3419 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3420 self.use_autoclose = autoclose;
3421 }
3422
3423 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3424 self.use_auto_surround = auto_surround;
3425 }
3426
3427 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3428 self.auto_replace_emoji_shortcode = auto_replace;
3429 }
3430
3431 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3432 self.buffer_serialization = should_serialize.then(|| {
3433 BufferSerialization::new(
3434 ProjectSettings::get_global(cx)
3435 .session
3436 .restore_unsaved_buffers,
3437 )
3438 })
3439 }
3440
3441 fn should_serialize_buffer(&self) -> bool {
3442 self.buffer_serialization.is_some()
3443 }
3444
3445 pub fn toggle_edit_predictions(
3446 &mut self,
3447 _: &ToggleEditPrediction,
3448 window: &mut Window,
3449 cx: &mut Context<Self>,
3450 ) {
3451 if self.show_edit_predictions_override.is_some() {
3452 self.set_show_edit_predictions(None, window, cx);
3453 } else {
3454 let show_edit_predictions = !self.edit_predictions_enabled();
3455 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3456 }
3457 }
3458
3459 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3460 self.show_completions_on_input_override = show_completions_on_input;
3461 }
3462
3463 pub fn set_show_edit_predictions(
3464 &mut self,
3465 show_edit_predictions: Option<bool>,
3466 window: &mut Window,
3467 cx: &mut Context<Self>,
3468 ) {
3469 self.show_edit_predictions_override = show_edit_predictions;
3470 self.update_edit_prediction_settings(cx);
3471
3472 if let Some(false) = show_edit_predictions {
3473 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3474 } else {
3475 self.refresh_edit_prediction(false, true, window, cx);
3476 }
3477 }
3478
3479 fn edit_predictions_disabled_in_scope(
3480 &self,
3481 buffer: &Entity<Buffer>,
3482 buffer_position: language::Anchor,
3483 cx: &App,
3484 ) -> bool {
3485 let snapshot = buffer.read(cx).snapshot();
3486 let settings = snapshot.settings_at(buffer_position, cx);
3487
3488 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3489 return false;
3490 };
3491
3492 scope.override_name().is_some_and(|scope_name| {
3493 settings
3494 .edit_predictions_disabled_in
3495 .iter()
3496 .any(|s| s == scope_name)
3497 })
3498 }
3499
3500 pub fn set_use_modal_editing(&mut self, to: bool) {
3501 self.use_modal_editing = to;
3502 }
3503
3504 pub fn use_modal_editing(&self) -> bool {
3505 self.use_modal_editing
3506 }
3507
3508 fn selections_did_change(
3509 &mut self,
3510 local: bool,
3511 old_cursor_position: &Anchor,
3512 effects: SelectionEffects,
3513 window: &mut Window,
3514 cx: &mut Context<Self>,
3515 ) {
3516 window.invalidate_character_coordinates();
3517
3518 // Copy selections to primary selection buffer
3519 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3520 if local {
3521 let selections = self
3522 .selections
3523 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3524 let buffer_handle = self.buffer.read(cx).read(cx);
3525
3526 let mut text = String::new();
3527 for (index, selection) in selections.iter().enumerate() {
3528 let text_for_selection = buffer_handle
3529 .text_for_range(selection.start..selection.end)
3530 .collect::<String>();
3531
3532 text.push_str(&text_for_selection);
3533 if index != selections.len() - 1 {
3534 text.push('\n');
3535 }
3536 }
3537
3538 if !text.is_empty() {
3539 cx.write_to_primary(ClipboardItem::new_string(text));
3540 }
3541 }
3542
3543 let selection_anchors = self.selections.disjoint_anchors_arc();
3544
3545 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3546 self.buffer.update(cx, |buffer, cx| {
3547 buffer.set_active_selections(
3548 &selection_anchors,
3549 self.selections.line_mode(),
3550 self.cursor_shape,
3551 cx,
3552 )
3553 });
3554 }
3555 let display_map = self
3556 .display_map
3557 .update(cx, |display_map, cx| display_map.snapshot(cx));
3558 let buffer = display_map.buffer_snapshot();
3559 if self.selections.count() == 1 {
3560 self.add_selections_state = None;
3561 }
3562 self.select_next_state = None;
3563 self.select_prev_state = None;
3564 self.select_syntax_node_history.try_clear();
3565 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3566 self.snippet_stack.invalidate(&selection_anchors, buffer);
3567 self.take_rename(false, window, cx);
3568
3569 let newest_selection = self.selections.newest_anchor();
3570 let new_cursor_position = newest_selection.head();
3571 let selection_start = newest_selection.start;
3572
3573 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3574 self.push_to_nav_history(
3575 *old_cursor_position,
3576 Some(new_cursor_position.to_point(buffer)),
3577 false,
3578 effects.nav_history == Some(true),
3579 cx,
3580 );
3581 }
3582
3583 if local {
3584 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3585 self.register_buffer(buffer_id, cx);
3586 }
3587
3588 let mut context_menu = self.context_menu.borrow_mut();
3589 let completion_menu = match context_menu.as_ref() {
3590 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3591 Some(CodeContextMenu::CodeActions(_)) => {
3592 *context_menu = None;
3593 None
3594 }
3595 None => None,
3596 };
3597 let completion_position = completion_menu.map(|menu| menu.initial_position);
3598 drop(context_menu);
3599
3600 if effects.completions
3601 && let Some(completion_position) = completion_position
3602 {
3603 let start_offset = selection_start.to_offset(buffer);
3604 let position_matches = start_offset == completion_position.to_offset(buffer);
3605 let continue_showing = if let Some((snap, ..)) =
3606 buffer.point_to_buffer_offset(completion_position)
3607 && !snap.capability.editable()
3608 {
3609 false
3610 } else if position_matches {
3611 if self.snippet_stack.is_empty() {
3612 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3613 == Some(CharKind::Word)
3614 } else {
3615 // Snippet choices can be shown even when the cursor is in whitespace.
3616 // Dismissing the menu with actions like backspace is handled by
3617 // invalidation regions.
3618 true
3619 }
3620 } else {
3621 false
3622 };
3623
3624 if continue_showing {
3625 self.open_or_update_completions_menu(None, None, false, window, cx);
3626 } else {
3627 self.hide_context_menu(window, cx);
3628 }
3629 }
3630
3631 hide_hover(self, cx);
3632
3633 if old_cursor_position.to_display_point(&display_map).row()
3634 != new_cursor_position.to_display_point(&display_map).row()
3635 {
3636 self.available_code_actions.take();
3637 }
3638 self.refresh_code_actions(window, cx);
3639 self.refresh_document_highlights(cx);
3640 refresh_linked_ranges(self, window, cx);
3641
3642 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3643 self.refresh_matching_bracket_highlights(&display_map, cx);
3644 self.refresh_outline_symbols_at_cursor(cx);
3645 self.update_visible_edit_prediction(window, cx);
3646 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3647 self.inline_blame_popover.take();
3648 if self.git_blame_inline_enabled {
3649 self.start_inline_blame_timer(window, cx);
3650 }
3651 }
3652
3653 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3654
3655 if local && !self.suppress_selection_callback {
3656 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3657 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3658 callback(cursor_position, window, cx);
3659 }
3660 }
3661
3662 cx.emit(EditorEvent::SelectionsChanged { local });
3663
3664 let selections = &self.selections.disjoint_anchors_arc();
3665 if selections.len() == 1 {
3666 cx.emit(SearchEvent::ActiveMatchChanged)
3667 }
3668 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3669 let inmemory_selections = selections
3670 .iter()
3671 .map(|s| {
3672 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3673 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3674 })
3675 .collect();
3676 self.update_restoration_data(cx, |data| {
3677 data.selections = inmemory_selections;
3678 });
3679
3680 if WorkspaceSettings::get(None, cx).restore_on_startup
3681 != RestoreOnStartupBehavior::EmptyTab
3682 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3683 {
3684 let snapshot = self.buffer().read(cx).snapshot(cx);
3685 let selections = selections.clone();
3686 let background_executor = cx.background_executor().clone();
3687 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3688 self.serialize_selections = cx.background_spawn(async move {
3689 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3690 let db_selections = selections
3691 .iter()
3692 .map(|selection| {
3693 (
3694 selection.start.to_offset(&snapshot).0,
3695 selection.end.to_offset(&snapshot).0,
3696 )
3697 })
3698 .collect();
3699
3700 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3701 .await
3702 .with_context(|| {
3703 format!(
3704 "persisting editor selections for editor {editor_id}, \
3705 workspace {workspace_id:?}"
3706 )
3707 })
3708 .log_err();
3709 });
3710 }
3711 }
3712
3713 cx.notify();
3714 }
3715
3716 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3717 use text::ToOffset as _;
3718 use text::ToPoint as _;
3719
3720 if self.mode.is_minimap()
3721 || WorkspaceSettings::get(None, cx).restore_on_startup
3722 == RestoreOnStartupBehavior::EmptyTab
3723 {
3724 return;
3725 }
3726
3727 if !self.buffer().read(cx).is_singleton() {
3728 return;
3729 }
3730
3731 let display_snapshot = self
3732 .display_map
3733 .update(cx, |display_map, cx| display_map.snapshot(cx));
3734 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3735 return;
3736 };
3737 let inmemory_folds = display_snapshot
3738 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3739 .map(|fold| {
3740 fold.range.start.text_anchor.to_point(&snapshot)
3741 ..fold.range.end.text_anchor.to_point(&snapshot)
3742 })
3743 .collect();
3744 self.update_restoration_data(cx, |data| {
3745 data.folds = inmemory_folds;
3746 });
3747
3748 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3749 return;
3750 };
3751
3752 // Get file path for path-based fold storage (survives tab close)
3753 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3754 project::File::from_dyn(buffer.read(cx).file())
3755 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3756 }) else {
3757 return;
3758 };
3759
3760 let background_executor = cx.background_executor().clone();
3761 const FINGERPRINT_LEN: usize = 32;
3762 let db_folds = display_snapshot
3763 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3764 .map(|fold| {
3765 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3766 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3767
3768 // Extract fingerprints - content at fold boundaries for validation on restore
3769 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3770 // content that might change independently.
3771 // start_fp: first min(32, fold_len) bytes of fold content
3772 // end_fp: last min(32, fold_len) bytes of fold content
3773 // Clip to character boundaries to handle multibyte UTF-8 characters.
3774 let fold_len = end - start;
3775 let start_fp_end = snapshot
3776 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3777 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3778 let end_fp_start = snapshot
3779 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3780 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3781
3782 (start, end, start_fp, end_fp)
3783 })
3784 .collect::<Vec<_>>();
3785 self.serialize_folds = cx.background_spawn(async move {
3786 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3787 if db_folds.is_empty() {
3788 // No folds - delete any persisted folds for this file
3789 DB.delete_file_folds(workspace_id, file_path)
3790 .await
3791 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3792 .log_err();
3793 } else {
3794 DB.save_file_folds(workspace_id, file_path, db_folds)
3795 .await
3796 .with_context(|| {
3797 format!("persisting file folds for workspace {workspace_id:?}")
3798 })
3799 .log_err();
3800 }
3801 });
3802 }
3803
3804 pub fn sync_selections(
3805 &mut self,
3806 other: Entity<Editor>,
3807 cx: &mut Context<Self>,
3808 ) -> gpui::Subscription {
3809 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3810 if !other_selections.is_empty() {
3811 self.selections
3812 .change_with(&self.display_snapshot(cx), |selections| {
3813 selections.select_anchors(other_selections);
3814 });
3815 }
3816
3817 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3818 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3819 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3820 if other_selections.is_empty() {
3821 return;
3822 }
3823 let snapshot = this.display_snapshot(cx);
3824 this.selections.change_with(&snapshot, |selections| {
3825 selections.select_anchors(other_selections);
3826 });
3827 }
3828 });
3829
3830 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3831 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3832 let these_selections = this.selections.disjoint_anchors().to_vec();
3833 if these_selections.is_empty() {
3834 return;
3835 }
3836 other.update(cx, |other_editor, cx| {
3837 let snapshot = other_editor.display_snapshot(cx);
3838 other_editor
3839 .selections
3840 .change_with(&snapshot, |selections| {
3841 selections.select_anchors(these_selections);
3842 })
3843 });
3844 }
3845 });
3846
3847 Subscription::join(other_subscription, this_subscription)
3848 }
3849
3850 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3851 if self.buffer().read(cx).is_singleton() {
3852 return;
3853 }
3854 let snapshot = self.buffer.read(cx).snapshot(cx);
3855 let buffer_ids: HashSet<BufferId> = self
3856 .selections
3857 .disjoint_anchor_ranges()
3858 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3859 .collect();
3860 for buffer_id in buffer_ids {
3861 self.unfold_buffer(buffer_id, cx);
3862 }
3863 }
3864
3865 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3866 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3867 /// effects of selection change occur at the end of the transaction.
3868 pub fn change_selections<R>(
3869 &mut self,
3870 effects: SelectionEffects,
3871 window: &mut Window,
3872 cx: &mut Context<Self>,
3873 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3874 ) -> R {
3875 let snapshot = self.display_snapshot(cx);
3876 if let Some(state) = &mut self.deferred_selection_effects_state {
3877 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3878 state.effects.completions = effects.completions;
3879 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3880 let (changed, result) = self.selections.change_with(&snapshot, change);
3881 state.changed |= changed;
3882 return result;
3883 }
3884 let mut state = DeferredSelectionEffectsState {
3885 changed: false,
3886 effects,
3887 old_cursor_position: self.selections.newest_anchor().head(),
3888 history_entry: SelectionHistoryEntry {
3889 selections: self.selections.disjoint_anchors_arc(),
3890 select_next_state: self.select_next_state.clone(),
3891 select_prev_state: self.select_prev_state.clone(),
3892 add_selections_state: self.add_selections_state.clone(),
3893 },
3894 };
3895 let (changed, result) = self.selections.change_with(&snapshot, change);
3896 state.changed = state.changed || changed;
3897 if self.defer_selection_effects {
3898 self.deferred_selection_effects_state = Some(state);
3899 } else {
3900 self.apply_selection_effects(state, window, cx);
3901 }
3902 result
3903 }
3904
3905 /// Defers the effects of selection change, so that the effects of multiple calls to
3906 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3907 /// to selection history and the state of popovers based on selection position aren't
3908 /// erroneously updated.
3909 pub fn with_selection_effects_deferred<R>(
3910 &mut self,
3911 window: &mut Window,
3912 cx: &mut Context<Self>,
3913 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3914 ) -> R {
3915 let already_deferred = self.defer_selection_effects;
3916 self.defer_selection_effects = true;
3917 let result = update(self, window, cx);
3918 if !already_deferred {
3919 self.defer_selection_effects = false;
3920 if let Some(state) = self.deferred_selection_effects_state.take() {
3921 self.apply_selection_effects(state, window, cx);
3922 }
3923 }
3924 result
3925 }
3926
3927 fn apply_selection_effects(
3928 &mut self,
3929 state: DeferredSelectionEffectsState,
3930 window: &mut Window,
3931 cx: &mut Context<Self>,
3932 ) {
3933 if state.changed {
3934 self.selection_history.push(state.history_entry);
3935
3936 if let Some(autoscroll) = state.effects.scroll {
3937 self.request_autoscroll(autoscroll, cx);
3938 }
3939
3940 let old_cursor_position = &state.old_cursor_position;
3941
3942 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3943
3944 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3945 self.show_signature_help_auto(window, cx);
3946 }
3947 }
3948 }
3949
3950 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3951 where
3952 I: IntoIterator<Item = (Range<S>, T)>,
3953 S: ToOffset,
3954 T: Into<Arc<str>>,
3955 {
3956 if self.read_only(cx) {
3957 return;
3958 }
3959
3960 self.buffer
3961 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3962 }
3963
3964 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3965 where
3966 I: IntoIterator<Item = (Range<S>, T)>,
3967 S: ToOffset,
3968 T: Into<Arc<str>>,
3969 {
3970 if self.read_only(cx) {
3971 return;
3972 }
3973
3974 self.buffer.update(cx, |buffer, cx| {
3975 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3976 });
3977 }
3978
3979 pub fn edit_with_block_indent<I, S, T>(
3980 &mut self,
3981 edits: I,
3982 original_indent_columns: Vec<Option<u32>>,
3983 cx: &mut Context<Self>,
3984 ) where
3985 I: IntoIterator<Item = (Range<S>, T)>,
3986 S: ToOffset,
3987 T: Into<Arc<str>>,
3988 {
3989 if self.read_only(cx) {
3990 return;
3991 }
3992
3993 self.buffer.update(cx, |buffer, cx| {
3994 buffer.edit(
3995 edits,
3996 Some(AutoindentMode::Block {
3997 original_indent_columns,
3998 }),
3999 cx,
4000 )
4001 });
4002 }
4003
4004 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4005 self.hide_context_menu(window, cx);
4006
4007 match phase {
4008 SelectPhase::Begin {
4009 position,
4010 add,
4011 click_count,
4012 } => self.begin_selection(position, add, click_count, window, cx),
4013 SelectPhase::BeginColumnar {
4014 position,
4015 goal_column,
4016 reset,
4017 mode,
4018 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4019 SelectPhase::Extend {
4020 position,
4021 click_count,
4022 } => self.extend_selection(position, click_count, window, cx),
4023 SelectPhase::Update {
4024 position,
4025 goal_column,
4026 scroll_delta,
4027 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4028 SelectPhase::End => self.end_selection(window, cx),
4029 }
4030 }
4031
4032 fn extend_selection(
4033 &mut self,
4034 position: DisplayPoint,
4035 click_count: usize,
4036 window: &mut Window,
4037 cx: &mut Context<Self>,
4038 ) {
4039 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4040 let tail = self
4041 .selections
4042 .newest::<MultiBufferOffset>(&display_map)
4043 .tail();
4044 let click_count = click_count.max(match self.selections.select_mode() {
4045 SelectMode::Character => 1,
4046 SelectMode::Word(_) => 2,
4047 SelectMode::Line(_) => 3,
4048 SelectMode::All => 4,
4049 });
4050 self.begin_selection(position, false, click_count, window, cx);
4051
4052 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4053
4054 let current_selection = match self.selections.select_mode() {
4055 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4056 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4057 };
4058
4059 let mut pending_selection = self
4060 .selections
4061 .pending_anchor()
4062 .cloned()
4063 .expect("extend_selection not called with pending selection");
4064
4065 if pending_selection
4066 .start
4067 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4068 == Ordering::Greater
4069 {
4070 pending_selection.start = current_selection.start;
4071 }
4072 if pending_selection
4073 .end
4074 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4075 == Ordering::Less
4076 {
4077 pending_selection.end = current_selection.end;
4078 pending_selection.reversed = true;
4079 }
4080
4081 let mut pending_mode = self.selections.pending_mode().unwrap();
4082 match &mut pending_mode {
4083 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4084 _ => {}
4085 }
4086
4087 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4088 SelectionEffects::scroll(Autoscroll::fit())
4089 } else {
4090 SelectionEffects::no_scroll()
4091 };
4092
4093 self.change_selections(effects, window, cx, |s| {
4094 s.set_pending(pending_selection.clone(), pending_mode);
4095 s.set_is_extending(true);
4096 });
4097 }
4098
4099 fn begin_selection(
4100 &mut self,
4101 position: DisplayPoint,
4102 add: bool,
4103 click_count: usize,
4104 window: &mut Window,
4105 cx: &mut Context<Self>,
4106 ) {
4107 if !self.focus_handle.is_focused(window) {
4108 self.last_focused_descendant = None;
4109 window.focus(&self.focus_handle, cx);
4110 }
4111
4112 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4113 let buffer = display_map.buffer_snapshot();
4114 let position = display_map.clip_point(position, Bias::Left);
4115
4116 let start;
4117 let end;
4118 let mode;
4119 let mut auto_scroll;
4120 match click_count {
4121 1 => {
4122 start = buffer.anchor_before(position.to_point(&display_map));
4123 end = start;
4124 mode = SelectMode::Character;
4125 auto_scroll = true;
4126 }
4127 2 => {
4128 let position = display_map
4129 .clip_point(position, Bias::Left)
4130 .to_offset(&display_map, Bias::Left);
4131 let (range, _) = buffer.surrounding_word(position, None);
4132 start = buffer.anchor_before(range.start);
4133 end = buffer.anchor_before(range.end);
4134 mode = SelectMode::Word(start..end);
4135 auto_scroll = true;
4136 }
4137 3 => {
4138 let position = display_map
4139 .clip_point(position, Bias::Left)
4140 .to_point(&display_map);
4141 let line_start = display_map.prev_line_boundary(position).0;
4142 let next_line_start = buffer.clip_point(
4143 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4144 Bias::Left,
4145 );
4146 start = buffer.anchor_before(line_start);
4147 end = buffer.anchor_before(next_line_start);
4148 mode = SelectMode::Line(start..end);
4149 auto_scroll = true;
4150 }
4151 _ => {
4152 start = buffer.anchor_before(MultiBufferOffset(0));
4153 end = buffer.anchor_before(buffer.len());
4154 mode = SelectMode::All;
4155 auto_scroll = false;
4156 }
4157 }
4158 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4159
4160 let point_to_delete: Option<usize> = {
4161 let selected_points: Vec<Selection<Point>> =
4162 self.selections.disjoint_in_range(start..end, &display_map);
4163
4164 if !add || click_count > 1 {
4165 None
4166 } else if !selected_points.is_empty() {
4167 Some(selected_points[0].id)
4168 } else {
4169 let clicked_point_already_selected =
4170 self.selections.disjoint_anchors().iter().find(|selection| {
4171 selection.start.to_point(buffer) == start.to_point(buffer)
4172 || selection.end.to_point(buffer) == end.to_point(buffer)
4173 });
4174
4175 clicked_point_already_selected.map(|selection| selection.id)
4176 }
4177 };
4178
4179 let selections_count = self.selections.count();
4180 let effects = if auto_scroll {
4181 SelectionEffects::default()
4182 } else {
4183 SelectionEffects::no_scroll()
4184 };
4185
4186 self.change_selections(effects, window, cx, |s| {
4187 if let Some(point_to_delete) = point_to_delete {
4188 s.delete(point_to_delete);
4189
4190 if selections_count == 1 {
4191 s.set_pending_anchor_range(start..end, mode);
4192 }
4193 } else {
4194 if !add {
4195 s.clear_disjoint();
4196 }
4197
4198 s.set_pending_anchor_range(start..end, mode);
4199 }
4200 });
4201 }
4202
4203 fn begin_columnar_selection(
4204 &mut self,
4205 position: DisplayPoint,
4206 goal_column: u32,
4207 reset: bool,
4208 mode: ColumnarMode,
4209 window: &mut Window,
4210 cx: &mut Context<Self>,
4211 ) {
4212 if !self.focus_handle.is_focused(window) {
4213 self.last_focused_descendant = None;
4214 window.focus(&self.focus_handle, cx);
4215 }
4216
4217 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4218
4219 if reset {
4220 let pointer_position = display_map
4221 .buffer_snapshot()
4222 .anchor_before(position.to_point(&display_map));
4223
4224 self.change_selections(
4225 SelectionEffects::scroll(Autoscroll::newest()),
4226 window,
4227 cx,
4228 |s| {
4229 s.clear_disjoint();
4230 s.set_pending_anchor_range(
4231 pointer_position..pointer_position,
4232 SelectMode::Character,
4233 );
4234 },
4235 );
4236 };
4237
4238 let tail = self.selections.newest::<Point>(&display_map).tail();
4239 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4240 self.columnar_selection_state = match mode {
4241 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4242 selection_tail: selection_anchor,
4243 display_point: if reset {
4244 if position.column() != goal_column {
4245 Some(DisplayPoint::new(position.row(), goal_column))
4246 } else {
4247 None
4248 }
4249 } else {
4250 None
4251 },
4252 }),
4253 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4254 selection_tail: selection_anchor,
4255 }),
4256 };
4257
4258 if !reset {
4259 self.select_columns(position, goal_column, &display_map, window, cx);
4260 }
4261 }
4262
4263 fn update_selection(
4264 &mut self,
4265 position: DisplayPoint,
4266 goal_column: u32,
4267 scroll_delta: gpui::Point<f32>,
4268 window: &mut Window,
4269 cx: &mut Context<Self>,
4270 ) {
4271 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4272
4273 if self.columnar_selection_state.is_some() {
4274 self.select_columns(position, goal_column, &display_map, window, cx);
4275 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4276 let buffer = display_map.buffer_snapshot();
4277 let head;
4278 let tail;
4279 let mode = self.selections.pending_mode().unwrap();
4280 match &mode {
4281 SelectMode::Character => {
4282 head = position.to_point(&display_map);
4283 tail = pending.tail().to_point(buffer);
4284 }
4285 SelectMode::Word(original_range) => {
4286 let offset = display_map
4287 .clip_point(position, Bias::Left)
4288 .to_offset(&display_map, Bias::Left);
4289 let original_range = original_range.to_offset(buffer);
4290
4291 let head_offset = if buffer.is_inside_word(offset, None)
4292 || original_range.contains(&offset)
4293 {
4294 let (word_range, _) = buffer.surrounding_word(offset, None);
4295 if word_range.start < original_range.start {
4296 word_range.start
4297 } else {
4298 word_range.end
4299 }
4300 } else {
4301 offset
4302 };
4303
4304 head = head_offset.to_point(buffer);
4305 if head_offset <= original_range.start {
4306 tail = original_range.end.to_point(buffer);
4307 } else {
4308 tail = original_range.start.to_point(buffer);
4309 }
4310 }
4311 SelectMode::Line(original_range) => {
4312 let original_range = original_range.to_point(display_map.buffer_snapshot());
4313
4314 let position = display_map
4315 .clip_point(position, Bias::Left)
4316 .to_point(&display_map);
4317 let line_start = display_map.prev_line_boundary(position).0;
4318 let next_line_start = buffer.clip_point(
4319 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4320 Bias::Left,
4321 );
4322
4323 if line_start < original_range.start {
4324 head = line_start
4325 } else {
4326 head = next_line_start
4327 }
4328
4329 if head <= original_range.start {
4330 tail = original_range.end;
4331 } else {
4332 tail = original_range.start;
4333 }
4334 }
4335 SelectMode::All => {
4336 return;
4337 }
4338 };
4339
4340 if head < tail {
4341 pending.start = buffer.anchor_before(head);
4342 pending.end = buffer.anchor_before(tail);
4343 pending.reversed = true;
4344 } else {
4345 pending.start = buffer.anchor_before(tail);
4346 pending.end = buffer.anchor_before(head);
4347 pending.reversed = false;
4348 }
4349
4350 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4351 s.set_pending(pending.clone(), mode);
4352 });
4353 } else {
4354 log::error!("update_selection dispatched with no pending selection");
4355 return;
4356 }
4357
4358 self.apply_scroll_delta(scroll_delta, window, cx);
4359 cx.notify();
4360 }
4361
4362 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4363 self.columnar_selection_state.take();
4364 if let Some(pending_mode) = self.selections.pending_mode() {
4365 let selections = self
4366 .selections
4367 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4368 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4369 s.select(selections);
4370 s.clear_pending();
4371 if s.is_extending() {
4372 s.set_is_extending(false);
4373 } else {
4374 s.set_select_mode(pending_mode);
4375 }
4376 });
4377 }
4378 }
4379
4380 fn select_columns(
4381 &mut self,
4382 head: DisplayPoint,
4383 goal_column: u32,
4384 display_map: &DisplaySnapshot,
4385 window: &mut Window,
4386 cx: &mut Context<Self>,
4387 ) {
4388 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4389 return;
4390 };
4391
4392 let tail = match columnar_state {
4393 ColumnarSelectionState::FromMouse {
4394 selection_tail,
4395 display_point,
4396 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4397 ColumnarSelectionState::FromSelection { selection_tail } => {
4398 selection_tail.to_display_point(display_map)
4399 }
4400 };
4401
4402 let start_row = cmp::min(tail.row(), head.row());
4403 let end_row = cmp::max(tail.row(), head.row());
4404 let start_column = cmp::min(tail.column(), goal_column);
4405 let end_column = cmp::max(tail.column(), goal_column);
4406 let reversed = start_column < tail.column();
4407
4408 let selection_ranges = (start_row.0..=end_row.0)
4409 .map(DisplayRow)
4410 .filter_map(|row| {
4411 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4412 || start_column <= display_map.line_len(row))
4413 && !display_map.is_block_line(row)
4414 {
4415 let start = display_map
4416 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4417 .to_point(display_map);
4418 let end = display_map
4419 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4420 .to_point(display_map);
4421 if reversed {
4422 Some(end..start)
4423 } else {
4424 Some(start..end)
4425 }
4426 } else {
4427 None
4428 }
4429 })
4430 .collect::<Vec<_>>();
4431 if selection_ranges.is_empty() {
4432 return;
4433 }
4434
4435 let ranges = match columnar_state {
4436 ColumnarSelectionState::FromMouse { .. } => {
4437 let mut non_empty_ranges = selection_ranges
4438 .iter()
4439 .filter(|selection_range| selection_range.start != selection_range.end)
4440 .peekable();
4441 if non_empty_ranges.peek().is_some() {
4442 non_empty_ranges.cloned().collect()
4443 } else {
4444 selection_ranges
4445 }
4446 }
4447 _ => selection_ranges,
4448 };
4449
4450 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4451 s.select_ranges(ranges);
4452 });
4453 cx.notify();
4454 }
4455
4456 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4457 self.selections
4458 .all_adjusted(snapshot)
4459 .iter()
4460 .any(|selection| !selection.is_empty())
4461 }
4462
4463 pub fn has_pending_nonempty_selection(&self) -> bool {
4464 let pending_nonempty_selection = match self.selections.pending_anchor() {
4465 Some(Selection { start, end, .. }) => start != end,
4466 None => false,
4467 };
4468
4469 pending_nonempty_selection
4470 || (self.columnar_selection_state.is_some()
4471 && self.selections.disjoint_anchors().len() > 1)
4472 }
4473
4474 pub fn has_pending_selection(&self) -> bool {
4475 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4476 }
4477
4478 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4479 self.selection_mark_mode = false;
4480 self.selection_drag_state = SelectionDragState::None;
4481
4482 if self.dismiss_menus_and_popups(true, window, cx) {
4483 cx.notify();
4484 return;
4485 }
4486 if self.clear_expanded_diff_hunks(cx) {
4487 cx.notify();
4488 return;
4489 }
4490 if self.show_git_blame_gutter {
4491 self.show_git_blame_gutter = false;
4492 cx.notify();
4493 return;
4494 }
4495
4496 if self.mode.is_full()
4497 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4498 {
4499 cx.notify();
4500 return;
4501 }
4502
4503 cx.propagate();
4504 }
4505
4506 pub fn dismiss_menus_and_popups(
4507 &mut self,
4508 is_user_requested: bool,
4509 window: &mut Window,
4510 cx: &mut Context<Self>,
4511 ) -> bool {
4512 let mut dismissed = false;
4513
4514 dismissed |= self.take_rename(false, window, cx).is_some();
4515 dismissed |= self.hide_blame_popover(true, cx);
4516 dismissed |= hide_hover(self, cx);
4517 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4518 dismissed |= self.hide_context_menu(window, cx).is_some();
4519 dismissed |= self.mouse_context_menu.take().is_some();
4520 dismissed |= is_user_requested
4521 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4522 dismissed |= self.snippet_stack.pop().is_some();
4523 if self.diff_review_drag_state.is_some() {
4524 self.cancel_diff_review_drag(cx);
4525 dismissed = true;
4526 }
4527 if !self.diff_review_overlays.is_empty() {
4528 self.dismiss_all_diff_review_overlays(cx);
4529 dismissed = true;
4530 }
4531
4532 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4533 self.dismiss_diagnostics(cx);
4534 dismissed = true;
4535 }
4536
4537 dismissed
4538 }
4539
4540 fn linked_editing_ranges_for(
4541 &self,
4542 selection: Range<text::Anchor>,
4543 cx: &App,
4544 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4545 if self.linked_edit_ranges.is_empty() {
4546 return None;
4547 }
4548 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4549 selection.end.buffer_id.and_then(|end_buffer_id| {
4550 if selection.start.buffer_id != Some(end_buffer_id) {
4551 return None;
4552 }
4553 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4554 let snapshot = buffer.read(cx).snapshot();
4555 self.linked_edit_ranges
4556 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4557 .map(|ranges| (ranges, snapshot, buffer))
4558 })?;
4559 use text::ToOffset as TO;
4560 // find offset from the start of current range to current cursor position
4561 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4562
4563 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4564 let start_difference = start_offset - start_byte_offset;
4565 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4566 let end_difference = end_offset - start_byte_offset;
4567
4568 // Current range has associated linked ranges.
4569 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4570 for range in linked_ranges.iter() {
4571 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4572 let end_offset = start_offset + end_difference;
4573 let start_offset = start_offset + start_difference;
4574 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4575 continue;
4576 }
4577 if self.selections.disjoint_anchor_ranges().any(|s| {
4578 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4579 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4580 {
4581 return false;
4582 }
4583 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4584 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4585 }) {
4586 continue;
4587 }
4588 let start = buffer_snapshot.anchor_after(start_offset);
4589 let end = buffer_snapshot.anchor_after(end_offset);
4590 linked_edits
4591 .entry(buffer.clone())
4592 .or_default()
4593 .push(start..end);
4594 }
4595 Some(linked_edits)
4596 }
4597
4598 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4599 let text: Arc<str> = text.into();
4600
4601 if self.read_only(cx) {
4602 return;
4603 }
4604
4605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4606
4607 self.unfold_buffers_with_selections(cx);
4608
4609 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4610 let mut bracket_inserted = false;
4611 let mut edits = Vec::new();
4612 let mut linked_edits = LinkedEdits::new();
4613 let mut new_selections = Vec::with_capacity(selections.len());
4614 let mut new_autoclose_regions = Vec::new();
4615 let snapshot = self.buffer.read(cx).read(cx);
4616 let mut clear_linked_edit_ranges = false;
4617 let mut all_selections_read_only = true;
4618 let mut has_adjacent_edits = false;
4619 let mut in_adjacent_group = false;
4620
4621 let mut regions = self
4622 .selections_with_autoclose_regions(selections, &snapshot)
4623 .peekable();
4624
4625 while let Some((selection, autoclose_region)) = regions.next() {
4626 if snapshot
4627 .point_to_buffer_point(selection.head())
4628 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4629 {
4630 continue;
4631 }
4632 if snapshot
4633 .point_to_buffer_point(selection.tail())
4634 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4635 {
4636 // note, ideally we'd clip the tail to the closest writeable region towards the head
4637 continue;
4638 }
4639 all_selections_read_only = false;
4640
4641 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4642 // Determine if the inserted text matches the opening or closing
4643 // bracket of any of this language's bracket pairs.
4644 let mut bracket_pair = None;
4645 let mut is_bracket_pair_start = false;
4646 let mut is_bracket_pair_end = false;
4647 if !text.is_empty() {
4648 let mut bracket_pair_matching_end = None;
4649 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4650 // and they are removing the character that triggered IME popup.
4651 for (pair, enabled) in scope.brackets() {
4652 if !pair.close && !pair.surround {
4653 continue;
4654 }
4655
4656 if enabled && pair.start.ends_with(text.as_ref()) {
4657 let prefix_len = pair.start.len() - text.len();
4658 let preceding_text_matches_prefix = prefix_len == 0
4659 || (selection.start.column >= (prefix_len as u32)
4660 && snapshot.contains_str_at(
4661 Point::new(
4662 selection.start.row,
4663 selection.start.column - (prefix_len as u32),
4664 ),
4665 &pair.start[..prefix_len],
4666 ));
4667 if preceding_text_matches_prefix {
4668 bracket_pair = Some(pair.clone());
4669 is_bracket_pair_start = true;
4670 break;
4671 }
4672 }
4673 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4674 {
4675 // take first bracket pair matching end, but don't break in case a later bracket
4676 // pair matches start
4677 bracket_pair_matching_end = Some(pair.clone());
4678 }
4679 }
4680 if let Some(end) = bracket_pair_matching_end
4681 && bracket_pair.is_none()
4682 {
4683 bracket_pair = Some(end);
4684 is_bracket_pair_end = true;
4685 }
4686 }
4687
4688 if let Some(bracket_pair) = bracket_pair {
4689 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4690 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4691 let auto_surround =
4692 self.use_auto_surround && snapshot_settings.use_auto_surround;
4693 if selection.is_empty() {
4694 if is_bracket_pair_start {
4695 // If the inserted text is a suffix of an opening bracket and the
4696 // selection is preceded by the rest of the opening bracket, then
4697 // insert the closing bracket.
4698 let following_text_allows_autoclose = snapshot
4699 .chars_at(selection.start)
4700 .next()
4701 .is_none_or(|c| scope.should_autoclose_before(c));
4702
4703 let preceding_text_allows_autoclose = selection.start.column == 0
4704 || snapshot
4705 .reversed_chars_at(selection.start)
4706 .next()
4707 .is_none_or(|c| {
4708 bracket_pair.start != bracket_pair.end
4709 || !snapshot
4710 .char_classifier_at(selection.start)
4711 .is_word(c)
4712 });
4713
4714 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4715 && bracket_pair.start.len() == 1
4716 {
4717 let target = bracket_pair.start.chars().next().unwrap();
4718 let mut byte_offset = 0u32;
4719 let current_line_count = snapshot
4720 .reversed_chars_at(selection.start)
4721 .take_while(|&c| c != '\n')
4722 .filter(|c| {
4723 byte_offset += c.len_utf8() as u32;
4724 if *c != target {
4725 return false;
4726 }
4727
4728 let point = Point::new(
4729 selection.start.row,
4730 selection.start.column.saturating_sub(byte_offset),
4731 );
4732
4733 let is_enabled = snapshot
4734 .language_scope_at(point)
4735 .and_then(|scope| {
4736 scope
4737 .brackets()
4738 .find(|(pair, _)| {
4739 pair.start == bracket_pair.start
4740 })
4741 .map(|(_, enabled)| enabled)
4742 })
4743 .unwrap_or(true);
4744
4745 let is_delimiter = snapshot
4746 .language_scope_at(Point::new(
4747 point.row,
4748 point.column + 1,
4749 ))
4750 .and_then(|scope| {
4751 scope
4752 .brackets()
4753 .find(|(pair, _)| {
4754 pair.start == bracket_pair.start
4755 })
4756 .map(|(_, enabled)| !enabled)
4757 })
4758 .unwrap_or(false);
4759
4760 is_enabled && !is_delimiter
4761 })
4762 .count();
4763 current_line_count % 2 == 1
4764 } else {
4765 false
4766 };
4767
4768 if autoclose
4769 && bracket_pair.close
4770 && following_text_allows_autoclose
4771 && preceding_text_allows_autoclose
4772 && !is_closing_quote
4773 {
4774 let anchor = snapshot.anchor_before(selection.end);
4775 new_selections.push((selection.map(|_| anchor), text.len()));
4776 new_autoclose_regions.push((
4777 anchor,
4778 text.len(),
4779 selection.id,
4780 bracket_pair.clone(),
4781 ));
4782 edits.push((
4783 selection.range(),
4784 format!("{}{}", text, bracket_pair.end).into(),
4785 ));
4786 bracket_inserted = true;
4787 continue;
4788 }
4789 }
4790
4791 if let Some(region) = autoclose_region {
4792 // If the selection is followed by an auto-inserted closing bracket,
4793 // then don't insert that closing bracket again; just move the selection
4794 // past the closing bracket.
4795 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4796 && text.as_ref() == region.pair.end.as_str()
4797 && snapshot.contains_str_at(region.range.end, text.as_ref());
4798 if should_skip {
4799 let anchor = snapshot.anchor_after(selection.end);
4800 new_selections
4801 .push((selection.map(|_| anchor), region.pair.end.len()));
4802 continue;
4803 }
4804 }
4805
4806 let always_treat_brackets_as_autoclosed = snapshot
4807 .language_settings_at(selection.start, cx)
4808 .always_treat_brackets_as_autoclosed;
4809 if always_treat_brackets_as_autoclosed
4810 && is_bracket_pair_end
4811 && snapshot.contains_str_at(selection.end, text.as_ref())
4812 {
4813 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4814 // and the inserted text is a closing bracket and the selection is followed
4815 // by the closing bracket then move the selection past the closing bracket.
4816 let anchor = snapshot.anchor_after(selection.end);
4817 new_selections.push((selection.map(|_| anchor), text.len()));
4818 continue;
4819 }
4820 }
4821 // If an opening bracket is 1 character long and is typed while
4822 // text is selected, then surround that text with the bracket pair.
4823 else if auto_surround
4824 && bracket_pair.surround
4825 && is_bracket_pair_start
4826 && bracket_pair.start.chars().count() == 1
4827 {
4828 edits.push((selection.start..selection.start, text.clone()));
4829 edits.push((
4830 selection.end..selection.end,
4831 bracket_pair.end.as_str().into(),
4832 ));
4833 bracket_inserted = true;
4834 new_selections.push((
4835 Selection {
4836 id: selection.id,
4837 start: snapshot.anchor_after(selection.start),
4838 end: snapshot.anchor_before(selection.end),
4839 reversed: selection.reversed,
4840 goal: selection.goal,
4841 },
4842 0,
4843 ));
4844 continue;
4845 }
4846 }
4847 }
4848
4849 if self.auto_replace_emoji_shortcode
4850 && selection.is_empty()
4851 && text.as_ref().ends_with(':')
4852 && let Some(possible_emoji_short_code) =
4853 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4854 && !possible_emoji_short_code.is_empty()
4855 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4856 {
4857 let emoji_shortcode_start = Point::new(
4858 selection.start.row,
4859 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4860 );
4861
4862 // Remove shortcode from buffer
4863 edits.push((
4864 emoji_shortcode_start..selection.start,
4865 "".to_string().into(),
4866 ));
4867 new_selections.push((
4868 Selection {
4869 id: selection.id,
4870 start: snapshot.anchor_after(emoji_shortcode_start),
4871 end: snapshot.anchor_before(selection.start),
4872 reversed: selection.reversed,
4873 goal: selection.goal,
4874 },
4875 0,
4876 ));
4877
4878 // Insert emoji
4879 let selection_start_anchor = snapshot.anchor_after(selection.start);
4880 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4881 edits.push((selection.start..selection.end, emoji.to_string().into()));
4882
4883 continue;
4884 }
4885
4886 let next_is_adjacent = regions
4887 .peek()
4888 .is_some_and(|(next, _)| selection.end == next.start);
4889
4890 // If not handling any auto-close operation, then just replace the selected
4891 // text with the given input and move the selection to the end of the
4892 // newly inserted text.
4893 let anchor = if in_adjacent_group || next_is_adjacent {
4894 // After edits the right bias would shift those anchor to the next visible fragment
4895 // but we want to resolve to the previous one
4896 snapshot.anchor_before(selection.end)
4897 } else {
4898 snapshot.anchor_after(selection.end)
4899 };
4900
4901 if !self.linked_edit_ranges.is_empty() {
4902 let start_anchor = snapshot.anchor_before(selection.start);
4903
4904 let is_word_char = text.chars().next().is_none_or(|char| {
4905 let classifier = snapshot
4906 .char_classifier_at(start_anchor.to_offset(&snapshot))
4907 .scope_context(Some(CharScopeContext::LinkedEdit));
4908 classifier.is_word(char)
4909 });
4910 let is_dot = text.as_ref() == ".";
4911 let should_apply_linked_edit = is_word_char || is_dot;
4912
4913 if should_apply_linked_edit {
4914 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
4915 linked_edits.push(&self, anchor_range, text.clone(), cx);
4916 } else {
4917 clear_linked_edit_ranges = true;
4918 }
4919 }
4920
4921 new_selections.push((selection.map(|_| anchor), 0));
4922 edits.push((selection.start..selection.end, text.clone()));
4923
4924 has_adjacent_edits |= next_is_adjacent;
4925 in_adjacent_group = next_is_adjacent;
4926 }
4927
4928 if all_selections_read_only {
4929 return;
4930 }
4931
4932 drop(regions);
4933 drop(snapshot);
4934
4935 self.transact(window, cx, |this, window, cx| {
4936 if clear_linked_edit_ranges {
4937 this.linked_edit_ranges.clear();
4938 }
4939 let initial_buffer_versions =
4940 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4941
4942 this.buffer.update(cx, |buffer, cx| {
4943 if has_adjacent_edits {
4944 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4945 } else {
4946 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4947 }
4948 });
4949 linked_edits.apply(cx);
4950 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4951 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4952 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4953 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4954 new_anchor_selections,
4955 &map,
4956 )
4957 .zip(new_selection_deltas)
4958 .map(|(selection, delta)| Selection {
4959 id: selection.id,
4960 start: selection.start + delta,
4961 end: selection.end + delta,
4962 reversed: selection.reversed,
4963 goal: SelectionGoal::None,
4964 })
4965 .collect::<Vec<_>>();
4966
4967 let mut i = 0;
4968 for (position, delta, selection_id, pair) in new_autoclose_regions {
4969 let position = position.to_offset(map.buffer_snapshot()) + delta;
4970 let start = map.buffer_snapshot().anchor_before(position);
4971 let end = map.buffer_snapshot().anchor_after(position);
4972 while let Some(existing_state) = this.autoclose_regions.get(i) {
4973 match existing_state
4974 .range
4975 .start
4976 .cmp(&start, map.buffer_snapshot())
4977 {
4978 Ordering::Less => i += 1,
4979 Ordering::Greater => break,
4980 Ordering::Equal => {
4981 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4982 Ordering::Less => i += 1,
4983 Ordering::Equal => break,
4984 Ordering::Greater => break,
4985 }
4986 }
4987 }
4988 }
4989 this.autoclose_regions.insert(
4990 i,
4991 AutocloseRegion {
4992 selection_id,
4993 range: start..end,
4994 pair,
4995 },
4996 );
4997 }
4998
4999 let had_active_edit_prediction = this.has_active_edit_prediction();
5000 this.change_selections(
5001 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5002 window,
5003 cx,
5004 |s| s.select(new_selections),
5005 );
5006
5007 if !bracket_inserted
5008 && let Some(on_type_format_task) =
5009 this.trigger_on_type_formatting(text.to_string(), window, cx)
5010 {
5011 on_type_format_task.detach_and_log_err(cx);
5012 }
5013
5014 let editor_settings = EditorSettings::get_global(cx);
5015 if bracket_inserted
5016 && (editor_settings.auto_signature_help
5017 || editor_settings.show_signature_help_after_edits)
5018 {
5019 this.show_signature_help(&ShowSignatureHelp, window, cx);
5020 }
5021
5022 let trigger_in_words =
5023 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5024 if this.hard_wrap.is_some() {
5025 let latest: Range<Point> = this.selections.newest(&map).range();
5026 if latest.is_empty()
5027 && this
5028 .buffer()
5029 .read(cx)
5030 .snapshot(cx)
5031 .line_len(MultiBufferRow(latest.start.row))
5032 == latest.start.column
5033 {
5034 this.rewrap_impl(
5035 RewrapOptions {
5036 override_language_settings: true,
5037 preserve_existing_whitespace: true,
5038 },
5039 cx,
5040 )
5041 }
5042 }
5043 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5044 refresh_linked_ranges(this, window, cx);
5045 this.refresh_edit_prediction(true, false, window, cx);
5046 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5047 });
5048 }
5049
5050 fn find_possible_emoji_shortcode_at_position(
5051 snapshot: &MultiBufferSnapshot,
5052 position: Point,
5053 ) -> Option<String> {
5054 let mut chars = Vec::new();
5055 let mut found_colon = false;
5056 for char in snapshot.reversed_chars_at(position).take(100) {
5057 // Found a possible emoji shortcode in the middle of the buffer
5058 if found_colon {
5059 if char.is_whitespace() {
5060 chars.reverse();
5061 return Some(chars.iter().collect());
5062 }
5063 // If the previous character is not a whitespace, we are in the middle of a word
5064 // and we only want to complete the shortcode if the word is made up of other emojis
5065 let mut containing_word = String::new();
5066 for ch in snapshot
5067 .reversed_chars_at(position)
5068 .skip(chars.len() + 1)
5069 .take(100)
5070 {
5071 if ch.is_whitespace() {
5072 break;
5073 }
5074 containing_word.push(ch);
5075 }
5076 let containing_word = containing_word.chars().rev().collect::<String>();
5077 if util::word_consists_of_emojis(containing_word.as_str()) {
5078 chars.reverse();
5079 return Some(chars.iter().collect());
5080 }
5081 }
5082
5083 if char.is_whitespace() || !char.is_ascii() {
5084 return None;
5085 }
5086 if char == ':' {
5087 found_colon = true;
5088 } else {
5089 chars.push(char);
5090 }
5091 }
5092 // Found a possible emoji shortcode at the beginning of the buffer
5093 chars.reverse();
5094 Some(chars.iter().collect())
5095 }
5096
5097 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5098 if self.read_only(cx) {
5099 return;
5100 }
5101
5102 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5103 self.transact(window, cx, |this, window, cx| {
5104 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5105 let selections = this
5106 .selections
5107 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5108 let multi_buffer = this.buffer.read(cx);
5109 let buffer = multi_buffer.snapshot(cx);
5110 selections
5111 .iter()
5112 .map(|selection| {
5113 let start_point = selection.start.to_point(&buffer);
5114 let mut existing_indent =
5115 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5116 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5117 let start = selection.start;
5118 let end = selection.end;
5119 let selection_is_empty = start == end;
5120 let language_scope = buffer.language_scope_at(start);
5121 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5122 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5123 &buffer,
5124 start..end,
5125 language,
5126 )
5127 || NewlineConfig::insert_extra_newline_tree_sitter(
5128 &buffer,
5129 start..end,
5130 );
5131
5132 let mut newline_config = NewlineConfig::Newline {
5133 additional_indent: IndentSize::spaces(0),
5134 extra_line_additional_indent: if needs_extra_newline {
5135 Some(IndentSize::spaces(0))
5136 } else {
5137 None
5138 },
5139 prevent_auto_indent: false,
5140 };
5141
5142 let comment_delimiter = maybe!({
5143 if !selection_is_empty {
5144 return None;
5145 }
5146
5147 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5148 return None;
5149 }
5150
5151 return comment_delimiter_for_newline(
5152 &start_point,
5153 &buffer,
5154 language,
5155 );
5156 });
5157
5158 let doc_delimiter = maybe!({
5159 if !selection_is_empty {
5160 return None;
5161 }
5162
5163 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5164 return None;
5165 }
5166
5167 return documentation_delimiter_for_newline(
5168 &start_point,
5169 &buffer,
5170 language,
5171 &mut newline_config,
5172 );
5173 });
5174
5175 let list_delimiter = maybe!({
5176 if !selection_is_empty {
5177 return None;
5178 }
5179
5180 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5181 return None;
5182 }
5183
5184 return list_delimiter_for_newline(
5185 &start_point,
5186 &buffer,
5187 language,
5188 &mut newline_config,
5189 );
5190 });
5191
5192 (
5193 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5194 newline_config,
5195 )
5196 } else {
5197 (
5198 None,
5199 NewlineConfig::Newline {
5200 additional_indent: IndentSize::spaces(0),
5201 extra_line_additional_indent: None,
5202 prevent_auto_indent: false,
5203 },
5204 )
5205 };
5206
5207 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5208 NewlineConfig::ClearCurrentLine => {
5209 let row_start =
5210 buffer.point_to_offset(Point::new(start_point.row, 0));
5211 (row_start, String::new(), false)
5212 }
5213 NewlineConfig::UnindentCurrentLine { continuation } => {
5214 let row_start =
5215 buffer.point_to_offset(Point::new(start_point.row, 0));
5216 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5217 let tab_size_indent = IndentSize::spaces(tab_size.get());
5218 let reduced_indent =
5219 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5220 let mut new_text = String::new();
5221 new_text.extend(reduced_indent.chars());
5222 new_text.push_str(continuation);
5223 (row_start, new_text, true)
5224 }
5225 NewlineConfig::Newline {
5226 additional_indent,
5227 extra_line_additional_indent,
5228 prevent_auto_indent,
5229 } => {
5230 let auto_indent_mode =
5231 buffer.language_settings_at(start, cx).auto_indent;
5232 let preserve_indent =
5233 auto_indent_mode != language::AutoIndentMode::None;
5234 let apply_syntax_indent =
5235 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5236 let capacity_for_delimiter =
5237 delimiter.as_deref().map(str::len).unwrap_or_default();
5238 let existing_indent_len = if preserve_indent {
5239 existing_indent.len as usize
5240 } else {
5241 0
5242 };
5243 let extra_line_len = extra_line_additional_indent
5244 .map(|i| 1 + existing_indent_len + i.len as usize)
5245 .unwrap_or(0);
5246 let mut new_text = String::with_capacity(
5247 1 + capacity_for_delimiter
5248 + existing_indent_len
5249 + additional_indent.len as usize
5250 + extra_line_len,
5251 );
5252 new_text.push('\n');
5253 if preserve_indent {
5254 new_text.extend(existing_indent.chars());
5255 }
5256 new_text.extend(additional_indent.chars());
5257 if let Some(delimiter) = &delimiter {
5258 new_text.push_str(delimiter);
5259 }
5260 if let Some(extra_indent) = extra_line_additional_indent {
5261 new_text.push('\n');
5262 if preserve_indent {
5263 new_text.extend(existing_indent.chars());
5264 }
5265 new_text.extend(extra_indent.chars());
5266 }
5267 (
5268 start,
5269 new_text,
5270 *prevent_auto_indent || !apply_syntax_indent,
5271 )
5272 }
5273 };
5274
5275 let anchor = buffer.anchor_after(end);
5276 let new_selection = selection.map(|_| anchor);
5277 (
5278 ((edit_start..end, new_text), prevent_auto_indent),
5279 (newline_config.has_extra_line(), new_selection),
5280 )
5281 })
5282 .unzip()
5283 };
5284
5285 let mut auto_indent_edits = Vec::new();
5286 let mut edits = Vec::new();
5287 for (edit, prevent_auto_indent) in edits_with_flags {
5288 if prevent_auto_indent {
5289 edits.push(edit);
5290 } else {
5291 auto_indent_edits.push(edit);
5292 }
5293 }
5294 if !edits.is_empty() {
5295 this.edit(edits, cx);
5296 }
5297 if !auto_indent_edits.is_empty() {
5298 this.edit_with_autoindent(auto_indent_edits, cx);
5299 }
5300
5301 let buffer = this.buffer.read(cx).snapshot(cx);
5302 let new_selections = selection_info
5303 .into_iter()
5304 .map(|(extra_newline_inserted, new_selection)| {
5305 let mut cursor = new_selection.end.to_point(&buffer);
5306 if extra_newline_inserted {
5307 cursor.row -= 1;
5308 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5309 }
5310 new_selection.map(|_| cursor)
5311 })
5312 .collect();
5313
5314 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5315 this.refresh_edit_prediction(true, false, window, cx);
5316 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5317 task.detach_and_log_err(cx);
5318 }
5319 });
5320 }
5321
5322 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5323 if self.read_only(cx) {
5324 return;
5325 }
5326
5327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5328
5329 let buffer = self.buffer.read(cx);
5330 let snapshot = buffer.snapshot(cx);
5331
5332 let mut edits = Vec::new();
5333 let mut rows = Vec::new();
5334
5335 for (rows_inserted, selection) in self
5336 .selections
5337 .all_adjusted(&self.display_snapshot(cx))
5338 .into_iter()
5339 .enumerate()
5340 {
5341 let cursor = selection.head();
5342 let row = cursor.row;
5343
5344 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5345
5346 let newline = "\n".to_string();
5347 edits.push((start_of_line..start_of_line, newline));
5348
5349 rows.push(row + rows_inserted as u32);
5350 }
5351
5352 self.transact(window, cx, |editor, window, cx| {
5353 editor.edit(edits, cx);
5354
5355 editor.change_selections(Default::default(), window, cx, |s| {
5356 let mut index = 0;
5357 s.move_cursors_with(&mut |map, _, _| {
5358 let row = rows[index];
5359 index += 1;
5360
5361 let point = Point::new(row, 0);
5362 let boundary = map.next_line_boundary(point).1;
5363 let clipped = map.clip_point(boundary, Bias::Left);
5364
5365 (clipped, SelectionGoal::None)
5366 });
5367 });
5368
5369 let mut indent_edits = Vec::new();
5370 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5371 for row in rows {
5372 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5373 for (row, indent) in indents {
5374 if indent.len == 0 {
5375 continue;
5376 }
5377
5378 let text = match indent.kind {
5379 IndentKind::Space => " ".repeat(indent.len as usize),
5380 IndentKind::Tab => "\t".repeat(indent.len as usize),
5381 };
5382 let point = Point::new(row.0, 0);
5383 indent_edits.push((point..point, text));
5384 }
5385 }
5386 editor.edit(indent_edits, cx);
5387 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5388 format.detach_and_log_err(cx);
5389 }
5390 });
5391 }
5392
5393 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5394 if self.read_only(cx) {
5395 return;
5396 }
5397
5398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5399
5400 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5401 let mut rows = Vec::new();
5402 let mut rows_inserted = 0;
5403
5404 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5405 let cursor = selection.head();
5406 let row = cursor.row;
5407
5408 let point = Point::new(row, 0);
5409 let Some((buffer_handle, buffer_point, _)) =
5410 self.buffer.read(cx).point_to_buffer_point(point, cx)
5411 else {
5412 continue;
5413 };
5414
5415 buffer_edits
5416 .entry(buffer_handle.entity_id())
5417 .or_insert_with(|| (buffer_handle, Vec::new()))
5418 .1
5419 .push(buffer_point);
5420
5421 rows_inserted += 1;
5422 rows.push(row + rows_inserted);
5423 }
5424
5425 self.transact(window, cx, |editor, window, cx| {
5426 for (_, (buffer_handle, points)) in &buffer_edits {
5427 buffer_handle.update(cx, |buffer, cx| {
5428 let edits: Vec<_> = points
5429 .iter()
5430 .map(|point| {
5431 let target = Point::new(point.row + 1, 0);
5432 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5433 (start_of_line..start_of_line, "\n")
5434 })
5435 .collect();
5436 buffer.edit(edits, None, cx);
5437 });
5438 }
5439
5440 editor.change_selections(Default::default(), window, cx, |s| {
5441 let mut index = 0;
5442 s.move_cursors_with(&mut |map, _, _| {
5443 let row = rows[index];
5444 index += 1;
5445
5446 let point = Point::new(row, 0);
5447 let boundary = map.next_line_boundary(point).1;
5448 let clipped = map.clip_point(boundary, Bias::Left);
5449
5450 (clipped, SelectionGoal::None)
5451 });
5452 });
5453
5454 let mut indent_edits = Vec::new();
5455 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5456 for row in rows {
5457 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5458 for (row, indent) in indents {
5459 if indent.len == 0 {
5460 continue;
5461 }
5462
5463 let text = match indent.kind {
5464 IndentKind::Space => " ".repeat(indent.len as usize),
5465 IndentKind::Tab => "\t".repeat(indent.len as usize),
5466 };
5467 let point = Point::new(row.0, 0);
5468 indent_edits.push((point..point, text));
5469 }
5470 }
5471 editor.edit(indent_edits, cx);
5472 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5473 format.detach_and_log_err(cx);
5474 }
5475 });
5476 }
5477
5478 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5479 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5480 original_indent_columns: Vec::new(),
5481 });
5482 self.replace_selections(text, autoindent, window, cx, false);
5483 }
5484
5485 /// Replaces the editor's selections with the provided `text`, applying the
5486 /// given `autoindent_mode` (`None` will skip autoindentation).
5487 ///
5488 /// Early returns if the editor is in read-only mode, without applying any
5489 /// edits.
5490 fn replace_selections(
5491 &mut self,
5492 text: &str,
5493 autoindent_mode: Option<AutoindentMode>,
5494 window: &mut Window,
5495 cx: &mut Context<Self>,
5496 apply_linked_edits: bool,
5497 ) {
5498 if self.read_only(cx) {
5499 return;
5500 }
5501
5502 let text: Arc<str> = text.into();
5503 self.transact(window, cx, |this, window, cx| {
5504 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5505 let linked_edits = if apply_linked_edits {
5506 this.linked_edits_for_selections(text.clone(), cx)
5507 } else {
5508 LinkedEdits::new()
5509 };
5510
5511 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5512 let anchors = {
5513 let snapshot = buffer.read(cx);
5514 old_selections
5515 .iter()
5516 .map(|s| {
5517 let anchor = snapshot.anchor_after(s.head());
5518 s.map(|_| anchor)
5519 })
5520 .collect::<Vec<_>>()
5521 };
5522 buffer.edit(
5523 old_selections
5524 .iter()
5525 .map(|s| (s.start..s.end, text.clone())),
5526 autoindent_mode,
5527 cx,
5528 );
5529 anchors
5530 });
5531
5532 linked_edits.apply(cx);
5533
5534 this.change_selections(Default::default(), window, cx, |s| {
5535 s.select_anchors(selection_anchors);
5536 });
5537
5538 if apply_linked_edits {
5539 refresh_linked_ranges(this, window, cx);
5540 }
5541
5542 cx.notify();
5543 });
5544 }
5545
5546 /// Collects linked edits for the current selections, pairing each linked
5547 /// range with `text`.
5548 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5549 let mut linked_edits = LinkedEdits::new();
5550 if !self.linked_edit_ranges.is_empty() {
5551 for selection in self.selections.disjoint_anchors() {
5552 let start = selection.start.text_anchor;
5553 let end = selection.end.text_anchor;
5554 linked_edits.push(self, start..end, text.clone(), cx);
5555 }
5556 }
5557 linked_edits
5558 }
5559
5560 /// Deletes the content covered by the current selections and applies
5561 /// linked edits.
5562 pub fn delete_selections_with_linked_edits(
5563 &mut self,
5564 window: &mut Window,
5565 cx: &mut Context<Self>,
5566 ) {
5567 self.replace_selections("", None, window, cx, true);
5568 }
5569
5570 #[cfg(any(test, feature = "test-support"))]
5571 pub fn set_linked_edit_ranges_for_testing(
5572 &mut self,
5573 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5574 cx: &mut Context<Self>,
5575 ) -> Option<()> {
5576 let Some((buffer, _)) = self
5577 .buffer
5578 .read(cx)
5579 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5580 else {
5581 return None;
5582 };
5583 let buffer = buffer.read(cx);
5584 let buffer_id = buffer.remote_id();
5585 let mut linked_ranges = Vec::with_capacity(ranges.len());
5586 for (base_range, linked_ranges_points) in ranges {
5587 let base_anchor =
5588 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5589 let linked_anchors = linked_ranges_points
5590 .into_iter()
5591 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5592 .collect();
5593 linked_ranges.push((base_anchor, linked_anchors));
5594 }
5595 let mut map = HashMap::default();
5596 map.insert(buffer_id, linked_ranges);
5597 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5598 Some(())
5599 }
5600
5601 fn trigger_completion_on_input(
5602 &mut self,
5603 text: &str,
5604 trigger_in_words: bool,
5605 window: &mut Window,
5606 cx: &mut Context<Self>,
5607 ) {
5608 let completions_source = self
5609 .context_menu
5610 .borrow()
5611 .as_ref()
5612 .and_then(|menu| match menu {
5613 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5614 CodeContextMenu::CodeActions(_) => None,
5615 });
5616
5617 match completions_source {
5618 Some(CompletionsMenuSource::Words { .. }) => {
5619 self.open_or_update_completions_menu(
5620 Some(CompletionsMenuSource::Words {
5621 ignore_threshold: false,
5622 }),
5623 None,
5624 trigger_in_words,
5625 window,
5626 cx,
5627 );
5628 }
5629 _ => self.open_or_update_completions_menu(
5630 None,
5631 Some(text.to_owned()).filter(|x| !x.is_empty()),
5632 true,
5633 window,
5634 cx,
5635 ),
5636 }
5637 }
5638
5639 /// If any empty selections is touching the start of its innermost containing autoclose
5640 /// region, expand it to select the brackets.
5641 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5642 let selections = self
5643 .selections
5644 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5645 let buffer = self.buffer.read(cx).read(cx);
5646 let new_selections = self
5647 .selections_with_autoclose_regions(selections, &buffer)
5648 .map(|(mut selection, region)| {
5649 if !selection.is_empty() {
5650 return selection;
5651 }
5652
5653 if let Some(region) = region {
5654 let mut range = region.range.to_offset(&buffer);
5655 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5656 range.start -= region.pair.start.len();
5657 if buffer.contains_str_at(range.start, ®ion.pair.start)
5658 && buffer.contains_str_at(range.end, ®ion.pair.end)
5659 {
5660 range.end += region.pair.end.len();
5661 selection.start = range.start;
5662 selection.end = range.end;
5663
5664 return selection;
5665 }
5666 }
5667 }
5668
5669 let always_treat_brackets_as_autoclosed = buffer
5670 .language_settings_at(selection.start, cx)
5671 .always_treat_brackets_as_autoclosed;
5672
5673 if !always_treat_brackets_as_autoclosed {
5674 return selection;
5675 }
5676
5677 if let Some(scope) = buffer.language_scope_at(selection.start) {
5678 for (pair, enabled) in scope.brackets() {
5679 if !enabled || !pair.close {
5680 continue;
5681 }
5682
5683 if buffer.contains_str_at(selection.start, &pair.end) {
5684 let pair_start_len = pair.start.len();
5685 if buffer.contains_str_at(
5686 selection.start.saturating_sub_usize(pair_start_len),
5687 &pair.start,
5688 ) {
5689 selection.start -= pair_start_len;
5690 selection.end += pair.end.len();
5691
5692 return selection;
5693 }
5694 }
5695 }
5696 }
5697
5698 selection
5699 })
5700 .collect();
5701
5702 drop(buffer);
5703 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5704 selections.select(new_selections)
5705 });
5706 }
5707
5708 /// Iterate the given selections, and for each one, find the smallest surrounding
5709 /// autoclose region. This uses the ordering of the selections and the autoclose
5710 /// regions to avoid repeated comparisons.
5711 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5712 &'a self,
5713 selections: impl IntoIterator<Item = Selection<D>>,
5714 buffer: &'a MultiBufferSnapshot,
5715 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5716 let mut i = 0;
5717 let mut regions = self.autoclose_regions.as_slice();
5718 selections.into_iter().map(move |selection| {
5719 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5720
5721 let mut enclosing = None;
5722 while let Some(pair_state) = regions.get(i) {
5723 if pair_state.range.end.to_offset(buffer) < range.start {
5724 regions = ®ions[i + 1..];
5725 i = 0;
5726 } else if pair_state.range.start.to_offset(buffer) > range.end {
5727 break;
5728 } else {
5729 if pair_state.selection_id == selection.id {
5730 enclosing = Some(pair_state);
5731 }
5732 i += 1;
5733 }
5734 }
5735
5736 (selection, enclosing)
5737 })
5738 }
5739
5740 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5741 fn invalidate_autoclose_regions(
5742 &mut self,
5743 mut selections: &[Selection<Anchor>],
5744 buffer: &MultiBufferSnapshot,
5745 ) {
5746 self.autoclose_regions.retain(|state| {
5747 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5748 return false;
5749 }
5750
5751 let mut i = 0;
5752 while let Some(selection) = selections.get(i) {
5753 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5754 selections = &selections[1..];
5755 continue;
5756 }
5757 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5758 break;
5759 }
5760 if selection.id == state.selection_id {
5761 return true;
5762 } else {
5763 i += 1;
5764 }
5765 }
5766 false
5767 });
5768 }
5769
5770 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5771 let offset = position.to_offset(buffer);
5772 let (word_range, kind) =
5773 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5774 if offset > word_range.start && kind == Some(CharKind::Word) {
5775 Some(
5776 buffer
5777 .text_for_range(word_range.start..offset)
5778 .collect::<String>(),
5779 )
5780 } else {
5781 None
5782 }
5783 }
5784
5785 pub fn visible_excerpts(
5786 &self,
5787 lsp_related_only: bool,
5788 cx: &mut Context<Editor>,
5789 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5790 let project = self.project().cloned();
5791 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5792 let multi_buffer = self.buffer().read(cx);
5793 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5794 let multi_buffer_visible_start = self
5795 .scroll_manager
5796 .native_anchor(&display_snapshot, cx)
5797 .anchor
5798 .to_point(&multi_buffer_snapshot);
5799 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5800 multi_buffer_visible_start
5801 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5802 Bias::Left,
5803 );
5804 multi_buffer_snapshot
5805 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5806 .into_iter()
5807 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5808 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5809 if !lsp_related_only {
5810 return Some((
5811 excerpt_id,
5812 (
5813 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5814 buffer.version().clone(),
5815 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5816 ),
5817 ));
5818 }
5819
5820 let project = project.as_ref()?.read(cx);
5821 let buffer_file = project::File::from_dyn(buffer.file())?;
5822 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5823 let worktree_entry = buffer_worktree
5824 .read(cx)
5825 .entry_for_id(buffer_file.project_entry_id()?)?;
5826 if worktree_entry.is_ignored {
5827 None
5828 } else {
5829 Some((
5830 excerpt_id,
5831 (
5832 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5833 buffer.version().clone(),
5834 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5835 ),
5836 ))
5837 }
5838 })
5839 .collect()
5840 }
5841
5842 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5843 TextLayoutDetails {
5844 text_system: window.text_system().clone(),
5845 editor_style: self.style.clone().unwrap(),
5846 rem_size: window.rem_size(),
5847 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5848 visible_rows: self.visible_line_count(),
5849 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5850 }
5851 }
5852
5853 fn trigger_on_type_formatting(
5854 &self,
5855 input: String,
5856 window: &mut Window,
5857 cx: &mut Context<Self>,
5858 ) -> Option<Task<Result<()>>> {
5859 if input.chars().count() != 1 {
5860 return None;
5861 }
5862
5863 let project = self.project()?;
5864 let position = self.selections.newest_anchor().head();
5865 let (buffer, buffer_position) = self
5866 .buffer
5867 .read(cx)
5868 .text_anchor_for_position(position, cx)?;
5869
5870 let settings = language_settings::language_settings(
5871 buffer
5872 .read(cx)
5873 .language_at(buffer_position)
5874 .map(|l| l.name()),
5875 buffer.read(cx).file(),
5876 cx,
5877 );
5878 if !settings.use_on_type_format {
5879 return None;
5880 }
5881
5882 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5883 // hence we do LSP request & edit on host side only — add formats to host's history.
5884 let push_to_lsp_host_history = true;
5885 // If this is not the host, append its history with new edits.
5886 let push_to_client_history = project.read(cx).is_via_collab();
5887
5888 let on_type_formatting = project.update(cx, |project, cx| {
5889 project.on_type_format(
5890 buffer.clone(),
5891 buffer_position,
5892 input,
5893 push_to_lsp_host_history,
5894 cx,
5895 )
5896 });
5897 Some(cx.spawn_in(window, async move |editor, cx| {
5898 if let Some(transaction) = on_type_formatting.await? {
5899 if push_to_client_history {
5900 buffer.update(cx, |buffer, _| {
5901 buffer.push_transaction(transaction, Instant::now());
5902 buffer.finalize_last_transaction();
5903 });
5904 }
5905 editor.update(cx, |editor, cx| {
5906 editor.refresh_document_highlights(cx);
5907 })?;
5908 }
5909 Ok(())
5910 }))
5911 }
5912
5913 pub fn show_word_completions(
5914 &mut self,
5915 _: &ShowWordCompletions,
5916 window: &mut Window,
5917 cx: &mut Context<Self>,
5918 ) {
5919 self.open_or_update_completions_menu(
5920 Some(CompletionsMenuSource::Words {
5921 ignore_threshold: true,
5922 }),
5923 None,
5924 false,
5925 window,
5926 cx,
5927 );
5928 }
5929
5930 pub fn show_completions(
5931 &mut self,
5932 _: &ShowCompletions,
5933 window: &mut Window,
5934 cx: &mut Context<Self>,
5935 ) {
5936 self.open_or_update_completions_menu(None, None, false, window, cx);
5937 }
5938
5939 fn open_or_update_completions_menu(
5940 &mut self,
5941 requested_source: Option<CompletionsMenuSource>,
5942 trigger: Option<String>,
5943 trigger_in_words: bool,
5944 window: &mut Window,
5945 cx: &mut Context<Self>,
5946 ) {
5947 if self.pending_rename.is_some() {
5948 return;
5949 }
5950
5951 let completions_source = self
5952 .context_menu
5953 .borrow()
5954 .as_ref()
5955 .and_then(|menu| match menu {
5956 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5957 CodeContextMenu::CodeActions(_) => None,
5958 });
5959
5960 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5961
5962 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5963 // inserted and selected. To handle that case, the start of the selection is used so that
5964 // the menu starts with all choices.
5965 let position = self
5966 .selections
5967 .newest_anchor()
5968 .start
5969 .bias_right(&multibuffer_snapshot);
5970 if position.diff_base_anchor.is_some() {
5971 return;
5972 }
5973 let buffer_position = multibuffer_snapshot.anchor_before(position);
5974 let Some(buffer) = buffer_position
5975 .text_anchor
5976 .buffer_id
5977 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5978 else {
5979 return;
5980 };
5981 let buffer_snapshot = buffer.read(cx).snapshot();
5982
5983 let menu_is_open = matches!(
5984 self.context_menu.borrow().as_ref(),
5985 Some(CodeContextMenu::Completions(_))
5986 );
5987
5988 let language = buffer_snapshot
5989 .language_at(buffer_position.text_anchor)
5990 .map(|language| language.name());
5991
5992 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5993 let completion_settings = language_settings.completions.clone();
5994
5995 let show_completions_on_input = self
5996 .show_completions_on_input_override
5997 .unwrap_or(language_settings.show_completions_on_input);
5998 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5999 return;
6000 }
6001
6002 let query: Option<Arc<String>> =
6003 Self::completion_query(&multibuffer_snapshot, buffer_position)
6004 .map(|query| query.into());
6005
6006 drop(multibuffer_snapshot);
6007
6008 // Hide the current completions menu when query is empty. Without this, cached
6009 // completions from before the trigger char may be reused (#32774).
6010 if query.is_none() && menu_is_open {
6011 self.hide_context_menu(window, cx);
6012 }
6013
6014 let mut ignore_word_threshold = false;
6015 let provider = match requested_source {
6016 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6017 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6018 ignore_word_threshold = ignore_threshold;
6019 None
6020 }
6021 Some(CompletionsMenuSource::SnippetChoices)
6022 | Some(CompletionsMenuSource::SnippetsOnly) => {
6023 log::error!("bug: SnippetChoices requested_source is not handled");
6024 None
6025 }
6026 };
6027
6028 let sort_completions = provider
6029 .as_ref()
6030 .is_some_and(|provider| provider.sort_completions());
6031
6032 let filter_completions = provider
6033 .as_ref()
6034 .is_none_or(|provider| provider.filter_completions());
6035
6036 let was_snippets_only = matches!(
6037 completions_source,
6038 Some(CompletionsMenuSource::SnippetsOnly)
6039 );
6040
6041 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6042 if filter_completions {
6043 menu.filter(
6044 query.clone().unwrap_or_default(),
6045 buffer_position.text_anchor,
6046 &buffer,
6047 provider.clone(),
6048 window,
6049 cx,
6050 );
6051 }
6052 // When `is_incomplete` is false, no need to re-query completions when the current query
6053 // is a suffix of the initial query.
6054 let was_complete = !menu.is_incomplete;
6055 if was_complete && !was_snippets_only {
6056 // If the new query is a suffix of the old query (typing more characters) and
6057 // the previous result was complete, the existing completions can be filtered.
6058 //
6059 // Note that snippet completions are always complete.
6060 let query_matches = match (&menu.initial_query, &query) {
6061 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6062 (None, _) => true,
6063 _ => false,
6064 };
6065 if query_matches {
6066 let position_matches = if menu.initial_position == position {
6067 true
6068 } else {
6069 let snapshot = self.buffer.read(cx).read(cx);
6070 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6071 };
6072 if position_matches {
6073 return;
6074 }
6075 }
6076 }
6077 };
6078
6079 let Anchor {
6080 excerpt_id: buffer_excerpt_id,
6081 text_anchor: buffer_position,
6082 ..
6083 } = buffer_position;
6084
6085 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6086 buffer_snapshot.surrounding_word(buffer_position, None)
6087 {
6088 let word_to_exclude = buffer_snapshot
6089 .text_for_range(word_range.clone())
6090 .collect::<String>();
6091 (
6092 buffer_snapshot.anchor_before(word_range.start)
6093 ..buffer_snapshot.anchor_after(buffer_position),
6094 Some(word_to_exclude),
6095 )
6096 } else {
6097 (buffer_position..buffer_position, None)
6098 };
6099
6100 let show_completion_documentation = buffer_snapshot
6101 .settings_at(buffer_position, cx)
6102 .show_completion_documentation;
6103
6104 // The document can be large, so stay in reasonable bounds when searching for words,
6105 // otherwise completion pop-up might be slow to appear.
6106 const WORD_LOOKUP_ROWS: u32 = 5_000;
6107 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6108 let min_word_search = buffer_snapshot.clip_point(
6109 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6110 Bias::Left,
6111 );
6112 let max_word_search = buffer_snapshot.clip_point(
6113 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6114 Bias::Right,
6115 );
6116 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6117 ..buffer_snapshot.point_to_offset(max_word_search);
6118
6119 let skip_digits = query
6120 .as_ref()
6121 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6122
6123 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6124 trigger.as_ref().is_none_or(|trigger| {
6125 provider.is_completion_trigger(
6126 &buffer,
6127 position.text_anchor,
6128 trigger,
6129 trigger_in_words,
6130 cx,
6131 )
6132 })
6133 });
6134
6135 let provider_responses = if let Some(provider) = &provider
6136 && load_provider_completions
6137 {
6138 let trigger_character =
6139 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6140 let completion_context = CompletionContext {
6141 trigger_kind: match &trigger_character {
6142 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6143 None => CompletionTriggerKind::INVOKED,
6144 },
6145 trigger_character,
6146 };
6147
6148 provider.completions(
6149 buffer_excerpt_id,
6150 &buffer,
6151 buffer_position,
6152 completion_context,
6153 window,
6154 cx,
6155 )
6156 } else {
6157 Task::ready(Ok(Vec::new()))
6158 };
6159
6160 let load_word_completions = if !self.word_completions_enabled {
6161 false
6162 } else if requested_source
6163 == Some(CompletionsMenuSource::Words {
6164 ignore_threshold: true,
6165 })
6166 {
6167 true
6168 } else {
6169 load_provider_completions
6170 && completion_settings.words != WordsCompletionMode::Disabled
6171 && (ignore_word_threshold || {
6172 let words_min_length = completion_settings.words_min_length;
6173 // check whether word has at least `words_min_length` characters
6174 let query_chars = query.iter().flat_map(|q| q.chars());
6175 query_chars.take(words_min_length).count() == words_min_length
6176 })
6177 };
6178
6179 let mut words = if load_word_completions {
6180 cx.background_spawn({
6181 let buffer_snapshot = buffer_snapshot.clone();
6182 async move {
6183 buffer_snapshot.words_in_range(WordsQuery {
6184 fuzzy_contents: None,
6185 range: word_search_range,
6186 skip_digits,
6187 })
6188 }
6189 })
6190 } else {
6191 Task::ready(BTreeMap::default())
6192 };
6193
6194 let snippets = if let Some(provider) = &provider
6195 && provider.show_snippets()
6196 && let Some(project) = self.project()
6197 {
6198 let char_classifier = buffer_snapshot
6199 .char_classifier_at(buffer_position)
6200 .scope_context(Some(CharScopeContext::Completion));
6201 project.update(cx, |project, cx| {
6202 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6203 })
6204 } else {
6205 Task::ready(Ok(CompletionResponse {
6206 completions: Vec::new(),
6207 display_options: Default::default(),
6208 is_incomplete: false,
6209 }))
6210 };
6211
6212 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6213
6214 let id = post_inc(&mut self.next_completion_id);
6215 let task = cx.spawn_in(window, async move |editor, cx| {
6216 let Ok(()) = editor.update(cx, |this, _| {
6217 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6218 }) else {
6219 return;
6220 };
6221
6222 // TODO: Ideally completions from different sources would be selectively re-queried, so
6223 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6224 let mut completions = Vec::new();
6225 let mut is_incomplete = false;
6226 let mut display_options: Option<CompletionDisplayOptions> = None;
6227 if let Some(provider_responses) = provider_responses.await.log_err()
6228 && !provider_responses.is_empty()
6229 {
6230 for response in provider_responses {
6231 completions.extend(response.completions);
6232 is_incomplete = is_incomplete || response.is_incomplete;
6233 match display_options.as_mut() {
6234 None => {
6235 display_options = Some(response.display_options);
6236 }
6237 Some(options) => options.merge(&response.display_options),
6238 }
6239 }
6240 if completion_settings.words == WordsCompletionMode::Fallback {
6241 words = Task::ready(BTreeMap::default());
6242 }
6243 }
6244 let display_options = display_options.unwrap_or_default();
6245
6246 let mut words = words.await;
6247 if let Some(word_to_exclude) = &word_to_exclude {
6248 words.remove(word_to_exclude);
6249 }
6250 for lsp_completion in &completions {
6251 words.remove(&lsp_completion.new_text);
6252 }
6253 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6254 replace_range: word_replace_range.clone(),
6255 new_text: word.clone(),
6256 label: CodeLabel::plain(word, None),
6257 match_start: None,
6258 snippet_deduplication_key: None,
6259 icon_path: None,
6260 documentation: None,
6261 source: CompletionSource::BufferWord {
6262 word_range,
6263 resolved: false,
6264 },
6265 insert_text_mode: Some(InsertTextMode::AS_IS),
6266 confirm: None,
6267 }));
6268
6269 completions.extend(
6270 snippets
6271 .await
6272 .into_iter()
6273 .flat_map(|response| response.completions),
6274 );
6275
6276 let menu = if completions.is_empty() {
6277 None
6278 } else {
6279 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6280 let languages = editor
6281 .workspace
6282 .as_ref()
6283 .and_then(|(workspace, _)| workspace.upgrade())
6284 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6285 let menu = CompletionsMenu::new(
6286 id,
6287 requested_source.unwrap_or(if load_provider_completions {
6288 CompletionsMenuSource::Normal
6289 } else {
6290 CompletionsMenuSource::SnippetsOnly
6291 }),
6292 sort_completions,
6293 show_completion_documentation,
6294 position,
6295 query.clone(),
6296 is_incomplete,
6297 buffer.clone(),
6298 completions.into(),
6299 editor
6300 .context_menu()
6301 .borrow_mut()
6302 .as_ref()
6303 .map(|menu| menu.primary_scroll_handle()),
6304 display_options,
6305 snippet_sort_order,
6306 languages,
6307 language,
6308 cx,
6309 );
6310
6311 let query = if filter_completions { query } else { None };
6312 let matches_task = menu.do_async_filtering(
6313 query.unwrap_or_default(),
6314 buffer_position,
6315 &buffer,
6316 cx,
6317 );
6318 (menu, matches_task)
6319 }) else {
6320 return;
6321 };
6322
6323 let matches = matches_task.await;
6324
6325 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6326 // Newer menu already set, so exit.
6327 if let Some(CodeContextMenu::Completions(prev_menu)) =
6328 editor.context_menu.borrow().as_ref()
6329 && prev_menu.id > id
6330 {
6331 return;
6332 };
6333
6334 // Only valid to take prev_menu because either the new menu is immediately set
6335 // below, or the menu is hidden.
6336 if let Some(CodeContextMenu::Completions(prev_menu)) =
6337 editor.context_menu.borrow_mut().take()
6338 {
6339 let position_matches =
6340 if prev_menu.initial_position == menu.initial_position {
6341 true
6342 } else {
6343 let snapshot = editor.buffer.read(cx).read(cx);
6344 prev_menu.initial_position.to_offset(&snapshot)
6345 == menu.initial_position.to_offset(&snapshot)
6346 };
6347 if position_matches {
6348 // Preserve markdown cache before `set_filter_results` because it will
6349 // try to populate the documentation cache.
6350 menu.preserve_markdown_cache(prev_menu);
6351 }
6352 };
6353
6354 menu.set_filter_results(matches, provider, window, cx);
6355 }) else {
6356 return;
6357 };
6358
6359 menu.visible().then_some(menu)
6360 };
6361
6362 editor
6363 .update_in(cx, |editor, window, cx| {
6364 if editor.focus_handle.is_focused(window)
6365 && let Some(menu) = menu
6366 {
6367 *editor.context_menu.borrow_mut() =
6368 Some(CodeContextMenu::Completions(menu));
6369
6370 crate::hover_popover::hide_hover(editor, cx);
6371 if editor.show_edit_predictions_in_menu() {
6372 editor.update_visible_edit_prediction(window, cx);
6373 } else {
6374 editor
6375 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6376 }
6377
6378 cx.notify();
6379 return;
6380 }
6381
6382 if editor.completion_tasks.len() <= 1 {
6383 // If there are no more completion tasks and the last menu was empty, we should hide it.
6384 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6385 // If it was already hidden and we don't show edit predictions in the menu,
6386 // we should also show the edit prediction when available.
6387 if was_hidden && editor.show_edit_predictions_in_menu() {
6388 editor.update_visible_edit_prediction(window, cx);
6389 }
6390 }
6391 })
6392 .ok();
6393 });
6394
6395 self.completion_tasks.push((id, task));
6396 }
6397
6398 #[cfg(any(test, feature = "test-support"))]
6399 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6400 let menu = self.context_menu.borrow();
6401 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6402 let completions = menu.completions.borrow();
6403 Some(completions.to_vec())
6404 } else {
6405 None
6406 }
6407 }
6408
6409 pub fn with_completions_menu_matching_id<R>(
6410 &self,
6411 id: CompletionId,
6412 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6413 ) -> R {
6414 let mut context_menu = self.context_menu.borrow_mut();
6415 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6416 return f(None);
6417 };
6418 if completions_menu.id != id {
6419 return f(None);
6420 }
6421 f(Some(completions_menu))
6422 }
6423
6424 pub fn confirm_completion(
6425 &mut self,
6426 action: &ConfirmCompletion,
6427 window: &mut Window,
6428 cx: &mut Context<Self>,
6429 ) -> Option<Task<Result<()>>> {
6430 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6431 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6432 }
6433
6434 pub fn confirm_completion_insert(
6435 &mut self,
6436 _: &ConfirmCompletionInsert,
6437 window: &mut Window,
6438 cx: &mut Context<Self>,
6439 ) -> Option<Task<Result<()>>> {
6440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6441 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6442 }
6443
6444 pub fn confirm_completion_replace(
6445 &mut self,
6446 _: &ConfirmCompletionReplace,
6447 window: &mut Window,
6448 cx: &mut Context<Self>,
6449 ) -> Option<Task<Result<()>>> {
6450 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6451 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6452 }
6453
6454 pub fn compose_completion(
6455 &mut self,
6456 action: &ComposeCompletion,
6457 window: &mut Window,
6458 cx: &mut Context<Self>,
6459 ) -> Option<Task<Result<()>>> {
6460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6461 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6462 }
6463
6464 fn do_completion(
6465 &mut self,
6466 item_ix: Option<usize>,
6467 intent: CompletionIntent,
6468 window: &mut Window,
6469 cx: &mut Context<Editor>,
6470 ) -> Option<Task<Result<()>>> {
6471 use language::ToOffset as _;
6472
6473 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6474 else {
6475 return None;
6476 };
6477
6478 let candidate_id = {
6479 let entries = completions_menu.entries.borrow();
6480 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6481 if self.show_edit_predictions_in_menu() {
6482 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6483 }
6484 mat.candidate_id
6485 };
6486
6487 let completion = completions_menu
6488 .completions
6489 .borrow()
6490 .get(candidate_id)?
6491 .clone();
6492 cx.stop_propagation();
6493
6494 let buffer_handle = completions_menu.buffer.clone();
6495
6496 let CompletionEdit {
6497 new_text,
6498 snippet,
6499 replace_range,
6500 } = process_completion_for_edit(
6501 &completion,
6502 intent,
6503 &buffer_handle,
6504 &completions_menu.initial_position.text_anchor,
6505 cx,
6506 );
6507
6508 let buffer = buffer_handle.read(cx);
6509 let snapshot = self.buffer.read(cx).snapshot(cx);
6510 let newest_anchor = self.selections.newest_anchor();
6511 let replace_range_multibuffer = {
6512 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6513 excerpt.map_range_from_buffer(replace_range.clone())
6514 };
6515 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6516 return None;
6517 }
6518
6519 let old_text = buffer
6520 .text_for_range(replace_range.clone())
6521 .collect::<String>();
6522 let lookbehind = newest_anchor
6523 .start
6524 .text_anchor
6525 .to_offset(buffer)
6526 .saturating_sub(replace_range.start.0);
6527 let lookahead = replace_range
6528 .end
6529 .0
6530 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6531 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6532 let suffix = &old_text[lookbehind.min(old_text.len())..];
6533
6534 let selections = self
6535 .selections
6536 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6537 let mut ranges = Vec::new();
6538 let mut linked_edits = LinkedEdits::new();
6539
6540 let text: Arc<str> = new_text.clone().into();
6541 for selection in &selections {
6542 let range = if selection.id == newest_anchor.id {
6543 replace_range_multibuffer.clone()
6544 } else {
6545 let mut range = selection.range();
6546
6547 // if prefix is present, don't duplicate it
6548 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6549 range.start = range.start.saturating_sub_usize(lookbehind);
6550
6551 // if suffix is also present, mimic the newest cursor and replace it
6552 if selection.id != newest_anchor.id
6553 && snapshot.contains_str_at(range.end, suffix)
6554 {
6555 range.end += lookahead;
6556 }
6557 }
6558 range
6559 };
6560
6561 ranges.push(range.clone());
6562
6563 if !self.linked_edit_ranges.is_empty() {
6564 let start_anchor = snapshot.anchor_before(range.start);
6565 let end_anchor = snapshot.anchor_after(range.end);
6566 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6567 linked_edits.push(&self, anchor_range, text.clone(), cx);
6568 }
6569 }
6570
6571 let common_prefix_len = old_text
6572 .chars()
6573 .zip(new_text.chars())
6574 .take_while(|(a, b)| a == b)
6575 .map(|(a, _)| a.len_utf8())
6576 .sum::<usize>();
6577
6578 cx.emit(EditorEvent::InputHandled {
6579 utf16_range_to_replace: None,
6580 text: new_text[common_prefix_len..].into(),
6581 });
6582
6583 self.transact(window, cx, |editor, window, cx| {
6584 if let Some(mut snippet) = snippet {
6585 snippet.text = new_text.to_string();
6586 editor
6587 .insert_snippet(&ranges, snippet, window, cx)
6588 .log_err();
6589 } else {
6590 editor.buffer.update(cx, |multi_buffer, cx| {
6591 let auto_indent = match completion.insert_text_mode {
6592 Some(InsertTextMode::AS_IS) => None,
6593 _ => editor.autoindent_mode.clone(),
6594 };
6595 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6596 multi_buffer.edit(edits, auto_indent, cx);
6597 });
6598 }
6599 linked_edits.apply(cx);
6600 editor.refresh_edit_prediction(true, false, window, cx);
6601 });
6602 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6603
6604 let show_new_completions_on_confirm = completion
6605 .confirm
6606 .as_ref()
6607 .is_some_and(|confirm| confirm(intent, window, cx));
6608 if show_new_completions_on_confirm {
6609 self.open_or_update_completions_menu(None, None, false, window, cx);
6610 }
6611
6612 let provider = self.completion_provider.as_ref()?;
6613
6614 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6615 let command = lsp_store.as_ref().and_then(|lsp_store| {
6616 let CompletionSource::Lsp {
6617 lsp_completion,
6618 server_id,
6619 ..
6620 } = &completion.source
6621 else {
6622 return None;
6623 };
6624 let lsp_command = lsp_completion.command.as_ref()?;
6625 let available_commands = lsp_store
6626 .read(cx)
6627 .lsp_server_capabilities
6628 .get(server_id)
6629 .and_then(|server_capabilities| {
6630 server_capabilities
6631 .execute_command_provider
6632 .as_ref()
6633 .map(|options| options.commands.as_slice())
6634 })?;
6635 if available_commands.contains(&lsp_command.command) {
6636 Some(CodeAction {
6637 server_id: *server_id,
6638 range: language::Anchor::MIN..language::Anchor::MIN,
6639 lsp_action: LspAction::Command(lsp_command.clone()),
6640 resolved: false,
6641 })
6642 } else {
6643 None
6644 }
6645 });
6646
6647 drop(completion);
6648 let apply_edits = provider.apply_additional_edits_for_completion(
6649 buffer_handle.clone(),
6650 completions_menu.completions.clone(),
6651 candidate_id,
6652 true,
6653 cx,
6654 );
6655
6656 let editor_settings = EditorSettings::get_global(cx);
6657 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6658 // After the code completion is finished, users often want to know what signatures are needed.
6659 // so we should automatically call signature_help
6660 self.show_signature_help(&ShowSignatureHelp, window, cx);
6661 }
6662
6663 Some(cx.spawn_in(window, async move |editor, cx| {
6664 apply_edits.await?;
6665
6666 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6667 let title = command.lsp_action.title().to_owned();
6668 let project_transaction = lsp_store
6669 .update(cx, |lsp_store, cx| {
6670 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6671 })
6672 .await
6673 .context("applying post-completion command")?;
6674 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6675 Self::open_project_transaction(
6676 &editor,
6677 workspace.downgrade(),
6678 project_transaction,
6679 title,
6680 cx,
6681 )
6682 .await?;
6683 }
6684 }
6685
6686 Ok(())
6687 }))
6688 }
6689
6690 pub fn toggle_code_actions(
6691 &mut self,
6692 action: &ToggleCodeActions,
6693 window: &mut Window,
6694 cx: &mut Context<Self>,
6695 ) {
6696 let quick_launch = action.quick_launch;
6697 let mut context_menu = self.context_menu.borrow_mut();
6698 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6699 if code_actions.deployed_from == action.deployed_from {
6700 // Toggle if we're selecting the same one
6701 *context_menu = None;
6702 cx.notify();
6703 return;
6704 } else {
6705 // Otherwise, clear it and start a new one
6706 *context_menu = None;
6707 cx.notify();
6708 }
6709 }
6710 drop(context_menu);
6711 let snapshot = self.snapshot(window, cx);
6712 let deployed_from = action.deployed_from.clone();
6713 let action = action.clone();
6714 self.completion_tasks.clear();
6715 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6716
6717 let multibuffer_point = match &action.deployed_from {
6718 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6719 DisplayPoint::new(*row, 0).to_point(&snapshot)
6720 }
6721 _ => self
6722 .selections
6723 .newest::<Point>(&snapshot.display_snapshot)
6724 .head(),
6725 };
6726 let Some((buffer, buffer_row)) = snapshot
6727 .buffer_snapshot()
6728 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6729 .and_then(|(buffer_snapshot, range)| {
6730 self.buffer()
6731 .read(cx)
6732 .buffer(buffer_snapshot.remote_id())
6733 .map(|buffer| (buffer, range.start.row))
6734 })
6735 else {
6736 return;
6737 };
6738 let buffer_id = buffer.read(cx).remote_id();
6739 let tasks = self
6740 .tasks
6741 .get(&(buffer_id, buffer_row))
6742 .map(|t| Arc::new(t.to_owned()));
6743
6744 if !self.focus_handle.is_focused(window) {
6745 return;
6746 }
6747 let project = self.project.clone();
6748
6749 let code_actions_task = match deployed_from {
6750 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6751 _ => self.code_actions(buffer_row, window, cx),
6752 };
6753
6754 let runnable_task = match deployed_from {
6755 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6756 _ => {
6757 let mut task_context_task = Task::ready(None);
6758 if let Some(tasks) = &tasks
6759 && let Some(project) = project
6760 {
6761 task_context_task =
6762 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6763 }
6764
6765 cx.spawn_in(window, {
6766 let buffer = buffer.clone();
6767 async move |editor, cx| {
6768 let task_context = task_context_task.await;
6769
6770 let resolved_tasks =
6771 tasks
6772 .zip(task_context.clone())
6773 .map(|(tasks, task_context)| ResolvedTasks {
6774 templates: tasks.resolve(&task_context).collect(),
6775 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6776 multibuffer_point.row,
6777 tasks.column,
6778 )),
6779 });
6780 let debug_scenarios = editor
6781 .update(cx, |editor, cx| {
6782 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6783 })?
6784 .await;
6785 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6786 }
6787 })
6788 }
6789 };
6790
6791 cx.spawn_in(window, async move |editor, cx| {
6792 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6793 let code_actions = code_actions_task.await;
6794 let spawn_straight_away = quick_launch
6795 && resolved_tasks
6796 .as_ref()
6797 .is_some_and(|tasks| tasks.templates.len() == 1)
6798 && code_actions
6799 .as_ref()
6800 .is_none_or(|actions| actions.is_empty())
6801 && debug_scenarios.is_empty();
6802
6803 editor.update_in(cx, |editor, window, cx| {
6804 crate::hover_popover::hide_hover(editor, cx);
6805 let actions = CodeActionContents::new(
6806 resolved_tasks,
6807 code_actions,
6808 debug_scenarios,
6809 task_context.unwrap_or_default(),
6810 );
6811
6812 // Don't show the menu if there are no actions available
6813 if actions.is_empty() {
6814 cx.notify();
6815 return Task::ready(Ok(()));
6816 }
6817
6818 *editor.context_menu.borrow_mut() =
6819 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6820 buffer,
6821 actions,
6822 selected_item: Default::default(),
6823 scroll_handle: UniformListScrollHandle::default(),
6824 deployed_from,
6825 }));
6826 cx.notify();
6827 if spawn_straight_away
6828 && let Some(task) = editor.confirm_code_action(
6829 &ConfirmCodeAction { item_ix: Some(0) },
6830 window,
6831 cx,
6832 )
6833 {
6834 return task;
6835 }
6836
6837 Task::ready(Ok(()))
6838 })
6839 })
6840 .detach_and_log_err(cx);
6841 }
6842
6843 fn debug_scenarios(
6844 &mut self,
6845 resolved_tasks: &Option<ResolvedTasks>,
6846 buffer: &Entity<Buffer>,
6847 cx: &mut App,
6848 ) -> Task<Vec<task::DebugScenario>> {
6849 maybe!({
6850 let project = self.project()?;
6851 let dap_store = project.read(cx).dap_store();
6852 let mut scenarios = vec![];
6853 let resolved_tasks = resolved_tasks.as_ref()?;
6854 let buffer = buffer.read(cx);
6855 let language = buffer.language()?;
6856 let file = buffer.file();
6857 let debug_adapter = language_settings(language.name().into(), file, cx)
6858 .debuggers
6859 .first()
6860 .map(SharedString::from)
6861 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6862
6863 dap_store.update(cx, |dap_store, cx| {
6864 for (_, task) in &resolved_tasks.templates {
6865 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6866 task.original_task().clone(),
6867 debug_adapter.clone().into(),
6868 task.display_label().to_owned().into(),
6869 cx,
6870 );
6871 scenarios.push(maybe_scenario);
6872 }
6873 });
6874 Some(cx.background_spawn(async move {
6875 futures::future::join_all(scenarios)
6876 .await
6877 .into_iter()
6878 .flatten()
6879 .collect::<Vec<_>>()
6880 }))
6881 })
6882 .unwrap_or_else(|| Task::ready(vec![]))
6883 }
6884
6885 fn code_actions(
6886 &mut self,
6887 buffer_row: u32,
6888 window: &mut Window,
6889 cx: &mut Context<Self>,
6890 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6891 let mut task = self.code_actions_task.take();
6892 cx.spawn_in(window, async move |editor, cx| {
6893 while let Some(prev_task) = task {
6894 prev_task.await.log_err();
6895 task = editor
6896 .update(cx, |this, _| this.code_actions_task.take())
6897 .ok()?;
6898 }
6899
6900 editor
6901 .update(cx, |editor, cx| {
6902 editor
6903 .available_code_actions
6904 .clone()
6905 .and_then(|(location, code_actions)| {
6906 let snapshot = location.buffer.read(cx).snapshot();
6907 let point_range = location.range.to_point(&snapshot);
6908 let point_range = point_range.start.row..=point_range.end.row;
6909 if point_range.contains(&buffer_row) {
6910 Some(code_actions)
6911 } else {
6912 None
6913 }
6914 })
6915 })
6916 .ok()
6917 .flatten()
6918 })
6919 }
6920
6921 pub fn confirm_code_action(
6922 &mut self,
6923 action: &ConfirmCodeAction,
6924 window: &mut Window,
6925 cx: &mut Context<Self>,
6926 ) -> Option<Task<Result<()>>> {
6927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6928
6929 let actions_menu =
6930 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6931 menu
6932 } else {
6933 return None;
6934 };
6935
6936 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6937 let action = actions_menu.actions.get(action_ix)?;
6938 let title = action.label();
6939 let buffer = actions_menu.buffer;
6940 let workspace = self.workspace()?;
6941
6942 match action {
6943 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6944 workspace.update(cx, |workspace, cx| {
6945 workspace.schedule_resolved_task(
6946 task_source_kind,
6947 resolved_task,
6948 false,
6949 window,
6950 cx,
6951 );
6952
6953 Some(Task::ready(Ok(())))
6954 })
6955 }
6956 CodeActionsItem::CodeAction {
6957 excerpt_id,
6958 action,
6959 provider,
6960 } => {
6961 let apply_code_action =
6962 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6963 let workspace = workspace.downgrade();
6964 Some(cx.spawn_in(window, async move |editor, cx| {
6965 let project_transaction = apply_code_action.await?;
6966 Self::open_project_transaction(
6967 &editor,
6968 workspace,
6969 project_transaction,
6970 title,
6971 cx,
6972 )
6973 .await
6974 }))
6975 }
6976 CodeActionsItem::DebugScenario(scenario) => {
6977 let context = actions_menu.actions.context.into();
6978
6979 workspace.update(cx, |workspace, cx| {
6980 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6981 workspace.start_debug_session(
6982 scenario,
6983 context,
6984 Some(buffer),
6985 None,
6986 window,
6987 cx,
6988 );
6989 });
6990 Some(Task::ready(Ok(())))
6991 }
6992 }
6993 }
6994
6995 fn open_transaction_for_hidden_buffers(
6996 workspace: Entity<Workspace>,
6997 transaction: ProjectTransaction,
6998 title: String,
6999 window: &mut Window,
7000 cx: &mut Context<Self>,
7001 ) {
7002 if transaction.0.is_empty() {
7003 return;
7004 }
7005
7006 let edited_buffers_already_open = {
7007 let other_editors: Vec<Entity<Editor>> = workspace
7008 .read(cx)
7009 .panes()
7010 .iter()
7011 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7012 .filter(|editor| editor.entity_id() != cx.entity_id())
7013 .collect();
7014
7015 transaction.0.keys().all(|buffer| {
7016 other_editors.iter().any(|editor| {
7017 let multi_buffer = editor.read(cx).buffer();
7018 multi_buffer.read(cx).is_singleton()
7019 && multi_buffer
7020 .read(cx)
7021 .as_singleton()
7022 .map_or(false, |singleton| {
7023 singleton.entity_id() == buffer.entity_id()
7024 })
7025 })
7026 })
7027 };
7028 if !edited_buffers_already_open {
7029 let workspace = workspace.downgrade();
7030 cx.defer_in(window, move |_, window, cx| {
7031 cx.spawn_in(window, async move |editor, cx| {
7032 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7033 .await
7034 .ok()
7035 })
7036 .detach();
7037 });
7038 }
7039 }
7040
7041 pub async fn open_project_transaction(
7042 editor: &WeakEntity<Editor>,
7043 workspace: WeakEntity<Workspace>,
7044 transaction: ProjectTransaction,
7045 title: String,
7046 cx: &mut AsyncWindowContext,
7047 ) -> Result<()> {
7048 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7049 cx.update(|_, cx| {
7050 entries.sort_unstable_by_key(|(buffer, _)| {
7051 buffer.read(cx).file().map(|f| f.path().clone())
7052 });
7053 })?;
7054 if entries.is_empty() {
7055 return Ok(());
7056 }
7057
7058 // If the project transaction's edits are all contained within this editor, then
7059 // avoid opening a new editor to display them.
7060
7061 if let [(buffer, transaction)] = &*entries {
7062 let excerpt = editor.update(cx, |editor, cx| {
7063 editor
7064 .buffer()
7065 .read(cx)
7066 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7067 })?;
7068 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7069 && excerpted_buffer == *buffer
7070 {
7071 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7072 let excerpt_range = excerpt_range.to_offset(buffer);
7073 buffer
7074 .edited_ranges_for_transaction::<usize>(transaction)
7075 .all(|range| {
7076 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7077 })
7078 });
7079
7080 if all_edits_within_excerpt {
7081 return Ok(());
7082 }
7083 }
7084 }
7085
7086 let mut ranges_to_highlight = Vec::new();
7087 let excerpt_buffer = cx.new(|cx| {
7088 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7089 for (buffer_handle, transaction) in &entries {
7090 let edited_ranges = buffer_handle
7091 .read(cx)
7092 .edited_ranges_for_transaction::<Point>(transaction)
7093 .collect::<Vec<_>>();
7094 let (ranges, _) = multibuffer.set_excerpts_for_path(
7095 PathKey::for_buffer(buffer_handle, cx),
7096 buffer_handle.clone(),
7097 edited_ranges,
7098 multibuffer_context_lines(cx),
7099 cx,
7100 );
7101
7102 ranges_to_highlight.extend(ranges);
7103 }
7104 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7105 multibuffer
7106 });
7107
7108 workspace.update_in(cx, |workspace, window, cx| {
7109 let project = workspace.project().clone();
7110 let editor =
7111 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7112 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7113 editor.update(cx, |editor, cx| {
7114 editor.highlight_background(
7115 HighlightKey::Editor,
7116 &ranges_to_highlight,
7117 |_, theme| theme.colors().editor_highlighted_line_background,
7118 cx,
7119 );
7120 });
7121 })?;
7122
7123 Ok(())
7124 }
7125
7126 pub fn clear_code_action_providers(&mut self) {
7127 self.code_action_providers.clear();
7128 self.available_code_actions.take();
7129 }
7130
7131 pub fn add_code_action_provider(
7132 &mut self,
7133 provider: Rc<dyn CodeActionProvider>,
7134 window: &mut Window,
7135 cx: &mut Context<Self>,
7136 ) {
7137 if self
7138 .code_action_providers
7139 .iter()
7140 .any(|existing_provider| existing_provider.id() == provider.id())
7141 {
7142 return;
7143 }
7144
7145 self.code_action_providers.push(provider);
7146 self.refresh_code_actions(window, cx);
7147 }
7148
7149 pub fn remove_code_action_provider(
7150 &mut self,
7151 id: Arc<str>,
7152 window: &mut Window,
7153 cx: &mut Context<Self>,
7154 ) {
7155 self.code_action_providers
7156 .retain(|provider| provider.id() != id);
7157 self.refresh_code_actions(window, cx);
7158 }
7159
7160 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7161 !self.code_action_providers.is_empty()
7162 && EditorSettings::get_global(cx).toolbar.code_actions
7163 }
7164
7165 pub fn has_available_code_actions(&self) -> bool {
7166 self.available_code_actions
7167 .as_ref()
7168 .is_some_and(|(_, actions)| !actions.is_empty())
7169 }
7170
7171 fn render_inline_code_actions(
7172 &self,
7173 icon_size: ui::IconSize,
7174 display_row: DisplayRow,
7175 is_active: bool,
7176 cx: &mut Context<Self>,
7177 ) -> AnyElement {
7178 let show_tooltip = !self.context_menu_visible();
7179 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7180 .icon_size(icon_size)
7181 .shape(ui::IconButtonShape::Square)
7182 .icon_color(ui::Color::Hidden)
7183 .toggle_state(is_active)
7184 .when(show_tooltip, |this| {
7185 this.tooltip({
7186 let focus_handle = self.focus_handle.clone();
7187 move |_window, cx| {
7188 Tooltip::for_action_in(
7189 "Toggle Code Actions",
7190 &ToggleCodeActions {
7191 deployed_from: None,
7192 quick_launch: false,
7193 },
7194 &focus_handle,
7195 cx,
7196 )
7197 }
7198 })
7199 })
7200 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7201 window.focus(&editor.focus_handle(cx), cx);
7202 editor.toggle_code_actions(
7203 &crate::actions::ToggleCodeActions {
7204 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7205 display_row,
7206 )),
7207 quick_launch: false,
7208 },
7209 window,
7210 cx,
7211 );
7212 }))
7213 .into_any_element()
7214 }
7215
7216 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7217 &self.context_menu
7218 }
7219
7220 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7221 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7222 cx.background_executor()
7223 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7224 .await;
7225
7226 let (start_buffer, start, _, end, newest_selection) = this
7227 .update(cx, |this, cx| {
7228 let newest_selection = this.selections.newest_anchor().clone();
7229 if newest_selection.head().diff_base_anchor.is_some() {
7230 return None;
7231 }
7232 let display_snapshot = this.display_snapshot(cx);
7233 let newest_selection_adjusted =
7234 this.selections.newest_adjusted(&display_snapshot);
7235 let buffer = this.buffer.read(cx);
7236
7237 let (start_buffer, start) =
7238 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7239 let (end_buffer, end) =
7240 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7241
7242 Some((start_buffer, start, end_buffer, end, newest_selection))
7243 })?
7244 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7245 .context(
7246 "Expected selection to lie in a single buffer when refreshing code actions",
7247 )?;
7248 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7249 let providers = this.code_action_providers.clone();
7250 let tasks = this
7251 .code_action_providers
7252 .iter()
7253 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7254 .collect::<Vec<_>>();
7255 (providers, tasks)
7256 })?;
7257
7258 let mut actions = Vec::new();
7259 for (provider, provider_actions) in
7260 providers.into_iter().zip(future::join_all(tasks).await)
7261 {
7262 if let Some(provider_actions) = provider_actions.log_err() {
7263 actions.extend(provider_actions.into_iter().map(|action| {
7264 AvailableCodeAction {
7265 excerpt_id: newest_selection.start.excerpt_id,
7266 action,
7267 provider: provider.clone(),
7268 }
7269 }));
7270 }
7271 }
7272
7273 this.update(cx, |this, cx| {
7274 this.available_code_actions = if actions.is_empty() {
7275 None
7276 } else {
7277 Some((
7278 Location {
7279 buffer: start_buffer,
7280 range: start..end,
7281 },
7282 actions.into(),
7283 ))
7284 };
7285 cx.notify();
7286 })
7287 }));
7288 }
7289
7290 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7291 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7292 self.show_git_blame_inline = false;
7293
7294 self.show_git_blame_inline_delay_task =
7295 Some(cx.spawn_in(window, async move |this, cx| {
7296 cx.background_executor().timer(delay).await;
7297
7298 this.update(cx, |this, cx| {
7299 this.show_git_blame_inline = true;
7300 cx.notify();
7301 })
7302 .log_err();
7303 }));
7304 }
7305 }
7306
7307 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7308 let snapshot = self.snapshot(window, cx);
7309 let cursor = self
7310 .selections
7311 .newest::<Point>(&snapshot.display_snapshot)
7312 .head();
7313 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7314 else {
7315 return;
7316 };
7317
7318 if self.blame.is_none() {
7319 self.start_git_blame(true, window, cx);
7320 }
7321 let Some(blame) = self.blame.as_ref() else {
7322 return;
7323 };
7324
7325 let row_info = RowInfo {
7326 buffer_id: Some(buffer.remote_id()),
7327 buffer_row: Some(point.row),
7328 ..Default::default()
7329 };
7330 let Some((buffer, blame_entry)) = blame
7331 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7332 .flatten()
7333 else {
7334 return;
7335 };
7336
7337 let anchor = self.selections.newest_anchor().head();
7338 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7339 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7340 self.show_blame_popover(
7341 buffer,
7342 &blame_entry,
7343 position + last_bounds.origin,
7344 true,
7345 cx,
7346 );
7347 };
7348 }
7349
7350 fn show_blame_popover(
7351 &mut self,
7352 buffer: BufferId,
7353 blame_entry: &BlameEntry,
7354 position: gpui::Point<Pixels>,
7355 ignore_timeout: bool,
7356 cx: &mut Context<Self>,
7357 ) {
7358 if let Some(state) = &mut self.inline_blame_popover {
7359 state.hide_task.take();
7360 } else {
7361 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7362 let blame_entry = blame_entry.clone();
7363 let show_task = cx.spawn(async move |editor, cx| {
7364 if !ignore_timeout {
7365 cx.background_executor()
7366 .timer(std::time::Duration::from_millis(blame_popover_delay))
7367 .await;
7368 }
7369 editor
7370 .update(cx, |editor, cx| {
7371 editor.inline_blame_popover_show_task.take();
7372 let Some(blame) = editor.blame.as_ref() else {
7373 return;
7374 };
7375 let blame = blame.read(cx);
7376 let details = blame.details_for_entry(buffer, &blame_entry);
7377 let markdown = cx.new(|cx| {
7378 Markdown::new(
7379 details
7380 .as_ref()
7381 .map(|message| message.message.clone())
7382 .unwrap_or_default(),
7383 None,
7384 None,
7385 cx,
7386 )
7387 });
7388 editor.inline_blame_popover = Some(InlineBlamePopover {
7389 position,
7390 hide_task: None,
7391 popover_bounds: None,
7392 popover_state: InlineBlamePopoverState {
7393 scroll_handle: ScrollHandle::new(),
7394 commit_message: details,
7395 markdown,
7396 },
7397 keyboard_grace: ignore_timeout,
7398 });
7399 cx.notify();
7400 })
7401 .ok();
7402 });
7403 self.inline_blame_popover_show_task = Some(show_task);
7404 }
7405 }
7406
7407 pub fn has_mouse_context_menu(&self) -> bool {
7408 self.mouse_context_menu.is_some()
7409 }
7410
7411 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7412 self.inline_blame_popover_show_task.take();
7413 if let Some(state) = &mut self.inline_blame_popover {
7414 let hide_task = cx.spawn(async move |editor, cx| {
7415 if !ignore_timeout {
7416 cx.background_executor()
7417 .timer(std::time::Duration::from_millis(100))
7418 .await;
7419 }
7420 editor
7421 .update(cx, |editor, cx| {
7422 editor.inline_blame_popover.take();
7423 cx.notify();
7424 })
7425 .ok();
7426 });
7427 state.hide_task = Some(hide_task);
7428 true
7429 } else {
7430 false
7431 }
7432 }
7433
7434 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7435 if self.pending_rename.is_some() {
7436 return None;
7437 }
7438
7439 let provider = self.semantics_provider.clone()?;
7440 let buffer = self.buffer.read(cx);
7441 let newest_selection = self.selections.newest_anchor().clone();
7442 let cursor_position = newest_selection.head();
7443 let (cursor_buffer, cursor_buffer_position) =
7444 buffer.text_anchor_for_position(cursor_position, cx)?;
7445 let (tail_buffer, tail_buffer_position) =
7446 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7447 if cursor_buffer != tail_buffer {
7448 return None;
7449 }
7450
7451 let snapshot = cursor_buffer.read(cx).snapshot();
7452 let word_ranges = cx.background_spawn(async move {
7453 // this might look odd to put on the background thread, but
7454 // `surrounding_word` can be quite expensive as it calls into
7455 // tree-sitter language scopes
7456 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7457 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7458 (start_word_range, end_word_range)
7459 });
7460
7461 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7462 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7463 let (start_word_range, end_word_range) = word_ranges.await;
7464 if start_word_range != end_word_range {
7465 this.update(cx, |this, cx| {
7466 this.document_highlights_task.take();
7467 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7468 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7469 })
7470 .ok();
7471 return;
7472 }
7473 cx.background_executor()
7474 .timer(Duration::from_millis(debounce))
7475 .await;
7476
7477 let highlights = if let Some(highlights) = cx.update(|cx| {
7478 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7479 }) {
7480 highlights.await.log_err()
7481 } else {
7482 None
7483 };
7484
7485 if let Some(highlights) = highlights {
7486 this.update(cx, |this, cx| {
7487 if this.pending_rename.is_some() {
7488 return;
7489 }
7490
7491 let buffer = this.buffer.read(cx);
7492 if buffer
7493 .text_anchor_for_position(cursor_position, cx)
7494 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7495 {
7496 return;
7497 }
7498
7499 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7500 let mut write_ranges = Vec::new();
7501 let mut read_ranges = Vec::new();
7502 for highlight in highlights {
7503 let buffer_id = cursor_buffer.read(cx).remote_id();
7504 for (excerpt_id, _, excerpt_range) in
7505 buffer.excerpts_for_buffer(buffer_id, cx)
7506 {
7507 let start = highlight
7508 .range
7509 .start
7510 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7511 let end = highlight
7512 .range
7513 .end
7514 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7515 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7516 continue;
7517 }
7518
7519 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7520 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7521 write_ranges.push(range);
7522 } else {
7523 read_ranges.push(range);
7524 }
7525 }
7526 }
7527
7528 this.highlight_background(
7529 HighlightKey::DocumentHighlightRead,
7530 &read_ranges,
7531 |_, theme| theme.colors().editor_document_highlight_read_background,
7532 cx,
7533 );
7534 this.highlight_background(
7535 HighlightKey::DocumentHighlightWrite,
7536 &write_ranges,
7537 |_, theme| theme.colors().editor_document_highlight_write_background,
7538 cx,
7539 );
7540 cx.notify();
7541 })
7542 .log_err();
7543 }
7544 }));
7545 None
7546 }
7547
7548 fn prepare_highlight_query_from_selection(
7549 &mut self,
7550 snapshot: &DisplaySnapshot,
7551 cx: &mut Context<Editor>,
7552 ) -> Option<(String, Range<Anchor>)> {
7553 if matches!(self.mode, EditorMode::SingleLine) {
7554 return None;
7555 }
7556 if !EditorSettings::get_global(cx).selection_highlight {
7557 return None;
7558 }
7559 if self.selections.count() != 1 || self.selections.line_mode() {
7560 return None;
7561 }
7562 let selection = self.selections.newest::<Point>(&snapshot);
7563 // If the selection spans multiple rows OR it is empty
7564 if selection.start.row != selection.end.row
7565 || selection.start.column == selection.end.column
7566 {
7567 return None;
7568 }
7569 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7570 let query = snapshot
7571 .buffer_snapshot()
7572 .text_for_range(selection_anchor_range.clone())
7573 .collect::<String>();
7574 if query.trim().is_empty() {
7575 return None;
7576 }
7577 Some((query, selection_anchor_range))
7578 }
7579
7580 #[ztracing::instrument(skip_all)]
7581 fn update_selection_occurrence_highlights(
7582 &mut self,
7583 multi_buffer_snapshot: MultiBufferSnapshot,
7584 query_text: String,
7585 query_range: Range<Anchor>,
7586 multi_buffer_range_to_query: Range<Point>,
7587 use_debounce: bool,
7588 window: &mut Window,
7589 cx: &mut Context<Editor>,
7590 ) -> Task<()> {
7591 cx.spawn_in(window, async move |editor, cx| {
7592 if use_debounce {
7593 cx.background_executor()
7594 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7595 .await;
7596 }
7597 let match_task = cx.background_spawn(async move {
7598 let buffer_ranges = multi_buffer_snapshot
7599 .range_to_buffer_ranges(
7600 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7601 )
7602 .into_iter()
7603 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7604 let mut match_ranges = Vec::new();
7605 let Ok(regex) = project::search::SearchQuery::text(
7606 query_text,
7607 false,
7608 false,
7609 false,
7610 Default::default(),
7611 Default::default(),
7612 false,
7613 None,
7614 ) else {
7615 return Vec::default();
7616 };
7617 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7618 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7619 match_ranges.extend(
7620 regex
7621 .search(
7622 buffer_snapshot,
7623 Some(search_range.start.0..search_range.end.0),
7624 )
7625 .await
7626 .into_iter()
7627 .filter_map(|match_range| {
7628 let match_start = buffer_snapshot
7629 .anchor_after(search_range.start + match_range.start);
7630 let match_end = buffer_snapshot
7631 .anchor_before(search_range.start + match_range.end);
7632 let match_anchor_range =
7633 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7634 (match_anchor_range != query_range).then_some(match_anchor_range)
7635 }),
7636 );
7637 }
7638 match_ranges
7639 });
7640 let match_ranges = match_task.await;
7641 editor
7642 .update_in(cx, |editor, _, cx| {
7643 if use_debounce {
7644 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7645 editor.debounced_selection_highlight_complete = true;
7646 } else if editor.debounced_selection_highlight_complete {
7647 return;
7648 }
7649 if !match_ranges.is_empty() {
7650 editor.highlight_background(
7651 HighlightKey::SelectedTextHighlight,
7652 &match_ranges,
7653 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7654 cx,
7655 )
7656 }
7657 })
7658 .log_err();
7659 })
7660 }
7661
7662 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7663 struct NewlineFold;
7664 let type_id = std::any::TypeId::of::<NewlineFold>();
7665 if !self.mode.is_single_line() {
7666 return;
7667 }
7668 let snapshot = self.snapshot(window, cx);
7669 if snapshot.buffer_snapshot().max_point().row == 0 {
7670 return;
7671 }
7672 let task = cx.background_spawn(async move {
7673 let new_newlines = snapshot
7674 .buffer_chars_at(MultiBufferOffset(0))
7675 .filter_map(|(c, i)| {
7676 if c == '\n' {
7677 Some(
7678 snapshot.buffer_snapshot().anchor_after(i)
7679 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7680 )
7681 } else {
7682 None
7683 }
7684 })
7685 .collect::<Vec<_>>();
7686 let existing_newlines = snapshot
7687 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7688 .filter_map(|fold| {
7689 if fold.placeholder.type_tag == Some(type_id) {
7690 Some(fold.range.start..fold.range.end)
7691 } else {
7692 None
7693 }
7694 })
7695 .collect::<Vec<_>>();
7696
7697 (new_newlines, existing_newlines)
7698 });
7699 self.folding_newlines = cx.spawn(async move |this, cx| {
7700 let (new_newlines, existing_newlines) = task.await;
7701 if new_newlines == existing_newlines {
7702 return;
7703 }
7704 let placeholder = FoldPlaceholder {
7705 render: Arc::new(move |_, _, cx| {
7706 div()
7707 .bg(cx.theme().status().hint_background)
7708 .border_b_1()
7709 .size_full()
7710 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7711 .border_color(cx.theme().status().hint)
7712 .child("\\n")
7713 .into_any()
7714 }),
7715 constrain_width: false,
7716 merge_adjacent: false,
7717 type_tag: Some(type_id),
7718 collapsed_text: None,
7719 };
7720 let creases = new_newlines
7721 .into_iter()
7722 .map(|range| Crease::simple(range, placeholder.clone()))
7723 .collect();
7724 this.update(cx, |this, cx| {
7725 this.display_map.update(cx, |display_map, cx| {
7726 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7727 display_map.fold(creases, cx);
7728 });
7729 })
7730 .ok();
7731 });
7732 }
7733
7734 #[ztracing::instrument(skip_all)]
7735 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7736 if !self.lsp_data_enabled() {
7737 return;
7738 }
7739 let cursor = self.selections.newest_anchor().head();
7740 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7741
7742 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7743 self.outline_symbols_at_cursor =
7744 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7745 cx.emit(EditorEvent::OutlineSymbolsChanged);
7746 cx.notify();
7747 } else {
7748 let syntax = cx.theme().syntax().clone();
7749 let background_task = cx.background_spawn(async move {
7750 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7751 });
7752 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7753 cx.spawn(async move |this, cx| {
7754 let symbols = background_task.await;
7755 this.update(cx, |this, cx| {
7756 this.outline_symbols_at_cursor = symbols;
7757 cx.emit(EditorEvent::OutlineSymbolsChanged);
7758 cx.notify();
7759 })
7760 .ok();
7761 });
7762 }
7763 }
7764
7765 #[ztracing::instrument(skip_all)]
7766 fn refresh_selected_text_highlights(
7767 &mut self,
7768 snapshot: &DisplaySnapshot,
7769 on_buffer_edit: bool,
7770 window: &mut Window,
7771 cx: &mut Context<Editor>,
7772 ) {
7773 let Some((query_text, query_range)) =
7774 self.prepare_highlight_query_from_selection(snapshot, cx)
7775 else {
7776 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7777 self.quick_selection_highlight_task.take();
7778 self.debounced_selection_highlight_task.take();
7779 self.debounced_selection_highlight_complete = false;
7780 return;
7781 };
7782 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7783 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7784 let query_changed = self
7785 .quick_selection_highlight_task
7786 .as_ref()
7787 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7788 if query_changed {
7789 self.debounced_selection_highlight_complete = false;
7790 }
7791 if on_buffer_edit || query_changed {
7792 let multi_buffer_visible_start = self
7793 .scroll_manager
7794 .native_anchor(&display_snapshot, cx)
7795 .anchor
7796 .to_point(&multi_buffer_snapshot);
7797 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7798 multi_buffer_visible_start
7799 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7800 Bias::Left,
7801 );
7802 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7803 self.quick_selection_highlight_task = Some((
7804 query_range.clone(),
7805 self.update_selection_occurrence_highlights(
7806 snapshot.buffer.clone(),
7807 query_text.clone(),
7808 query_range.clone(),
7809 multi_buffer_visible_range,
7810 false,
7811 window,
7812 cx,
7813 ),
7814 ));
7815 }
7816 if on_buffer_edit
7817 || self
7818 .debounced_selection_highlight_task
7819 .as_ref()
7820 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7821 {
7822 let multi_buffer_start = multi_buffer_snapshot
7823 .anchor_before(MultiBufferOffset(0))
7824 .to_point(&multi_buffer_snapshot);
7825 let multi_buffer_end = multi_buffer_snapshot
7826 .anchor_after(multi_buffer_snapshot.len())
7827 .to_point(&multi_buffer_snapshot);
7828 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7829 self.debounced_selection_highlight_task = Some((
7830 query_range.clone(),
7831 self.update_selection_occurrence_highlights(
7832 snapshot.buffer.clone(),
7833 query_text,
7834 query_range,
7835 multi_buffer_full_range,
7836 true,
7837 window,
7838 cx,
7839 ),
7840 ));
7841 }
7842 }
7843
7844 pub fn refresh_edit_prediction(
7845 &mut self,
7846 debounce: bool,
7847 user_requested: bool,
7848 window: &mut Window,
7849 cx: &mut Context<Self>,
7850 ) -> Option<()> {
7851 let provider = self.edit_prediction_provider()?;
7852 let cursor = self.selections.newest_anchor().head();
7853 let (buffer, cursor_buffer_position) =
7854 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7855
7856 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7857 return None;
7858 }
7859
7860 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7861 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7862 return None;
7863 }
7864
7865 self.update_visible_edit_prediction(window, cx);
7866
7867 if !user_requested
7868 && (!self.should_show_edit_predictions()
7869 || !self.is_focused(window)
7870 || buffer.read(cx).is_empty())
7871 {
7872 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7873 return None;
7874 }
7875
7876 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7877 Some(())
7878 }
7879
7880 fn show_edit_predictions_in_menu(&self) -> bool {
7881 match self.edit_prediction_settings {
7882 EditPredictionSettings::Disabled => false,
7883 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7884 }
7885 }
7886
7887 pub fn edit_predictions_enabled(&self) -> bool {
7888 match self.edit_prediction_settings {
7889 EditPredictionSettings::Disabled => false,
7890 EditPredictionSettings::Enabled { .. } => true,
7891 }
7892 }
7893
7894 fn edit_prediction_requires_modifier(&self) -> bool {
7895 match self.edit_prediction_settings {
7896 EditPredictionSettings::Disabled => false,
7897 EditPredictionSettings::Enabled {
7898 preview_requires_modifier,
7899 ..
7900 } => preview_requires_modifier,
7901 }
7902 }
7903
7904 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7905 if self.edit_prediction_provider.is_none() {
7906 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7907 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7908 return;
7909 }
7910
7911 let selection = self.selections.newest_anchor();
7912 let cursor = selection.head();
7913
7914 if let Some((buffer, cursor_buffer_position)) =
7915 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7916 {
7917 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7918 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7919 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7920 return;
7921 }
7922 self.edit_prediction_settings =
7923 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7924 }
7925 }
7926
7927 fn edit_prediction_settings_at_position(
7928 &self,
7929 buffer: &Entity<Buffer>,
7930 buffer_position: language::Anchor,
7931 cx: &App,
7932 ) -> EditPredictionSettings {
7933 if !self.mode.is_full()
7934 || !self.show_edit_predictions_override.unwrap_or(true)
7935 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7936 {
7937 return EditPredictionSettings::Disabled;
7938 }
7939
7940 let buffer = buffer.read(cx);
7941
7942 let file = buffer.file();
7943
7944 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7945 return EditPredictionSettings::Disabled;
7946 };
7947
7948 let by_provider = matches!(
7949 self.menu_edit_predictions_policy,
7950 MenuEditPredictionsPolicy::ByProvider
7951 );
7952
7953 let show_in_menu = by_provider
7954 && self
7955 .edit_prediction_provider
7956 .as_ref()
7957 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7958
7959 let preview_requires_modifier =
7960 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7961
7962 EditPredictionSettings::Enabled {
7963 show_in_menu,
7964 preview_requires_modifier,
7965 }
7966 }
7967
7968 fn should_show_edit_predictions(&self) -> bool {
7969 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7970 }
7971
7972 pub fn edit_prediction_preview_is_active(&self) -> bool {
7973 matches!(
7974 self.edit_prediction_preview,
7975 EditPredictionPreview::Active { .. }
7976 )
7977 }
7978
7979 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7980 let cursor = self.selections.newest_anchor().head();
7981 if let Some((buffer, cursor_position)) =
7982 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7983 {
7984 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7985 } else {
7986 false
7987 }
7988 }
7989
7990 pub fn supports_minimap(&self, cx: &App) -> bool {
7991 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7992 }
7993
7994 fn edit_predictions_enabled_in_buffer(
7995 &self,
7996 buffer: &Entity<Buffer>,
7997 buffer_position: language::Anchor,
7998 cx: &App,
7999 ) -> bool {
8000 maybe!({
8001 if self.read_only(cx) {
8002 return Some(false);
8003 }
8004 let provider = self.edit_prediction_provider()?;
8005 if !provider.is_enabled(buffer, buffer_position, cx) {
8006 return Some(false);
8007 }
8008 let buffer = buffer.read(cx);
8009 let Some(file) = buffer.file() else {
8010 return Some(true);
8011 };
8012 let settings = all_language_settings(Some(file), cx);
8013 Some(settings.edit_predictions_enabled_for_file(file, cx))
8014 })
8015 .unwrap_or(false)
8016 }
8017
8018 pub fn show_edit_prediction(
8019 &mut self,
8020 _: &ShowEditPrediction,
8021 window: &mut Window,
8022 cx: &mut Context<Self>,
8023 ) {
8024 if !self.has_active_edit_prediction() {
8025 self.refresh_edit_prediction(false, true, window, cx);
8026 return;
8027 }
8028
8029 self.update_visible_edit_prediction(window, cx);
8030 }
8031
8032 pub fn display_cursor_names(
8033 &mut self,
8034 _: &DisplayCursorNames,
8035 window: &mut Window,
8036 cx: &mut Context<Self>,
8037 ) {
8038 self.show_cursor_names(window, cx);
8039 }
8040
8041 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8042 self.show_cursor_names = true;
8043 cx.notify();
8044 cx.spawn_in(window, async move |this, cx| {
8045 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8046 this.update(cx, |this, cx| {
8047 this.show_cursor_names = false;
8048 cx.notify()
8049 })
8050 .ok()
8051 })
8052 .detach();
8053 }
8054
8055 pub fn accept_partial_edit_prediction(
8056 &mut self,
8057 granularity: EditPredictionGranularity,
8058 window: &mut Window,
8059 cx: &mut Context<Self>,
8060 ) {
8061 if self.show_edit_predictions_in_menu() {
8062 self.hide_context_menu(window, cx);
8063 }
8064
8065 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8066 return;
8067 };
8068
8069 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8070 return;
8071 }
8072
8073 match &active_edit_prediction.completion {
8074 EditPrediction::MoveWithin { target, .. } => {
8075 let target = *target;
8076
8077 if matches!(granularity, EditPredictionGranularity::Full) {
8078 if let Some(position_map) = &self.last_position_map {
8079 let target_row = target.to_display_point(&position_map.snapshot).row();
8080 let is_visible = position_map.visible_row_range.contains(&target_row);
8081
8082 if is_visible || !self.edit_prediction_requires_modifier() {
8083 self.unfold_ranges(&[target..target], true, false, cx);
8084 self.change_selections(
8085 SelectionEffects::scroll(Autoscroll::newest()),
8086 window,
8087 cx,
8088 |selections| {
8089 selections.select_anchor_ranges([target..target]);
8090 },
8091 );
8092 self.clear_row_highlights::<EditPredictionPreview>();
8093 self.edit_prediction_preview
8094 .set_previous_scroll_position(None);
8095 } else {
8096 // Highlight and request scroll
8097 self.edit_prediction_preview
8098 .set_previous_scroll_position(Some(
8099 position_map.snapshot.scroll_anchor,
8100 ));
8101 self.highlight_rows::<EditPredictionPreview>(
8102 target..target,
8103 cx.theme().colors().editor_highlighted_line_background,
8104 RowHighlightOptions {
8105 autoscroll: true,
8106 ..Default::default()
8107 },
8108 cx,
8109 );
8110 self.request_autoscroll(Autoscroll::fit(), cx);
8111 }
8112 }
8113 } else {
8114 self.change_selections(
8115 SelectionEffects::scroll(Autoscroll::newest()),
8116 window,
8117 cx,
8118 |selections| {
8119 selections.select_anchor_ranges([target..target]);
8120 },
8121 );
8122 }
8123 }
8124 EditPrediction::MoveOutside { snapshot, target } => {
8125 if let Some(workspace) = self.workspace() {
8126 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8127 .detach_and_log_err(cx);
8128 }
8129 }
8130 EditPrediction::Edit {
8131 edits,
8132 cursor_position,
8133 ..
8134 } => {
8135 self.report_edit_prediction_event(
8136 active_edit_prediction.completion_id.clone(),
8137 true,
8138 cx,
8139 );
8140
8141 match granularity {
8142 EditPredictionGranularity::Full => {
8143 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8144
8145 // Compute fallback cursor position BEFORE applying the edit,
8146 // so the anchor tracks through the edit correctly
8147 let fallback_cursor_target = {
8148 let snapshot = self.buffer.read(cx).snapshot(cx);
8149 edits.last().unwrap().0.end.bias_right(&snapshot)
8150 };
8151
8152 self.buffer.update(cx, |buffer, cx| {
8153 buffer.edit(edits.iter().cloned(), None, cx)
8154 });
8155
8156 if let Some(provider) = self.edit_prediction_provider() {
8157 provider.accept(cx);
8158 }
8159
8160 // Resolve cursor position after the edit is applied
8161 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8162 // The anchor tracks through the edit, then we add the offset
8163 let snapshot = self.buffer.read(cx).snapshot(cx);
8164 let base_offset = anchor.to_offset(&snapshot).0;
8165 let target_offset =
8166 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8167 snapshot.anchor_after(target_offset)
8168 } else {
8169 fallback_cursor_target
8170 };
8171
8172 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8173 s.select_anchor_ranges([cursor_target..cursor_target]);
8174 });
8175
8176 let selections = self.selections.disjoint_anchors_arc();
8177 if let Some(transaction_id_now) =
8178 self.buffer.read(cx).last_transaction_id(cx)
8179 {
8180 if transaction_id_prev != Some(transaction_id_now) {
8181 self.selection_history
8182 .insert_transaction(transaction_id_now, selections);
8183 }
8184 }
8185
8186 self.update_visible_edit_prediction(window, cx);
8187 if self.active_edit_prediction.is_none() {
8188 self.refresh_edit_prediction(true, true, window, cx);
8189 }
8190 cx.notify();
8191 }
8192 _ => {
8193 let snapshot = self.buffer.read(cx).snapshot(cx);
8194 let cursor_offset = self
8195 .selections
8196 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8197 .head();
8198
8199 let insertion = edits.iter().find_map(|(range, text)| {
8200 let range = range.to_offset(&snapshot);
8201 if range.is_empty() && range.start == cursor_offset {
8202 Some(text)
8203 } else {
8204 None
8205 }
8206 });
8207
8208 if let Some(text) = insertion {
8209 let text_to_insert = match granularity {
8210 EditPredictionGranularity::Word => {
8211 let mut partial = text
8212 .chars()
8213 .by_ref()
8214 .take_while(|c| c.is_alphabetic())
8215 .collect::<String>();
8216 if partial.is_empty() {
8217 partial = text
8218 .chars()
8219 .by_ref()
8220 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8221 .collect::<String>();
8222 }
8223 partial
8224 }
8225 EditPredictionGranularity::Line => {
8226 if let Some(line) = text.split_inclusive('\n').next() {
8227 line.to_string()
8228 } else {
8229 text.to_string()
8230 }
8231 }
8232 EditPredictionGranularity::Full => unreachable!(),
8233 };
8234
8235 cx.emit(EditorEvent::InputHandled {
8236 utf16_range_to_replace: None,
8237 text: text_to_insert.clone().into(),
8238 });
8239
8240 self.replace_selections(&text_to_insert, None, window, cx, false);
8241 self.refresh_edit_prediction(true, true, window, cx);
8242 cx.notify();
8243 } else {
8244 self.accept_partial_edit_prediction(
8245 EditPredictionGranularity::Full,
8246 window,
8247 cx,
8248 );
8249 }
8250 }
8251 }
8252 }
8253 }
8254
8255 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8256 }
8257
8258 pub fn accept_next_word_edit_prediction(
8259 &mut self,
8260 _: &AcceptNextWordEditPrediction,
8261 window: &mut Window,
8262 cx: &mut Context<Self>,
8263 ) {
8264 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8265 }
8266
8267 pub fn accept_next_line_edit_prediction(
8268 &mut self,
8269 _: &AcceptNextLineEditPrediction,
8270 window: &mut Window,
8271 cx: &mut Context<Self>,
8272 ) {
8273 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8274 }
8275
8276 pub fn accept_edit_prediction(
8277 &mut self,
8278 _: &AcceptEditPrediction,
8279 window: &mut Window,
8280 cx: &mut Context<Self>,
8281 ) {
8282 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8283 }
8284
8285 fn discard_edit_prediction(
8286 &mut self,
8287 reason: EditPredictionDiscardReason,
8288 cx: &mut Context<Self>,
8289 ) -> bool {
8290 if reason == EditPredictionDiscardReason::Rejected {
8291 let completion_id = self
8292 .active_edit_prediction
8293 .as_ref()
8294 .and_then(|active_completion| active_completion.completion_id.clone());
8295
8296 self.report_edit_prediction_event(completion_id, false, cx);
8297 }
8298
8299 if let Some(provider) = self.edit_prediction_provider() {
8300 provider.discard(reason, cx);
8301 }
8302
8303 self.take_active_edit_prediction(cx)
8304 }
8305
8306 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8307 let Some(provider) = self.edit_prediction_provider() else {
8308 return;
8309 };
8310
8311 let Some((_, buffer, _)) = self
8312 .buffer
8313 .read(cx)
8314 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8315 else {
8316 return;
8317 };
8318
8319 let extension = buffer
8320 .read(cx)
8321 .file()
8322 .and_then(|file| Some(file.path().extension()?.to_string()));
8323
8324 let event_type = match accepted {
8325 true => "Edit Prediction Accepted",
8326 false => "Edit Prediction Discarded",
8327 };
8328 telemetry::event!(
8329 event_type,
8330 provider = provider.name(),
8331 prediction_id = id,
8332 suggestion_accepted = accepted,
8333 file_extension = extension,
8334 );
8335 }
8336
8337 fn open_editor_at_anchor(
8338 snapshot: &language::BufferSnapshot,
8339 target: language::Anchor,
8340 workspace: &Entity<Workspace>,
8341 window: &mut Window,
8342 cx: &mut App,
8343 ) -> Task<Result<()>> {
8344 workspace.update(cx, |workspace, cx| {
8345 let path = snapshot.file().map(|file| file.full_path(cx));
8346 let Some(path) =
8347 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8348 else {
8349 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8350 };
8351 let target = text::ToPoint::to_point(&target, snapshot);
8352 let item = workspace.open_path(path, None, true, window, cx);
8353 window.spawn(cx, async move |cx| {
8354 let Some(editor) = item.await?.downcast::<Editor>() else {
8355 return Ok(());
8356 };
8357 editor
8358 .update_in(cx, |editor, window, cx| {
8359 editor.go_to_singleton_buffer_point(target, window, cx);
8360 })
8361 .ok();
8362 anyhow::Ok(())
8363 })
8364 })
8365 }
8366
8367 pub fn has_active_edit_prediction(&self) -> bool {
8368 self.active_edit_prediction.is_some()
8369 }
8370
8371 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8372 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8373 return false;
8374 };
8375
8376 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8377 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8378 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8379 true
8380 }
8381
8382 /// Returns true when we're displaying the edit prediction popover below the cursor
8383 /// like we are not previewing and the LSP autocomplete menu is visible
8384 /// or we are in `when_holding_modifier` mode.
8385 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8386 if self.edit_prediction_preview_is_active()
8387 || !self.show_edit_predictions_in_menu()
8388 || !self.edit_predictions_enabled()
8389 {
8390 return false;
8391 }
8392
8393 if self.has_visible_completions_menu() {
8394 return true;
8395 }
8396
8397 has_completion && self.edit_prediction_requires_modifier()
8398 }
8399
8400 fn handle_modifiers_changed(
8401 &mut self,
8402 modifiers: Modifiers,
8403 position_map: &PositionMap,
8404 window: &mut Window,
8405 cx: &mut Context<Self>,
8406 ) {
8407 // Ensure that the edit prediction preview is updated, even when not
8408 // enabled, if there's an active edit prediction preview.
8409 if self.show_edit_predictions_in_menu()
8410 || matches!(
8411 self.edit_prediction_preview,
8412 EditPredictionPreview::Active { .. }
8413 )
8414 {
8415 self.update_edit_prediction_preview(&modifiers, window, cx);
8416 }
8417
8418 self.update_selection_mode(&modifiers, position_map, window, cx);
8419
8420 let mouse_position = window.mouse_position();
8421 if !position_map.text_hitbox.is_hovered(window) {
8422 return;
8423 }
8424
8425 self.update_hovered_link(
8426 position_map.point_for_position(mouse_position),
8427 &position_map.snapshot,
8428 modifiers,
8429 window,
8430 cx,
8431 )
8432 }
8433
8434 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8435 match EditorSettings::get_global(cx).multi_cursor_modifier {
8436 MultiCursorModifier::Alt => modifiers.secondary(),
8437 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8438 }
8439 }
8440
8441 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8442 match EditorSettings::get_global(cx).multi_cursor_modifier {
8443 MultiCursorModifier::Alt => modifiers.alt,
8444 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8445 }
8446 }
8447
8448 fn columnar_selection_mode(
8449 modifiers: &Modifiers,
8450 cx: &mut Context<Self>,
8451 ) -> Option<ColumnarMode> {
8452 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8453 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8454 Some(ColumnarMode::FromMouse)
8455 } else if Self::is_alt_pressed(modifiers, cx) {
8456 Some(ColumnarMode::FromSelection)
8457 } else {
8458 None
8459 }
8460 } else {
8461 None
8462 }
8463 }
8464
8465 fn update_selection_mode(
8466 &mut self,
8467 modifiers: &Modifiers,
8468 position_map: &PositionMap,
8469 window: &mut Window,
8470 cx: &mut Context<Self>,
8471 ) {
8472 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8473 return;
8474 };
8475 if self.selections.pending_anchor().is_none() {
8476 return;
8477 }
8478
8479 let mouse_position = window.mouse_position();
8480 let point_for_position = position_map.point_for_position(mouse_position);
8481 let position = point_for_position.previous_valid;
8482
8483 self.select(
8484 SelectPhase::BeginColumnar {
8485 position,
8486 reset: false,
8487 mode,
8488 goal_column: point_for_position.exact_unclipped.column(),
8489 },
8490 window,
8491 cx,
8492 );
8493 }
8494
8495 fn update_edit_prediction_preview(
8496 &mut self,
8497 modifiers: &Modifiers,
8498 window: &mut Window,
8499 cx: &mut Context<Self>,
8500 ) {
8501 let mut modifiers_held = false;
8502
8503 // Check bindings for all granularities.
8504 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8505 let granularities = [
8506 EditPredictionGranularity::Full,
8507 EditPredictionGranularity::Line,
8508 EditPredictionGranularity::Word,
8509 ];
8510
8511 for granularity in granularities {
8512 if let Some(keystroke) = self
8513 .accept_edit_prediction_keybind(granularity, window, cx)
8514 .keystroke()
8515 {
8516 modifiers_held = modifiers_held
8517 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8518 }
8519 }
8520
8521 if modifiers_held {
8522 if matches!(
8523 self.edit_prediction_preview,
8524 EditPredictionPreview::Inactive { .. }
8525 ) {
8526 self.edit_prediction_preview = EditPredictionPreview::Active {
8527 previous_scroll_position: None,
8528 since: Instant::now(),
8529 };
8530
8531 self.update_visible_edit_prediction(window, cx);
8532 cx.notify();
8533 }
8534 } else if let EditPredictionPreview::Active {
8535 previous_scroll_position,
8536 since,
8537 } = self.edit_prediction_preview
8538 {
8539 if let (Some(previous_scroll_position), Some(position_map)) =
8540 (previous_scroll_position, self.last_position_map.as_ref())
8541 {
8542 self.set_scroll_position(
8543 previous_scroll_position
8544 .scroll_position(&position_map.snapshot.display_snapshot),
8545 window,
8546 cx,
8547 );
8548 }
8549
8550 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8551 released_too_fast: since.elapsed() < Duration::from_millis(200),
8552 };
8553 self.clear_row_highlights::<EditPredictionPreview>();
8554 self.update_visible_edit_prediction(window, cx);
8555 cx.notify();
8556 }
8557 }
8558
8559 fn update_visible_edit_prediction(
8560 &mut self,
8561 _window: &mut Window,
8562 cx: &mut Context<Self>,
8563 ) -> Option<()> {
8564 if self.ime_transaction.is_some() {
8565 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8566 return None;
8567 }
8568
8569 let selection = self.selections.newest_anchor();
8570 let cursor = selection.head();
8571 let multibuffer = self.buffer.read(cx).snapshot(cx);
8572
8573 // Check project-level disable_ai setting for the current buffer
8574 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8575 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8576 return None;
8577 }
8578 }
8579 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8580 let excerpt_id = cursor.excerpt_id;
8581
8582 let show_in_menu = self.show_edit_predictions_in_menu();
8583 let completions_menu_has_precedence = !show_in_menu
8584 && (self.context_menu.borrow().is_some()
8585 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8586
8587 if completions_menu_has_precedence
8588 || !offset_selection.is_empty()
8589 || self
8590 .active_edit_prediction
8591 .as_ref()
8592 .is_some_and(|completion| {
8593 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8594 return false;
8595 };
8596 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8597 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8598 !invalidation_range.contains(&offset_selection.head())
8599 })
8600 {
8601 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8602 return None;
8603 }
8604
8605 self.take_active_edit_prediction(cx);
8606 let Some(provider) = self.edit_prediction_provider() else {
8607 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8608 return None;
8609 };
8610
8611 let (buffer, cursor_buffer_position) =
8612 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8613
8614 self.edit_prediction_settings =
8615 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8616
8617 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8618
8619 if self.edit_prediction_indent_conflict {
8620 let cursor_point = cursor.to_point(&multibuffer);
8621 let mut suggested_indent = None;
8622 multibuffer.suggested_indents_callback(
8623 cursor_point.row..cursor_point.row + 1,
8624 &mut |_, indent| {
8625 suggested_indent = Some(indent);
8626 ControlFlow::Break(())
8627 },
8628 cx,
8629 );
8630
8631 if let Some(indent) = suggested_indent
8632 && indent.len == cursor_point.column
8633 {
8634 self.edit_prediction_indent_conflict = false;
8635 }
8636 }
8637
8638 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8639
8640 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8641 {
8642 edit_prediction_types::EditPrediction::Local {
8643 id,
8644 edits,
8645 cursor_position,
8646 edit_preview,
8647 } => (id, edits, cursor_position, edit_preview),
8648 edit_prediction_types::EditPrediction::Jump {
8649 id,
8650 snapshot,
8651 target,
8652 } => {
8653 if let Some(provider) = &self.edit_prediction_provider {
8654 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8655 }
8656 self.stale_edit_prediction_in_menu = None;
8657 self.active_edit_prediction = Some(EditPredictionState {
8658 inlay_ids: vec![],
8659 completion: EditPrediction::MoveOutside { snapshot, target },
8660 completion_id: id,
8661 invalidation_range: None,
8662 });
8663 cx.notify();
8664 return Some(());
8665 }
8666 };
8667
8668 let edits = edits
8669 .into_iter()
8670 .flat_map(|(range, new_text)| {
8671 Some((
8672 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8673 new_text,
8674 ))
8675 })
8676 .collect::<Vec<_>>();
8677 if edits.is_empty() {
8678 return None;
8679 }
8680
8681 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8682 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8683 Some((anchor, predicted.offset))
8684 });
8685
8686 let first_edit_start = edits.first().unwrap().0.start;
8687 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8688 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8689
8690 let last_edit_end = edits.last().unwrap().0.end;
8691 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8692 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8693
8694 let cursor_row = cursor.to_point(&multibuffer).row;
8695
8696 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8697
8698 let mut inlay_ids = Vec::new();
8699 let invalidation_row_range;
8700 let move_invalidation_row_range = if cursor_row < edit_start_row {
8701 Some(cursor_row..edit_end_row)
8702 } else if cursor_row > edit_end_row {
8703 Some(edit_start_row..cursor_row)
8704 } else {
8705 None
8706 };
8707 let supports_jump = self
8708 .edit_prediction_provider
8709 .as_ref()
8710 .map(|provider| provider.provider.supports_jump_to_edit())
8711 .unwrap_or(true);
8712
8713 let is_move = supports_jump
8714 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8715 let completion = if is_move {
8716 if let Some(provider) = &self.edit_prediction_provider {
8717 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8718 }
8719 invalidation_row_range =
8720 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8721 let target = first_edit_start;
8722 EditPrediction::MoveWithin { target, snapshot }
8723 } else {
8724 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8725 && !self.edit_predictions_hidden_for_vim_mode;
8726
8727 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8728 if provider.show_tab_accept_marker() {
8729 EditDisplayMode::TabAccept
8730 } else {
8731 EditDisplayMode::Inline
8732 }
8733 } else {
8734 EditDisplayMode::DiffPopover
8735 };
8736
8737 if show_completions_in_buffer {
8738 if let Some(provider) = &self.edit_prediction_provider {
8739 let suggestion_display_type = match display_mode {
8740 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8741 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8742 SuggestionDisplayType::GhostText
8743 }
8744 };
8745 provider.provider.did_show(suggestion_display_type, cx);
8746 }
8747 if edits
8748 .iter()
8749 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8750 {
8751 let mut inlays = Vec::new();
8752 for (range, new_text) in &edits {
8753 let inlay = Inlay::edit_prediction(
8754 post_inc(&mut self.next_inlay_id),
8755 range.start,
8756 new_text.as_ref(),
8757 );
8758 inlay_ids.push(inlay.id);
8759 inlays.push(inlay);
8760 }
8761
8762 self.splice_inlays(&[], inlays, cx);
8763 } else {
8764 let background_color = cx.theme().status().deleted_background;
8765 self.highlight_text(
8766 HighlightKey::EditPredictionHighlight,
8767 edits.iter().map(|(range, _)| range.clone()).collect(),
8768 HighlightStyle {
8769 background_color: Some(background_color),
8770 ..Default::default()
8771 },
8772 cx,
8773 );
8774 }
8775 }
8776
8777 invalidation_row_range = edit_start_row..edit_end_row;
8778
8779 EditPrediction::Edit {
8780 edits,
8781 cursor_position,
8782 edit_preview,
8783 display_mode,
8784 snapshot,
8785 }
8786 };
8787
8788 let invalidation_range = multibuffer
8789 .anchor_before(Point::new(invalidation_row_range.start, 0))
8790 ..multibuffer.anchor_after(Point::new(
8791 invalidation_row_range.end,
8792 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8793 ));
8794
8795 self.stale_edit_prediction_in_menu = None;
8796 self.active_edit_prediction = Some(EditPredictionState {
8797 inlay_ids,
8798 completion,
8799 completion_id,
8800 invalidation_range: Some(invalidation_range),
8801 });
8802
8803 cx.notify();
8804
8805 Some(())
8806 }
8807
8808 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8809 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8810 }
8811
8812 fn clear_tasks(&mut self) {
8813 self.tasks.clear()
8814 }
8815
8816 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8817 if self.tasks.insert(key, value).is_some() {
8818 // This case should hopefully be rare, but just in case...
8819 log::error!(
8820 "multiple different run targets found on a single line, only the last target will be rendered"
8821 )
8822 }
8823 }
8824
8825 /// Get all display points of breakpoints that will be rendered within editor
8826 ///
8827 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8828 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8829 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8830 fn active_breakpoints(
8831 &self,
8832 range: Range<DisplayRow>,
8833 window: &mut Window,
8834 cx: &mut Context<Self>,
8835 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8836 let mut breakpoint_display_points = HashMap::default();
8837
8838 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8839 return breakpoint_display_points;
8840 };
8841
8842 let snapshot = self.snapshot(window, cx);
8843
8844 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8845 let Some(project) = self.project() else {
8846 return breakpoint_display_points;
8847 };
8848
8849 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8850 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8851
8852 for (buffer_snapshot, range, excerpt_id) in
8853 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8854 {
8855 let Some(buffer) = project
8856 .read(cx)
8857 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8858 else {
8859 continue;
8860 };
8861 let breakpoints = breakpoint_store.read(cx).breakpoints(
8862 &buffer,
8863 Some(
8864 buffer_snapshot.anchor_before(range.start)
8865 ..buffer_snapshot.anchor_after(range.end),
8866 ),
8867 buffer_snapshot,
8868 cx,
8869 );
8870 for (breakpoint, state) in breakpoints {
8871 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8872 let position = multi_buffer_anchor
8873 .to_point(&multi_buffer_snapshot)
8874 .to_display_point(&snapshot);
8875
8876 breakpoint_display_points.insert(
8877 position.row(),
8878 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8879 );
8880 }
8881 }
8882
8883 breakpoint_display_points
8884 }
8885
8886 fn breakpoint_context_menu(
8887 &self,
8888 anchor: Anchor,
8889 window: &mut Window,
8890 cx: &mut Context<Self>,
8891 ) -> Entity<ui::ContextMenu> {
8892 let weak_editor = cx.weak_entity();
8893 let focus_handle = self.focus_handle(cx);
8894
8895 let row = self
8896 .buffer
8897 .read(cx)
8898 .snapshot(cx)
8899 .summary_for_anchor::<Point>(&anchor)
8900 .row;
8901
8902 let breakpoint = self
8903 .breakpoint_at_row(row, window, cx)
8904 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8905
8906 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8907 "Edit Log Breakpoint"
8908 } else {
8909 "Set Log Breakpoint"
8910 };
8911
8912 let condition_breakpoint_msg = if breakpoint
8913 .as_ref()
8914 .is_some_and(|bp| bp.1.condition.is_some())
8915 {
8916 "Edit Condition Breakpoint"
8917 } else {
8918 "Set Condition Breakpoint"
8919 };
8920
8921 let hit_condition_breakpoint_msg = if breakpoint
8922 .as_ref()
8923 .is_some_and(|bp| bp.1.hit_condition.is_some())
8924 {
8925 "Edit Hit Condition Breakpoint"
8926 } else {
8927 "Set Hit Condition Breakpoint"
8928 };
8929
8930 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8931 "Unset Breakpoint"
8932 } else {
8933 "Set Breakpoint"
8934 };
8935
8936 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8937
8938 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8939 BreakpointState::Enabled => Some("Disable"),
8940 BreakpointState::Disabled => Some("Enable"),
8941 });
8942
8943 let (anchor, breakpoint) =
8944 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8945
8946 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8947 menu.on_blur_subscription(Subscription::new(|| {}))
8948 .context(focus_handle)
8949 .when(run_to_cursor, |this| {
8950 let weak_editor = weak_editor.clone();
8951 this.entry("Run to Cursor", None, move |window, cx| {
8952 weak_editor
8953 .update(cx, |editor, cx| {
8954 editor.change_selections(
8955 SelectionEffects::no_scroll(),
8956 window,
8957 cx,
8958 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8959 );
8960 })
8961 .ok();
8962
8963 window.dispatch_action(Box::new(RunToCursor), cx);
8964 })
8965 .separator()
8966 })
8967 .when_some(toggle_state_msg, |this, msg| {
8968 this.entry(msg, None, {
8969 let weak_editor = weak_editor.clone();
8970 let breakpoint = breakpoint.clone();
8971 move |_window, cx| {
8972 weak_editor
8973 .update(cx, |this, cx| {
8974 this.edit_breakpoint_at_anchor(
8975 anchor,
8976 breakpoint.as_ref().clone(),
8977 BreakpointEditAction::InvertState,
8978 cx,
8979 );
8980 })
8981 .log_err();
8982 }
8983 })
8984 })
8985 .entry(set_breakpoint_msg, None, {
8986 let weak_editor = weak_editor.clone();
8987 let breakpoint = breakpoint.clone();
8988 move |_window, cx| {
8989 weak_editor
8990 .update(cx, |this, cx| {
8991 this.edit_breakpoint_at_anchor(
8992 anchor,
8993 breakpoint.as_ref().clone(),
8994 BreakpointEditAction::Toggle,
8995 cx,
8996 );
8997 })
8998 .log_err();
8999 }
9000 })
9001 .entry(log_breakpoint_msg, None, {
9002 let breakpoint = breakpoint.clone();
9003 let weak_editor = weak_editor.clone();
9004 move |window, cx| {
9005 weak_editor
9006 .update(cx, |this, cx| {
9007 this.add_edit_breakpoint_block(
9008 anchor,
9009 breakpoint.as_ref(),
9010 BreakpointPromptEditAction::Log,
9011 window,
9012 cx,
9013 );
9014 })
9015 .log_err();
9016 }
9017 })
9018 .entry(condition_breakpoint_msg, None, {
9019 let breakpoint = breakpoint.clone();
9020 let weak_editor = weak_editor.clone();
9021 move |window, cx| {
9022 weak_editor
9023 .update(cx, |this, cx| {
9024 this.add_edit_breakpoint_block(
9025 anchor,
9026 breakpoint.as_ref(),
9027 BreakpointPromptEditAction::Condition,
9028 window,
9029 cx,
9030 );
9031 })
9032 .log_err();
9033 }
9034 })
9035 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9036 weak_editor
9037 .update(cx, |this, cx| {
9038 this.add_edit_breakpoint_block(
9039 anchor,
9040 breakpoint.as_ref(),
9041 BreakpointPromptEditAction::HitCondition,
9042 window,
9043 cx,
9044 );
9045 })
9046 .log_err();
9047 })
9048 })
9049 }
9050
9051 fn render_breakpoint(
9052 &self,
9053 position: Anchor,
9054 row: DisplayRow,
9055 breakpoint: &Breakpoint,
9056 state: Option<BreakpointSessionState>,
9057 cx: &mut Context<Self>,
9058 ) -> IconButton {
9059 let is_rejected = state.is_some_and(|s| !s.verified);
9060 // Is it a breakpoint that shows up when hovering over gutter?
9061 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9062 (false, false),
9063 |PhantomBreakpointIndicator {
9064 is_active,
9065 display_row,
9066 collides_with_existing_breakpoint,
9067 }| {
9068 (
9069 is_active && display_row == row,
9070 collides_with_existing_breakpoint,
9071 )
9072 },
9073 );
9074
9075 let (color, icon) = {
9076 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9077 (false, false) => ui::IconName::DebugBreakpoint,
9078 (true, false) => ui::IconName::DebugLogBreakpoint,
9079 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9080 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9081 };
9082
9083 let theme_colors = cx.theme().colors();
9084
9085 let color = if is_phantom {
9086 if collides_with_existing {
9087 Color::Custom(
9088 theme_colors
9089 .debugger_accent
9090 .blend(theme_colors.text.opacity(0.6)),
9091 )
9092 } else {
9093 Color::Hint
9094 }
9095 } else if is_rejected {
9096 Color::Disabled
9097 } else {
9098 Color::Debugger
9099 };
9100
9101 (color, icon)
9102 };
9103
9104 let breakpoint = Arc::from(breakpoint.clone());
9105
9106 let alt_as_text = gpui::Keystroke {
9107 modifiers: Modifiers::secondary_key(),
9108 ..Default::default()
9109 };
9110 let primary_action_text = if breakpoint.is_disabled() {
9111 "Enable breakpoint"
9112 } else if is_phantom && !collides_with_existing {
9113 "Set breakpoint"
9114 } else {
9115 "Unset breakpoint"
9116 };
9117 let focus_handle = self.focus_handle.clone();
9118
9119 let meta = if is_rejected {
9120 SharedString::from("No executable code is associated with this line.")
9121 } else if collides_with_existing && !breakpoint.is_disabled() {
9122 SharedString::from(format!(
9123 "{alt_as_text}-click to disable,\nright-click for more options."
9124 ))
9125 } else {
9126 SharedString::from("Right-click for more options.")
9127 };
9128 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9129 .icon_size(IconSize::XSmall)
9130 .size(ui::ButtonSize::None)
9131 .when(is_rejected, |this| {
9132 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9133 })
9134 .icon_color(color)
9135 .style(ButtonStyle::Transparent)
9136 .on_click(cx.listener({
9137 move |editor, event: &ClickEvent, window, cx| {
9138 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9139 BreakpointEditAction::InvertState
9140 } else {
9141 BreakpointEditAction::Toggle
9142 };
9143
9144 window.focus(&editor.focus_handle(cx), cx);
9145 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9146 editor.edit_breakpoint_at_anchor(
9147 position,
9148 breakpoint.as_ref().clone(),
9149 edit_action,
9150 cx,
9151 );
9152 }
9153 }))
9154 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9155 editor.set_breakpoint_context_menu(
9156 row,
9157 Some(position),
9158 event.position(),
9159 window,
9160 cx,
9161 );
9162 }))
9163 .tooltip(move |_window, cx| {
9164 Tooltip::with_meta_in(
9165 primary_action_text,
9166 Some(&ToggleBreakpoint),
9167 meta.clone(),
9168 &focus_handle,
9169 cx,
9170 )
9171 })
9172 }
9173
9174 fn build_tasks_context(
9175 project: &Entity<Project>,
9176 buffer: &Entity<Buffer>,
9177 buffer_row: u32,
9178 tasks: &Arc<RunnableTasks>,
9179 cx: &mut Context<Self>,
9180 ) -> Task<Option<task::TaskContext>> {
9181 let position = Point::new(buffer_row, tasks.column);
9182 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9183 let location = Location {
9184 buffer: buffer.clone(),
9185 range: range_start..range_start,
9186 };
9187 // Fill in the environmental variables from the tree-sitter captures
9188 let mut captured_task_variables = TaskVariables::default();
9189 for (capture_name, value) in tasks.extra_variables.clone() {
9190 captured_task_variables.insert(
9191 task::VariableName::Custom(capture_name.into()),
9192 value.clone(),
9193 );
9194 }
9195 project.update(cx, |project, cx| {
9196 project.task_store().update(cx, |task_store, cx| {
9197 task_store.task_context_for_location(captured_task_variables, location, cx)
9198 })
9199 })
9200 }
9201
9202 pub fn spawn_nearest_task(
9203 &mut self,
9204 action: &SpawnNearestTask,
9205 window: &mut Window,
9206 cx: &mut Context<Self>,
9207 ) {
9208 let Some((workspace, _)) = self.workspace.clone() else {
9209 return;
9210 };
9211 let Some(project) = self.project.clone() else {
9212 return;
9213 };
9214
9215 // Try to find a closest, enclosing node using tree-sitter that has a task
9216 let Some((buffer, buffer_row, tasks)) = self
9217 .find_enclosing_node_task(cx)
9218 // Or find the task that's closest in row-distance.
9219 .or_else(|| self.find_closest_task(cx))
9220 else {
9221 return;
9222 };
9223
9224 let reveal_strategy = action.reveal;
9225 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
9226 cx.spawn_in(window, async move |_, cx| {
9227 let context = task_context.await?;
9228 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
9229
9230 let resolved = &mut resolved_task.resolved;
9231 resolved.reveal = reveal_strategy;
9232
9233 workspace
9234 .update_in(cx, |workspace, window, cx| {
9235 workspace.schedule_resolved_task(
9236 task_source_kind,
9237 resolved_task,
9238 false,
9239 window,
9240 cx,
9241 );
9242 })
9243 .ok()
9244 })
9245 .detach();
9246 }
9247
9248 fn find_closest_task(
9249 &mut self,
9250 cx: &mut Context<Self>,
9251 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9252 let cursor_row = self
9253 .selections
9254 .newest_adjusted(&self.display_snapshot(cx))
9255 .head()
9256 .row;
9257
9258 let ((buffer_id, row), tasks) = self
9259 .tasks
9260 .iter()
9261 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
9262
9263 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
9264 let tasks = Arc::new(tasks.to_owned());
9265 Some((buffer, *row, tasks))
9266 }
9267
9268 fn find_enclosing_node_task(
9269 &mut self,
9270 cx: &mut Context<Self>,
9271 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9272 let snapshot = self.buffer.read(cx).snapshot(cx);
9273 let offset = self
9274 .selections
9275 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
9276 .head();
9277 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
9278 let offset = excerpt.map_offset_to_buffer(offset);
9279 let buffer_id = excerpt.buffer().remote_id();
9280
9281 let layer = excerpt.buffer().syntax_layer_at(offset)?;
9282 let mut cursor = layer.node().walk();
9283
9284 while cursor.goto_first_child_for_byte(offset.0).is_some() {
9285 if cursor.node().end_byte() == offset.0 {
9286 cursor.goto_next_sibling();
9287 }
9288 }
9289
9290 // Ascend to the smallest ancestor that contains the range and has a task.
9291 loop {
9292 let node = cursor.node();
9293 let node_range = node.byte_range();
9294 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9295
9296 // Check if this node contains our offset
9297 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9298 // If it contains offset, check for task
9299 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9300 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9301 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9302 }
9303 }
9304
9305 if !cursor.goto_parent() {
9306 break;
9307 }
9308 }
9309 None
9310 }
9311
9312 fn render_run_indicator(
9313 &self,
9314 _style: &EditorStyle,
9315 is_active: bool,
9316 row: DisplayRow,
9317 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9318 cx: &mut Context<Self>,
9319 ) -> IconButton {
9320 let color = Color::Muted;
9321 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9322
9323 IconButton::new(
9324 ("run_indicator", row.0 as usize),
9325 ui::IconName::PlayOutlined,
9326 )
9327 .shape(ui::IconButtonShape::Square)
9328 .icon_size(IconSize::XSmall)
9329 .icon_color(color)
9330 .toggle_state(is_active)
9331 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9332 let quick_launch = match e {
9333 ClickEvent::Keyboard(_) => true,
9334 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9335 };
9336
9337 window.focus(&editor.focus_handle(cx), cx);
9338 editor.toggle_code_actions(
9339 &ToggleCodeActions {
9340 deployed_from: Some(CodeActionSource::RunMenu(row)),
9341 quick_launch,
9342 },
9343 window,
9344 cx,
9345 );
9346 }))
9347 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9348 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9349 }))
9350 }
9351
9352 pub fn context_menu_visible(&self) -> bool {
9353 !self.edit_prediction_preview_is_active()
9354 && self
9355 .context_menu
9356 .borrow()
9357 .as_ref()
9358 .is_some_and(|menu| menu.visible())
9359 }
9360
9361 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9362 self.context_menu
9363 .borrow()
9364 .as_ref()
9365 .map(|menu| menu.origin())
9366 }
9367
9368 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9369 self.context_menu_options = Some(options);
9370 }
9371
9372 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9373 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9374
9375 fn render_edit_prediction_popover(
9376 &mut self,
9377 text_bounds: &Bounds<Pixels>,
9378 content_origin: gpui::Point<Pixels>,
9379 right_margin: Pixels,
9380 editor_snapshot: &EditorSnapshot,
9381 visible_row_range: Range<DisplayRow>,
9382 scroll_top: ScrollOffset,
9383 scroll_bottom: ScrollOffset,
9384 line_layouts: &[LineWithInvisibles],
9385 line_height: Pixels,
9386 scroll_position: gpui::Point<ScrollOffset>,
9387 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9388 newest_selection_head: Option<DisplayPoint>,
9389 editor_width: Pixels,
9390 style: &EditorStyle,
9391 window: &mut Window,
9392 cx: &mut App,
9393 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9394 if self.mode().is_minimap() {
9395 return None;
9396 }
9397 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9398
9399 if self.edit_prediction_visible_in_cursor_popover(true) {
9400 return None;
9401 }
9402
9403 match &active_edit_prediction.completion {
9404 EditPrediction::MoveWithin { target, .. } => {
9405 let target_display_point = target.to_display_point(editor_snapshot);
9406
9407 if self.edit_prediction_requires_modifier() {
9408 if !self.edit_prediction_preview_is_active() {
9409 return None;
9410 }
9411
9412 self.render_edit_prediction_modifier_jump_popover(
9413 text_bounds,
9414 content_origin,
9415 visible_row_range,
9416 line_layouts,
9417 line_height,
9418 scroll_pixel_position,
9419 newest_selection_head,
9420 target_display_point,
9421 window,
9422 cx,
9423 )
9424 } else {
9425 self.render_edit_prediction_eager_jump_popover(
9426 text_bounds,
9427 content_origin,
9428 editor_snapshot,
9429 visible_row_range,
9430 scroll_top,
9431 scroll_bottom,
9432 line_height,
9433 scroll_pixel_position,
9434 target_display_point,
9435 editor_width,
9436 window,
9437 cx,
9438 )
9439 }
9440 }
9441 EditPrediction::Edit {
9442 display_mode: EditDisplayMode::Inline,
9443 ..
9444 } => None,
9445 EditPrediction::Edit {
9446 display_mode: EditDisplayMode::TabAccept,
9447 edits,
9448 ..
9449 } => {
9450 let range = &edits.first()?.0;
9451 let target_display_point = range.end.to_display_point(editor_snapshot);
9452
9453 self.render_edit_prediction_end_of_line_popover(
9454 "Accept",
9455 editor_snapshot,
9456 visible_row_range,
9457 target_display_point,
9458 line_height,
9459 scroll_pixel_position,
9460 content_origin,
9461 editor_width,
9462 window,
9463 cx,
9464 )
9465 }
9466 EditPrediction::Edit {
9467 edits,
9468 edit_preview,
9469 display_mode: EditDisplayMode::DiffPopover,
9470 snapshot,
9471 ..
9472 } => self.render_edit_prediction_diff_popover(
9473 text_bounds,
9474 content_origin,
9475 right_margin,
9476 editor_snapshot,
9477 visible_row_range,
9478 line_layouts,
9479 line_height,
9480 scroll_position,
9481 scroll_pixel_position,
9482 newest_selection_head,
9483 editor_width,
9484 style,
9485 edits,
9486 edit_preview,
9487 snapshot,
9488 window,
9489 cx,
9490 ),
9491 EditPrediction::MoveOutside { snapshot, .. } => {
9492 let mut element = self
9493 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9494 .into_any();
9495
9496 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9497 let origin_x = text_bounds.size.width - size.width - px(30.);
9498 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9499 element.prepaint_at(origin, window, cx);
9500
9501 Some((element, origin))
9502 }
9503 }
9504 }
9505
9506 fn render_edit_prediction_modifier_jump_popover(
9507 &mut self,
9508 text_bounds: &Bounds<Pixels>,
9509 content_origin: gpui::Point<Pixels>,
9510 visible_row_range: Range<DisplayRow>,
9511 line_layouts: &[LineWithInvisibles],
9512 line_height: Pixels,
9513 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9514 newest_selection_head: Option<DisplayPoint>,
9515 target_display_point: DisplayPoint,
9516 window: &mut Window,
9517 cx: &mut App,
9518 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9519 let scrolled_content_origin =
9520 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9521
9522 const SCROLL_PADDING_Y: Pixels = px(12.);
9523
9524 if target_display_point.row() < visible_row_range.start {
9525 return self.render_edit_prediction_scroll_popover(
9526 &|_| SCROLL_PADDING_Y,
9527 IconName::ArrowUp,
9528 visible_row_range,
9529 line_layouts,
9530 newest_selection_head,
9531 scrolled_content_origin,
9532 window,
9533 cx,
9534 );
9535 } else if target_display_point.row() >= visible_row_range.end {
9536 return self.render_edit_prediction_scroll_popover(
9537 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9538 IconName::ArrowDown,
9539 visible_row_range,
9540 line_layouts,
9541 newest_selection_head,
9542 scrolled_content_origin,
9543 window,
9544 cx,
9545 );
9546 }
9547
9548 const POLE_WIDTH: Pixels = px(2.);
9549
9550 let line_layout =
9551 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9552 let target_column = target_display_point.column() as usize;
9553
9554 let target_x = line_layout.x_for_index(target_column);
9555 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9556 - scroll_pixel_position.y;
9557
9558 let flag_on_right = target_x < text_bounds.size.width / 2.;
9559
9560 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9561 border_color.l += 0.001;
9562
9563 let mut element = v_flex()
9564 .items_end()
9565 .when(flag_on_right, |el| el.items_start())
9566 .child(if flag_on_right {
9567 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9568 .rounded_bl(px(0.))
9569 .rounded_tl(px(0.))
9570 .border_l_2()
9571 .border_color(border_color)
9572 } else {
9573 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9574 .rounded_br(px(0.))
9575 .rounded_tr(px(0.))
9576 .border_r_2()
9577 .border_color(border_color)
9578 })
9579 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9580 .into_any();
9581
9582 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9583
9584 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9585 - point(
9586 if flag_on_right {
9587 POLE_WIDTH
9588 } else {
9589 size.width - POLE_WIDTH
9590 },
9591 size.height - line_height,
9592 );
9593
9594 origin.x = origin.x.max(content_origin.x);
9595
9596 element.prepaint_at(origin, window, cx);
9597
9598 Some((element, origin))
9599 }
9600
9601 fn render_edit_prediction_scroll_popover(
9602 &mut self,
9603 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9604 scroll_icon: IconName,
9605 visible_row_range: Range<DisplayRow>,
9606 line_layouts: &[LineWithInvisibles],
9607 newest_selection_head: Option<DisplayPoint>,
9608 scrolled_content_origin: gpui::Point<Pixels>,
9609 window: &mut Window,
9610 cx: &mut App,
9611 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9612 let mut element = self
9613 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9614 .into_any();
9615
9616 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9617
9618 let cursor = newest_selection_head?;
9619 let cursor_row_layout =
9620 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9621 let cursor_column = cursor.column() as usize;
9622
9623 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9624
9625 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9626
9627 element.prepaint_at(origin, window, cx);
9628 Some((element, origin))
9629 }
9630
9631 fn render_edit_prediction_eager_jump_popover(
9632 &mut self,
9633 text_bounds: &Bounds<Pixels>,
9634 content_origin: gpui::Point<Pixels>,
9635 editor_snapshot: &EditorSnapshot,
9636 visible_row_range: Range<DisplayRow>,
9637 scroll_top: ScrollOffset,
9638 scroll_bottom: ScrollOffset,
9639 line_height: Pixels,
9640 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9641 target_display_point: DisplayPoint,
9642 editor_width: Pixels,
9643 window: &mut Window,
9644 cx: &mut App,
9645 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9646 if target_display_point.row().as_f64() < scroll_top {
9647 let mut element = self
9648 .render_edit_prediction_line_popover(
9649 "Jump to Edit",
9650 Some(IconName::ArrowUp),
9651 window,
9652 cx,
9653 )
9654 .into_any();
9655
9656 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9657 let offset = point(
9658 (text_bounds.size.width - size.width) / 2.,
9659 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9660 );
9661
9662 let origin = text_bounds.origin + offset;
9663 element.prepaint_at(origin, window, cx);
9664 Some((element, origin))
9665 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9666 let mut element = self
9667 .render_edit_prediction_line_popover(
9668 "Jump to Edit",
9669 Some(IconName::ArrowDown),
9670 window,
9671 cx,
9672 )
9673 .into_any();
9674
9675 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9676 let offset = point(
9677 (text_bounds.size.width - size.width) / 2.,
9678 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9679 );
9680
9681 let origin = text_bounds.origin + offset;
9682 element.prepaint_at(origin, window, cx);
9683 Some((element, origin))
9684 } else {
9685 self.render_edit_prediction_end_of_line_popover(
9686 "Jump to Edit",
9687 editor_snapshot,
9688 visible_row_range,
9689 target_display_point,
9690 line_height,
9691 scroll_pixel_position,
9692 content_origin,
9693 editor_width,
9694 window,
9695 cx,
9696 )
9697 }
9698 }
9699
9700 fn render_edit_prediction_end_of_line_popover(
9701 self: &mut Editor,
9702 label: &'static str,
9703 editor_snapshot: &EditorSnapshot,
9704 visible_row_range: Range<DisplayRow>,
9705 target_display_point: DisplayPoint,
9706 line_height: Pixels,
9707 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9708 content_origin: gpui::Point<Pixels>,
9709 editor_width: Pixels,
9710 window: &mut Window,
9711 cx: &mut App,
9712 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9713 let target_line_end = DisplayPoint::new(
9714 target_display_point.row(),
9715 editor_snapshot.line_len(target_display_point.row()),
9716 );
9717
9718 let mut element = self
9719 .render_edit_prediction_line_popover(label, None, window, cx)
9720 .into_any();
9721
9722 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9723
9724 let line_origin =
9725 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9726
9727 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9728 let mut origin = start_point
9729 + line_origin
9730 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9731 origin.x = origin.x.max(content_origin.x);
9732
9733 let max_x = content_origin.x + editor_width - size.width;
9734
9735 if origin.x > max_x {
9736 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9737
9738 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9739 origin.y += offset;
9740 IconName::ArrowUp
9741 } else {
9742 origin.y -= offset;
9743 IconName::ArrowDown
9744 };
9745
9746 element = self
9747 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9748 .into_any();
9749
9750 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9751
9752 origin.x = content_origin.x + editor_width - size.width - px(2.);
9753 }
9754
9755 element.prepaint_at(origin, window, cx);
9756 Some((element, origin))
9757 }
9758
9759 fn render_edit_prediction_diff_popover(
9760 self: &Editor,
9761 text_bounds: &Bounds<Pixels>,
9762 content_origin: gpui::Point<Pixels>,
9763 right_margin: Pixels,
9764 editor_snapshot: &EditorSnapshot,
9765 visible_row_range: Range<DisplayRow>,
9766 line_layouts: &[LineWithInvisibles],
9767 line_height: Pixels,
9768 scroll_position: gpui::Point<ScrollOffset>,
9769 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9770 newest_selection_head: Option<DisplayPoint>,
9771 editor_width: Pixels,
9772 style: &EditorStyle,
9773 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9774 edit_preview: &Option<language::EditPreview>,
9775 snapshot: &language::BufferSnapshot,
9776 window: &mut Window,
9777 cx: &mut App,
9778 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9779 let edit_start = edits
9780 .first()
9781 .unwrap()
9782 .0
9783 .start
9784 .to_display_point(editor_snapshot);
9785 let edit_end = edits
9786 .last()
9787 .unwrap()
9788 .0
9789 .end
9790 .to_display_point(editor_snapshot);
9791
9792 let is_visible = visible_row_range.contains(&edit_start.row())
9793 || visible_row_range.contains(&edit_end.row());
9794 if !is_visible {
9795 return None;
9796 }
9797
9798 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9799 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9800 } else {
9801 // Fallback for providers without edit_preview
9802 crate::edit_prediction_fallback_text(edits, cx)
9803 };
9804
9805 let styled_text = highlighted_edits.to_styled_text(&style.text);
9806 let line_count = highlighted_edits.text.lines().count();
9807
9808 const BORDER_WIDTH: Pixels = px(1.);
9809
9810 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9811 let has_keybind = keybind.is_some();
9812
9813 let mut element = h_flex()
9814 .items_start()
9815 .child(
9816 h_flex()
9817 .bg(cx.theme().colors().editor_background)
9818 .border(BORDER_WIDTH)
9819 .shadow_xs()
9820 .border_color(cx.theme().colors().border)
9821 .rounded_l_lg()
9822 .when(line_count > 1, |el| el.rounded_br_lg())
9823 .pr_1()
9824 .child(styled_text),
9825 )
9826 .child(
9827 h_flex()
9828 .h(line_height + BORDER_WIDTH * 2.)
9829 .px_1p5()
9830 .gap_1()
9831 // Workaround: For some reason, there's a gap if we don't do this
9832 .ml(-BORDER_WIDTH)
9833 .shadow(vec![gpui::BoxShadow {
9834 color: gpui::black().opacity(0.05),
9835 offset: point(px(1.), px(1.)),
9836 blur_radius: px(2.),
9837 spread_radius: px(0.),
9838 }])
9839 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9840 .border(BORDER_WIDTH)
9841 .border_color(cx.theme().colors().border)
9842 .rounded_r_lg()
9843 .id("edit_prediction_diff_popover_keybind")
9844 .when(!has_keybind, |el| {
9845 let status_colors = cx.theme().status();
9846
9847 el.bg(status_colors.error_background)
9848 .border_color(status_colors.error.opacity(0.6))
9849 .child(Icon::new(IconName::Info).color(Color::Error))
9850 .cursor_default()
9851 .hoverable_tooltip(move |_window, cx| {
9852 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9853 })
9854 })
9855 .children(keybind),
9856 )
9857 .into_any();
9858
9859 let longest_row =
9860 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9861 let longest_line_width = if visible_row_range.contains(&longest_row) {
9862 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9863 } else {
9864 layout_line(
9865 longest_row,
9866 editor_snapshot,
9867 style,
9868 editor_width,
9869 |_| false,
9870 window,
9871 cx,
9872 )
9873 .width
9874 };
9875
9876 let viewport_bounds =
9877 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9878 right: -right_margin,
9879 ..Default::default()
9880 });
9881
9882 let x_after_longest = Pixels::from(
9883 ScrollPixelOffset::from(
9884 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9885 ) - scroll_pixel_position.x,
9886 );
9887
9888 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9889
9890 // Fully visible if it can be displayed within the window (allow overlapping other
9891 // panes). However, this is only allowed if the popover starts within text_bounds.
9892 let can_position_to_the_right = x_after_longest < text_bounds.right()
9893 && x_after_longest + element_bounds.width < viewport_bounds.right();
9894
9895 let mut origin = if can_position_to_the_right {
9896 point(
9897 x_after_longest,
9898 text_bounds.origin.y
9899 + Pixels::from(
9900 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9901 - scroll_pixel_position.y,
9902 ),
9903 )
9904 } else {
9905 let cursor_row = newest_selection_head.map(|head| head.row());
9906 let above_edit = edit_start
9907 .row()
9908 .0
9909 .checked_sub(line_count as u32)
9910 .map(DisplayRow);
9911 let below_edit = Some(edit_end.row() + 1);
9912 let above_cursor =
9913 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9914 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9915
9916 // Place the edit popover adjacent to the edit if there is a location
9917 // available that is onscreen and does not obscure the cursor. Otherwise,
9918 // place it adjacent to the cursor.
9919 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9920 .into_iter()
9921 .flatten()
9922 .find(|&start_row| {
9923 let end_row = start_row + line_count as u32;
9924 visible_row_range.contains(&start_row)
9925 && visible_row_range.contains(&end_row)
9926 && cursor_row
9927 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9928 })?;
9929
9930 content_origin
9931 + point(
9932 Pixels::from(-scroll_pixel_position.x),
9933 Pixels::from(
9934 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9935 ),
9936 )
9937 };
9938
9939 origin.x -= BORDER_WIDTH;
9940
9941 window.with_content_mask(
9942 Some(gpui::ContentMask {
9943 bounds: *text_bounds,
9944 }),
9945 |window| {
9946 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9947 },
9948 );
9949
9950 // Do not return an element, since it will already be drawn due to defer_draw.
9951 None
9952 }
9953
9954 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9955 px(30.)
9956 }
9957
9958 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9959 if self.read_only(cx) {
9960 cx.theme().players().read_only()
9961 } else {
9962 self.style.as_ref().unwrap().local_player
9963 }
9964 }
9965
9966 fn render_edit_prediction_accept_keybind(
9967 &self,
9968 window: &mut Window,
9969 cx: &mut App,
9970 ) -> Option<AnyElement> {
9971 let accept_binding =
9972 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9973 let accept_keystroke = accept_binding.keystroke()?;
9974
9975 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9976
9977 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9978 Color::Accent
9979 } else {
9980 Color::Muted
9981 };
9982
9983 h_flex()
9984 .px_0p5()
9985 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9986 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9987 .text_size(TextSize::XSmall.rems(cx))
9988 .child(h_flex().children(ui::render_modifiers(
9989 accept_keystroke.modifiers(),
9990 PlatformStyle::platform(),
9991 Some(modifiers_color),
9992 Some(IconSize::XSmall.rems().into()),
9993 true,
9994 )))
9995 .when(is_platform_style_mac, |parent| {
9996 parent.child(accept_keystroke.key().to_string())
9997 })
9998 .when(!is_platform_style_mac, |parent| {
9999 parent.child(
10000 Key::new(
10001 util::capitalize(accept_keystroke.key()),
10002 Some(Color::Default),
10003 )
10004 .size(Some(IconSize::XSmall.rems().into())),
10005 )
10006 })
10007 .into_any()
10008 .into()
10009 }
10010
10011 fn render_edit_prediction_line_popover(
10012 &self,
10013 label: impl Into<SharedString>,
10014 icon: Option<IconName>,
10015 window: &mut Window,
10016 cx: &mut App,
10017 ) -> Stateful<Div> {
10018 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10019
10020 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
10021 let has_keybind = keybind.is_some();
10022 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10023
10024 h_flex()
10025 .id("ep-line-popover")
10026 .py_0p5()
10027 .pl_1()
10028 .pr(padding_right)
10029 .gap_1()
10030 .rounded_md()
10031 .border_1()
10032 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10033 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10034 .shadow_xs()
10035 .when(!has_keybind, |el| {
10036 let status_colors = cx.theme().status();
10037
10038 el.bg(status_colors.error_background)
10039 .border_color(status_colors.error.opacity(0.6))
10040 .pl_2()
10041 .child(Icon::new(icons.error).color(Color::Error))
10042 .cursor_default()
10043 .hoverable_tooltip(move |_window, cx| {
10044 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10045 })
10046 })
10047 .children(keybind)
10048 .child(
10049 Label::new(label)
10050 .size(LabelSize::Small)
10051 .when(!has_keybind, |el| {
10052 el.color(cx.theme().status().error.into()).strikethrough()
10053 }),
10054 )
10055 .when(!has_keybind, |el| {
10056 el.child(
10057 h_flex().ml_1().child(
10058 Icon::new(IconName::Info)
10059 .size(IconSize::Small)
10060 .color(cx.theme().status().error.into()),
10061 ),
10062 )
10063 })
10064 .when_some(icon, |element, icon| {
10065 element.child(
10066 div()
10067 .mt(px(1.5))
10068 .child(Icon::new(icon).size(IconSize::Small)),
10069 )
10070 })
10071 }
10072
10073 fn render_edit_prediction_jump_outside_popover(
10074 &self,
10075 snapshot: &BufferSnapshot,
10076 window: &mut Window,
10077 cx: &mut App,
10078 ) -> Stateful<Div> {
10079 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
10080 let has_keybind = keybind.is_some();
10081 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10082
10083 let file_name = snapshot
10084 .file()
10085 .map(|file| SharedString::new(file.file_name(cx)))
10086 .unwrap_or(SharedString::new_static("untitled"));
10087
10088 h_flex()
10089 .id("ep-jump-outside-popover")
10090 .py_1()
10091 .px_2()
10092 .gap_1()
10093 .rounded_md()
10094 .border_1()
10095 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10096 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10097 .shadow_xs()
10098 .when(!has_keybind, |el| {
10099 let status_colors = cx.theme().status();
10100
10101 el.bg(status_colors.error_background)
10102 .border_color(status_colors.error.opacity(0.6))
10103 .pl_2()
10104 .child(Icon::new(icons.error).color(Color::Error))
10105 .cursor_default()
10106 .hoverable_tooltip(move |_window, cx| {
10107 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10108 })
10109 })
10110 .children(keybind)
10111 .child(
10112 Label::new(file_name)
10113 .size(LabelSize::Small)
10114 .buffer_font(cx)
10115 .when(!has_keybind, |el| {
10116 el.color(cx.theme().status().error.into()).strikethrough()
10117 }),
10118 )
10119 .when(!has_keybind, |el| {
10120 el.child(
10121 h_flex().ml_1().child(
10122 Icon::new(IconName::Info)
10123 .size(IconSize::Small)
10124 .color(cx.theme().status().error.into()),
10125 ),
10126 )
10127 })
10128 .child(
10129 div()
10130 .mt(px(1.5))
10131 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10132 )
10133 }
10134
10135 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10136 let accent_color = cx.theme().colors().text_accent;
10137 let editor_bg_color = cx.theme().colors().editor_background;
10138 editor_bg_color.blend(accent_color.opacity(0.1))
10139 }
10140
10141 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10142 let accent_color = cx.theme().colors().text_accent;
10143 let editor_bg_color = cx.theme().colors().editor_background;
10144 editor_bg_color.blend(accent_color.opacity(0.6))
10145 }
10146 fn get_prediction_provider_icons(
10147 provider: &Option<RegisteredEditPredictionDelegate>,
10148 cx: &App,
10149 ) -> edit_prediction_types::EditPredictionIconSet {
10150 match provider {
10151 Some(provider) => provider.provider.icons(cx),
10152 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10153 }
10154 }
10155
10156 fn render_edit_prediction_cursor_popover(
10157 &self,
10158 min_width: Pixels,
10159 max_width: Pixels,
10160 cursor_point: Point,
10161 style: &EditorStyle,
10162 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
10163 _window: &Window,
10164 cx: &mut Context<Editor>,
10165 ) -> Option<AnyElement> {
10166 let provider = self.edit_prediction_provider.as_ref()?;
10167 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10168
10169 let is_refreshing = provider.provider.is_refreshing(cx);
10170
10171 fn pending_completion_container(icon: IconName) -> Div {
10172 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10173 }
10174
10175 let completion = match &self.active_edit_prediction {
10176 Some(prediction) => {
10177 if !self.has_visible_completions_menu() {
10178 const RADIUS: Pixels = px(6.);
10179 const BORDER_WIDTH: Pixels = px(1.);
10180
10181 return Some(
10182 h_flex()
10183 .elevation_2(cx)
10184 .border(BORDER_WIDTH)
10185 .border_color(cx.theme().colors().border)
10186 .when(accept_keystroke.is_none(), |el| {
10187 el.border_color(cx.theme().status().error)
10188 })
10189 .rounded(RADIUS)
10190 .rounded_tl(px(0.))
10191 .overflow_hidden()
10192 .child(div().px_1p5().child(match &prediction.completion {
10193 EditPrediction::MoveWithin { target, snapshot } => {
10194 use text::ToPoint as _;
10195 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10196 {
10197 Icon::new(icons.down)
10198 } else {
10199 Icon::new(icons.up)
10200 }
10201 }
10202 EditPrediction::MoveOutside { .. } => {
10203 // TODO [zeta2] custom icon for external jump?
10204 Icon::new(icons.base)
10205 }
10206 EditPrediction::Edit { .. } => Icon::new(icons.base),
10207 }))
10208 .child(
10209 h_flex()
10210 .gap_1()
10211 .py_1()
10212 .px_2()
10213 .rounded_r(RADIUS - BORDER_WIDTH)
10214 .border_l_1()
10215 .border_color(cx.theme().colors().border)
10216 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10217 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10218 el.child(
10219 Label::new("Hold")
10220 .size(LabelSize::Small)
10221 .when(accept_keystroke.is_none(), |el| {
10222 el.strikethrough()
10223 })
10224 .line_height_style(LineHeightStyle::UiLabel),
10225 )
10226 })
10227 .id("edit_prediction_cursor_popover_keybind")
10228 .when(accept_keystroke.is_none(), |el| {
10229 let status_colors = cx.theme().status();
10230
10231 el.bg(status_colors.error_background)
10232 .border_color(status_colors.error.opacity(0.6))
10233 .child(Icon::new(IconName::Info).color(Color::Error))
10234 .cursor_default()
10235 .hoverable_tooltip(move |_window, cx| {
10236 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10237 .into()
10238 })
10239 })
10240 .when_some(
10241 accept_keystroke.as_ref(),
10242 |el, accept_keystroke| {
10243 el.child(h_flex().children(ui::render_modifiers(
10244 accept_keystroke.modifiers(),
10245 PlatformStyle::platform(),
10246 Some(Color::Default),
10247 Some(IconSize::XSmall.rems().into()),
10248 false,
10249 )))
10250 },
10251 ),
10252 )
10253 .into_any(),
10254 );
10255 }
10256
10257 self.render_edit_prediction_cursor_popover_preview(
10258 prediction,
10259 cursor_point,
10260 style,
10261 cx,
10262 )?
10263 }
10264
10265 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10266 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10267 stale_completion,
10268 cursor_point,
10269 style,
10270 cx,
10271 )?,
10272
10273 None => pending_completion_container(icons.base)
10274 .child(Label::new("...").size(LabelSize::Small)),
10275 },
10276
10277 None => pending_completion_container(icons.base)
10278 .child(Label::new("...").size(LabelSize::Small)),
10279 };
10280
10281 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10282 completion
10283 .with_animation(
10284 "loading-completion",
10285 Animation::new(Duration::from_secs(2))
10286 .repeat()
10287 .with_easing(pulsating_between(0.4, 0.8)),
10288 |label, delta| label.opacity(delta),
10289 )
10290 .into_any_element()
10291 } else {
10292 completion.into_any_element()
10293 };
10294
10295 let has_completion = self.active_edit_prediction.is_some();
10296
10297 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10298 Some(
10299 h_flex()
10300 .min_w(min_width)
10301 .max_w(max_width)
10302 .flex_1()
10303 .elevation_2(cx)
10304 .border_color(cx.theme().colors().border)
10305 .child(
10306 div()
10307 .flex_1()
10308 .py_1()
10309 .px_2()
10310 .overflow_hidden()
10311 .child(completion),
10312 )
10313 .when_some(accept_keystroke, |el, accept_keystroke| {
10314 if !accept_keystroke.modifiers().modified() {
10315 return el;
10316 }
10317
10318 el.child(
10319 h_flex()
10320 .h_full()
10321 .border_l_1()
10322 .rounded_r_lg()
10323 .border_color(cx.theme().colors().border)
10324 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10325 .gap_1()
10326 .py_1()
10327 .px_2()
10328 .child(
10329 h_flex()
10330 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10331 .when(is_platform_style_mac, |parent| parent.gap_1())
10332 .child(h_flex().children(ui::render_modifiers(
10333 accept_keystroke.modifiers(),
10334 PlatformStyle::platform(),
10335 Some(if !has_completion {
10336 Color::Muted
10337 } else {
10338 Color::Default
10339 }),
10340 None,
10341 false,
10342 ))),
10343 )
10344 .child(Label::new("Preview").into_any_element())
10345 .opacity(if has_completion { 1.0 } else { 0.4 }),
10346 )
10347 })
10348 .into_any(),
10349 )
10350 }
10351
10352 fn render_edit_prediction_cursor_popover_preview(
10353 &self,
10354 completion: &EditPredictionState,
10355 cursor_point: Point,
10356 style: &EditorStyle,
10357 cx: &mut Context<Editor>,
10358 ) -> Option<Div> {
10359 use text::ToPoint as _;
10360
10361 fn render_relative_row_jump(
10362 prefix: impl Into<String>,
10363 current_row: u32,
10364 target_row: u32,
10365 ) -> Div {
10366 let (row_diff, arrow) = if target_row < current_row {
10367 (current_row - target_row, IconName::ArrowUp)
10368 } else {
10369 (target_row - current_row, IconName::ArrowDown)
10370 };
10371
10372 h_flex()
10373 .child(
10374 Label::new(format!("{}{}", prefix.into(), row_diff))
10375 .color(Color::Muted)
10376 .size(LabelSize::Small),
10377 )
10378 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10379 }
10380
10381 let supports_jump = self
10382 .edit_prediction_provider
10383 .as_ref()
10384 .map(|provider| provider.provider.supports_jump_to_edit())
10385 .unwrap_or(true);
10386
10387 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10388
10389 match &completion.completion {
10390 EditPrediction::MoveWithin {
10391 target, snapshot, ..
10392 } => {
10393 if !supports_jump {
10394 return None;
10395 }
10396
10397 Some(
10398 h_flex()
10399 .px_2()
10400 .gap_2()
10401 .flex_1()
10402 .child(
10403 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10404 Icon::new(icons.down)
10405 } else {
10406 Icon::new(icons.up)
10407 },
10408 )
10409 .child(Label::new("Jump to Edit")),
10410 )
10411 }
10412 EditPrediction::MoveOutside { snapshot, .. } => {
10413 let file_name = snapshot
10414 .file()
10415 .map(|file| file.file_name(cx))
10416 .unwrap_or("untitled");
10417 Some(
10418 h_flex()
10419 .px_2()
10420 .gap_2()
10421 .flex_1()
10422 .child(Icon::new(icons.base))
10423 .child(Label::new(format!("Jump to {file_name}"))),
10424 )
10425 }
10426 EditPrediction::Edit {
10427 edits,
10428 edit_preview,
10429 snapshot,
10430 ..
10431 } => {
10432 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10433
10434 let (highlighted_edits, has_more_lines) =
10435 if let Some(edit_preview) = edit_preview.as_ref() {
10436 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10437 .first_line_preview()
10438 } else {
10439 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10440 };
10441
10442 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10443 .with_default_highlights(&style.text, highlighted_edits.highlights);
10444
10445 let preview = h_flex()
10446 .gap_1()
10447 .min_w_16()
10448 .child(styled_text)
10449 .when(has_more_lines, |parent| parent.child("…"));
10450
10451 let left = if supports_jump && first_edit_row != cursor_point.row {
10452 render_relative_row_jump("", cursor_point.row, first_edit_row)
10453 .into_any_element()
10454 } else {
10455 Icon::new(icons.base).into_any_element()
10456 };
10457
10458 Some(
10459 h_flex()
10460 .h_full()
10461 .flex_1()
10462 .gap_2()
10463 .pr_1()
10464 .overflow_x_hidden()
10465 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10466 .child(left)
10467 .child(preview),
10468 )
10469 }
10470 }
10471 }
10472
10473 pub fn render_context_menu(
10474 &mut self,
10475 max_height_in_lines: u32,
10476 window: &mut Window,
10477 cx: &mut Context<Editor>,
10478 ) -> Option<AnyElement> {
10479 let menu = self.context_menu.borrow();
10480 let menu = menu.as_ref()?;
10481 if !menu.visible() {
10482 return None;
10483 };
10484 self.style
10485 .as_ref()
10486 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10487 }
10488
10489 fn render_context_menu_aside(
10490 &mut self,
10491 max_size: Size<Pixels>,
10492 window: &mut Window,
10493 cx: &mut Context<Editor>,
10494 ) -> Option<AnyElement> {
10495 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10496 if menu.visible() {
10497 menu.render_aside(max_size, window, cx)
10498 } else {
10499 None
10500 }
10501 })
10502 }
10503
10504 fn hide_context_menu(
10505 &mut self,
10506 window: &mut Window,
10507 cx: &mut Context<Self>,
10508 ) -> Option<CodeContextMenu> {
10509 cx.notify();
10510 self.completion_tasks.clear();
10511 let context_menu = self.context_menu.borrow_mut().take();
10512 self.stale_edit_prediction_in_menu.take();
10513 self.update_visible_edit_prediction(window, cx);
10514 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10515 && let Some(completion_provider) = &self.completion_provider
10516 {
10517 completion_provider.selection_changed(None, window, cx);
10518 }
10519 context_menu
10520 }
10521
10522 fn show_snippet_choices(
10523 &mut self,
10524 choices: &Vec<String>,
10525 selection: Range<Anchor>,
10526 cx: &mut Context<Self>,
10527 ) {
10528 let Some((_, buffer, _)) = self
10529 .buffer()
10530 .read(cx)
10531 .excerpt_containing(selection.start, cx)
10532 else {
10533 return;
10534 };
10535 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10536 else {
10537 return;
10538 };
10539 if buffer != end_buffer {
10540 log::error!("expected anchor range to have matching buffer IDs");
10541 return;
10542 }
10543
10544 let id = post_inc(&mut self.next_completion_id);
10545 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10546 let mut context_menu = self.context_menu.borrow_mut();
10547 let old_menu = context_menu.take();
10548 *context_menu = Some(CodeContextMenu::Completions(
10549 CompletionsMenu::new_snippet_choices(
10550 id,
10551 true,
10552 choices,
10553 selection,
10554 buffer,
10555 old_menu.map(|menu| menu.primary_scroll_handle()),
10556 snippet_sort_order,
10557 ),
10558 ));
10559 }
10560
10561 pub fn insert_snippet(
10562 &mut self,
10563 insertion_ranges: &[Range<MultiBufferOffset>],
10564 snippet: Snippet,
10565 window: &mut Window,
10566 cx: &mut Context<Self>,
10567 ) -> Result<()> {
10568 struct Tabstop<T> {
10569 is_end_tabstop: bool,
10570 ranges: Vec<Range<T>>,
10571 choices: Option<Vec<String>>,
10572 }
10573
10574 let tabstops = self.buffer.update(cx, |buffer, cx| {
10575 let snippet_text: Arc<str> = snippet.text.clone().into();
10576 let edits = insertion_ranges
10577 .iter()
10578 .cloned()
10579 .map(|range| (range, snippet_text.clone()));
10580 let autoindent_mode = AutoindentMode::Block {
10581 original_indent_columns: Vec::new(),
10582 };
10583 buffer.edit(edits, Some(autoindent_mode), cx);
10584
10585 let snapshot = &*buffer.read(cx);
10586 let snippet = &snippet;
10587 snippet
10588 .tabstops
10589 .iter()
10590 .map(|tabstop| {
10591 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10592 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10593 });
10594 let mut tabstop_ranges = tabstop
10595 .ranges
10596 .iter()
10597 .flat_map(|tabstop_range| {
10598 let mut delta = 0_isize;
10599 insertion_ranges.iter().map(move |insertion_range| {
10600 let insertion_start = insertion_range.start + delta;
10601 delta += snippet.text.len() as isize
10602 - (insertion_range.end - insertion_range.start) as isize;
10603
10604 let start =
10605 (insertion_start + tabstop_range.start).min(snapshot.len());
10606 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10607 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10608 })
10609 })
10610 .collect::<Vec<_>>();
10611 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10612
10613 Tabstop {
10614 is_end_tabstop,
10615 ranges: tabstop_ranges,
10616 choices: tabstop.choices.clone(),
10617 }
10618 })
10619 .collect::<Vec<_>>()
10620 });
10621 if let Some(tabstop) = tabstops.first() {
10622 self.change_selections(Default::default(), window, cx, |s| {
10623 // Reverse order so that the first range is the newest created selection.
10624 // Completions will use it and autoscroll will prioritize it.
10625 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10626 });
10627
10628 if let Some(choices) = &tabstop.choices
10629 && let Some(selection) = tabstop.ranges.first()
10630 {
10631 self.show_snippet_choices(choices, selection.clone(), cx)
10632 }
10633
10634 // If we're already at the last tabstop and it's at the end of the snippet,
10635 // we're done, we don't need to keep the state around.
10636 if !tabstop.is_end_tabstop {
10637 let choices = tabstops
10638 .iter()
10639 .map(|tabstop| tabstop.choices.clone())
10640 .collect();
10641
10642 let ranges = tabstops
10643 .into_iter()
10644 .map(|tabstop| tabstop.ranges)
10645 .collect::<Vec<_>>();
10646
10647 self.snippet_stack.push(SnippetState {
10648 active_index: 0,
10649 ranges,
10650 choices,
10651 });
10652 }
10653
10654 // Check whether the just-entered snippet ends with an auto-closable bracket.
10655 if self.autoclose_regions.is_empty() {
10656 let snapshot = self.buffer.read(cx).snapshot(cx);
10657 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10658 let selection_head = selection.head();
10659 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10660 continue;
10661 };
10662
10663 let mut bracket_pair = None;
10664 let max_lookup_length = scope
10665 .brackets()
10666 .map(|(pair, _)| {
10667 pair.start
10668 .as_str()
10669 .chars()
10670 .count()
10671 .max(pair.end.as_str().chars().count())
10672 })
10673 .max();
10674 if let Some(max_lookup_length) = max_lookup_length {
10675 let next_text = snapshot
10676 .chars_at(selection_head)
10677 .take(max_lookup_length)
10678 .collect::<String>();
10679 let prev_text = snapshot
10680 .reversed_chars_at(selection_head)
10681 .take(max_lookup_length)
10682 .collect::<String>();
10683
10684 for (pair, enabled) in scope.brackets() {
10685 if enabled
10686 && pair.close
10687 && prev_text.starts_with(pair.start.as_str())
10688 && next_text.starts_with(pair.end.as_str())
10689 {
10690 bracket_pair = Some(pair.clone());
10691 break;
10692 }
10693 }
10694 }
10695
10696 if let Some(pair) = bracket_pair {
10697 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10698 let autoclose_enabled =
10699 self.use_autoclose && snapshot_settings.use_autoclose;
10700 if autoclose_enabled {
10701 let start = snapshot.anchor_after(selection_head);
10702 let end = snapshot.anchor_after(selection_head);
10703 self.autoclose_regions.push(AutocloseRegion {
10704 selection_id: selection.id,
10705 range: start..end,
10706 pair,
10707 });
10708 }
10709 }
10710 }
10711 }
10712 }
10713 Ok(())
10714 }
10715
10716 pub fn move_to_next_snippet_tabstop(
10717 &mut self,
10718 window: &mut Window,
10719 cx: &mut Context<Self>,
10720 ) -> bool {
10721 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10722 }
10723
10724 pub fn move_to_prev_snippet_tabstop(
10725 &mut self,
10726 window: &mut Window,
10727 cx: &mut Context<Self>,
10728 ) -> bool {
10729 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10730 }
10731
10732 pub fn move_to_snippet_tabstop(
10733 &mut self,
10734 bias: Bias,
10735 window: &mut Window,
10736 cx: &mut Context<Self>,
10737 ) -> bool {
10738 if let Some(mut snippet) = self.snippet_stack.pop() {
10739 match bias {
10740 Bias::Left => {
10741 if snippet.active_index > 0 {
10742 snippet.active_index -= 1;
10743 } else {
10744 self.snippet_stack.push(snippet);
10745 return false;
10746 }
10747 }
10748 Bias::Right => {
10749 if snippet.active_index + 1 < snippet.ranges.len() {
10750 snippet.active_index += 1;
10751 } else {
10752 self.snippet_stack.push(snippet);
10753 return false;
10754 }
10755 }
10756 }
10757 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10758 self.change_selections(Default::default(), window, cx, |s| {
10759 // Reverse order so that the first range is the newest created selection.
10760 // Completions will use it and autoscroll will prioritize it.
10761 s.select_ranges(current_ranges.iter().rev().cloned())
10762 });
10763
10764 if let Some(choices) = &snippet.choices[snippet.active_index]
10765 && let Some(selection) = current_ranges.first()
10766 {
10767 self.show_snippet_choices(choices, selection.clone(), cx);
10768 }
10769
10770 // If snippet state is not at the last tabstop, push it back on the stack
10771 if snippet.active_index + 1 < snippet.ranges.len() {
10772 self.snippet_stack.push(snippet);
10773 }
10774 return true;
10775 }
10776 }
10777
10778 false
10779 }
10780
10781 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10782 self.transact(window, cx, |this, window, cx| {
10783 this.select_all(&SelectAll, window, cx);
10784 this.insert("", window, cx);
10785 });
10786 }
10787
10788 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10789 if self.read_only(cx) {
10790 return;
10791 }
10792 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10793 self.transact(window, cx, |this, window, cx| {
10794 this.select_autoclose_pair(window, cx);
10795
10796 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10797
10798 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10799 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10800 for selection in &mut selections {
10801 if selection.is_empty() {
10802 let old_head = selection.head();
10803 let mut new_head =
10804 movement::left(&display_map, old_head.to_display_point(&display_map))
10805 .to_point(&display_map);
10806 if let Some((buffer, line_buffer_range)) = display_map
10807 .buffer_snapshot()
10808 .buffer_line_for_row(MultiBufferRow(old_head.row))
10809 {
10810 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10811 let indent_len = match indent_size.kind {
10812 IndentKind::Space => {
10813 buffer.settings_at(line_buffer_range.start, cx).tab_size
10814 }
10815 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10816 };
10817 if old_head.column <= indent_size.len && old_head.column > 0 {
10818 let indent_len = indent_len.get();
10819 new_head = cmp::min(
10820 new_head,
10821 MultiBufferPoint::new(
10822 old_head.row,
10823 ((old_head.column - 1) / indent_len) * indent_len,
10824 ),
10825 );
10826 }
10827 }
10828
10829 selection.set_head(new_head, SelectionGoal::None);
10830 }
10831 }
10832
10833 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10834 this.insert("", window, cx);
10835 linked_edits.apply_with_left_expansion(cx);
10836 this.refresh_edit_prediction(true, false, window, cx);
10837 refresh_linked_ranges(this, window, cx);
10838 });
10839 }
10840
10841 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10842 if self.read_only(cx) {
10843 return;
10844 }
10845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10846 self.transact(window, cx, |this, window, cx| {
10847 this.change_selections(Default::default(), window, cx, |s| {
10848 s.move_with(&mut |map, selection| {
10849 if selection.is_empty() {
10850 let cursor = movement::right(map, selection.head());
10851 selection.end = cursor;
10852 selection.reversed = true;
10853 selection.goal = SelectionGoal::None;
10854 }
10855 })
10856 });
10857 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10858 this.insert("", window, cx);
10859 linked_edits.apply(cx);
10860 this.refresh_edit_prediction(true, false, window, cx);
10861 refresh_linked_ranges(this, window, cx);
10862 });
10863 }
10864
10865 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10866 if self.mode.is_single_line() {
10867 cx.propagate();
10868 return;
10869 }
10870
10871 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10872 if self.move_to_prev_snippet_tabstop(window, cx) {
10873 return;
10874 }
10875 self.outdent(&Outdent, window, cx);
10876 }
10877
10878 pub fn next_snippet_tabstop(
10879 &mut self,
10880 _: &NextSnippetTabstop,
10881 window: &mut Window,
10882 cx: &mut Context<Self>,
10883 ) {
10884 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10885 cx.propagate();
10886 return;
10887 }
10888
10889 if self.move_to_next_snippet_tabstop(window, cx) {
10890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10891 return;
10892 }
10893 cx.propagate();
10894 }
10895
10896 pub fn previous_snippet_tabstop(
10897 &mut self,
10898 _: &PreviousSnippetTabstop,
10899 window: &mut Window,
10900 cx: &mut Context<Self>,
10901 ) {
10902 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10903 cx.propagate();
10904 return;
10905 }
10906
10907 if self.move_to_prev_snippet_tabstop(window, cx) {
10908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10909 return;
10910 }
10911 cx.propagate();
10912 }
10913
10914 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10915 if self.mode.is_single_line() {
10916 cx.propagate();
10917 return;
10918 }
10919
10920 if self.move_to_next_snippet_tabstop(window, cx) {
10921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10922 return;
10923 }
10924 if self.read_only(cx) {
10925 return;
10926 }
10927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10928 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10929 let buffer = self.buffer.read(cx);
10930 let snapshot = buffer.snapshot(cx);
10931 let rows_iter = selections.iter().map(|s| s.head().row);
10932 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10933
10934 let has_some_cursor_in_whitespace = selections
10935 .iter()
10936 .filter(|selection| selection.is_empty())
10937 .any(|selection| {
10938 let cursor = selection.head();
10939 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10940 cursor.column < current_indent.len
10941 });
10942
10943 let mut edits = Vec::new();
10944 let mut prev_edited_row = 0;
10945 let mut row_delta = 0;
10946 for selection in &mut selections {
10947 if selection.start.row != prev_edited_row {
10948 row_delta = 0;
10949 }
10950 prev_edited_row = selection.end.row;
10951
10952 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10953 if selection.is_empty() {
10954 let cursor = selection.head();
10955 let settings = buffer.language_settings_at(cursor, cx);
10956 if settings.indent_list_on_tab {
10957 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10958 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10959 row_delta = Self::indent_selection(
10960 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10961 );
10962 continue;
10963 }
10964 }
10965 }
10966 }
10967
10968 // If the selection is non-empty, then increase the indentation of the selected lines.
10969 if !selection.is_empty() {
10970 row_delta =
10971 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10972 continue;
10973 }
10974
10975 let cursor = selection.head();
10976 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10977 if let Some(suggested_indent) =
10978 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10979 {
10980 // Don't do anything if already at suggested indent
10981 // and there is any other cursor which is not
10982 if has_some_cursor_in_whitespace
10983 && cursor.column == current_indent.len
10984 && current_indent.len == suggested_indent.len
10985 {
10986 continue;
10987 }
10988
10989 // Adjust line and move cursor to suggested indent
10990 // if cursor is not at suggested indent
10991 if cursor.column < suggested_indent.len
10992 && cursor.column <= current_indent.len
10993 && current_indent.len <= suggested_indent.len
10994 {
10995 selection.start = Point::new(cursor.row, suggested_indent.len);
10996 selection.end = selection.start;
10997 if row_delta == 0 {
10998 edits.extend(Buffer::edit_for_indent_size_adjustment(
10999 cursor.row,
11000 current_indent,
11001 suggested_indent,
11002 ));
11003 row_delta = suggested_indent.len - current_indent.len;
11004 }
11005 continue;
11006 }
11007
11008 // If current indent is more than suggested indent
11009 // only move cursor to current indent and skip indent
11010 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11011 selection.start = Point::new(cursor.row, current_indent.len);
11012 selection.end = selection.start;
11013 continue;
11014 }
11015 }
11016
11017 // Otherwise, insert a hard or soft tab.
11018 let settings = buffer.language_settings_at(cursor, cx);
11019 let tab_size = if settings.hard_tabs {
11020 IndentSize::tab()
11021 } else {
11022 let tab_size = settings.tab_size.get();
11023 let indent_remainder = snapshot
11024 .text_for_range(Point::new(cursor.row, 0)..cursor)
11025 .flat_map(str::chars)
11026 .fold(row_delta % tab_size, |counter: u32, c| {
11027 if c == '\t' {
11028 0
11029 } else {
11030 (counter + 1) % tab_size
11031 }
11032 });
11033
11034 let chars_to_next_tab_stop = tab_size - indent_remainder;
11035 IndentSize::spaces(chars_to_next_tab_stop)
11036 };
11037 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11038 selection.end = selection.start;
11039 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11040 row_delta += tab_size.len;
11041 }
11042
11043 self.transact(window, cx, |this, window, cx| {
11044 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11045 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11046 this.refresh_edit_prediction(true, false, window, cx);
11047 });
11048 }
11049
11050 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11051 if self.read_only(cx) {
11052 return;
11053 }
11054 if self.mode.is_single_line() {
11055 cx.propagate();
11056 return;
11057 }
11058
11059 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11060 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11061 let mut prev_edited_row = 0;
11062 let mut row_delta = 0;
11063 let mut edits = Vec::new();
11064 let buffer = self.buffer.read(cx);
11065 let snapshot = buffer.snapshot(cx);
11066 for selection in &mut selections {
11067 if selection.start.row != prev_edited_row {
11068 row_delta = 0;
11069 }
11070 prev_edited_row = selection.end.row;
11071
11072 row_delta =
11073 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11074 }
11075
11076 self.transact(window, cx, |this, window, cx| {
11077 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11078 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11079 });
11080 }
11081
11082 fn indent_selection(
11083 buffer: &MultiBuffer,
11084 snapshot: &MultiBufferSnapshot,
11085 selection: &mut Selection<Point>,
11086 edits: &mut Vec<(Range<Point>, String)>,
11087 delta_for_start_row: u32,
11088 cx: &App,
11089 ) -> u32 {
11090 let settings = buffer.language_settings_at(selection.start, cx);
11091 let tab_size = settings.tab_size.get();
11092 let indent_kind = if settings.hard_tabs {
11093 IndentKind::Tab
11094 } else {
11095 IndentKind::Space
11096 };
11097 let mut start_row = selection.start.row;
11098 let mut end_row = selection.end.row + 1;
11099
11100 // If a selection ends at the beginning of a line, don't indent
11101 // that last line.
11102 if selection.end.column == 0 && selection.end.row > selection.start.row {
11103 end_row -= 1;
11104 }
11105
11106 // Avoid re-indenting a row that has already been indented by a
11107 // previous selection, but still update this selection's column
11108 // to reflect that indentation.
11109 if delta_for_start_row > 0 {
11110 start_row += 1;
11111 selection.start.column += delta_for_start_row;
11112 if selection.end.row == selection.start.row {
11113 selection.end.column += delta_for_start_row;
11114 }
11115 }
11116
11117 let mut delta_for_end_row = 0;
11118 let has_multiple_rows = start_row + 1 != end_row;
11119 for row in start_row..end_row {
11120 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11121 let indent_delta = match (current_indent.kind, indent_kind) {
11122 (IndentKind::Space, IndentKind::Space) => {
11123 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11124 IndentSize::spaces(columns_to_next_tab_stop)
11125 }
11126 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11127 (_, IndentKind::Tab) => IndentSize::tab(),
11128 };
11129
11130 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11131 0
11132 } else {
11133 selection.start.column
11134 };
11135 let row_start = Point::new(row, start);
11136 edits.push((
11137 row_start..row_start,
11138 indent_delta.chars().collect::<String>(),
11139 ));
11140
11141 // Update this selection's endpoints to reflect the indentation.
11142 if row == selection.start.row {
11143 selection.start.column += indent_delta.len;
11144 }
11145 if row == selection.end.row {
11146 selection.end.column += indent_delta.len;
11147 delta_for_end_row = indent_delta.len;
11148 }
11149 }
11150
11151 if selection.start.row == selection.end.row {
11152 delta_for_start_row + delta_for_end_row
11153 } else {
11154 delta_for_end_row
11155 }
11156 }
11157
11158 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11159 if self.read_only(cx) {
11160 return;
11161 }
11162 if self.mode.is_single_line() {
11163 cx.propagate();
11164 return;
11165 }
11166
11167 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11168 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11169 let selections = self.selections.all::<Point>(&display_map);
11170 let mut deletion_ranges = Vec::new();
11171 let mut last_outdent = None;
11172 {
11173 let buffer = self.buffer.read(cx);
11174 let snapshot = buffer.snapshot(cx);
11175 for selection in &selections {
11176 let settings = buffer.language_settings_at(selection.start, cx);
11177 let tab_size = settings.tab_size.get();
11178 let mut rows = selection.spanned_rows(false, &display_map);
11179
11180 // Avoid re-outdenting a row that has already been outdented by a
11181 // previous selection.
11182 if let Some(last_row) = last_outdent
11183 && last_row == rows.start
11184 {
11185 rows.start = rows.start.next_row();
11186 }
11187 let has_multiple_rows = rows.len() > 1;
11188 for row in rows.iter_rows() {
11189 let indent_size = snapshot.indent_size_for_line(row);
11190 if indent_size.len > 0 {
11191 let deletion_len = match indent_size.kind {
11192 IndentKind::Space => {
11193 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11194 if columns_to_prev_tab_stop == 0 {
11195 tab_size
11196 } else {
11197 columns_to_prev_tab_stop
11198 }
11199 }
11200 IndentKind::Tab => 1,
11201 };
11202 let start = if has_multiple_rows
11203 || deletion_len > selection.start.column
11204 || indent_size.len < selection.start.column
11205 {
11206 0
11207 } else {
11208 selection.start.column - deletion_len
11209 };
11210 deletion_ranges.push(
11211 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11212 );
11213 last_outdent = Some(row);
11214 }
11215 }
11216 }
11217 }
11218
11219 self.transact(window, cx, |this, window, cx| {
11220 this.buffer.update(cx, |buffer, cx| {
11221 let empty_str: Arc<str> = Arc::default();
11222 buffer.edit(
11223 deletion_ranges
11224 .into_iter()
11225 .map(|range| (range, empty_str.clone())),
11226 None,
11227 cx,
11228 );
11229 });
11230 let selections = this
11231 .selections
11232 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11233 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11234 });
11235 }
11236
11237 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11238 if self.read_only(cx) {
11239 return;
11240 }
11241 if self.mode.is_single_line() {
11242 cx.propagate();
11243 return;
11244 }
11245
11246 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11247 let selections = self
11248 .selections
11249 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11250 .into_iter()
11251 .map(|s| s.range());
11252
11253 self.transact(window, cx, |this, window, cx| {
11254 this.buffer.update(cx, |buffer, cx| {
11255 buffer.autoindent_ranges(selections, cx);
11256 });
11257 let selections = this
11258 .selections
11259 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11260 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11261 });
11262 }
11263
11264 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11265 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11266 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11267 let selections = self.selections.all::<Point>(&display_map);
11268
11269 let mut new_cursors = Vec::new();
11270 let mut edit_ranges = Vec::new();
11271 let mut selections = selections.iter().peekable();
11272 while let Some(selection) = selections.next() {
11273 let mut rows = selection.spanned_rows(false, &display_map);
11274
11275 // Accumulate contiguous regions of rows that we want to delete.
11276 while let Some(next_selection) = selections.peek() {
11277 let next_rows = next_selection.spanned_rows(false, &display_map);
11278 if next_rows.start <= rows.end {
11279 rows.end = next_rows.end;
11280 selections.next().unwrap();
11281 } else {
11282 break;
11283 }
11284 }
11285
11286 let buffer = display_map.buffer_snapshot();
11287 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11288 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11289 // If there's a line after the range, delete the \n from the end of the row range
11290 (
11291 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11292 rows.end,
11293 )
11294 } else {
11295 // If there isn't a line after the range, delete the \n from the line before the
11296 // start of the row range
11297 edit_start = edit_start.saturating_sub_usize(1);
11298 (buffer.len(), rows.start.previous_row())
11299 };
11300
11301 let text_layout_details = self.text_layout_details(window, cx);
11302 let x = display_map.x_for_display_point(
11303 selection.head().to_display_point(&display_map),
11304 &text_layout_details,
11305 );
11306 let row = Point::new(target_row.0, 0)
11307 .to_display_point(&display_map)
11308 .row();
11309 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11310
11311 new_cursors.push((
11312 selection.id,
11313 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11314 SelectionGoal::None,
11315 ));
11316 edit_ranges.push(edit_start..edit_end);
11317 }
11318
11319 self.transact(window, cx, |this, window, cx| {
11320 let buffer = this.buffer.update(cx, |buffer, cx| {
11321 let empty_str: Arc<str> = Arc::default();
11322 buffer.edit(
11323 edit_ranges
11324 .into_iter()
11325 .map(|range| (range, empty_str.clone())),
11326 None,
11327 cx,
11328 );
11329 buffer.snapshot(cx)
11330 });
11331 let new_selections = new_cursors
11332 .into_iter()
11333 .map(|(id, cursor, goal)| {
11334 let cursor = cursor.to_point(&buffer);
11335 Selection {
11336 id,
11337 start: cursor,
11338 end: cursor,
11339 reversed: false,
11340 goal,
11341 }
11342 })
11343 .collect();
11344
11345 this.change_selections(Default::default(), window, cx, |s| {
11346 s.select(new_selections);
11347 });
11348 });
11349 }
11350
11351 pub fn join_lines_impl(
11352 &mut self,
11353 insert_whitespace: bool,
11354 window: &mut Window,
11355 cx: &mut Context<Self>,
11356 ) {
11357 if self.read_only(cx) {
11358 return;
11359 }
11360 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11361 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11362 let start = MultiBufferRow(selection.start.row);
11363 // Treat single line selections as if they include the next line. Otherwise this action
11364 // would do nothing for single line selections individual cursors.
11365 let end = if selection.start.row == selection.end.row {
11366 MultiBufferRow(selection.start.row + 1)
11367 } else if selection.end.column == 0 {
11368 // If the selection ends at the start of a line, it's logically at the end of the
11369 // previous line (plus its newline).
11370 // Don't include the end line unless there's only one line selected.
11371 if selection.start.row + 1 == selection.end.row {
11372 MultiBufferRow(selection.end.row)
11373 } else {
11374 MultiBufferRow(selection.end.row - 1)
11375 }
11376 } else {
11377 MultiBufferRow(selection.end.row)
11378 };
11379
11380 if let Some(last_row_range) = row_ranges.last_mut()
11381 && start <= last_row_range.end
11382 {
11383 last_row_range.end = end;
11384 continue;
11385 }
11386 row_ranges.push(start..end);
11387 }
11388
11389 let snapshot = self.buffer.read(cx).snapshot(cx);
11390 let mut cursor_positions = Vec::new();
11391 for row_range in &row_ranges {
11392 let anchor = snapshot.anchor_before(Point::new(
11393 row_range.end.previous_row().0,
11394 snapshot.line_len(row_range.end.previous_row()),
11395 ));
11396 cursor_positions.push(anchor..anchor);
11397 }
11398
11399 self.transact(window, cx, |this, window, cx| {
11400 for row_range in row_ranges.into_iter().rev() {
11401 for row in row_range.iter_rows().rev() {
11402 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11403 let next_line_row = row.next_row();
11404 let indent = snapshot.indent_size_for_line(next_line_row);
11405 let mut join_start_column = indent.len;
11406
11407 if let Some(language_scope) =
11408 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11409 {
11410 let line_end =
11411 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11412 let line_text_after_indent = snapshot
11413 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11414 .collect::<String>();
11415
11416 if !line_text_after_indent.is_empty() {
11417 let block_prefix = language_scope
11418 .block_comment()
11419 .map(|c| c.prefix.as_ref())
11420 .filter(|p| !p.is_empty());
11421 let doc_prefix = language_scope
11422 .documentation_comment()
11423 .map(|c| c.prefix.as_ref())
11424 .filter(|p| !p.is_empty());
11425 let all_prefixes = language_scope
11426 .line_comment_prefixes()
11427 .iter()
11428 .map(|p| p.as_ref())
11429 .chain(block_prefix)
11430 .chain(doc_prefix)
11431 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11432
11433 let mut longest_prefix_len = None;
11434 for prefix in all_prefixes {
11435 let trimmed = prefix.trim_end();
11436 if line_text_after_indent.starts_with(trimmed) {
11437 let candidate_len =
11438 if line_text_after_indent.starts_with(prefix) {
11439 prefix.len()
11440 } else {
11441 trimmed.len()
11442 };
11443 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11444 longest_prefix_len = Some(candidate_len);
11445 }
11446 }
11447 }
11448
11449 if let Some(prefix_len) = longest_prefix_len {
11450 join_start_column =
11451 join_start_column.saturating_add(prefix_len as u32);
11452 }
11453 }
11454 }
11455
11456 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11457
11458 let replace = if snapshot.line_len(next_line_row) > join_start_column
11459 && insert_whitespace
11460 {
11461 " "
11462 } else {
11463 ""
11464 };
11465
11466 this.buffer.update(cx, |buffer, cx| {
11467 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11468 });
11469 }
11470 }
11471
11472 this.change_selections(Default::default(), window, cx, |s| {
11473 s.select_anchor_ranges(cursor_positions)
11474 });
11475 });
11476 }
11477
11478 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11480 self.join_lines_impl(true, window, cx);
11481 }
11482
11483 pub fn sort_lines_case_sensitive(
11484 &mut self,
11485 _: &SortLinesCaseSensitive,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11490 }
11491
11492 pub fn sort_lines_by_length(
11493 &mut self,
11494 _: &SortLinesByLength,
11495 window: &mut Window,
11496 cx: &mut Context<Self>,
11497 ) {
11498 self.manipulate_immutable_lines(window, cx, |lines| {
11499 lines.sort_by_key(|&line| line.chars().count())
11500 })
11501 }
11502
11503 pub fn sort_lines_case_insensitive(
11504 &mut self,
11505 _: &SortLinesCaseInsensitive,
11506 window: &mut Window,
11507 cx: &mut Context<Self>,
11508 ) {
11509 self.manipulate_immutable_lines(window, cx, |lines| {
11510 lines.sort_by_key(|line| line.to_lowercase())
11511 })
11512 }
11513
11514 pub fn unique_lines_case_insensitive(
11515 &mut self,
11516 _: &UniqueLinesCaseInsensitive,
11517 window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) {
11520 self.manipulate_immutable_lines(window, cx, |lines| {
11521 let mut seen = HashSet::default();
11522 lines.retain(|line| seen.insert(line.to_lowercase()));
11523 })
11524 }
11525
11526 pub fn unique_lines_case_sensitive(
11527 &mut self,
11528 _: &UniqueLinesCaseSensitive,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.manipulate_immutable_lines(window, cx, |lines| {
11533 let mut seen = HashSet::default();
11534 lines.retain(|line| seen.insert(*line));
11535 })
11536 }
11537
11538 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11539 let snapshot = self.buffer.read(cx).snapshot(cx);
11540 for selection in self.selections.disjoint_anchors_arc().iter() {
11541 if snapshot
11542 .language_at(selection.start)
11543 .and_then(|lang| lang.config().wrap_characters.as_ref())
11544 .is_some()
11545 {
11546 return true;
11547 }
11548 }
11549 false
11550 }
11551
11552 fn wrap_selections_in_tag(
11553 &mut self,
11554 _: &WrapSelectionsInTag,
11555 window: &mut Window,
11556 cx: &mut Context<Self>,
11557 ) {
11558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11559
11560 let snapshot = self.buffer.read(cx).snapshot(cx);
11561
11562 let mut edits = Vec::new();
11563 let mut boundaries = Vec::new();
11564
11565 for selection in self
11566 .selections
11567 .all_adjusted(&self.display_snapshot(cx))
11568 .iter()
11569 {
11570 let Some(wrap_config) = snapshot
11571 .language_at(selection.start)
11572 .and_then(|lang| lang.config().wrap_characters.clone())
11573 else {
11574 continue;
11575 };
11576
11577 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11578 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11579
11580 let start_before = snapshot.anchor_before(selection.start);
11581 let end_after = snapshot.anchor_after(selection.end);
11582
11583 edits.push((start_before..start_before, open_tag));
11584 edits.push((end_after..end_after, close_tag));
11585
11586 boundaries.push((
11587 start_before,
11588 end_after,
11589 wrap_config.start_prefix.len(),
11590 wrap_config.end_suffix.len(),
11591 ));
11592 }
11593
11594 if edits.is_empty() {
11595 return;
11596 }
11597
11598 self.transact(window, cx, |this, window, cx| {
11599 let buffer = this.buffer.update(cx, |buffer, cx| {
11600 buffer.edit(edits, None, cx);
11601 buffer.snapshot(cx)
11602 });
11603
11604 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11605 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11606 boundaries.into_iter()
11607 {
11608 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11609 let close_offset = end_after
11610 .to_offset(&buffer)
11611 .saturating_sub_usize(end_suffix_len);
11612 new_selections.push(open_offset..open_offset);
11613 new_selections.push(close_offset..close_offset);
11614 }
11615
11616 this.change_selections(Default::default(), window, cx, |s| {
11617 s.select_ranges(new_selections);
11618 });
11619
11620 this.request_autoscroll(Autoscroll::fit(), cx);
11621 });
11622 }
11623
11624 pub fn toggle_read_only(
11625 &mut self,
11626 _: &workspace::ToggleReadOnlyFile,
11627 _: &mut Window,
11628 cx: &mut Context<Self>,
11629 ) {
11630 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11631 buffer.update(cx, |buffer, cx| {
11632 buffer.set_capability(
11633 match buffer.capability() {
11634 Capability::ReadWrite => Capability::Read,
11635 Capability::Read => Capability::ReadWrite,
11636 Capability::ReadOnly => Capability::ReadOnly,
11637 },
11638 cx,
11639 );
11640 })
11641 }
11642 }
11643
11644 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11645 let Some(project) = self.project.clone() else {
11646 return;
11647 };
11648 let task = self.reload(project, window, cx);
11649 self.detach_and_notify_err(task, window, cx);
11650 }
11651
11652 pub fn restore_file(
11653 &mut self,
11654 _: &::git::RestoreFile,
11655 window: &mut Window,
11656 cx: &mut Context<Self>,
11657 ) {
11658 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11659 let mut buffer_ids = HashSet::default();
11660 let snapshot = self.buffer().read(cx).snapshot(cx);
11661 for selection in self
11662 .selections
11663 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11664 {
11665 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11666 }
11667
11668 let buffer = self.buffer().read(cx);
11669 let ranges = buffer_ids
11670 .into_iter()
11671 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11672 .collect::<Vec<_>>();
11673
11674 self.restore_hunks_in_ranges(ranges, window, cx);
11675 }
11676
11677 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11678 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11679 let selections = self
11680 .selections
11681 .all(&self.display_snapshot(cx))
11682 .into_iter()
11683 .map(|s| s.range())
11684 .collect();
11685 self.restore_hunks_in_ranges(selections, window, cx);
11686 }
11687
11688 /// Restores the diff hunks in the editor's selections and moves the cursor
11689 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11690 /// not all diff hunks are expanded.
11691 pub fn restore_and_next(
11692 &mut self,
11693 _: &::git::RestoreAndNext,
11694 window: &mut Window,
11695 cx: &mut Context<Self>,
11696 ) {
11697 let selections = self
11698 .selections
11699 .all(&self.display_snapshot(cx))
11700 .into_iter()
11701 .map(|selection| selection.range())
11702 .collect();
11703
11704 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11705 self.restore_hunks_in_ranges(selections, window, cx);
11706
11707 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11708 let wrap_around = !all_diff_hunks_expanded;
11709 let snapshot = self.snapshot(window, cx);
11710 let position = self
11711 .selections
11712 .newest::<Point>(&snapshot.display_snapshot)
11713 .head();
11714
11715 self.go_to_hunk_before_or_after_position(
11716 &snapshot,
11717 position,
11718 Direction::Next,
11719 wrap_around,
11720 window,
11721 cx,
11722 );
11723 }
11724
11725 pub fn restore_hunks_in_ranges(
11726 &mut self,
11727 ranges: Vec<Range<Point>>,
11728 window: &mut Window,
11729 cx: &mut Context<Editor>,
11730 ) {
11731 if self.delegate_stage_and_restore {
11732 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11733 if !hunks.is_empty() {
11734 cx.emit(EditorEvent::RestoreRequested { hunks });
11735 }
11736 return;
11737 }
11738 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11739 self.transact(window, cx, |editor, window, cx| {
11740 editor.restore_diff_hunks(hunks, cx);
11741 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11742 selections.refresh()
11743 });
11744 });
11745 }
11746
11747 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11748 let mut revert_changes = HashMap::default();
11749 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11750 for (buffer_id, hunks) in &chunk_by {
11751 let hunks = hunks.collect::<Vec<_>>();
11752 for hunk in &hunks {
11753 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11754 }
11755 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11756 }
11757 if !revert_changes.is_empty() {
11758 self.buffer().update(cx, |multi_buffer, cx| {
11759 for (buffer_id, changes) in revert_changes {
11760 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11761 buffer.update(cx, |buffer, cx| {
11762 buffer.edit(
11763 changes
11764 .into_iter()
11765 .map(|(range, text)| (range, text.to_string())),
11766 None,
11767 cx,
11768 );
11769 });
11770 }
11771 }
11772 });
11773 }
11774 }
11775
11776 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11777 if let Some(status) = self
11778 .addons
11779 .iter()
11780 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11781 {
11782 return Some(status);
11783 }
11784 self.project
11785 .as_ref()?
11786 .read(cx)
11787 .status_for_buffer_id(buffer_id, cx)
11788 }
11789
11790 pub fn open_active_item_in_terminal(
11791 &mut self,
11792 _: &OpenInTerminal,
11793 window: &mut Window,
11794 cx: &mut Context<Self>,
11795 ) {
11796 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11797 let project_path = buffer.read(cx).project_path(cx)?;
11798 let project = self.project()?.read(cx);
11799 let entry = project.entry_for_path(&project_path, cx)?;
11800 let parent = match &entry.canonical_path {
11801 Some(canonical_path) => canonical_path.to_path_buf(),
11802 None => project.absolute_path(&project_path, cx)?,
11803 }
11804 .parent()?
11805 .to_path_buf();
11806 Some(parent)
11807 }) {
11808 window.dispatch_action(
11809 OpenTerminal {
11810 working_directory,
11811 local: false,
11812 }
11813 .boxed_clone(),
11814 cx,
11815 );
11816 }
11817 }
11818
11819 fn set_breakpoint_context_menu(
11820 &mut self,
11821 display_row: DisplayRow,
11822 position: Option<Anchor>,
11823 clicked_point: gpui::Point<Pixels>,
11824 window: &mut Window,
11825 cx: &mut Context<Self>,
11826 ) {
11827 let source = self
11828 .buffer
11829 .read(cx)
11830 .snapshot(cx)
11831 .anchor_before(Point::new(display_row.0, 0u32));
11832
11833 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11834
11835 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11836 self,
11837 source,
11838 clicked_point,
11839 context_menu,
11840 window,
11841 cx,
11842 );
11843 }
11844
11845 fn add_edit_breakpoint_block(
11846 &mut self,
11847 anchor: Anchor,
11848 breakpoint: &Breakpoint,
11849 edit_action: BreakpointPromptEditAction,
11850 window: &mut Window,
11851 cx: &mut Context<Self>,
11852 ) {
11853 let weak_editor = cx.weak_entity();
11854 let bp_prompt = cx.new(|cx| {
11855 BreakpointPromptEditor::new(
11856 weak_editor,
11857 anchor,
11858 breakpoint.clone(),
11859 edit_action,
11860 window,
11861 cx,
11862 )
11863 });
11864
11865 let height = bp_prompt.update(cx, |this, cx| {
11866 this.prompt
11867 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11868 });
11869 let cloned_prompt = bp_prompt.clone();
11870 let blocks = vec![BlockProperties {
11871 style: BlockStyle::Sticky,
11872 placement: BlockPlacement::Above(anchor),
11873 height: Some(height),
11874 render: Arc::new(move |cx| {
11875 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11876 cloned_prompt.clone().into_any_element()
11877 }),
11878 priority: 0,
11879 }];
11880
11881 let focus_handle = bp_prompt.focus_handle(cx);
11882 window.focus(&focus_handle, cx);
11883
11884 let block_ids = self.insert_blocks(blocks, None, cx);
11885 bp_prompt.update(cx, |prompt, _| {
11886 prompt.add_block_ids(block_ids);
11887 });
11888 }
11889
11890 pub(crate) fn breakpoint_at_row(
11891 &self,
11892 row: u32,
11893 window: &mut Window,
11894 cx: &mut Context<Self>,
11895 ) -> Option<(Anchor, Breakpoint)> {
11896 let snapshot = self.snapshot(window, cx);
11897 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11898
11899 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11900 }
11901
11902 pub(crate) fn breakpoint_at_anchor(
11903 &self,
11904 breakpoint_position: Anchor,
11905 snapshot: &EditorSnapshot,
11906 cx: &mut Context<Self>,
11907 ) -> Option<(Anchor, Breakpoint)> {
11908 let buffer = self
11909 .buffer
11910 .read(cx)
11911 .buffer_for_anchor(breakpoint_position, cx)?;
11912
11913 let enclosing_excerpt = breakpoint_position.excerpt_id;
11914 let buffer_snapshot = buffer.read(cx).snapshot();
11915
11916 let row = buffer_snapshot
11917 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11918 .row;
11919
11920 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11921 let anchor_end = snapshot
11922 .buffer_snapshot()
11923 .anchor_after(Point::new(row, line_len));
11924
11925 self.breakpoint_store
11926 .as_ref()?
11927 .read_with(cx, |breakpoint_store, cx| {
11928 breakpoint_store
11929 .breakpoints(
11930 &buffer,
11931 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11932 &buffer_snapshot,
11933 cx,
11934 )
11935 .next()
11936 .and_then(|(bp, _)| {
11937 let breakpoint_row = buffer_snapshot
11938 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11939 .row;
11940
11941 if breakpoint_row == row {
11942 snapshot
11943 .buffer_snapshot()
11944 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11945 .map(|position| (position, bp.bp.clone()))
11946 } else {
11947 None
11948 }
11949 })
11950 })
11951 }
11952
11953 pub fn edit_log_breakpoint(
11954 &mut self,
11955 _: &EditLogBreakpoint,
11956 window: &mut Window,
11957 cx: &mut Context<Self>,
11958 ) {
11959 if self.breakpoint_store.is_none() {
11960 return;
11961 }
11962
11963 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11964 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11965 message: None,
11966 state: BreakpointState::Enabled,
11967 condition: None,
11968 hit_condition: None,
11969 });
11970
11971 self.add_edit_breakpoint_block(
11972 anchor,
11973 &breakpoint,
11974 BreakpointPromptEditAction::Log,
11975 window,
11976 cx,
11977 );
11978 }
11979 }
11980
11981 fn breakpoints_at_cursors(
11982 &self,
11983 window: &mut Window,
11984 cx: &mut Context<Self>,
11985 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11986 let snapshot = self.snapshot(window, cx);
11987 let cursors = self
11988 .selections
11989 .disjoint_anchors_arc()
11990 .iter()
11991 .map(|selection| {
11992 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11993
11994 let breakpoint_position = self
11995 .breakpoint_at_row(cursor_position.row, window, cx)
11996 .map(|bp| bp.0)
11997 .unwrap_or_else(|| {
11998 snapshot
11999 .display_snapshot
12000 .buffer_snapshot()
12001 .anchor_after(Point::new(cursor_position.row, 0))
12002 });
12003
12004 let breakpoint = self
12005 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12006 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12007
12008 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12009 })
12010 // 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.
12011 .collect::<HashMap<Anchor, _>>();
12012
12013 cursors.into_iter().collect()
12014 }
12015
12016 pub fn enable_breakpoint(
12017 &mut self,
12018 _: &crate::actions::EnableBreakpoint,
12019 window: &mut Window,
12020 cx: &mut Context<Self>,
12021 ) {
12022 if self.breakpoint_store.is_none() {
12023 return;
12024 }
12025
12026 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12027 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12028 continue;
12029 };
12030 self.edit_breakpoint_at_anchor(
12031 anchor,
12032 breakpoint,
12033 BreakpointEditAction::InvertState,
12034 cx,
12035 );
12036 }
12037 }
12038
12039 pub fn disable_breakpoint(
12040 &mut self,
12041 _: &crate::actions::DisableBreakpoint,
12042 window: &mut Window,
12043 cx: &mut Context<Self>,
12044 ) {
12045 if self.breakpoint_store.is_none() {
12046 return;
12047 }
12048
12049 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12050 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12051 continue;
12052 };
12053 self.edit_breakpoint_at_anchor(
12054 anchor,
12055 breakpoint,
12056 BreakpointEditAction::InvertState,
12057 cx,
12058 );
12059 }
12060 }
12061
12062 pub fn toggle_breakpoint(
12063 &mut self,
12064 _: &crate::actions::ToggleBreakpoint,
12065 window: &mut Window,
12066 cx: &mut Context<Self>,
12067 ) {
12068 if self.breakpoint_store.is_none() {
12069 return;
12070 }
12071
12072 let snapshot = self.snapshot(window, cx);
12073 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12074 if self.gutter_breakpoint_indicator.0.is_some() {
12075 let display_row = anchor
12076 .to_point(snapshot.buffer_snapshot())
12077 .to_display_point(&snapshot.display_snapshot)
12078 .row();
12079 self.update_breakpoint_collision_on_toggle(
12080 display_row,
12081 &BreakpointEditAction::Toggle,
12082 );
12083 }
12084
12085 if let Some(breakpoint) = breakpoint {
12086 self.edit_breakpoint_at_anchor(
12087 anchor,
12088 breakpoint,
12089 BreakpointEditAction::Toggle,
12090 cx,
12091 );
12092 } else {
12093 self.edit_breakpoint_at_anchor(
12094 anchor,
12095 Breakpoint::new_standard(),
12096 BreakpointEditAction::Toggle,
12097 cx,
12098 );
12099 }
12100 }
12101 }
12102
12103 fn update_breakpoint_collision_on_toggle(
12104 &mut self,
12105 display_row: DisplayRow,
12106 edit_action: &BreakpointEditAction,
12107 ) {
12108 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12109 if breakpoint_indicator.display_row == display_row
12110 && matches!(edit_action, BreakpointEditAction::Toggle)
12111 {
12112 breakpoint_indicator.collides_with_existing_breakpoint =
12113 !breakpoint_indicator.collides_with_existing_breakpoint;
12114 }
12115 }
12116 }
12117
12118 pub fn edit_breakpoint_at_anchor(
12119 &mut self,
12120 breakpoint_position: Anchor,
12121 breakpoint: Breakpoint,
12122 edit_action: BreakpointEditAction,
12123 cx: &mut Context<Self>,
12124 ) {
12125 let Some(breakpoint_store) = &self.breakpoint_store else {
12126 return;
12127 };
12128
12129 let Some(buffer) = self
12130 .buffer
12131 .read(cx)
12132 .buffer_for_anchor(breakpoint_position, cx)
12133 else {
12134 return;
12135 };
12136
12137 breakpoint_store.update(cx, |breakpoint_store, cx| {
12138 breakpoint_store.toggle_breakpoint(
12139 buffer,
12140 BreakpointWithPosition {
12141 position: breakpoint_position.text_anchor,
12142 bp: breakpoint,
12143 },
12144 edit_action,
12145 cx,
12146 );
12147 });
12148
12149 cx.notify();
12150 }
12151
12152 #[cfg(any(test, feature = "test-support"))]
12153 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12154 self.breakpoint_store.clone()
12155 }
12156
12157 pub fn prepare_restore_change(
12158 &self,
12159 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12160 hunk: &MultiBufferDiffHunk,
12161 cx: &mut App,
12162 ) -> Option<()> {
12163 if hunk.is_created_file() {
12164 return None;
12165 }
12166 let buffer = self.buffer.read(cx);
12167 let diff = buffer.diff_for(hunk.buffer_id)?;
12168 let buffer = buffer.buffer(hunk.buffer_id)?;
12169 let buffer = buffer.read(cx);
12170 let original_text = diff
12171 .read(cx)
12172 .base_text(cx)
12173 .as_rope()
12174 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12175 let buffer_snapshot = buffer.snapshot();
12176 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12177 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12178 probe
12179 .0
12180 .start
12181 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12182 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12183 }) {
12184 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12185 Some(())
12186 } else {
12187 None
12188 }
12189 }
12190
12191 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12192 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12193 }
12194
12195 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12196 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12197 }
12198
12199 pub fn rotate_selections_forward(
12200 &mut self,
12201 _: &RotateSelectionsForward,
12202 window: &mut Window,
12203 cx: &mut Context<Self>,
12204 ) {
12205 self.rotate_selections(window, cx, false)
12206 }
12207
12208 pub fn rotate_selections_backward(
12209 &mut self,
12210 _: &RotateSelectionsBackward,
12211 window: &mut Window,
12212 cx: &mut Context<Self>,
12213 ) {
12214 self.rotate_selections(window, cx, true)
12215 }
12216
12217 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12218 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12219 let display_snapshot = self.display_snapshot(cx);
12220 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12221
12222 if selections.len() < 2 {
12223 return;
12224 }
12225
12226 let (edits, new_selections) = {
12227 let buffer = self.buffer.read(cx).read(cx);
12228 let has_selections = selections.iter().any(|s| !s.is_empty());
12229 if has_selections {
12230 let mut selected_texts: Vec<String> = selections
12231 .iter()
12232 .map(|selection| {
12233 buffer
12234 .text_for_range(selection.start..selection.end)
12235 .collect()
12236 })
12237 .collect();
12238
12239 if reverse {
12240 selected_texts.rotate_left(1);
12241 } else {
12242 selected_texts.rotate_right(1);
12243 }
12244
12245 let mut offset_delta: i64 = 0;
12246 let mut new_selections = Vec::new();
12247 let edits: Vec<_> = selections
12248 .iter()
12249 .zip(selected_texts.iter())
12250 .map(|(selection, new_text)| {
12251 let old_len = (selection.end.0 - selection.start.0) as i64;
12252 let new_len = new_text.len() as i64;
12253 let adjusted_start =
12254 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12255 let adjusted_end =
12256 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12257
12258 new_selections.push(Selection {
12259 id: selection.id,
12260 start: adjusted_start,
12261 end: adjusted_end,
12262 reversed: selection.reversed,
12263 goal: selection.goal,
12264 });
12265
12266 offset_delta += new_len - old_len;
12267 (selection.start..selection.end, new_text.clone())
12268 })
12269 .collect();
12270 (edits, new_selections)
12271 } else {
12272 let mut all_rows: Vec<u32> = selections
12273 .iter()
12274 .map(|selection| buffer.offset_to_point(selection.start).row)
12275 .collect();
12276 all_rows.sort_unstable();
12277 all_rows.dedup();
12278
12279 if all_rows.len() < 2 {
12280 return;
12281 }
12282
12283 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12284 .iter()
12285 .map(|&row| {
12286 let start = Point::new(row, 0);
12287 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12288 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12289 })
12290 .collect();
12291
12292 let mut line_texts: Vec<String> = line_ranges
12293 .iter()
12294 .map(|range| buffer.text_for_range(range.clone()).collect())
12295 .collect();
12296
12297 if reverse {
12298 line_texts.rotate_left(1);
12299 } else {
12300 line_texts.rotate_right(1);
12301 }
12302
12303 let edits = line_ranges
12304 .iter()
12305 .zip(line_texts.iter())
12306 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12307 .collect();
12308
12309 let num_rows = all_rows.len();
12310 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12311 .iter()
12312 .enumerate()
12313 .map(|(i, &row)| (row, i))
12314 .collect();
12315
12316 // Compute new line start offsets after rotation (handles CRLF)
12317 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12318 let first_line_start = line_ranges[0].start.0;
12319 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12320 for text in line_texts.iter().take(num_rows - 1) {
12321 let prev_start = *new_line_starts.last().unwrap();
12322 new_line_starts.push(prev_start + text.len() + newline_len);
12323 }
12324
12325 let new_selections = selections
12326 .iter()
12327 .map(|selection| {
12328 let point = buffer.offset_to_point(selection.start);
12329 let old_index = row_to_index[&point.row];
12330 let new_index = if reverse {
12331 (old_index + num_rows - 1) % num_rows
12332 } else {
12333 (old_index + 1) % num_rows
12334 };
12335 let new_offset =
12336 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12337 Selection {
12338 id: selection.id,
12339 start: new_offset,
12340 end: new_offset,
12341 reversed: selection.reversed,
12342 goal: selection.goal,
12343 }
12344 })
12345 .collect();
12346
12347 (edits, new_selections)
12348 }
12349 };
12350
12351 self.transact(window, cx, |this, window, cx| {
12352 this.buffer.update(cx, |buffer, cx| {
12353 buffer.edit(edits, None, cx);
12354 });
12355 this.change_selections(Default::default(), window, cx, |s| {
12356 s.select(new_selections);
12357 });
12358 });
12359 }
12360
12361 fn manipulate_lines<M>(
12362 &mut self,
12363 window: &mut Window,
12364 cx: &mut Context<Self>,
12365 mut manipulate: M,
12366 ) where
12367 M: FnMut(&str) -> LineManipulationResult,
12368 {
12369 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12370
12371 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12372 let buffer = self.buffer.read(cx).snapshot(cx);
12373
12374 let mut edits = Vec::new();
12375
12376 let selections = self.selections.all::<Point>(&display_map);
12377 let mut selections = selections.iter().peekable();
12378 let mut contiguous_row_selections = Vec::new();
12379 let mut new_selections = Vec::new();
12380 let mut added_lines = 0;
12381 let mut removed_lines = 0;
12382
12383 while let Some(selection) = selections.next() {
12384 let (start_row, end_row) = consume_contiguous_rows(
12385 &mut contiguous_row_selections,
12386 selection,
12387 &display_map,
12388 &mut selections,
12389 );
12390
12391 let start_point = Point::new(start_row.0, 0);
12392 let end_point = Point::new(
12393 end_row.previous_row().0,
12394 buffer.line_len(end_row.previous_row()),
12395 );
12396 let text = buffer
12397 .text_for_range(start_point..end_point)
12398 .collect::<String>();
12399
12400 let LineManipulationResult {
12401 new_text,
12402 line_count_before,
12403 line_count_after,
12404 } = manipulate(&text);
12405
12406 edits.push((start_point..end_point, new_text));
12407
12408 // Selections must change based on added and removed line count
12409 let start_row =
12410 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12411 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12412 new_selections.push(Selection {
12413 id: selection.id,
12414 start: start_row,
12415 end: end_row,
12416 goal: SelectionGoal::None,
12417 reversed: selection.reversed,
12418 });
12419
12420 if line_count_after > line_count_before {
12421 added_lines += line_count_after - line_count_before;
12422 } else if line_count_before > line_count_after {
12423 removed_lines += line_count_before - line_count_after;
12424 }
12425 }
12426
12427 self.transact(window, cx, |this, window, cx| {
12428 let buffer = this.buffer.update(cx, |buffer, cx| {
12429 buffer.edit(edits, None, cx);
12430 buffer.snapshot(cx)
12431 });
12432
12433 // Recalculate offsets on newly edited buffer
12434 let new_selections = new_selections
12435 .iter()
12436 .map(|s| {
12437 let start_point = Point::new(s.start.0, 0);
12438 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12439 Selection {
12440 id: s.id,
12441 start: buffer.point_to_offset(start_point),
12442 end: buffer.point_to_offset(end_point),
12443 goal: s.goal,
12444 reversed: s.reversed,
12445 }
12446 })
12447 .collect();
12448
12449 this.change_selections(Default::default(), window, cx, |s| {
12450 s.select(new_selections);
12451 });
12452
12453 this.request_autoscroll(Autoscroll::fit(), cx);
12454 });
12455 }
12456
12457 fn manipulate_immutable_lines<Fn>(
12458 &mut self,
12459 window: &mut Window,
12460 cx: &mut Context<Self>,
12461 mut callback: Fn,
12462 ) where
12463 Fn: FnMut(&mut Vec<&str>),
12464 {
12465 self.manipulate_lines(window, cx, |text| {
12466 let mut lines: Vec<&str> = text.split('\n').collect();
12467 let line_count_before = lines.len();
12468
12469 callback(&mut lines);
12470
12471 LineManipulationResult {
12472 new_text: lines.join("\n"),
12473 line_count_before,
12474 line_count_after: lines.len(),
12475 }
12476 });
12477 }
12478
12479 fn manipulate_mutable_lines<Fn>(
12480 &mut self,
12481 window: &mut Window,
12482 cx: &mut Context<Self>,
12483 mut callback: Fn,
12484 ) where
12485 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12486 {
12487 self.manipulate_lines(window, cx, |text| {
12488 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12489 let line_count_before = lines.len();
12490
12491 callback(&mut lines);
12492
12493 LineManipulationResult {
12494 new_text: lines.join("\n"),
12495 line_count_before,
12496 line_count_after: lines.len(),
12497 }
12498 });
12499 }
12500
12501 pub fn convert_indentation_to_spaces(
12502 &mut self,
12503 _: &ConvertIndentationToSpaces,
12504 window: &mut Window,
12505 cx: &mut Context<Self>,
12506 ) {
12507 let settings = self.buffer.read(cx).language_settings(cx);
12508 let tab_size = settings.tab_size.get() as usize;
12509
12510 self.manipulate_mutable_lines(window, cx, |lines| {
12511 // Allocates a reasonably sized scratch buffer once for the whole loop
12512 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12513 // Avoids recomputing spaces that could be inserted many times
12514 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12515 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12516 .collect();
12517
12518 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12519 let mut chars = line.as_ref().chars();
12520 let mut col = 0;
12521 let mut changed = false;
12522
12523 for ch in chars.by_ref() {
12524 match ch {
12525 ' ' => {
12526 reindented_line.push(' ');
12527 col += 1;
12528 }
12529 '\t' => {
12530 // \t are converted to spaces depending on the current column
12531 let spaces_len = tab_size - (col % tab_size);
12532 reindented_line.extend(&space_cache[spaces_len - 1]);
12533 col += spaces_len;
12534 changed = true;
12535 }
12536 _ => {
12537 // If we dont append before break, the character is consumed
12538 reindented_line.push(ch);
12539 break;
12540 }
12541 }
12542 }
12543
12544 if !changed {
12545 reindented_line.clear();
12546 continue;
12547 }
12548 // Append the rest of the line and replace old reference with new one
12549 reindented_line.extend(chars);
12550 *line = Cow::Owned(reindented_line.clone());
12551 reindented_line.clear();
12552 }
12553 });
12554 }
12555
12556 pub fn convert_indentation_to_tabs(
12557 &mut self,
12558 _: &ConvertIndentationToTabs,
12559 window: &mut Window,
12560 cx: &mut Context<Self>,
12561 ) {
12562 let settings = self.buffer.read(cx).language_settings(cx);
12563 let tab_size = settings.tab_size.get() as usize;
12564
12565 self.manipulate_mutable_lines(window, cx, |lines| {
12566 // Allocates a reasonably sized buffer once for the whole loop
12567 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12568 // Avoids recomputing spaces that could be inserted many times
12569 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12570 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12571 .collect();
12572
12573 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12574 let mut chars = line.chars();
12575 let mut spaces_count = 0;
12576 let mut first_non_indent_char = None;
12577 let mut changed = false;
12578
12579 for ch in chars.by_ref() {
12580 match ch {
12581 ' ' => {
12582 // Keep track of spaces. Append \t when we reach tab_size
12583 spaces_count += 1;
12584 changed = true;
12585 if spaces_count == tab_size {
12586 reindented_line.push('\t');
12587 spaces_count = 0;
12588 }
12589 }
12590 '\t' => {
12591 reindented_line.push('\t');
12592 spaces_count = 0;
12593 }
12594 _ => {
12595 // Dont append it yet, we might have remaining spaces
12596 first_non_indent_char = Some(ch);
12597 break;
12598 }
12599 }
12600 }
12601
12602 if !changed {
12603 reindented_line.clear();
12604 continue;
12605 }
12606 // Remaining spaces that didn't make a full tab stop
12607 if spaces_count > 0 {
12608 reindented_line.extend(&space_cache[spaces_count - 1]);
12609 }
12610 // If we consume an extra character that was not indentation, add it back
12611 if let Some(extra_char) = first_non_indent_char {
12612 reindented_line.push(extra_char);
12613 }
12614 // Append the rest of the line and replace old reference with new one
12615 reindented_line.extend(chars);
12616 *line = Cow::Owned(reindented_line.clone());
12617 reindented_line.clear();
12618 }
12619 });
12620 }
12621
12622 pub fn convert_to_upper_case(
12623 &mut self,
12624 _: &ConvertToUpperCase,
12625 window: &mut Window,
12626 cx: &mut Context<Self>,
12627 ) {
12628 self.manipulate_text(window, cx, |text| text.to_uppercase())
12629 }
12630
12631 pub fn convert_to_lower_case(
12632 &mut self,
12633 _: &ConvertToLowerCase,
12634 window: &mut Window,
12635 cx: &mut Context<Self>,
12636 ) {
12637 self.manipulate_text(window, cx, |text| text.to_lowercase())
12638 }
12639
12640 pub fn convert_to_title_case(
12641 &mut self,
12642 _: &ConvertToTitleCase,
12643 window: &mut Window,
12644 cx: &mut Context<Self>,
12645 ) {
12646 self.manipulate_text(window, cx, |text| {
12647 text.split('\n')
12648 .map(|line| line.to_case(Case::Title))
12649 .join("\n")
12650 })
12651 }
12652
12653 pub fn convert_to_snake_case(
12654 &mut self,
12655 _: &ConvertToSnakeCase,
12656 window: &mut Window,
12657 cx: &mut Context<Self>,
12658 ) {
12659 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12660 }
12661
12662 pub fn convert_to_kebab_case(
12663 &mut self,
12664 _: &ConvertToKebabCase,
12665 window: &mut Window,
12666 cx: &mut Context<Self>,
12667 ) {
12668 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12669 }
12670
12671 pub fn convert_to_upper_camel_case(
12672 &mut self,
12673 _: &ConvertToUpperCamelCase,
12674 window: &mut Window,
12675 cx: &mut Context<Self>,
12676 ) {
12677 self.manipulate_text(window, cx, |text| {
12678 text.split('\n')
12679 .map(|line| line.to_case(Case::UpperCamel))
12680 .join("\n")
12681 })
12682 }
12683
12684 pub fn convert_to_lower_camel_case(
12685 &mut self,
12686 _: &ConvertToLowerCamelCase,
12687 window: &mut Window,
12688 cx: &mut Context<Self>,
12689 ) {
12690 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12691 }
12692
12693 pub fn convert_to_opposite_case(
12694 &mut self,
12695 _: &ConvertToOppositeCase,
12696 window: &mut Window,
12697 cx: &mut Context<Self>,
12698 ) {
12699 self.manipulate_text(window, cx, |text| {
12700 text.chars()
12701 .fold(String::with_capacity(text.len()), |mut t, c| {
12702 if c.is_uppercase() {
12703 t.extend(c.to_lowercase());
12704 } else {
12705 t.extend(c.to_uppercase());
12706 }
12707 t
12708 })
12709 })
12710 }
12711
12712 pub fn convert_to_sentence_case(
12713 &mut self,
12714 _: &ConvertToSentenceCase,
12715 window: &mut Window,
12716 cx: &mut Context<Self>,
12717 ) {
12718 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12719 }
12720
12721 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12722 self.manipulate_text(window, cx, |text| {
12723 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12724 if has_upper_case_characters {
12725 text.to_lowercase()
12726 } else {
12727 text.to_uppercase()
12728 }
12729 })
12730 }
12731
12732 pub fn convert_to_rot13(
12733 &mut self,
12734 _: &ConvertToRot13,
12735 window: &mut Window,
12736 cx: &mut Context<Self>,
12737 ) {
12738 self.manipulate_text(window, cx, |text| {
12739 text.chars()
12740 .map(|c| match c {
12741 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12742 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12743 _ => c,
12744 })
12745 .collect()
12746 })
12747 }
12748
12749 pub fn convert_to_rot47(
12750 &mut self,
12751 _: &ConvertToRot47,
12752 window: &mut Window,
12753 cx: &mut Context<Self>,
12754 ) {
12755 self.manipulate_text(window, cx, |text| {
12756 text.chars()
12757 .map(|c| {
12758 let code_point = c as u32;
12759 if code_point >= 33 && code_point <= 126 {
12760 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12761 }
12762 c
12763 })
12764 .collect()
12765 })
12766 }
12767
12768 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12769 where
12770 Fn: FnMut(&str) -> String,
12771 {
12772 let buffer = self.buffer.read(cx).snapshot(cx);
12773
12774 let mut new_selections = Vec::new();
12775 let mut edits = Vec::new();
12776 let mut selection_adjustment = 0isize;
12777
12778 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12779 let selection_is_empty = selection.is_empty();
12780
12781 let (start, end) = if selection_is_empty {
12782 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12783 (word_range.start, word_range.end)
12784 } else {
12785 (
12786 buffer.point_to_offset(selection.start),
12787 buffer.point_to_offset(selection.end),
12788 )
12789 };
12790
12791 let text = buffer.text_for_range(start..end).collect::<String>();
12792 let old_length = text.len() as isize;
12793 let text = callback(&text);
12794
12795 new_selections.push(Selection {
12796 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12797 end: MultiBufferOffset(
12798 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12799 ),
12800 goal: SelectionGoal::None,
12801 id: selection.id,
12802 reversed: selection.reversed,
12803 });
12804
12805 selection_adjustment += old_length - text.len() as isize;
12806
12807 edits.push((start..end, text));
12808 }
12809
12810 self.transact(window, cx, |this, window, cx| {
12811 this.buffer.update(cx, |buffer, cx| {
12812 buffer.edit(edits, None, cx);
12813 });
12814
12815 this.change_selections(Default::default(), window, cx, |s| {
12816 s.select(new_selections);
12817 });
12818
12819 this.request_autoscroll(Autoscroll::fit(), cx);
12820 });
12821 }
12822
12823 pub fn move_selection_on_drop(
12824 &mut self,
12825 selection: &Selection<Anchor>,
12826 target: DisplayPoint,
12827 is_cut: bool,
12828 window: &mut Window,
12829 cx: &mut Context<Self>,
12830 ) {
12831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12832 let buffer = display_map.buffer_snapshot();
12833 let mut edits = Vec::new();
12834 let insert_point = display_map
12835 .clip_point(target, Bias::Left)
12836 .to_point(&display_map);
12837 let text = buffer
12838 .text_for_range(selection.start..selection.end)
12839 .collect::<String>();
12840 if is_cut {
12841 edits.push(((selection.start..selection.end), String::new()));
12842 }
12843 let insert_anchor = buffer.anchor_before(insert_point);
12844 edits.push(((insert_anchor..insert_anchor), text));
12845 let last_edit_start = insert_anchor.bias_left(buffer);
12846 let last_edit_end = insert_anchor.bias_right(buffer);
12847 self.transact(window, cx, |this, window, cx| {
12848 this.buffer.update(cx, |buffer, cx| {
12849 buffer.edit(edits, None, cx);
12850 });
12851 this.change_selections(Default::default(), window, cx, |s| {
12852 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12853 });
12854 });
12855 }
12856
12857 pub fn clear_selection_drag_state(&mut self) {
12858 self.selection_drag_state = SelectionDragState::None;
12859 }
12860
12861 pub fn duplicate(
12862 &mut self,
12863 upwards: bool,
12864 whole_lines: bool,
12865 window: &mut Window,
12866 cx: &mut Context<Self>,
12867 ) {
12868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12869
12870 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12871 let buffer = display_map.buffer_snapshot();
12872 let selections = self.selections.all::<Point>(&display_map);
12873
12874 let mut edits = Vec::new();
12875 let mut selections_iter = selections.iter().peekable();
12876 while let Some(selection) = selections_iter.next() {
12877 let mut rows = selection.spanned_rows(false, &display_map);
12878 // duplicate line-wise
12879 if whole_lines || selection.start == selection.end {
12880 // Avoid duplicating the same lines twice.
12881 while let Some(next_selection) = selections_iter.peek() {
12882 let next_rows = next_selection.spanned_rows(false, &display_map);
12883 if next_rows.start < rows.end {
12884 rows.end = next_rows.end;
12885 selections_iter.next().unwrap();
12886 } else {
12887 break;
12888 }
12889 }
12890
12891 // Copy the text from the selected row region and splice it either at the start
12892 // or end of the region.
12893 let start = Point::new(rows.start.0, 0);
12894 let end = Point::new(
12895 rows.end.previous_row().0,
12896 buffer.line_len(rows.end.previous_row()),
12897 );
12898
12899 let mut text = buffer.text_for_range(start..end).collect::<String>();
12900
12901 let insert_location = if upwards {
12902 // When duplicating upward, we need to insert before the current line.
12903 // If we're on the last line and it doesn't end with a newline,
12904 // we need to add a newline before the duplicated content.
12905 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12906 && buffer.max_point().column > 0
12907 && !text.ends_with('\n');
12908
12909 if needs_leading_newline {
12910 text.insert(0, '\n');
12911 end
12912 } else {
12913 text.push('\n');
12914 Point::new(rows.start.0, 0)
12915 }
12916 } else {
12917 text.push('\n');
12918 start
12919 };
12920 edits.push((insert_location..insert_location, text));
12921 } else {
12922 // duplicate character-wise
12923 let start = selection.start;
12924 let end = selection.end;
12925 let text = buffer.text_for_range(start..end).collect::<String>();
12926 edits.push((selection.end..selection.end, text));
12927 }
12928 }
12929
12930 self.transact(window, cx, |this, window, cx| {
12931 this.buffer.update(cx, |buffer, cx| {
12932 buffer.edit(edits, None, cx);
12933 });
12934
12935 // When duplicating upward with whole lines, move the cursor to the duplicated line
12936 if upwards && whole_lines {
12937 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12938
12939 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12940 let mut new_ranges = Vec::new();
12941 let selections = s.all::<Point>(&display_map);
12942 let mut selections_iter = selections.iter().peekable();
12943
12944 while let Some(first_selection) = selections_iter.next() {
12945 // Group contiguous selections together to find the total row span
12946 let mut group_selections = vec![first_selection];
12947 let mut rows = first_selection.spanned_rows(false, &display_map);
12948
12949 while let Some(next_selection) = selections_iter.peek() {
12950 let next_rows = next_selection.spanned_rows(false, &display_map);
12951 if next_rows.start < rows.end {
12952 rows.end = next_rows.end;
12953 group_selections.push(selections_iter.next().unwrap());
12954 } else {
12955 break;
12956 }
12957 }
12958
12959 let row_count = rows.end.0 - rows.start.0;
12960
12961 // Move all selections in this group up by the total number of duplicated rows
12962 for selection in group_selections {
12963 let new_start = Point::new(
12964 selection.start.row.saturating_sub(row_count),
12965 selection.start.column,
12966 );
12967
12968 let new_end = Point::new(
12969 selection.end.row.saturating_sub(row_count),
12970 selection.end.column,
12971 );
12972
12973 new_ranges.push(new_start..new_end);
12974 }
12975 }
12976
12977 s.select_ranges(new_ranges);
12978 });
12979 }
12980
12981 this.request_autoscroll(Autoscroll::fit(), cx);
12982 });
12983 }
12984
12985 pub fn duplicate_line_up(
12986 &mut self,
12987 _: &DuplicateLineUp,
12988 window: &mut Window,
12989 cx: &mut Context<Self>,
12990 ) {
12991 self.duplicate(true, true, window, cx);
12992 }
12993
12994 pub fn duplicate_line_down(
12995 &mut self,
12996 _: &DuplicateLineDown,
12997 window: &mut Window,
12998 cx: &mut Context<Self>,
12999 ) {
13000 self.duplicate(false, true, window, cx);
13001 }
13002
13003 pub fn duplicate_selection(
13004 &mut self,
13005 _: &DuplicateSelection,
13006 window: &mut Window,
13007 cx: &mut Context<Self>,
13008 ) {
13009 self.duplicate(false, false, window, cx);
13010 }
13011
13012 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13013 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13014 if self.mode.is_single_line() {
13015 cx.propagate();
13016 return;
13017 }
13018
13019 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13020 let buffer = self.buffer.read(cx).snapshot(cx);
13021
13022 let mut edits = Vec::new();
13023 let mut unfold_ranges = Vec::new();
13024 let mut refold_creases = Vec::new();
13025
13026 let selections = self.selections.all::<Point>(&display_map);
13027 let mut selections = selections.iter().peekable();
13028 let mut contiguous_row_selections = Vec::new();
13029 let mut new_selections = Vec::new();
13030
13031 while let Some(selection) = selections.next() {
13032 // Find all the selections that span a contiguous row range
13033 let (start_row, end_row) = consume_contiguous_rows(
13034 &mut contiguous_row_selections,
13035 selection,
13036 &display_map,
13037 &mut selections,
13038 );
13039
13040 // Move the text spanned by the row range to be before the line preceding the row range
13041 if start_row.0 > 0 {
13042 let range_to_move = Point::new(
13043 start_row.previous_row().0,
13044 buffer.line_len(start_row.previous_row()),
13045 )
13046 ..Point::new(
13047 end_row.previous_row().0,
13048 buffer.line_len(end_row.previous_row()),
13049 );
13050 let insertion_point = display_map
13051 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13052 .0;
13053
13054 // Don't move lines across excerpts
13055 if buffer
13056 .excerpt_containing(insertion_point..range_to_move.end)
13057 .is_some()
13058 {
13059 let text = buffer
13060 .text_for_range(range_to_move.clone())
13061 .flat_map(|s| s.chars())
13062 .skip(1)
13063 .chain(['\n'])
13064 .collect::<String>();
13065
13066 edits.push((
13067 buffer.anchor_after(range_to_move.start)
13068 ..buffer.anchor_before(range_to_move.end),
13069 String::new(),
13070 ));
13071 let insertion_anchor = buffer.anchor_after(insertion_point);
13072 edits.push((insertion_anchor..insertion_anchor, text));
13073
13074 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13075
13076 // Move selections up
13077 new_selections.extend(contiguous_row_selections.drain(..).map(
13078 |mut selection| {
13079 selection.start.row -= row_delta;
13080 selection.end.row -= row_delta;
13081 selection
13082 },
13083 ));
13084
13085 // Move folds up
13086 unfold_ranges.push(range_to_move.clone());
13087 for fold in display_map.folds_in_range(
13088 buffer.anchor_before(range_to_move.start)
13089 ..buffer.anchor_after(range_to_move.end),
13090 ) {
13091 let mut start = fold.range.start.to_point(&buffer);
13092 let mut end = fold.range.end.to_point(&buffer);
13093 start.row -= row_delta;
13094 end.row -= row_delta;
13095 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13096 }
13097 }
13098 }
13099
13100 // If we didn't move line(s), preserve the existing selections
13101 new_selections.append(&mut contiguous_row_selections);
13102 }
13103
13104 self.transact(window, cx, |this, window, cx| {
13105 this.unfold_ranges(&unfold_ranges, true, true, cx);
13106 this.buffer.update(cx, |buffer, cx| {
13107 for (range, text) in edits {
13108 buffer.edit([(range, text)], None, cx);
13109 }
13110 });
13111 this.fold_creases(refold_creases, true, window, cx);
13112 this.change_selections(Default::default(), window, cx, |s| {
13113 s.select(new_selections);
13114 })
13115 });
13116 }
13117
13118 pub fn move_line_down(
13119 &mut self,
13120 _: &MoveLineDown,
13121 window: &mut Window,
13122 cx: &mut Context<Self>,
13123 ) {
13124 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13125 if self.mode.is_single_line() {
13126 cx.propagate();
13127 return;
13128 }
13129
13130 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13131 let buffer = self.buffer.read(cx).snapshot(cx);
13132
13133 let mut edits = Vec::new();
13134 let mut unfold_ranges = Vec::new();
13135 let mut refold_creases = Vec::new();
13136
13137 let selections = self.selections.all::<Point>(&display_map);
13138 let mut selections = selections.iter().peekable();
13139 let mut contiguous_row_selections = Vec::new();
13140 let mut new_selections = Vec::new();
13141
13142 while let Some(selection) = selections.next() {
13143 // Find all the selections that span a contiguous row range
13144 let (start_row, end_row) = consume_contiguous_rows(
13145 &mut contiguous_row_selections,
13146 selection,
13147 &display_map,
13148 &mut selections,
13149 );
13150
13151 // Move the text spanned by the row range to be after the last line of the row range
13152 if end_row.0 <= buffer.max_point().row {
13153 let range_to_move =
13154 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13155 let insertion_point = display_map
13156 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13157 .0;
13158
13159 // Don't move lines across excerpt boundaries
13160 if buffer
13161 .excerpt_containing(range_to_move.start..insertion_point)
13162 .is_some()
13163 {
13164 let mut text = String::from("\n");
13165 text.extend(buffer.text_for_range(range_to_move.clone()));
13166 text.pop(); // Drop trailing newline
13167 edits.push((
13168 buffer.anchor_after(range_to_move.start)
13169 ..buffer.anchor_before(range_to_move.end),
13170 String::new(),
13171 ));
13172 let insertion_anchor = buffer.anchor_after(insertion_point);
13173 edits.push((insertion_anchor..insertion_anchor, text));
13174
13175 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13176
13177 // Move selections down
13178 new_selections.extend(contiguous_row_selections.drain(..).map(
13179 |mut selection| {
13180 selection.start.row += row_delta;
13181 selection.end.row += row_delta;
13182 selection
13183 },
13184 ));
13185
13186 // Move folds down
13187 unfold_ranges.push(range_to_move.clone());
13188 for fold in display_map.folds_in_range(
13189 buffer.anchor_before(range_to_move.start)
13190 ..buffer.anchor_after(range_to_move.end),
13191 ) {
13192 let mut start = fold.range.start.to_point(&buffer);
13193 let mut end = fold.range.end.to_point(&buffer);
13194 start.row += row_delta;
13195 end.row += row_delta;
13196 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13197 }
13198 }
13199 }
13200
13201 // If we didn't move line(s), preserve the existing selections
13202 new_selections.append(&mut contiguous_row_selections);
13203 }
13204
13205 self.transact(window, cx, |this, window, cx| {
13206 this.unfold_ranges(&unfold_ranges, true, true, cx);
13207 this.buffer.update(cx, |buffer, cx| {
13208 for (range, text) in edits {
13209 buffer.edit([(range, text)], None, cx);
13210 }
13211 });
13212 this.fold_creases(refold_creases, true, window, cx);
13213 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13214 });
13215 }
13216
13217 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13218 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13219 let text_layout_details = &self.text_layout_details(window, cx);
13220 self.transact(window, cx, |this, window, cx| {
13221 let edits = this.change_selections(Default::default(), window, cx, |s| {
13222 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13223 s.move_with(&mut |display_map, selection| {
13224 if !selection.is_empty() {
13225 return;
13226 }
13227
13228 let mut head = selection.head();
13229 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13230 if head.column() == display_map.line_len(head.row()) {
13231 transpose_offset = display_map
13232 .buffer_snapshot()
13233 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13234 }
13235
13236 if transpose_offset == MultiBufferOffset(0) {
13237 return;
13238 }
13239
13240 *head.column_mut() += 1;
13241 head = display_map.clip_point(head, Bias::Right);
13242 let goal = SelectionGoal::HorizontalPosition(
13243 display_map
13244 .x_for_display_point(head, text_layout_details)
13245 .into(),
13246 );
13247 selection.collapse_to(head, goal);
13248
13249 let transpose_start = display_map
13250 .buffer_snapshot()
13251 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13252 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13253 let transpose_end = display_map
13254 .buffer_snapshot()
13255 .clip_offset(transpose_offset + 1usize, Bias::Right);
13256 if let Some(ch) = display_map
13257 .buffer_snapshot()
13258 .chars_at(transpose_start)
13259 .next()
13260 {
13261 edits.push((transpose_start..transpose_offset, String::new()));
13262 edits.push((transpose_end..transpose_end, ch.to_string()));
13263 }
13264 }
13265 });
13266 edits
13267 });
13268 this.buffer
13269 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13270 let selections = this
13271 .selections
13272 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13273 this.change_selections(Default::default(), window, cx, |s| {
13274 s.select(selections);
13275 });
13276 });
13277 }
13278
13279 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13280 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13281 if self.mode.is_single_line() {
13282 cx.propagate();
13283 return;
13284 }
13285
13286 self.rewrap_impl(RewrapOptions::default(), cx)
13287 }
13288
13289 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13290 let buffer = self.buffer.read(cx).snapshot(cx);
13291 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13292
13293 #[derive(Clone, Debug, PartialEq)]
13294 enum CommentFormat {
13295 /// single line comment, with prefix for line
13296 Line(String),
13297 /// single line within a block comment, with prefix for line
13298 BlockLine(String),
13299 /// a single line of a block comment that includes the initial delimiter
13300 BlockCommentWithStart(BlockCommentConfig),
13301 /// a single line of a block comment that includes the ending delimiter
13302 BlockCommentWithEnd(BlockCommentConfig),
13303 }
13304
13305 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13306 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13307 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13308 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13309 .peekable();
13310
13311 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13312 row
13313 } else {
13314 return Vec::new();
13315 };
13316
13317 let language_settings = buffer.language_settings_at(selection.head(), cx);
13318 let language_scope = buffer.language_scope_at(selection.head());
13319
13320 let indent_and_prefix_for_row =
13321 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13322 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13323 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13324 &language_scope
13325 {
13326 let indent_end = Point::new(row, indent.len);
13327 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13328 let line_text_after_indent = buffer
13329 .text_for_range(indent_end..line_end)
13330 .collect::<String>();
13331
13332 let is_within_comment_override = buffer
13333 .language_scope_at(indent_end)
13334 .is_some_and(|scope| scope.override_name() == Some("comment"));
13335 let comment_delimiters = if is_within_comment_override {
13336 // we are within a comment syntax node, but we don't
13337 // yet know what kind of comment: block, doc or line
13338 match (
13339 language_scope.documentation_comment(),
13340 language_scope.block_comment(),
13341 ) {
13342 (Some(config), _) | (_, Some(config))
13343 if buffer.contains_str_at(indent_end, &config.start) =>
13344 {
13345 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13346 }
13347 (Some(config), _) | (_, Some(config))
13348 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13349 {
13350 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13351 }
13352 (Some(config), _) | (_, Some(config))
13353 if buffer.contains_str_at(indent_end, &config.prefix) =>
13354 {
13355 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13356 }
13357 (_, _) => language_scope
13358 .line_comment_prefixes()
13359 .iter()
13360 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13361 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13362 }
13363 } else {
13364 // we not in an overridden comment node, but we may
13365 // be within a non-overridden line comment node
13366 language_scope
13367 .line_comment_prefixes()
13368 .iter()
13369 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13370 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13371 };
13372
13373 let rewrap_prefix = language_scope
13374 .rewrap_prefixes()
13375 .iter()
13376 .find_map(|prefix_regex| {
13377 prefix_regex.find(&line_text_after_indent).map(|mat| {
13378 if mat.start() == 0 {
13379 Some(mat.as_str().to_string())
13380 } else {
13381 None
13382 }
13383 })
13384 })
13385 .flatten();
13386 (comment_delimiters, rewrap_prefix)
13387 } else {
13388 (None, None)
13389 };
13390 (indent, comment_prefix, rewrap_prefix)
13391 };
13392
13393 let mut ranges = Vec::new();
13394 let from_empty_selection = selection.is_empty();
13395
13396 let mut current_range_start = first_row;
13397 let mut prev_row = first_row;
13398 let (
13399 mut current_range_indent,
13400 mut current_range_comment_delimiters,
13401 mut current_range_rewrap_prefix,
13402 ) = indent_and_prefix_for_row(first_row);
13403
13404 for row in non_blank_rows_iter.skip(1) {
13405 let has_paragraph_break = row > prev_row + 1;
13406
13407 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13408 indent_and_prefix_for_row(row);
13409
13410 let has_indent_change = row_indent != current_range_indent;
13411 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13412
13413 let has_boundary_change = has_comment_change
13414 || row_rewrap_prefix.is_some()
13415 || (has_indent_change && current_range_comment_delimiters.is_some());
13416
13417 if has_paragraph_break || has_boundary_change {
13418 ranges.push((
13419 language_settings.clone(),
13420 Point::new(current_range_start, 0)
13421 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13422 current_range_indent,
13423 current_range_comment_delimiters.clone(),
13424 current_range_rewrap_prefix.clone(),
13425 from_empty_selection,
13426 ));
13427 current_range_start = row;
13428 current_range_indent = row_indent;
13429 current_range_comment_delimiters = row_comment_delimiters;
13430 current_range_rewrap_prefix = row_rewrap_prefix;
13431 }
13432 prev_row = row;
13433 }
13434
13435 ranges.push((
13436 language_settings.clone(),
13437 Point::new(current_range_start, 0)
13438 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13439 current_range_indent,
13440 current_range_comment_delimiters,
13441 current_range_rewrap_prefix,
13442 from_empty_selection,
13443 ));
13444
13445 ranges
13446 });
13447
13448 let mut edits = Vec::new();
13449 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13450
13451 for (
13452 language_settings,
13453 wrap_range,
13454 mut indent_size,
13455 comment_prefix,
13456 rewrap_prefix,
13457 from_empty_selection,
13458 ) in wrap_ranges
13459 {
13460 let mut start_row = wrap_range.start.row;
13461 let mut end_row = wrap_range.end.row;
13462
13463 // Skip selections that overlap with a range that has already been rewrapped.
13464 let selection_range = start_row..end_row;
13465 if rewrapped_row_ranges
13466 .iter()
13467 .any(|range| range.overlaps(&selection_range))
13468 {
13469 continue;
13470 }
13471
13472 let tab_size = language_settings.tab_size;
13473
13474 let (line_prefix, inside_comment) = match &comment_prefix {
13475 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13476 (Some(prefix.as_str()), true)
13477 }
13478 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13479 (Some(prefix.as_ref()), true)
13480 }
13481 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13482 start: _,
13483 end: _,
13484 prefix,
13485 tab_size,
13486 })) => {
13487 indent_size.len += tab_size;
13488 (Some(prefix.as_ref()), true)
13489 }
13490 None => (None, false),
13491 };
13492 let indent_prefix = indent_size.chars().collect::<String>();
13493 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13494
13495 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13496 RewrapBehavior::InComments => inside_comment,
13497 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13498 RewrapBehavior::Anywhere => true,
13499 };
13500
13501 let should_rewrap = options.override_language_settings
13502 || allow_rewrap_based_on_language
13503 || self.hard_wrap.is_some();
13504 if !should_rewrap {
13505 continue;
13506 }
13507
13508 if from_empty_selection {
13509 'expand_upwards: while start_row > 0 {
13510 let prev_row = start_row - 1;
13511 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13512 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13513 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13514 {
13515 start_row = prev_row;
13516 } else {
13517 break 'expand_upwards;
13518 }
13519 }
13520
13521 'expand_downwards: while end_row < buffer.max_point().row {
13522 let next_row = end_row + 1;
13523 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13524 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13525 && !buffer.is_line_blank(MultiBufferRow(next_row))
13526 {
13527 end_row = next_row;
13528 } else {
13529 break 'expand_downwards;
13530 }
13531 }
13532 }
13533
13534 let start = Point::new(start_row, 0);
13535 let start_offset = ToOffset::to_offset(&start, &buffer);
13536 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13537 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13538 let mut first_line_delimiter = None;
13539 let mut last_line_delimiter = None;
13540 let Some(lines_without_prefixes) = selection_text
13541 .lines()
13542 .enumerate()
13543 .map(|(ix, line)| {
13544 let line_trimmed = line.trim_start();
13545 if rewrap_prefix.is_some() && ix > 0 {
13546 Ok(line_trimmed)
13547 } else if let Some(
13548 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13549 start,
13550 prefix,
13551 end,
13552 tab_size,
13553 })
13554 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13555 start,
13556 prefix,
13557 end,
13558 tab_size,
13559 }),
13560 ) = &comment_prefix
13561 {
13562 let line_trimmed = line_trimmed
13563 .strip_prefix(start.as_ref())
13564 .map(|s| {
13565 let mut indent_size = indent_size;
13566 indent_size.len -= tab_size;
13567 let indent_prefix: String = indent_size.chars().collect();
13568 first_line_delimiter = Some((indent_prefix, start));
13569 s.trim_start()
13570 })
13571 .unwrap_or(line_trimmed);
13572 let line_trimmed = line_trimmed
13573 .strip_suffix(end.as_ref())
13574 .map(|s| {
13575 last_line_delimiter = Some(end);
13576 s.trim_end()
13577 })
13578 .unwrap_or(line_trimmed);
13579 let line_trimmed = line_trimmed
13580 .strip_prefix(prefix.as_ref())
13581 .unwrap_or(line_trimmed);
13582 Ok(line_trimmed)
13583 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13584 line_trimmed.strip_prefix(prefix).with_context(|| {
13585 format!("line did not start with prefix {prefix:?}: {line:?}")
13586 })
13587 } else {
13588 line_trimmed
13589 .strip_prefix(&line_prefix.trim_start())
13590 .with_context(|| {
13591 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13592 })
13593 }
13594 })
13595 .collect::<Result<Vec<_>, _>>()
13596 .log_err()
13597 else {
13598 continue;
13599 };
13600
13601 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13602 buffer
13603 .language_settings_at(Point::new(start_row, 0), cx)
13604 .preferred_line_length as usize
13605 });
13606
13607 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13608 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13609 } else {
13610 line_prefix.clone()
13611 };
13612
13613 let wrapped_text = {
13614 let mut wrapped_text = wrap_with_prefix(
13615 line_prefix,
13616 subsequent_lines_prefix,
13617 lines_without_prefixes.join("\n"),
13618 wrap_column,
13619 tab_size,
13620 options.preserve_existing_whitespace,
13621 );
13622
13623 if let Some((indent, delimiter)) = first_line_delimiter {
13624 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13625 }
13626 if let Some(last_line) = last_line_delimiter {
13627 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13628 }
13629
13630 wrapped_text
13631 };
13632
13633 // TODO: should always use char-based diff while still supporting cursor behavior that
13634 // matches vim.
13635 let mut diff_options = DiffOptions::default();
13636 if options.override_language_settings {
13637 diff_options.max_word_diff_len = 0;
13638 diff_options.max_word_diff_line_count = 0;
13639 } else {
13640 diff_options.max_word_diff_len = usize::MAX;
13641 diff_options.max_word_diff_line_count = usize::MAX;
13642 }
13643
13644 for (old_range, new_text) in
13645 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13646 {
13647 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13648 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13649 edits.push((edit_start..edit_end, new_text));
13650 }
13651
13652 rewrapped_row_ranges.push(start_row..=end_row);
13653 }
13654
13655 self.buffer
13656 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13657 }
13658
13659 pub fn cut_common(
13660 &mut self,
13661 cut_no_selection_line: bool,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) -> ClipboardItem {
13665 let mut text = String::new();
13666 let buffer = self.buffer.read(cx).snapshot(cx);
13667 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13668 let mut clipboard_selections = Vec::with_capacity(selections.len());
13669 {
13670 let max_point = buffer.max_point();
13671 let mut is_first = true;
13672 let mut prev_selection_was_entire_line = false;
13673 for selection in &mut selections {
13674 let is_entire_line =
13675 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13676 if is_entire_line {
13677 selection.start = Point::new(selection.start.row, 0);
13678 if !selection.is_empty() && selection.end.column == 0 {
13679 selection.end = cmp::min(max_point, selection.end);
13680 } else {
13681 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13682 }
13683 selection.goal = SelectionGoal::None;
13684 }
13685 if is_first {
13686 is_first = false;
13687 } else if !prev_selection_was_entire_line {
13688 text += "\n";
13689 }
13690 prev_selection_was_entire_line = is_entire_line;
13691 let mut len = 0;
13692 for chunk in buffer.text_for_range(selection.start..selection.end) {
13693 text.push_str(chunk);
13694 len += chunk.len();
13695 }
13696
13697 clipboard_selections.push(ClipboardSelection::for_buffer(
13698 len,
13699 is_entire_line,
13700 selection.range(),
13701 &buffer,
13702 self.project.as_ref(),
13703 cx,
13704 ));
13705 }
13706 }
13707
13708 self.transact(window, cx, |this, window, cx| {
13709 this.change_selections(Default::default(), window, cx, |s| {
13710 s.select(selections);
13711 });
13712 this.insert("", window, cx);
13713 });
13714 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13715 }
13716
13717 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13718 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13719 let item = self.cut_common(true, window, cx);
13720 cx.write_to_clipboard(item);
13721 }
13722
13723 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13725 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13726 s.move_with(&mut |snapshot, sel| {
13727 if sel.is_empty() {
13728 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13729 }
13730 if sel.is_empty() {
13731 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13732 }
13733 });
13734 });
13735 let item = self.cut_common(false, window, cx);
13736 cx.set_global(KillRing(item))
13737 }
13738
13739 pub fn kill_ring_yank(
13740 &mut self,
13741 _: &KillRingYank,
13742 window: &mut Window,
13743 cx: &mut Context<Self>,
13744 ) {
13745 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13746 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13747 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13748 (kill_ring.text().to_string(), kill_ring.metadata_json())
13749 } else {
13750 return;
13751 }
13752 } else {
13753 return;
13754 };
13755 self.do_paste(&text, metadata, false, window, cx);
13756 }
13757
13758 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13759 self.do_copy(true, cx);
13760 }
13761
13762 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13763 self.do_copy(false, cx);
13764 }
13765
13766 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13767 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13768 let buffer = self.buffer.read(cx).read(cx);
13769 let mut text = String::new();
13770 let mut clipboard_selections = Vec::with_capacity(selections.len());
13771
13772 let max_point = buffer.max_point();
13773 let mut is_first = true;
13774 for selection in &selections {
13775 let mut start = selection.start;
13776 let mut end = selection.end;
13777 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13778 let mut add_trailing_newline = false;
13779 if is_entire_line {
13780 start = Point::new(start.row, 0);
13781 let next_line_start = Point::new(end.row + 1, 0);
13782 if next_line_start <= max_point {
13783 end = next_line_start;
13784 } else {
13785 // We're on the last line without a trailing newline.
13786 // Copy to the end of the line and add a newline afterwards.
13787 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13788 add_trailing_newline = true;
13789 }
13790 }
13791
13792 let mut trimmed_selections = Vec::new();
13793 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13794 let row = MultiBufferRow(start.row);
13795 let first_indent = buffer.indent_size_for_line(row);
13796 if first_indent.len == 0 || start.column > first_indent.len {
13797 trimmed_selections.push(start..end);
13798 } else {
13799 trimmed_selections.push(
13800 Point::new(row.0, first_indent.len)
13801 ..Point::new(row.0, buffer.line_len(row)),
13802 );
13803 for row in start.row + 1..=end.row {
13804 let mut line_len = buffer.line_len(MultiBufferRow(row));
13805 if row == end.row {
13806 line_len = end.column;
13807 }
13808 if line_len == 0 {
13809 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13810 continue;
13811 }
13812 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13813 if row_indent_size.len >= first_indent.len {
13814 trimmed_selections
13815 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13816 } else {
13817 trimmed_selections.clear();
13818 trimmed_selections.push(start..end);
13819 break;
13820 }
13821 }
13822 }
13823 } else {
13824 trimmed_selections.push(start..end);
13825 }
13826
13827 let is_multiline_trim = trimmed_selections.len() > 1;
13828 let mut selection_len: usize = 0;
13829 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13830
13831 for trimmed_range in trimmed_selections {
13832 if is_first {
13833 is_first = false;
13834 } else if is_multiline_trim || !prev_selection_was_entire_line {
13835 text.push('\n');
13836 if is_multiline_trim {
13837 selection_len += 1;
13838 }
13839 }
13840 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13841 text.push_str(chunk);
13842 selection_len += chunk.len();
13843 }
13844 if add_trailing_newline {
13845 text.push('\n');
13846 selection_len += 1;
13847 }
13848 }
13849
13850 clipboard_selections.push(ClipboardSelection::for_buffer(
13851 selection_len,
13852 is_entire_line,
13853 start..end,
13854 &buffer,
13855 self.project.as_ref(),
13856 cx,
13857 ));
13858 }
13859
13860 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13861 text,
13862 clipboard_selections,
13863 ));
13864 }
13865
13866 pub fn do_paste(
13867 &mut self,
13868 text: &String,
13869 clipboard_selections: Option<Vec<ClipboardSelection>>,
13870 handle_entire_lines: bool,
13871 window: &mut Window,
13872 cx: &mut Context<Self>,
13873 ) {
13874 if self.read_only(cx) {
13875 return;
13876 }
13877
13878 let clipboard_text = Cow::Borrowed(text.as_str());
13879
13880 self.transact(window, cx, |this, window, cx| {
13881 let had_active_edit_prediction = this.has_active_edit_prediction();
13882 let display_map = this.display_snapshot(cx);
13883 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13884 let cursor_offset = this
13885 .selections
13886 .last::<MultiBufferOffset>(&display_map)
13887 .head();
13888
13889 if let Some(mut clipboard_selections) = clipboard_selections {
13890 let all_selections_were_entire_line =
13891 clipboard_selections.iter().all(|s| s.is_entire_line);
13892 let first_selection_indent_column =
13893 clipboard_selections.first().map(|s| s.first_line_indent);
13894 if clipboard_selections.len() != old_selections.len() {
13895 clipboard_selections.drain(..);
13896 }
13897 let mut auto_indent_on_paste = true;
13898
13899 this.buffer.update(cx, |buffer, cx| {
13900 let snapshot = buffer.read(cx);
13901 auto_indent_on_paste = snapshot
13902 .language_settings_at(cursor_offset, cx)
13903 .auto_indent_on_paste;
13904
13905 let mut start_offset = 0;
13906 let mut edits = Vec::new();
13907 let mut original_indent_columns = Vec::new();
13908 for (ix, selection) in old_selections.iter().enumerate() {
13909 let to_insert;
13910 let entire_line;
13911 let original_indent_column;
13912 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13913 let end_offset = start_offset + clipboard_selection.len;
13914 to_insert = &clipboard_text[start_offset..end_offset];
13915 entire_line = clipboard_selection.is_entire_line;
13916 start_offset = if entire_line {
13917 end_offset
13918 } else {
13919 end_offset + 1
13920 };
13921 original_indent_column = Some(clipboard_selection.first_line_indent);
13922 } else {
13923 to_insert = &*clipboard_text;
13924 entire_line = all_selections_were_entire_line;
13925 original_indent_column = first_selection_indent_column
13926 }
13927
13928 let (range, to_insert) =
13929 if selection.is_empty() && handle_entire_lines && entire_line {
13930 // If the corresponding selection was empty when this slice of the
13931 // clipboard text was written, then the entire line containing the
13932 // selection was copied. If this selection is also currently empty,
13933 // then paste the line before the current line of the buffer.
13934 let column = selection.start.to_point(&snapshot).column as usize;
13935 let line_start = selection.start - column;
13936 (line_start..line_start, Cow::Borrowed(to_insert))
13937 } else {
13938 let language = snapshot.language_at(selection.head());
13939 let range = selection.range();
13940 if let Some(language) = language
13941 && language.name() == "Markdown"
13942 {
13943 edit_for_markdown_paste(
13944 &snapshot,
13945 range,
13946 to_insert,
13947 url::Url::parse(to_insert).ok(),
13948 )
13949 } else {
13950 (range, Cow::Borrowed(to_insert))
13951 }
13952 };
13953
13954 edits.push((range, to_insert));
13955 original_indent_columns.push(original_indent_column);
13956 }
13957 drop(snapshot);
13958
13959 buffer.edit(
13960 edits,
13961 if auto_indent_on_paste {
13962 Some(AutoindentMode::Block {
13963 original_indent_columns,
13964 })
13965 } else {
13966 None
13967 },
13968 cx,
13969 );
13970 });
13971
13972 let selections = this
13973 .selections
13974 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13975 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13976 } else {
13977 let url = url::Url::parse(&clipboard_text).ok();
13978
13979 let auto_indent_mode = if !clipboard_text.is_empty() {
13980 Some(AutoindentMode::Block {
13981 original_indent_columns: Vec::new(),
13982 })
13983 } else {
13984 None
13985 };
13986
13987 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13988 let snapshot = buffer.snapshot(cx);
13989
13990 let anchors = old_selections
13991 .iter()
13992 .map(|s| {
13993 let anchor = snapshot.anchor_after(s.head());
13994 s.map(|_| anchor)
13995 })
13996 .collect::<Vec<_>>();
13997
13998 let mut edits = Vec::new();
13999
14000 // When pasting text without metadata (e.g. copied from an
14001 // external editor using multiple cursors) and the number of
14002 // lines matches the number of selections, distribute one
14003 // line per cursor instead of pasting the whole text at each.
14004 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14005 let distribute_lines =
14006 old_selections.len() > 1 && lines.len() == old_selections.len();
14007
14008 for (ix, selection) in old_selections.iter().enumerate() {
14009 let language = snapshot.language_at(selection.head());
14010 let range = selection.range();
14011
14012 let text_for_cursor: &str = if distribute_lines {
14013 lines[ix]
14014 } else {
14015 &clipboard_text
14016 };
14017
14018 let (edit_range, edit_text) = if let Some(language) = language
14019 && language.name() == "Markdown"
14020 {
14021 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14022 } else {
14023 (range, Cow::Borrowed(text_for_cursor))
14024 };
14025
14026 edits.push((edit_range, edit_text));
14027 }
14028
14029 drop(snapshot);
14030 buffer.edit(edits, auto_indent_mode, cx);
14031
14032 anchors
14033 });
14034
14035 this.change_selections(Default::default(), window, cx, |s| {
14036 s.select_anchors(selection_anchors);
14037 });
14038 }
14039
14040 // 🤔 | .. | show_in_menu |
14041 // | .. | true true
14042 // | had_edit_prediction | false true
14043
14044 let trigger_in_words =
14045 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14046
14047 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14048 });
14049 }
14050
14051 pub fn diff_clipboard_with_selection(
14052 &mut self,
14053 _: &DiffClipboardWithSelection,
14054 window: &mut Window,
14055 cx: &mut Context<Self>,
14056 ) {
14057 let selections = self
14058 .selections
14059 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14060
14061 if selections.is_empty() {
14062 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14063 return;
14064 };
14065
14066 let clipboard_text = match cx.read_from_clipboard() {
14067 Some(item) => match item.entries().first() {
14068 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
14069 _ => None,
14070 },
14071 None => None,
14072 };
14073
14074 let Some(clipboard_text) = clipboard_text else {
14075 log::warn!("Clipboard doesn't contain text.");
14076 return;
14077 };
14078
14079 window.dispatch_action(
14080 Box::new(DiffClipboardWithSelectionData {
14081 clipboard_text,
14082 editor: cx.entity(),
14083 }),
14084 cx,
14085 );
14086 }
14087
14088 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14090 if let Some(item) = cx.read_from_clipboard() {
14091 let entries = item.entries();
14092
14093 match entries.first() {
14094 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
14095 // of all the pasted entries.
14096 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
14097 .do_paste(
14098 clipboard_string.text(),
14099 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14100 true,
14101 window,
14102 cx,
14103 ),
14104 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14105 }
14106 }
14107 }
14108
14109 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14110 if self.read_only(cx) {
14111 return;
14112 }
14113
14114 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14115
14116 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14117 if let Some((selections, _)) =
14118 self.selection_history.transaction(transaction_id).cloned()
14119 {
14120 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14121 s.select_anchors(selections.to_vec());
14122 });
14123 } else {
14124 log::error!(
14125 "No entry in selection_history found for undo. \
14126 This may correspond to a bug where undo does not update the selection. \
14127 If this is occurring, please add details to \
14128 https://github.com/zed-industries/zed/issues/22692"
14129 );
14130 }
14131 self.request_autoscroll(Autoscroll::fit(), cx);
14132 self.unmark_text(window, cx);
14133 self.refresh_edit_prediction(true, false, window, cx);
14134 cx.emit(EditorEvent::Edited { transaction_id });
14135 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14136 }
14137 }
14138
14139 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14140 if self.read_only(cx) {
14141 return;
14142 }
14143
14144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14145
14146 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14147 if let Some((_, Some(selections))) =
14148 self.selection_history.transaction(transaction_id).cloned()
14149 {
14150 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14151 s.select_anchors(selections.to_vec());
14152 });
14153 } else {
14154 log::error!(
14155 "No entry in selection_history found for redo. \
14156 This may correspond to a bug where undo does not update the selection. \
14157 If this is occurring, please add details to \
14158 https://github.com/zed-industries/zed/issues/22692"
14159 );
14160 }
14161 self.request_autoscroll(Autoscroll::fit(), cx);
14162 self.unmark_text(window, cx);
14163 self.refresh_edit_prediction(true, false, window, cx);
14164 cx.emit(EditorEvent::Edited { transaction_id });
14165 }
14166 }
14167
14168 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14169 self.buffer
14170 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14171 }
14172
14173 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14174 self.buffer
14175 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14176 }
14177
14178 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14180 self.change_selections(Default::default(), window, cx, |s| {
14181 s.move_with(&mut |map, selection| {
14182 let cursor = if selection.is_empty() {
14183 movement::left(map, selection.start)
14184 } else {
14185 selection.start
14186 };
14187 selection.collapse_to(cursor, SelectionGoal::None);
14188 });
14189 })
14190 }
14191
14192 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14194 self.change_selections(Default::default(), window, cx, |s| {
14195 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14196 })
14197 }
14198
14199 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14201 self.change_selections(Default::default(), window, cx, |s| {
14202 s.move_with(&mut |map, selection| {
14203 let cursor = if selection.is_empty() {
14204 movement::right(map, selection.end)
14205 } else {
14206 selection.end
14207 };
14208 selection.collapse_to(cursor, SelectionGoal::None)
14209 });
14210 })
14211 }
14212
14213 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14215 self.change_selections(Default::default(), window, cx, |s| {
14216 s.move_heads_with(&mut |map, head, _| {
14217 (movement::right(map, head), SelectionGoal::None)
14218 });
14219 });
14220 }
14221
14222 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14223 if self.take_rename(true, window, cx).is_some() {
14224 return;
14225 }
14226
14227 if self.mode.is_single_line() {
14228 cx.propagate();
14229 return;
14230 }
14231
14232 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14233
14234 let text_layout_details = &self.text_layout_details(window, cx);
14235 let selection_count = self.selections.count();
14236 let first_selection = self.selections.first_anchor();
14237
14238 self.change_selections(Default::default(), window, cx, |s| {
14239 s.move_with(&mut |map, selection| {
14240 if !selection.is_empty() {
14241 selection.goal = SelectionGoal::None;
14242 }
14243 let (cursor, goal) = movement::up(
14244 map,
14245 selection.start,
14246 selection.goal,
14247 false,
14248 text_layout_details,
14249 );
14250 selection.collapse_to(cursor, goal);
14251 });
14252 });
14253
14254 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14255 {
14256 cx.propagate();
14257 }
14258 }
14259
14260 pub fn move_up_by_lines(
14261 &mut self,
14262 action: &MoveUpByLines,
14263 window: &mut Window,
14264 cx: &mut Context<Self>,
14265 ) {
14266 if self.take_rename(true, window, cx).is_some() {
14267 return;
14268 }
14269
14270 if self.mode.is_single_line() {
14271 cx.propagate();
14272 return;
14273 }
14274
14275 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14276
14277 let text_layout_details = &self.text_layout_details(window, cx);
14278
14279 self.change_selections(Default::default(), window, cx, |s| {
14280 s.move_with(&mut |map, selection| {
14281 if !selection.is_empty() {
14282 selection.goal = SelectionGoal::None;
14283 }
14284 let (cursor, goal) = movement::up_by_rows(
14285 map,
14286 selection.start,
14287 action.lines,
14288 selection.goal,
14289 false,
14290 text_layout_details,
14291 );
14292 selection.collapse_to(cursor, goal);
14293 });
14294 })
14295 }
14296
14297 pub fn move_down_by_lines(
14298 &mut self,
14299 action: &MoveDownByLines,
14300 window: &mut Window,
14301 cx: &mut Context<Self>,
14302 ) {
14303 if self.take_rename(true, window, cx).is_some() {
14304 return;
14305 }
14306
14307 if self.mode.is_single_line() {
14308 cx.propagate();
14309 return;
14310 }
14311
14312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14313
14314 let text_layout_details = &self.text_layout_details(window, cx);
14315
14316 self.change_selections(Default::default(), window, cx, |s| {
14317 s.move_with(&mut |map, selection| {
14318 if !selection.is_empty() {
14319 selection.goal = SelectionGoal::None;
14320 }
14321 let (cursor, goal) = movement::down_by_rows(
14322 map,
14323 selection.start,
14324 action.lines,
14325 selection.goal,
14326 false,
14327 text_layout_details,
14328 );
14329 selection.collapse_to(cursor, goal);
14330 });
14331 })
14332 }
14333
14334 pub fn select_down_by_lines(
14335 &mut self,
14336 action: &SelectDownByLines,
14337 window: &mut Window,
14338 cx: &mut Context<Self>,
14339 ) {
14340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14341 let text_layout_details = &self.text_layout_details(window, cx);
14342 self.change_selections(Default::default(), window, cx, |s| {
14343 s.move_heads_with(&mut |map, head, goal| {
14344 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14345 })
14346 })
14347 }
14348
14349 pub fn select_up_by_lines(
14350 &mut self,
14351 action: &SelectUpByLines,
14352 window: &mut Window,
14353 cx: &mut Context<Self>,
14354 ) {
14355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14356 let text_layout_details = &self.text_layout_details(window, cx);
14357 self.change_selections(Default::default(), window, cx, |s| {
14358 s.move_heads_with(&mut |map, head, goal| {
14359 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14360 })
14361 })
14362 }
14363
14364 pub fn select_page_up(
14365 &mut self,
14366 _: &SelectPageUp,
14367 window: &mut Window,
14368 cx: &mut Context<Self>,
14369 ) {
14370 let Some(row_count) = self.visible_row_count() else {
14371 return;
14372 };
14373
14374 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14375
14376 let text_layout_details = &self.text_layout_details(window, cx);
14377
14378 self.change_selections(Default::default(), window, cx, |s| {
14379 s.move_heads_with(&mut |map, head, goal| {
14380 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14381 })
14382 })
14383 }
14384
14385 pub fn move_page_up(
14386 &mut self,
14387 action: &MovePageUp,
14388 window: &mut Window,
14389 cx: &mut Context<Self>,
14390 ) {
14391 if self.take_rename(true, window, cx).is_some() {
14392 return;
14393 }
14394
14395 if self
14396 .context_menu
14397 .borrow_mut()
14398 .as_mut()
14399 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14400 .unwrap_or(false)
14401 {
14402 return;
14403 }
14404
14405 if matches!(self.mode, EditorMode::SingleLine) {
14406 cx.propagate();
14407 return;
14408 }
14409
14410 let Some(row_count) = self.visible_row_count() else {
14411 return;
14412 };
14413
14414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14415
14416 let effects = if action.center_cursor {
14417 SelectionEffects::scroll(Autoscroll::center())
14418 } else {
14419 SelectionEffects::default()
14420 };
14421
14422 let text_layout_details = &self.text_layout_details(window, cx);
14423
14424 self.change_selections(effects, window, cx, |s| {
14425 s.move_with(&mut |map, selection| {
14426 if !selection.is_empty() {
14427 selection.goal = SelectionGoal::None;
14428 }
14429 let (cursor, goal) = movement::up_by_rows(
14430 map,
14431 selection.end,
14432 row_count,
14433 selection.goal,
14434 false,
14435 text_layout_details,
14436 );
14437 selection.collapse_to(cursor, goal);
14438 });
14439 });
14440 }
14441
14442 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14444 let text_layout_details = &self.text_layout_details(window, cx);
14445 self.change_selections(Default::default(), window, cx, |s| {
14446 s.move_heads_with(&mut |map, head, goal| {
14447 movement::up(map, head, goal, false, text_layout_details)
14448 })
14449 })
14450 }
14451
14452 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14453 self.take_rename(true, window, cx);
14454
14455 if self.mode.is_single_line() {
14456 cx.propagate();
14457 return;
14458 }
14459
14460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14461
14462 let text_layout_details = &self.text_layout_details(window, cx);
14463 let selection_count = self.selections.count();
14464 let first_selection = self.selections.first_anchor();
14465
14466 self.change_selections(Default::default(), window, cx, |s| {
14467 s.move_with(&mut |map, selection| {
14468 if !selection.is_empty() {
14469 selection.goal = SelectionGoal::None;
14470 }
14471 let (cursor, goal) = movement::down(
14472 map,
14473 selection.end,
14474 selection.goal,
14475 false,
14476 text_layout_details,
14477 );
14478 selection.collapse_to(cursor, goal);
14479 });
14480 });
14481
14482 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14483 {
14484 cx.propagate();
14485 }
14486 }
14487
14488 pub fn select_page_down(
14489 &mut self,
14490 _: &SelectPageDown,
14491 window: &mut Window,
14492 cx: &mut Context<Self>,
14493 ) {
14494 let Some(row_count) = self.visible_row_count() else {
14495 return;
14496 };
14497
14498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14499
14500 let text_layout_details = &self.text_layout_details(window, cx);
14501
14502 self.change_selections(Default::default(), window, cx, |s| {
14503 s.move_heads_with(&mut |map, head, goal| {
14504 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14505 })
14506 })
14507 }
14508
14509 pub fn move_page_down(
14510 &mut self,
14511 action: &MovePageDown,
14512 window: &mut Window,
14513 cx: &mut Context<Self>,
14514 ) {
14515 if self.take_rename(true, window, cx).is_some() {
14516 return;
14517 }
14518
14519 if self
14520 .context_menu
14521 .borrow_mut()
14522 .as_mut()
14523 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14524 .unwrap_or(false)
14525 {
14526 return;
14527 }
14528
14529 if matches!(self.mode, EditorMode::SingleLine) {
14530 cx.propagate();
14531 return;
14532 }
14533
14534 let Some(row_count) = self.visible_row_count() else {
14535 return;
14536 };
14537
14538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14539
14540 let effects = if action.center_cursor {
14541 SelectionEffects::scroll(Autoscroll::center())
14542 } else {
14543 SelectionEffects::default()
14544 };
14545
14546 let text_layout_details = &self.text_layout_details(window, cx);
14547 self.change_selections(effects, window, cx, |s| {
14548 s.move_with(&mut |map, selection| {
14549 if !selection.is_empty() {
14550 selection.goal = SelectionGoal::None;
14551 }
14552 let (cursor, goal) = movement::down_by_rows(
14553 map,
14554 selection.end,
14555 row_count,
14556 selection.goal,
14557 false,
14558 text_layout_details,
14559 );
14560 selection.collapse_to(cursor, goal);
14561 });
14562 });
14563 }
14564
14565 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14567 let text_layout_details = &self.text_layout_details(window, cx);
14568 self.change_selections(Default::default(), window, cx, |s| {
14569 s.move_heads_with(&mut |map, head, goal| {
14570 movement::down(map, head, goal, false, text_layout_details)
14571 })
14572 });
14573 }
14574
14575 pub fn context_menu_first(
14576 &mut self,
14577 _: &ContextMenuFirst,
14578 window: &mut Window,
14579 cx: &mut Context<Self>,
14580 ) {
14581 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14582 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14583 }
14584 }
14585
14586 pub fn context_menu_prev(
14587 &mut self,
14588 _: &ContextMenuPrevious,
14589 window: &mut Window,
14590 cx: &mut Context<Self>,
14591 ) {
14592 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14593 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14594 }
14595 }
14596
14597 pub fn context_menu_next(
14598 &mut self,
14599 _: &ContextMenuNext,
14600 window: &mut Window,
14601 cx: &mut Context<Self>,
14602 ) {
14603 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14604 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14605 }
14606 }
14607
14608 pub fn context_menu_last(
14609 &mut self,
14610 _: &ContextMenuLast,
14611 window: &mut Window,
14612 cx: &mut Context<Self>,
14613 ) {
14614 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14615 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14616 }
14617 }
14618
14619 pub fn signature_help_prev(
14620 &mut self,
14621 _: &SignatureHelpPrevious,
14622 _: &mut Window,
14623 cx: &mut Context<Self>,
14624 ) {
14625 if let Some(popover) = self.signature_help_state.popover_mut() {
14626 if popover.current_signature == 0 {
14627 popover.current_signature = popover.signatures.len() - 1;
14628 } else {
14629 popover.current_signature -= 1;
14630 }
14631 cx.notify();
14632 }
14633 }
14634
14635 pub fn signature_help_next(
14636 &mut self,
14637 _: &SignatureHelpNext,
14638 _: &mut Window,
14639 cx: &mut Context<Self>,
14640 ) {
14641 if let Some(popover) = self.signature_help_state.popover_mut() {
14642 if popover.current_signature + 1 == popover.signatures.len() {
14643 popover.current_signature = 0;
14644 } else {
14645 popover.current_signature += 1;
14646 }
14647 cx.notify();
14648 }
14649 }
14650
14651 pub fn move_to_previous_word_start(
14652 &mut self,
14653 _: &MoveToPreviousWordStart,
14654 window: &mut Window,
14655 cx: &mut Context<Self>,
14656 ) {
14657 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14658 self.change_selections(Default::default(), window, cx, |s| {
14659 s.move_cursors_with(&mut |map, head, _| {
14660 (
14661 movement::previous_word_start(map, head),
14662 SelectionGoal::None,
14663 )
14664 });
14665 })
14666 }
14667
14668 pub fn move_to_previous_subword_start(
14669 &mut self,
14670 _: &MoveToPreviousSubwordStart,
14671 window: &mut Window,
14672 cx: &mut Context<Self>,
14673 ) {
14674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14675 self.change_selections(Default::default(), window, cx, |s| {
14676 s.move_cursors_with(&mut |map, head, _| {
14677 (
14678 movement::previous_subword_start(map, head),
14679 SelectionGoal::None,
14680 )
14681 });
14682 })
14683 }
14684
14685 pub fn select_to_previous_word_start(
14686 &mut self,
14687 _: &SelectToPreviousWordStart,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 ) {
14691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14692 self.change_selections(Default::default(), window, cx, |s| {
14693 s.move_heads_with(&mut |map, head, _| {
14694 (
14695 movement::previous_word_start(map, head),
14696 SelectionGoal::None,
14697 )
14698 });
14699 })
14700 }
14701
14702 pub fn select_to_previous_subword_start(
14703 &mut self,
14704 _: &SelectToPreviousSubwordStart,
14705 window: &mut Window,
14706 cx: &mut Context<Self>,
14707 ) {
14708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14709 self.change_selections(Default::default(), window, cx, |s| {
14710 s.move_heads_with(&mut |map, head, _| {
14711 (
14712 movement::previous_subword_start(map, head),
14713 SelectionGoal::None,
14714 )
14715 });
14716 })
14717 }
14718
14719 pub fn delete_to_previous_word_start(
14720 &mut self,
14721 action: &DeleteToPreviousWordStart,
14722 window: &mut Window,
14723 cx: &mut Context<Self>,
14724 ) {
14725 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14726 self.transact(window, cx, |this, window, cx| {
14727 this.select_autoclose_pair(window, cx);
14728 this.change_selections(Default::default(), window, cx, |s| {
14729 s.move_with(&mut |map, selection| {
14730 if selection.is_empty() {
14731 let mut cursor = if action.ignore_newlines {
14732 movement::previous_word_start(map, selection.head())
14733 } else {
14734 movement::previous_word_start_or_newline(map, selection.head())
14735 };
14736 cursor = movement::adjust_greedy_deletion(
14737 map,
14738 selection.head(),
14739 cursor,
14740 action.ignore_brackets,
14741 );
14742 selection.set_head(cursor, SelectionGoal::None);
14743 }
14744 });
14745 });
14746 this.insert("", window, cx);
14747 });
14748 }
14749
14750 pub fn delete_to_previous_subword_start(
14751 &mut self,
14752 action: &DeleteToPreviousSubwordStart,
14753 window: &mut Window,
14754 cx: &mut Context<Self>,
14755 ) {
14756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14757 self.transact(window, cx, |this, window, cx| {
14758 this.select_autoclose_pair(window, cx);
14759 this.change_selections(Default::default(), window, cx, |s| {
14760 s.move_with(&mut |map, selection| {
14761 if selection.is_empty() {
14762 let mut cursor = if action.ignore_newlines {
14763 movement::previous_subword_start(map, selection.head())
14764 } else {
14765 movement::previous_subword_start_or_newline(map, selection.head())
14766 };
14767 cursor = movement::adjust_greedy_deletion(
14768 map,
14769 selection.head(),
14770 cursor,
14771 action.ignore_brackets,
14772 );
14773 selection.set_head(cursor, SelectionGoal::None);
14774 }
14775 });
14776 });
14777 this.insert("", window, cx);
14778 });
14779 }
14780
14781 pub fn move_to_next_word_end(
14782 &mut self,
14783 _: &MoveToNextWordEnd,
14784 window: &mut Window,
14785 cx: &mut Context<Self>,
14786 ) {
14787 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14788 self.change_selections(Default::default(), window, cx, |s| {
14789 s.move_cursors_with(&mut |map, head, _| {
14790 (movement::next_word_end(map, head), SelectionGoal::None)
14791 });
14792 })
14793 }
14794
14795 pub fn move_to_next_subword_end(
14796 &mut self,
14797 _: &MoveToNextSubwordEnd,
14798 window: &mut Window,
14799 cx: &mut Context<Self>,
14800 ) {
14801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14802 self.change_selections(Default::default(), window, cx, |s| {
14803 s.move_cursors_with(&mut |map, head, _| {
14804 (movement::next_subword_end(map, head), SelectionGoal::None)
14805 });
14806 })
14807 }
14808
14809 pub fn select_to_next_word_end(
14810 &mut self,
14811 _: &SelectToNextWordEnd,
14812 window: &mut Window,
14813 cx: &mut Context<Self>,
14814 ) {
14815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14816 self.change_selections(Default::default(), window, cx, |s| {
14817 s.move_heads_with(&mut |map, head, _| {
14818 (movement::next_word_end(map, head), SelectionGoal::None)
14819 });
14820 })
14821 }
14822
14823 pub fn select_to_next_subword_end(
14824 &mut self,
14825 _: &SelectToNextSubwordEnd,
14826 window: &mut Window,
14827 cx: &mut Context<Self>,
14828 ) {
14829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14830 self.change_selections(Default::default(), window, cx, |s| {
14831 s.move_heads_with(&mut |map, head, _| {
14832 (movement::next_subword_end(map, head), SelectionGoal::None)
14833 });
14834 })
14835 }
14836
14837 pub fn delete_to_next_word_end(
14838 &mut self,
14839 action: &DeleteToNextWordEnd,
14840 window: &mut Window,
14841 cx: &mut Context<Self>,
14842 ) {
14843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14844 self.transact(window, cx, |this, window, cx| {
14845 this.change_selections(Default::default(), window, cx, |s| {
14846 s.move_with(&mut |map, selection| {
14847 if selection.is_empty() {
14848 let mut cursor = if action.ignore_newlines {
14849 movement::next_word_end(map, selection.head())
14850 } else {
14851 movement::next_word_end_or_newline(map, selection.head())
14852 };
14853 cursor = movement::adjust_greedy_deletion(
14854 map,
14855 selection.head(),
14856 cursor,
14857 action.ignore_brackets,
14858 );
14859 selection.set_head(cursor, SelectionGoal::None);
14860 }
14861 });
14862 });
14863 this.insert("", window, cx);
14864 });
14865 }
14866
14867 pub fn delete_to_next_subword_end(
14868 &mut self,
14869 action: &DeleteToNextSubwordEnd,
14870 window: &mut Window,
14871 cx: &mut Context<Self>,
14872 ) {
14873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14874 self.transact(window, cx, |this, window, cx| {
14875 this.change_selections(Default::default(), window, cx, |s| {
14876 s.move_with(&mut |map, selection| {
14877 if selection.is_empty() {
14878 let mut cursor = if action.ignore_newlines {
14879 movement::next_subword_end(map, selection.head())
14880 } else {
14881 movement::next_subword_end_or_newline(map, selection.head())
14882 };
14883 cursor = movement::adjust_greedy_deletion(
14884 map,
14885 selection.head(),
14886 cursor,
14887 action.ignore_brackets,
14888 );
14889 selection.set_head(cursor, SelectionGoal::None);
14890 }
14891 });
14892 });
14893 this.insert("", window, cx);
14894 });
14895 }
14896
14897 pub fn move_to_beginning_of_line(
14898 &mut self,
14899 action: &MoveToBeginningOfLine,
14900 window: &mut Window,
14901 cx: &mut Context<Self>,
14902 ) {
14903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14904 self.change_selections(Default::default(), window, cx, |s| {
14905 s.move_cursors_with(&mut |map, head, _| {
14906 (
14907 movement::indented_line_beginning(
14908 map,
14909 head,
14910 action.stop_at_soft_wraps,
14911 action.stop_at_indent,
14912 ),
14913 SelectionGoal::None,
14914 )
14915 });
14916 })
14917 }
14918
14919 pub fn select_to_beginning_of_line(
14920 &mut self,
14921 action: &SelectToBeginningOfLine,
14922 window: &mut Window,
14923 cx: &mut Context<Self>,
14924 ) {
14925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14926 self.change_selections(Default::default(), window, cx, |s| {
14927 s.move_heads_with(&mut |map, head, _| {
14928 (
14929 movement::indented_line_beginning(
14930 map,
14931 head,
14932 action.stop_at_soft_wraps,
14933 action.stop_at_indent,
14934 ),
14935 SelectionGoal::None,
14936 )
14937 });
14938 });
14939 }
14940
14941 pub fn delete_to_beginning_of_line(
14942 &mut self,
14943 action: &DeleteToBeginningOfLine,
14944 window: &mut Window,
14945 cx: &mut Context<Self>,
14946 ) {
14947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14948 self.transact(window, cx, |this, window, cx| {
14949 this.change_selections(Default::default(), window, cx, |s| {
14950 s.move_with(&mut |_, selection| {
14951 selection.reversed = true;
14952 });
14953 });
14954
14955 this.select_to_beginning_of_line(
14956 &SelectToBeginningOfLine {
14957 stop_at_soft_wraps: false,
14958 stop_at_indent: action.stop_at_indent,
14959 },
14960 window,
14961 cx,
14962 );
14963 this.backspace(&Backspace, window, cx);
14964 });
14965 }
14966
14967 pub fn move_to_end_of_line(
14968 &mut self,
14969 action: &MoveToEndOfLine,
14970 window: &mut Window,
14971 cx: &mut Context<Self>,
14972 ) {
14973 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14974 self.change_selections(Default::default(), window, cx, |s| {
14975 s.move_cursors_with(&mut |map, head, _| {
14976 (
14977 movement::line_end(map, head, action.stop_at_soft_wraps),
14978 SelectionGoal::None,
14979 )
14980 });
14981 })
14982 }
14983
14984 pub fn select_to_end_of_line(
14985 &mut self,
14986 action: &SelectToEndOfLine,
14987 window: &mut Window,
14988 cx: &mut Context<Self>,
14989 ) {
14990 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14991 self.change_selections(Default::default(), window, cx, |s| {
14992 s.move_heads_with(&mut |map, head, _| {
14993 (
14994 movement::line_end(map, head, action.stop_at_soft_wraps),
14995 SelectionGoal::None,
14996 )
14997 });
14998 })
14999 }
15000
15001 pub fn delete_to_end_of_line(
15002 &mut self,
15003 _: &DeleteToEndOfLine,
15004 window: &mut Window,
15005 cx: &mut Context<Self>,
15006 ) {
15007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15008 self.transact(window, cx, |this, window, cx| {
15009 this.select_to_end_of_line(
15010 &SelectToEndOfLine {
15011 stop_at_soft_wraps: false,
15012 },
15013 window,
15014 cx,
15015 );
15016 this.delete(&Delete, window, cx);
15017 });
15018 }
15019
15020 pub fn cut_to_end_of_line(
15021 &mut self,
15022 action: &CutToEndOfLine,
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.select_to_end_of_line(
15029 &SelectToEndOfLine {
15030 stop_at_soft_wraps: false,
15031 },
15032 window,
15033 cx,
15034 );
15035 if !action.stop_at_newlines {
15036 this.change_selections(Default::default(), window, cx, |s| {
15037 s.move_with(&mut |_, sel| {
15038 if sel.is_empty() {
15039 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15040 }
15041 });
15042 });
15043 }
15044 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15045 let item = this.cut_common(false, window, cx);
15046 cx.write_to_clipboard(item);
15047 });
15048 }
15049
15050 pub fn move_to_start_of_paragraph(
15051 &mut self,
15052 _: &MoveToStartOfParagraph,
15053 window: &mut Window,
15054 cx: &mut Context<Self>,
15055 ) {
15056 if matches!(self.mode, EditorMode::SingleLine) {
15057 cx.propagate();
15058 return;
15059 }
15060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15061 self.change_selections(Default::default(), window, cx, |s| {
15062 s.move_with(&mut |map, selection| {
15063 selection.collapse_to(
15064 movement::start_of_paragraph(map, selection.head(), 1),
15065 SelectionGoal::None,
15066 )
15067 });
15068 })
15069 }
15070
15071 pub fn move_to_end_of_paragraph(
15072 &mut self,
15073 _: &MoveToEndOfParagraph,
15074 window: &mut Window,
15075 cx: &mut Context<Self>,
15076 ) {
15077 if matches!(self.mode, EditorMode::SingleLine) {
15078 cx.propagate();
15079 return;
15080 }
15081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15082 self.change_selections(Default::default(), window, cx, |s| {
15083 s.move_with(&mut |map, selection| {
15084 selection.collapse_to(
15085 movement::end_of_paragraph(map, selection.head(), 1),
15086 SelectionGoal::None,
15087 )
15088 });
15089 })
15090 }
15091
15092 pub fn select_to_start_of_paragraph(
15093 &mut self,
15094 _: &SelectToStartOfParagraph,
15095 window: &mut Window,
15096 cx: &mut Context<Self>,
15097 ) {
15098 if matches!(self.mode, EditorMode::SingleLine) {
15099 cx.propagate();
15100 return;
15101 }
15102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15103 self.change_selections(Default::default(), window, cx, |s| {
15104 s.move_heads_with(&mut |map, head, _| {
15105 (
15106 movement::start_of_paragraph(map, head, 1),
15107 SelectionGoal::None,
15108 )
15109 });
15110 })
15111 }
15112
15113 pub fn select_to_end_of_paragraph(
15114 &mut self,
15115 _: &SelectToEndOfParagraph,
15116 window: &mut Window,
15117 cx: &mut Context<Self>,
15118 ) {
15119 if matches!(self.mode, EditorMode::SingleLine) {
15120 cx.propagate();
15121 return;
15122 }
15123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15124 self.change_selections(Default::default(), window, cx, |s| {
15125 s.move_heads_with(&mut |map, head, _| {
15126 (
15127 movement::end_of_paragraph(map, head, 1),
15128 SelectionGoal::None,
15129 )
15130 });
15131 })
15132 }
15133
15134 pub fn move_to_start_of_excerpt(
15135 &mut self,
15136 _: &MoveToStartOfExcerpt,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) {
15140 if matches!(self.mode, EditorMode::SingleLine) {
15141 cx.propagate();
15142 return;
15143 }
15144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15145 self.change_selections(Default::default(), window, cx, |s| {
15146 s.move_with(&mut |map, selection| {
15147 selection.collapse_to(
15148 movement::start_of_excerpt(
15149 map,
15150 selection.head(),
15151 workspace::searchable::Direction::Prev,
15152 ),
15153 SelectionGoal::None,
15154 )
15155 });
15156 })
15157 }
15158
15159 pub fn move_to_start_of_next_excerpt(
15160 &mut self,
15161 _: &MoveToStartOfNextExcerpt,
15162 window: &mut Window,
15163 cx: &mut Context<Self>,
15164 ) {
15165 if matches!(self.mode, EditorMode::SingleLine) {
15166 cx.propagate();
15167 return;
15168 }
15169
15170 self.change_selections(Default::default(), window, cx, |s| {
15171 s.move_with(&mut |map, selection| {
15172 selection.collapse_to(
15173 movement::start_of_excerpt(
15174 map,
15175 selection.head(),
15176 workspace::searchable::Direction::Next,
15177 ),
15178 SelectionGoal::None,
15179 )
15180 });
15181 })
15182 }
15183
15184 pub fn move_to_end_of_excerpt(
15185 &mut self,
15186 _: &MoveToEndOfExcerpt,
15187 window: &mut Window,
15188 cx: &mut Context<Self>,
15189 ) {
15190 if matches!(self.mode, EditorMode::SingleLine) {
15191 cx.propagate();
15192 return;
15193 }
15194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15195 self.change_selections(Default::default(), window, cx, |s| {
15196 s.move_with(&mut |map, selection| {
15197 selection.collapse_to(
15198 movement::end_of_excerpt(
15199 map,
15200 selection.head(),
15201 workspace::searchable::Direction::Next,
15202 ),
15203 SelectionGoal::None,
15204 )
15205 });
15206 })
15207 }
15208
15209 pub fn move_to_end_of_previous_excerpt(
15210 &mut self,
15211 _: &MoveToEndOfPreviousExcerpt,
15212 window: &mut Window,
15213 cx: &mut Context<Self>,
15214 ) {
15215 if matches!(self.mode, EditorMode::SingleLine) {
15216 cx.propagate();
15217 return;
15218 }
15219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15220 self.change_selections(Default::default(), window, cx, |s| {
15221 s.move_with(&mut |map, selection| {
15222 selection.collapse_to(
15223 movement::end_of_excerpt(
15224 map,
15225 selection.head(),
15226 workspace::searchable::Direction::Prev,
15227 ),
15228 SelectionGoal::None,
15229 )
15230 });
15231 })
15232 }
15233
15234 pub fn select_to_start_of_excerpt(
15235 &mut self,
15236 _: &SelectToStartOfExcerpt,
15237 window: &mut Window,
15238 cx: &mut Context<Self>,
15239 ) {
15240 if matches!(self.mode, EditorMode::SingleLine) {
15241 cx.propagate();
15242 return;
15243 }
15244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15245 self.change_selections(Default::default(), window, cx, |s| {
15246 s.move_heads_with(&mut |map, head, _| {
15247 (
15248 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15249 SelectionGoal::None,
15250 )
15251 });
15252 })
15253 }
15254
15255 pub fn select_to_start_of_next_excerpt(
15256 &mut self,
15257 _: &SelectToStartOfNextExcerpt,
15258 window: &mut Window,
15259 cx: &mut Context<Self>,
15260 ) {
15261 if matches!(self.mode, EditorMode::SingleLine) {
15262 cx.propagate();
15263 return;
15264 }
15265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15266 self.change_selections(Default::default(), window, cx, |s| {
15267 s.move_heads_with(&mut |map, head, _| {
15268 (
15269 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15270 SelectionGoal::None,
15271 )
15272 });
15273 })
15274 }
15275
15276 pub fn select_to_end_of_excerpt(
15277 &mut self,
15278 _: &SelectToEndOfExcerpt,
15279 window: &mut Window,
15280 cx: &mut Context<Self>,
15281 ) {
15282 if matches!(self.mode, EditorMode::SingleLine) {
15283 cx.propagate();
15284 return;
15285 }
15286 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15287 self.change_selections(Default::default(), window, cx, |s| {
15288 s.move_heads_with(&mut |map, head, _| {
15289 (
15290 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15291 SelectionGoal::None,
15292 )
15293 });
15294 })
15295 }
15296
15297 pub fn select_to_end_of_previous_excerpt(
15298 &mut self,
15299 _: &SelectToEndOfPreviousExcerpt,
15300 window: &mut Window,
15301 cx: &mut Context<Self>,
15302 ) {
15303 if matches!(self.mode, EditorMode::SingleLine) {
15304 cx.propagate();
15305 return;
15306 }
15307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15308 self.change_selections(Default::default(), window, cx, |s| {
15309 s.move_heads_with(&mut |map, head, _| {
15310 (
15311 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15312 SelectionGoal::None,
15313 )
15314 });
15315 })
15316 }
15317
15318 pub fn move_to_beginning(
15319 &mut self,
15320 _: &MoveToBeginning,
15321 window: &mut Window,
15322 cx: &mut Context<Self>,
15323 ) {
15324 if matches!(self.mode, EditorMode::SingleLine) {
15325 cx.propagate();
15326 return;
15327 }
15328 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15329 self.change_selections(Default::default(), window, cx, |s| {
15330 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15331 });
15332 }
15333
15334 pub fn select_to_beginning(
15335 &mut self,
15336 _: &SelectToBeginning,
15337 window: &mut Window,
15338 cx: &mut Context<Self>,
15339 ) {
15340 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15341 selection.set_head(Point::zero(), SelectionGoal::None);
15342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15343 self.change_selections(Default::default(), window, cx, |s| {
15344 s.select(vec![selection]);
15345 });
15346 }
15347
15348 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15349 if matches!(self.mode, EditorMode::SingleLine) {
15350 cx.propagate();
15351 return;
15352 }
15353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15354 let cursor = self.buffer.read(cx).read(cx).len();
15355 self.change_selections(Default::default(), window, cx, |s| {
15356 s.select_ranges(vec![cursor..cursor])
15357 });
15358 }
15359
15360 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15361 self.nav_history = nav_history;
15362 }
15363
15364 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15365 self.nav_history.as_ref()
15366 }
15367
15368 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15369 self.push_to_nav_history(
15370 self.selections.newest_anchor().head(),
15371 None,
15372 false,
15373 true,
15374 cx,
15375 );
15376 }
15377
15378 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15379 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15380 let buffer = self.buffer.read(cx).read(cx);
15381 let cursor_position = cursor_anchor.to_point(&buffer);
15382 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15383 let scroll_top_row = scroll_anchor.top_row(&buffer);
15384 drop(buffer);
15385
15386 NavigationData {
15387 cursor_anchor,
15388 cursor_position,
15389 scroll_anchor,
15390 scroll_top_row,
15391 }
15392 }
15393
15394 fn navigation_entry(
15395 &self,
15396 cursor_anchor: Anchor,
15397 cx: &mut Context<Self>,
15398 ) -> Option<NavigationEntry> {
15399 let Some(history) = self.nav_history.clone() else {
15400 return None;
15401 };
15402 let data = self.navigation_data(cursor_anchor, cx);
15403 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15404 }
15405
15406 fn push_to_nav_history(
15407 &mut self,
15408 cursor_anchor: Anchor,
15409 new_position: Option<Point>,
15410 is_deactivate: bool,
15411 always: bool,
15412 cx: &mut Context<Self>,
15413 ) {
15414 let data = self.navigation_data(cursor_anchor, cx);
15415 if let Some(nav_history) = self.nav_history.as_mut() {
15416 if let Some(new_position) = new_position {
15417 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15418 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15419 return;
15420 }
15421 }
15422
15423 nav_history.push(Some(data), cx);
15424 cx.emit(EditorEvent::PushedToNavHistory {
15425 anchor: cursor_anchor,
15426 is_deactivate,
15427 })
15428 }
15429 }
15430
15431 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15433 let buffer = self.buffer.read(cx).snapshot(cx);
15434 let mut selection = self
15435 .selections
15436 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15437 selection.set_head(buffer.len(), SelectionGoal::None);
15438 self.change_selections(Default::default(), window, cx, |s| {
15439 s.select(vec![selection]);
15440 });
15441 }
15442
15443 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15445 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15446 s.select_ranges([Anchor::min()..Anchor::max()]);
15447 });
15448 }
15449
15450 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15452 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15453 let mut selections = self.selections.all::<Point>(&display_map);
15454 let max_point = display_map.buffer_snapshot().max_point();
15455 for selection in &mut selections {
15456 let rows = selection.spanned_rows(true, &display_map);
15457 selection.start = Point::new(rows.start.0, 0);
15458 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15459 selection.reversed = false;
15460 }
15461 self.change_selections(Default::default(), window, cx, |s| {
15462 s.select(selections);
15463 });
15464 }
15465
15466 pub fn split_selection_into_lines(
15467 &mut self,
15468 action: &SplitSelectionIntoLines,
15469 window: &mut Window,
15470 cx: &mut Context<Self>,
15471 ) {
15472 let selections = self
15473 .selections
15474 .all::<Point>(&self.display_snapshot(cx))
15475 .into_iter()
15476 .map(|selection| selection.start..selection.end)
15477 .collect::<Vec<_>>();
15478 self.unfold_ranges(&selections, true, false, cx);
15479
15480 let mut new_selection_ranges = Vec::new();
15481 {
15482 let buffer = self.buffer.read(cx).read(cx);
15483 for selection in selections {
15484 for row in selection.start.row..selection.end.row {
15485 let line_start = Point::new(row, 0);
15486 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15487
15488 if action.keep_selections {
15489 // Keep the selection range for each line
15490 let selection_start = if row == selection.start.row {
15491 selection.start
15492 } else {
15493 line_start
15494 };
15495 new_selection_ranges.push(selection_start..line_end);
15496 } else {
15497 // Collapse to cursor at end of line
15498 new_selection_ranges.push(line_end..line_end);
15499 }
15500 }
15501
15502 let is_multiline_selection = selection.start.row != selection.end.row;
15503 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15504 // so this action feels more ergonomic when paired with other selection operations
15505 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15506 if !should_skip_last {
15507 if action.keep_selections {
15508 if is_multiline_selection {
15509 let line_start = Point::new(selection.end.row, 0);
15510 new_selection_ranges.push(line_start..selection.end);
15511 } else {
15512 new_selection_ranges.push(selection.start..selection.end);
15513 }
15514 } else {
15515 new_selection_ranges.push(selection.end..selection.end);
15516 }
15517 }
15518 }
15519 }
15520 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15521 s.select_ranges(new_selection_ranges);
15522 });
15523 }
15524
15525 pub fn add_selection_above(
15526 &mut self,
15527 action: &AddSelectionAbove,
15528 window: &mut Window,
15529 cx: &mut Context<Self>,
15530 ) {
15531 self.add_selection(true, action.skip_soft_wrap, window, cx);
15532 }
15533
15534 pub fn add_selection_below(
15535 &mut self,
15536 action: &AddSelectionBelow,
15537 window: &mut Window,
15538 cx: &mut Context<Self>,
15539 ) {
15540 self.add_selection(false, action.skip_soft_wrap, window, cx);
15541 }
15542
15543 fn add_selection(
15544 &mut self,
15545 above: bool,
15546 skip_soft_wrap: bool,
15547 window: &mut Window,
15548 cx: &mut Context<Self>,
15549 ) {
15550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15551
15552 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15553 let all_selections = self.selections.all::<Point>(&display_map);
15554 let text_layout_details = self.text_layout_details(window, cx);
15555
15556 let (mut columnar_selections, new_selections_to_columnarize) = {
15557 if let Some(state) = self.add_selections_state.as_ref() {
15558 let columnar_selection_ids: HashSet<_> = state
15559 .groups
15560 .iter()
15561 .flat_map(|group| group.stack.iter())
15562 .copied()
15563 .collect();
15564
15565 all_selections
15566 .into_iter()
15567 .partition(|s| columnar_selection_ids.contains(&s.id))
15568 } else {
15569 (Vec::new(), all_selections)
15570 }
15571 };
15572
15573 let mut state = self
15574 .add_selections_state
15575 .take()
15576 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15577
15578 for selection in new_selections_to_columnarize {
15579 let range = selection.display_range(&display_map).sorted();
15580 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15581 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15582 let positions = start_x.min(end_x)..start_x.max(end_x);
15583 let mut stack = Vec::new();
15584 for row in range.start.row().0..=range.end.row().0 {
15585 if let Some(selection) = self.selections.build_columnar_selection(
15586 &display_map,
15587 DisplayRow(row),
15588 &positions,
15589 selection.reversed,
15590 &text_layout_details,
15591 ) {
15592 stack.push(selection.id);
15593 columnar_selections.push(selection);
15594 }
15595 }
15596 if !stack.is_empty() {
15597 if above {
15598 stack.reverse();
15599 }
15600 state.groups.push(AddSelectionsGroup { above, stack });
15601 }
15602 }
15603
15604 let mut final_selections = Vec::new();
15605 let end_row = if above {
15606 DisplayRow(0)
15607 } else {
15608 display_map.max_point().row()
15609 };
15610
15611 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15612 // positions to place new selections, so we need to keep track of the
15613 // column range of the oldest selection in each group, because
15614 // intermediate selections may have been clamped to shorter lines.
15615 // selections may have been clamped to shorter lines.
15616 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15617 let mut map = HashMap::default();
15618 for group in state.groups.iter() {
15619 if let Some(oldest_id) = group.stack.first() {
15620 if let Some(oldest_selection) =
15621 columnar_selections.iter().find(|s| s.id == *oldest_id)
15622 {
15623 let start_col = oldest_selection.start.column;
15624 let end_col = oldest_selection.end.column;
15625 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15626 for id in &group.stack {
15627 map.insert(*id, goal_columns.clone());
15628 }
15629 }
15630 }
15631 }
15632 map
15633 } else {
15634 HashMap::default()
15635 };
15636
15637 let mut last_added_item_per_group = HashMap::default();
15638 for group in state.groups.iter_mut() {
15639 if let Some(last_id) = group.stack.last() {
15640 last_added_item_per_group.insert(*last_id, group);
15641 }
15642 }
15643
15644 for selection in columnar_selections {
15645 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15646 if above == group.above {
15647 let range = selection.display_range(&display_map).sorted();
15648 debug_assert_eq!(range.start.row(), range.end.row());
15649 let row = range.start.row();
15650 let positions =
15651 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15652 Pixels::from(start)..Pixels::from(end)
15653 } else {
15654 let start_x =
15655 display_map.x_for_display_point(range.start, &text_layout_details);
15656 let end_x =
15657 display_map.x_for_display_point(range.end, &text_layout_details);
15658 start_x.min(end_x)..start_x.max(end_x)
15659 };
15660
15661 let maybe_new_selection = if skip_soft_wrap {
15662 let goal_columns = goal_columns_by_selection_id
15663 .remove(&selection.id)
15664 .unwrap_or_else(|| {
15665 let start_col = selection.start.column;
15666 let end_col = selection.end.column;
15667 start_col.min(end_col)..start_col.max(end_col)
15668 });
15669 self.selections.find_next_columnar_selection_by_buffer_row(
15670 &display_map,
15671 row,
15672 end_row,
15673 above,
15674 &goal_columns,
15675 selection.reversed,
15676 &text_layout_details,
15677 )
15678 } else {
15679 self.selections.find_next_columnar_selection_by_display_row(
15680 &display_map,
15681 row,
15682 end_row,
15683 above,
15684 &positions,
15685 selection.reversed,
15686 &text_layout_details,
15687 )
15688 };
15689
15690 if let Some(new_selection) = maybe_new_selection {
15691 group.stack.push(new_selection.id);
15692 if above {
15693 final_selections.push(new_selection);
15694 final_selections.push(selection);
15695 } else {
15696 final_selections.push(selection);
15697 final_selections.push(new_selection);
15698 }
15699 } else {
15700 final_selections.push(selection);
15701 }
15702 } else {
15703 group.stack.pop();
15704 }
15705 } else {
15706 final_selections.push(selection);
15707 }
15708 }
15709
15710 self.change_selections(Default::default(), window, cx, |s| {
15711 s.select(final_selections);
15712 });
15713
15714 let final_selection_ids: HashSet<_> = self
15715 .selections
15716 .all::<Point>(&display_map)
15717 .iter()
15718 .map(|s| s.id)
15719 .collect();
15720 state.groups.retain_mut(|group| {
15721 // selections might get merged above so we remove invalid items from stacks
15722 group.stack.retain(|id| final_selection_ids.contains(id));
15723
15724 // single selection in stack can be treated as initial state
15725 group.stack.len() > 1
15726 });
15727
15728 if !state.groups.is_empty() {
15729 self.add_selections_state = Some(state);
15730 }
15731 }
15732
15733 pub fn insert_snippet_at_selections(
15734 &mut self,
15735 action: &InsertSnippet,
15736 window: &mut Window,
15737 cx: &mut Context<Self>,
15738 ) {
15739 self.try_insert_snippet_at_selections(action, window, cx)
15740 .log_err();
15741 }
15742
15743 fn try_insert_snippet_at_selections(
15744 &mut self,
15745 action: &InsertSnippet,
15746 window: &mut Window,
15747 cx: &mut Context<Self>,
15748 ) -> Result<()> {
15749 let insertion_ranges = self
15750 .selections
15751 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15752 .into_iter()
15753 .map(|selection| selection.range())
15754 .collect_vec();
15755
15756 let snippet = if let Some(snippet_body) = &action.snippet {
15757 if action.language.is_none() && action.name.is_none() {
15758 Snippet::parse(snippet_body)?
15759 } else {
15760 bail!("`snippet` is mutually exclusive with `language` and `name`")
15761 }
15762 } else if let Some(name) = &action.name {
15763 let project = self.project().context("no project")?;
15764 let snippet_store = project.read(cx).snippets().read(cx);
15765 let snippet = snippet_store
15766 .snippets_for(action.language.clone(), cx)
15767 .into_iter()
15768 .find(|snippet| snippet.name == *name)
15769 .context("snippet not found")?;
15770 Snippet::parse(&snippet.body)?
15771 } else {
15772 // todo(andrew): open modal to select snippet
15773 bail!("`name` or `snippet` is required")
15774 };
15775
15776 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15777 }
15778
15779 fn select_match_ranges(
15780 &mut self,
15781 range: Range<MultiBufferOffset>,
15782 reversed: bool,
15783 replace_newest: bool,
15784 auto_scroll: Option<Autoscroll>,
15785 window: &mut Window,
15786 cx: &mut Context<Editor>,
15787 ) {
15788 self.unfold_ranges(
15789 std::slice::from_ref(&range),
15790 false,
15791 auto_scroll.is_some(),
15792 cx,
15793 );
15794 let effects = if let Some(scroll) = auto_scroll {
15795 SelectionEffects::scroll(scroll)
15796 } else {
15797 SelectionEffects::no_scroll()
15798 };
15799 self.change_selections(effects, window, cx, |s| {
15800 if replace_newest {
15801 s.delete(s.newest_anchor().id);
15802 }
15803 if reversed {
15804 s.insert_range(range.end..range.start);
15805 } else {
15806 s.insert_range(range);
15807 }
15808 });
15809 }
15810
15811 pub fn select_next_match_internal(
15812 &mut self,
15813 display_map: &DisplaySnapshot,
15814 replace_newest: bool,
15815 autoscroll: Option<Autoscroll>,
15816 window: &mut Window,
15817 cx: &mut Context<Self>,
15818 ) -> Result<()> {
15819 let buffer = display_map.buffer_snapshot();
15820 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15821 if let Some(mut select_next_state) = self.select_next_state.take() {
15822 let query = &select_next_state.query;
15823 if !select_next_state.done {
15824 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15825 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15826 let mut next_selected_range = None;
15827
15828 let bytes_after_last_selection =
15829 buffer.bytes_in_range(last_selection.end..buffer.len());
15830 let bytes_before_first_selection =
15831 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15832 let query_matches = query
15833 .stream_find_iter(bytes_after_last_selection)
15834 .map(|result| (last_selection.end, result))
15835 .chain(
15836 query
15837 .stream_find_iter(bytes_before_first_selection)
15838 .map(|result| (MultiBufferOffset(0), result)),
15839 );
15840
15841 for (start_offset, query_match) in query_matches {
15842 let query_match = query_match.unwrap(); // can only fail due to I/O
15843 let offset_range =
15844 start_offset + query_match.start()..start_offset + query_match.end();
15845
15846 if !select_next_state.wordwise
15847 || (!buffer.is_inside_word(offset_range.start, None)
15848 && !buffer.is_inside_word(offset_range.end, None))
15849 {
15850 let idx = selections
15851 .partition_point(|selection| selection.end <= offset_range.start);
15852 let overlaps = selections
15853 .get(idx)
15854 .map_or(false, |selection| selection.start < offset_range.end);
15855
15856 if !overlaps {
15857 next_selected_range = Some(offset_range);
15858 break;
15859 }
15860 }
15861 }
15862
15863 if let Some(next_selected_range) = next_selected_range {
15864 self.select_match_ranges(
15865 next_selected_range,
15866 last_selection.reversed,
15867 replace_newest,
15868 autoscroll,
15869 window,
15870 cx,
15871 );
15872 } else {
15873 select_next_state.done = true;
15874 }
15875 }
15876
15877 self.select_next_state = Some(select_next_state);
15878 } else {
15879 let mut only_carets = true;
15880 let mut same_text_selected = true;
15881 let mut selected_text = None;
15882
15883 let mut selections_iter = selections.iter().peekable();
15884 while let Some(selection) = selections_iter.next() {
15885 if selection.start != selection.end {
15886 only_carets = false;
15887 }
15888
15889 if same_text_selected {
15890 if selected_text.is_none() {
15891 selected_text =
15892 Some(buffer.text_for_range(selection.range()).collect::<String>());
15893 }
15894
15895 if let Some(next_selection) = selections_iter.peek() {
15896 if next_selection.len() == selection.len() {
15897 let next_selected_text = buffer
15898 .text_for_range(next_selection.range())
15899 .collect::<String>();
15900 if Some(next_selected_text) != selected_text {
15901 same_text_selected = false;
15902 selected_text = None;
15903 }
15904 } else {
15905 same_text_selected = false;
15906 selected_text = None;
15907 }
15908 }
15909 }
15910 }
15911
15912 if only_carets {
15913 for selection in &mut selections {
15914 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15915 selection.start = word_range.start;
15916 selection.end = word_range.end;
15917 selection.goal = SelectionGoal::None;
15918 selection.reversed = false;
15919 self.select_match_ranges(
15920 selection.start..selection.end,
15921 selection.reversed,
15922 replace_newest,
15923 autoscroll,
15924 window,
15925 cx,
15926 );
15927 }
15928
15929 if selections.len() == 1 {
15930 let selection = selections
15931 .last()
15932 .expect("ensured that there's only one selection");
15933 let query = buffer
15934 .text_for_range(selection.start..selection.end)
15935 .collect::<String>();
15936 let is_empty = query.is_empty();
15937 let select_state = SelectNextState {
15938 query: self.build_query(&[query], cx)?,
15939 wordwise: true,
15940 done: is_empty,
15941 };
15942 self.select_next_state = Some(select_state);
15943 } else {
15944 self.select_next_state = None;
15945 }
15946 } else if let Some(selected_text) = selected_text {
15947 self.select_next_state = Some(SelectNextState {
15948 query: self.build_query(&[selected_text], cx)?,
15949 wordwise: false,
15950 done: false,
15951 });
15952 self.select_next_match_internal(
15953 display_map,
15954 replace_newest,
15955 autoscroll,
15956 window,
15957 cx,
15958 )?;
15959 }
15960 }
15961 Ok(())
15962 }
15963
15964 pub fn select_all_matches(
15965 &mut self,
15966 _action: &SelectAllMatches,
15967 window: &mut Window,
15968 cx: &mut Context<Self>,
15969 ) -> Result<()> {
15970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15971
15972 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15973
15974 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15975 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15976 else {
15977 return Ok(());
15978 };
15979
15980 let mut new_selections = Vec::new();
15981
15982 let reversed = self
15983 .selections
15984 .oldest::<MultiBufferOffset>(&display_map)
15985 .reversed;
15986 let buffer = display_map.buffer_snapshot();
15987 let query_matches = select_next_state
15988 .query
15989 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15990
15991 for query_match in query_matches.into_iter() {
15992 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15993 let offset_range = if reversed {
15994 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15995 } else {
15996 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15997 };
15998
15999 if !select_next_state.wordwise
16000 || (!buffer.is_inside_word(offset_range.start, None)
16001 && !buffer.is_inside_word(offset_range.end, None))
16002 {
16003 new_selections.push(offset_range.start..offset_range.end);
16004 }
16005 }
16006
16007 select_next_state.done = true;
16008
16009 if new_selections.is_empty() {
16010 log::error!("bug: new_selections is empty in select_all_matches");
16011 return Ok(());
16012 }
16013
16014 self.unfold_ranges(&new_selections, false, false, cx);
16015 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16016 selections.select_ranges(new_selections)
16017 });
16018
16019 Ok(())
16020 }
16021
16022 pub fn select_next(
16023 &mut self,
16024 action: &SelectNext,
16025 window: &mut Window,
16026 cx: &mut Context<Self>,
16027 ) -> Result<()> {
16028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16030 self.select_next_match_internal(
16031 &display_map,
16032 action.replace_newest,
16033 Some(Autoscroll::newest()),
16034 window,
16035 cx,
16036 )
16037 }
16038
16039 pub fn select_previous(
16040 &mut self,
16041 action: &SelectPrevious,
16042 window: &mut Window,
16043 cx: &mut Context<Self>,
16044 ) -> Result<()> {
16045 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16046 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16047 let buffer = display_map.buffer_snapshot();
16048 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16049 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16050 let query = &select_prev_state.query;
16051 if !select_prev_state.done {
16052 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16053 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16054 let mut next_selected_range = None;
16055 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16056 let bytes_before_last_selection =
16057 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16058 let bytes_after_first_selection =
16059 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16060 let query_matches = query
16061 .stream_find_iter(bytes_before_last_selection)
16062 .map(|result| (last_selection.start, result))
16063 .chain(
16064 query
16065 .stream_find_iter(bytes_after_first_selection)
16066 .map(|result| (buffer.len(), result)),
16067 );
16068 for (end_offset, query_match) in query_matches {
16069 let query_match = query_match.unwrap(); // can only fail due to I/O
16070 let offset_range =
16071 end_offset - query_match.end()..end_offset - query_match.start();
16072
16073 if !select_prev_state.wordwise
16074 || (!buffer.is_inside_word(offset_range.start, None)
16075 && !buffer.is_inside_word(offset_range.end, None))
16076 {
16077 next_selected_range = Some(offset_range);
16078 break;
16079 }
16080 }
16081
16082 if let Some(next_selected_range) = next_selected_range {
16083 self.select_match_ranges(
16084 next_selected_range,
16085 last_selection.reversed,
16086 action.replace_newest,
16087 Some(Autoscroll::newest()),
16088 window,
16089 cx,
16090 );
16091 } else {
16092 select_prev_state.done = true;
16093 }
16094 }
16095
16096 self.select_prev_state = Some(select_prev_state);
16097 } else {
16098 let mut only_carets = true;
16099 let mut same_text_selected = true;
16100 let mut selected_text = None;
16101
16102 let mut selections_iter = selections.iter().peekable();
16103 while let Some(selection) = selections_iter.next() {
16104 if selection.start != selection.end {
16105 only_carets = false;
16106 }
16107
16108 if same_text_selected {
16109 if selected_text.is_none() {
16110 selected_text =
16111 Some(buffer.text_for_range(selection.range()).collect::<String>());
16112 }
16113
16114 if let Some(next_selection) = selections_iter.peek() {
16115 if next_selection.len() == selection.len() {
16116 let next_selected_text = buffer
16117 .text_for_range(next_selection.range())
16118 .collect::<String>();
16119 if Some(next_selected_text) != selected_text {
16120 same_text_selected = false;
16121 selected_text = None;
16122 }
16123 } else {
16124 same_text_selected = false;
16125 selected_text = None;
16126 }
16127 }
16128 }
16129 }
16130
16131 if only_carets {
16132 for selection in &mut selections {
16133 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16134 selection.start = word_range.start;
16135 selection.end = word_range.end;
16136 selection.goal = SelectionGoal::None;
16137 selection.reversed = false;
16138 self.select_match_ranges(
16139 selection.start..selection.end,
16140 selection.reversed,
16141 action.replace_newest,
16142 Some(Autoscroll::newest()),
16143 window,
16144 cx,
16145 );
16146 }
16147 if selections.len() == 1 {
16148 let selection = selections
16149 .last()
16150 .expect("ensured that there's only one selection");
16151 let query = buffer
16152 .text_for_range(selection.start..selection.end)
16153 .collect::<String>();
16154 let is_empty = query.is_empty();
16155 let select_state = SelectNextState {
16156 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16157 wordwise: true,
16158 done: is_empty,
16159 };
16160 self.select_prev_state = Some(select_state);
16161 } else {
16162 self.select_prev_state = None;
16163 }
16164 } else if let Some(selected_text) = selected_text {
16165 self.select_prev_state = Some(SelectNextState {
16166 query: self
16167 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16168 wordwise: false,
16169 done: false,
16170 });
16171 self.select_previous(action, window, cx)?;
16172 }
16173 }
16174 Ok(())
16175 }
16176
16177 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16178 /// setting the case sensitivity based on the global
16179 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16180 /// editor's settings.
16181 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16182 where
16183 I: IntoIterator<Item = P>,
16184 P: AsRef<[u8]>,
16185 {
16186 let case_sensitive = self
16187 .select_next_is_case_sensitive
16188 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16189
16190 let mut builder = AhoCorasickBuilder::new();
16191 builder.ascii_case_insensitive(!case_sensitive);
16192 builder.build(patterns)
16193 }
16194
16195 pub fn find_next_match(
16196 &mut self,
16197 _: &FindNextMatch,
16198 window: &mut Window,
16199 cx: &mut Context<Self>,
16200 ) -> Result<()> {
16201 let selections = self.selections.disjoint_anchors_arc();
16202 match selections.first() {
16203 Some(first) if selections.len() >= 2 => {
16204 self.change_selections(Default::default(), window, cx, |s| {
16205 s.select_ranges([first.range()]);
16206 });
16207 }
16208 _ => self.select_next(
16209 &SelectNext {
16210 replace_newest: true,
16211 },
16212 window,
16213 cx,
16214 )?,
16215 }
16216 Ok(())
16217 }
16218
16219 pub fn find_previous_match(
16220 &mut self,
16221 _: &FindPreviousMatch,
16222 window: &mut Window,
16223 cx: &mut Context<Self>,
16224 ) -> Result<()> {
16225 let selections = self.selections.disjoint_anchors_arc();
16226 match selections.last() {
16227 Some(last) if selections.len() >= 2 => {
16228 self.change_selections(Default::default(), window, cx, |s| {
16229 s.select_ranges([last.range()]);
16230 });
16231 }
16232 _ => self.select_previous(
16233 &SelectPrevious {
16234 replace_newest: true,
16235 },
16236 window,
16237 cx,
16238 )?,
16239 }
16240 Ok(())
16241 }
16242
16243 pub fn toggle_comments(
16244 &mut self,
16245 action: &ToggleComments,
16246 window: &mut Window,
16247 cx: &mut Context<Self>,
16248 ) {
16249 if self.read_only(cx) {
16250 return;
16251 }
16252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16253 let text_layout_details = &self.text_layout_details(window, cx);
16254 self.transact(window, cx, |this, window, cx| {
16255 let mut selections = this
16256 .selections
16257 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16258 let mut edits = Vec::new();
16259 let mut selection_edit_ranges = Vec::new();
16260 let mut last_toggled_row = None;
16261 let snapshot = this.buffer.read(cx).read(cx);
16262 let empty_str: Arc<str> = Arc::default();
16263 let mut suffixes_inserted = Vec::new();
16264 let ignore_indent = action.ignore_indent;
16265
16266 fn comment_prefix_range(
16267 snapshot: &MultiBufferSnapshot,
16268 row: MultiBufferRow,
16269 comment_prefix: &str,
16270 comment_prefix_whitespace: &str,
16271 ignore_indent: bool,
16272 ) -> Range<Point> {
16273 let indent_size = if ignore_indent {
16274 0
16275 } else {
16276 snapshot.indent_size_for_line(row).len
16277 };
16278
16279 let start = Point::new(row.0, indent_size);
16280
16281 let mut line_bytes = snapshot
16282 .bytes_in_range(start..snapshot.max_point())
16283 .flatten()
16284 .copied();
16285
16286 // If this line currently begins with the line comment prefix, then record
16287 // the range containing the prefix.
16288 if line_bytes
16289 .by_ref()
16290 .take(comment_prefix.len())
16291 .eq(comment_prefix.bytes())
16292 {
16293 // Include any whitespace that matches the comment prefix.
16294 let matching_whitespace_len = line_bytes
16295 .zip(comment_prefix_whitespace.bytes())
16296 .take_while(|(a, b)| a == b)
16297 .count() as u32;
16298 let end = Point::new(
16299 start.row,
16300 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16301 );
16302 start..end
16303 } else {
16304 start..start
16305 }
16306 }
16307
16308 fn comment_suffix_range(
16309 snapshot: &MultiBufferSnapshot,
16310 row: MultiBufferRow,
16311 comment_suffix: &str,
16312 comment_suffix_has_leading_space: bool,
16313 ) -> Range<Point> {
16314 let end = Point::new(row.0, snapshot.line_len(row));
16315 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16316
16317 let mut line_end_bytes = snapshot
16318 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16319 .flatten()
16320 .copied();
16321
16322 let leading_space_len = if suffix_start_column > 0
16323 && line_end_bytes.next() == Some(b' ')
16324 && comment_suffix_has_leading_space
16325 {
16326 1
16327 } else {
16328 0
16329 };
16330
16331 // If this line currently begins with the line comment prefix, then record
16332 // the range containing the prefix.
16333 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16334 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16335 start..end
16336 } else {
16337 end..end
16338 }
16339 }
16340
16341 // TODO: Handle selections that cross excerpts
16342 for selection in &mut selections {
16343 let start_column = snapshot
16344 .indent_size_for_line(MultiBufferRow(selection.start.row))
16345 .len;
16346 let language = if let Some(language) =
16347 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16348 {
16349 language
16350 } else {
16351 continue;
16352 };
16353
16354 selection_edit_ranges.clear();
16355
16356 // If multiple selections contain a given row, avoid processing that
16357 // row more than once.
16358 let mut start_row = MultiBufferRow(selection.start.row);
16359 if last_toggled_row == Some(start_row) {
16360 start_row = start_row.next_row();
16361 }
16362 let end_row =
16363 if selection.end.row > selection.start.row && selection.end.column == 0 {
16364 MultiBufferRow(selection.end.row - 1)
16365 } else {
16366 MultiBufferRow(selection.end.row)
16367 };
16368 last_toggled_row = Some(end_row);
16369
16370 if start_row > end_row {
16371 continue;
16372 }
16373
16374 // If the language has line comments, toggle those.
16375 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16376
16377 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16378 if ignore_indent {
16379 full_comment_prefixes = full_comment_prefixes
16380 .into_iter()
16381 .map(|s| Arc::from(s.trim_end()))
16382 .collect();
16383 }
16384
16385 if !full_comment_prefixes.is_empty() {
16386 let first_prefix = full_comment_prefixes
16387 .first()
16388 .expect("prefixes is non-empty");
16389 let prefix_trimmed_lengths = full_comment_prefixes
16390 .iter()
16391 .map(|p| p.trim_end_matches(' ').len())
16392 .collect::<SmallVec<[usize; 4]>>();
16393
16394 let mut all_selection_lines_are_comments = true;
16395
16396 for row in start_row.0..=end_row.0 {
16397 let row = MultiBufferRow(row);
16398 if start_row < end_row && snapshot.is_line_blank(row) {
16399 continue;
16400 }
16401
16402 let prefix_range = full_comment_prefixes
16403 .iter()
16404 .zip(prefix_trimmed_lengths.iter().copied())
16405 .map(|(prefix, trimmed_prefix_len)| {
16406 comment_prefix_range(
16407 snapshot.deref(),
16408 row,
16409 &prefix[..trimmed_prefix_len],
16410 &prefix[trimmed_prefix_len..],
16411 ignore_indent,
16412 )
16413 })
16414 .max_by_key(|range| range.end.column - range.start.column)
16415 .expect("prefixes is non-empty");
16416
16417 if prefix_range.is_empty() {
16418 all_selection_lines_are_comments = false;
16419 }
16420
16421 selection_edit_ranges.push(prefix_range);
16422 }
16423
16424 if all_selection_lines_are_comments {
16425 edits.extend(
16426 selection_edit_ranges
16427 .iter()
16428 .cloned()
16429 .map(|range| (range, empty_str.clone())),
16430 );
16431 } else {
16432 let min_column = selection_edit_ranges
16433 .iter()
16434 .map(|range| range.start.column)
16435 .min()
16436 .unwrap_or(0);
16437 edits.extend(selection_edit_ranges.iter().map(|range| {
16438 let position = Point::new(range.start.row, min_column);
16439 (position..position, first_prefix.clone())
16440 }));
16441 }
16442 } else if let Some(BlockCommentConfig {
16443 start: full_comment_prefix,
16444 end: comment_suffix,
16445 ..
16446 }) = language.block_comment()
16447 {
16448 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16449 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16450 let prefix_range = comment_prefix_range(
16451 snapshot.deref(),
16452 start_row,
16453 comment_prefix,
16454 comment_prefix_whitespace,
16455 ignore_indent,
16456 );
16457 let suffix_range = comment_suffix_range(
16458 snapshot.deref(),
16459 end_row,
16460 comment_suffix.trim_start_matches(' '),
16461 comment_suffix.starts_with(' '),
16462 );
16463
16464 if prefix_range.is_empty() || suffix_range.is_empty() {
16465 edits.push((
16466 prefix_range.start..prefix_range.start,
16467 full_comment_prefix.clone(),
16468 ));
16469 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16470 suffixes_inserted.push((end_row, comment_suffix.len()));
16471 } else {
16472 edits.push((prefix_range, empty_str.clone()));
16473 edits.push((suffix_range, empty_str.clone()));
16474 }
16475 } else {
16476 continue;
16477 }
16478 }
16479
16480 drop(snapshot);
16481 this.buffer.update(cx, |buffer, cx| {
16482 buffer.edit(edits, None, cx);
16483 });
16484
16485 // Adjust selections so that they end before any comment suffixes that
16486 // were inserted.
16487 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16488 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16489 let snapshot = this.buffer.read(cx).read(cx);
16490 for selection in &mut selections {
16491 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16492 match row.cmp(&MultiBufferRow(selection.end.row)) {
16493 Ordering::Less => {
16494 suffixes_inserted.next();
16495 continue;
16496 }
16497 Ordering::Greater => break,
16498 Ordering::Equal => {
16499 if selection.end.column == snapshot.line_len(row) {
16500 if selection.is_empty() {
16501 selection.start.column -= suffix_len as u32;
16502 }
16503 selection.end.column -= suffix_len as u32;
16504 }
16505 break;
16506 }
16507 }
16508 }
16509 }
16510
16511 drop(snapshot);
16512 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16513
16514 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16515 let selections_on_single_row = selections.windows(2).all(|selections| {
16516 selections[0].start.row == selections[1].start.row
16517 && selections[0].end.row == selections[1].end.row
16518 && selections[0].start.row == selections[0].end.row
16519 });
16520 let selections_selecting = selections
16521 .iter()
16522 .any(|selection| selection.start != selection.end);
16523 let advance_downwards = action.advance_downwards
16524 && selections_on_single_row
16525 && !selections_selecting
16526 && !matches!(this.mode, EditorMode::SingleLine);
16527
16528 if advance_downwards {
16529 let snapshot = this.buffer.read(cx).snapshot(cx);
16530
16531 this.change_selections(Default::default(), window, cx, |s| {
16532 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16533 let mut point = display_point.to_point(display_snapshot);
16534 point.row += 1;
16535 point = snapshot.clip_point(point, Bias::Left);
16536 let display_point = point.to_display_point(display_snapshot);
16537 let goal = SelectionGoal::HorizontalPosition(
16538 display_snapshot
16539 .x_for_display_point(display_point, text_layout_details)
16540 .into(),
16541 );
16542 (display_point, goal)
16543 })
16544 });
16545 }
16546 });
16547 }
16548
16549 pub fn select_enclosing_symbol(
16550 &mut self,
16551 _: &SelectEnclosingSymbol,
16552 window: &mut Window,
16553 cx: &mut Context<Self>,
16554 ) {
16555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16556
16557 let buffer = self.buffer.read(cx).snapshot(cx);
16558 let old_selections = self
16559 .selections
16560 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16561 .into_boxed_slice();
16562
16563 fn update_selection(
16564 selection: &Selection<MultiBufferOffset>,
16565 buffer_snap: &MultiBufferSnapshot,
16566 ) -> Option<Selection<MultiBufferOffset>> {
16567 let cursor = selection.head();
16568 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16569 for symbol in symbols.iter().rev() {
16570 let start = symbol.range.start.to_offset(buffer_snap);
16571 let end = symbol.range.end.to_offset(buffer_snap);
16572 let new_range = start..end;
16573 if start < selection.start || end > selection.end {
16574 return Some(Selection {
16575 id: selection.id,
16576 start: new_range.start,
16577 end: new_range.end,
16578 goal: SelectionGoal::None,
16579 reversed: selection.reversed,
16580 });
16581 }
16582 }
16583 None
16584 }
16585
16586 let mut selected_larger_symbol = false;
16587 let new_selections = old_selections
16588 .iter()
16589 .map(|selection| match update_selection(selection, &buffer) {
16590 Some(new_selection) => {
16591 if new_selection.range() != selection.range() {
16592 selected_larger_symbol = true;
16593 }
16594 new_selection
16595 }
16596 None => selection.clone(),
16597 })
16598 .collect::<Vec<_>>();
16599
16600 if selected_larger_symbol {
16601 self.change_selections(Default::default(), window, cx, |s| {
16602 s.select(new_selections);
16603 });
16604 }
16605 }
16606
16607 pub fn select_larger_syntax_node(
16608 &mut self,
16609 _: &SelectLargerSyntaxNode,
16610 window: &mut Window,
16611 cx: &mut Context<Self>,
16612 ) {
16613 let Some(visible_row_count) = self.visible_row_count() else {
16614 return;
16615 };
16616 let old_selections: Box<[_]> = self
16617 .selections
16618 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16619 .into();
16620 if old_selections.is_empty() {
16621 return;
16622 }
16623
16624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16625
16626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16627 let buffer = self.buffer.read(cx).snapshot(cx);
16628
16629 let mut selected_larger_node = false;
16630 let mut new_selections = old_selections
16631 .iter()
16632 .map(|selection| {
16633 let old_range = selection.start..selection.end;
16634
16635 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16636 // manually select word at selection
16637 if ["string_content", "inline"].contains(&node.kind()) {
16638 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16639 // ignore if word is already selected
16640 if !word_range.is_empty() && old_range != word_range {
16641 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16642 // only select word if start and end point belongs to same word
16643 if word_range == last_word_range {
16644 selected_larger_node = true;
16645 return Selection {
16646 id: selection.id,
16647 start: word_range.start,
16648 end: word_range.end,
16649 goal: SelectionGoal::None,
16650 reversed: selection.reversed,
16651 };
16652 }
16653 }
16654 }
16655 }
16656
16657 let mut new_range = old_range.clone();
16658 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16659 new_range = range;
16660 if !node.is_named() {
16661 continue;
16662 }
16663 if !display_map.intersects_fold(new_range.start)
16664 && !display_map.intersects_fold(new_range.end)
16665 {
16666 break;
16667 }
16668 }
16669
16670 selected_larger_node |= new_range != old_range;
16671 Selection {
16672 id: selection.id,
16673 start: new_range.start,
16674 end: new_range.end,
16675 goal: SelectionGoal::None,
16676 reversed: selection.reversed,
16677 }
16678 })
16679 .collect::<Vec<_>>();
16680
16681 if !selected_larger_node {
16682 return; // don't put this call in the history
16683 }
16684
16685 // scroll based on transformation done to the last selection created by the user
16686 let (last_old, last_new) = old_selections
16687 .last()
16688 .zip(new_selections.last().cloned())
16689 .expect("old_selections isn't empty");
16690
16691 // revert selection
16692 let is_selection_reversed = {
16693 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16694 new_selections.last_mut().expect("checked above").reversed =
16695 should_newest_selection_be_reversed;
16696 should_newest_selection_be_reversed
16697 };
16698
16699 if selected_larger_node {
16700 self.select_syntax_node_history.disable_clearing = true;
16701 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16702 s.select(new_selections.clone());
16703 });
16704 self.select_syntax_node_history.disable_clearing = false;
16705 }
16706
16707 let start_row = last_new.start.to_display_point(&display_map).row().0;
16708 let end_row = last_new.end.to_display_point(&display_map).row().0;
16709 let selection_height = end_row - start_row + 1;
16710 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16711
16712 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16713 let scroll_behavior = if fits_on_the_screen {
16714 self.request_autoscroll(Autoscroll::fit(), cx);
16715 SelectSyntaxNodeScrollBehavior::FitSelection
16716 } else if is_selection_reversed {
16717 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16718 SelectSyntaxNodeScrollBehavior::CursorTop
16719 } else {
16720 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16721 SelectSyntaxNodeScrollBehavior::CursorBottom
16722 };
16723
16724 let old_selections: Box<[Selection<Anchor>]> = old_selections
16725 .iter()
16726 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16727 .collect();
16728 self.select_syntax_node_history.push((
16729 old_selections,
16730 scroll_behavior,
16731 is_selection_reversed,
16732 ));
16733 }
16734
16735 pub fn select_smaller_syntax_node(
16736 &mut self,
16737 _: &SelectSmallerSyntaxNode,
16738 window: &mut Window,
16739 cx: &mut Context<Self>,
16740 ) {
16741 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16742
16743 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16744 self.select_syntax_node_history.pop()
16745 {
16746 if let Some(selection) = selections.last_mut() {
16747 selection.reversed = is_selection_reversed;
16748 }
16749
16750 let snapshot = self.buffer.read(cx).snapshot(cx);
16751 let selections: Vec<Selection<MultiBufferOffset>> = selections
16752 .iter()
16753 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16754 .collect();
16755
16756 self.select_syntax_node_history.disable_clearing = true;
16757 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16758 s.select(selections);
16759 });
16760 self.select_syntax_node_history.disable_clearing = false;
16761
16762 match scroll_behavior {
16763 SelectSyntaxNodeScrollBehavior::CursorTop => {
16764 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16765 }
16766 SelectSyntaxNodeScrollBehavior::FitSelection => {
16767 self.request_autoscroll(Autoscroll::fit(), cx);
16768 }
16769 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16770 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16771 }
16772 }
16773 }
16774 }
16775
16776 pub fn unwrap_syntax_node(
16777 &mut self,
16778 _: &UnwrapSyntaxNode,
16779 window: &mut Window,
16780 cx: &mut Context<Self>,
16781 ) {
16782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16783
16784 let buffer = self.buffer.read(cx).snapshot(cx);
16785 let selections = self
16786 .selections
16787 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16788 .into_iter()
16789 // subtracting the offset requires sorting
16790 .sorted_by_key(|i| i.start);
16791
16792 let full_edits = selections
16793 .into_iter()
16794 .filter_map(|selection| {
16795 let child = if selection.is_empty()
16796 && let Some((_, ancestor_range)) =
16797 buffer.syntax_ancestor(selection.start..selection.end)
16798 {
16799 ancestor_range
16800 } else {
16801 selection.range()
16802 };
16803
16804 let mut parent = child.clone();
16805 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16806 parent = ancestor_range;
16807 if parent.start < child.start || parent.end > child.end {
16808 break;
16809 }
16810 }
16811
16812 if parent == child {
16813 return None;
16814 }
16815 let text = buffer.text_for_range(child).collect::<String>();
16816 Some((selection.id, parent, text))
16817 })
16818 .collect::<Vec<_>>();
16819 if full_edits.is_empty() {
16820 return;
16821 }
16822
16823 self.transact(window, cx, |this, window, cx| {
16824 this.buffer.update(cx, |buffer, cx| {
16825 buffer.edit(
16826 full_edits
16827 .iter()
16828 .map(|(_, p, t)| (p.clone(), t.clone()))
16829 .collect::<Vec<_>>(),
16830 None,
16831 cx,
16832 );
16833 });
16834 this.change_selections(Default::default(), window, cx, |s| {
16835 let mut offset = 0;
16836 let mut selections = vec![];
16837 for (id, parent, text) in full_edits {
16838 let start = parent.start - offset;
16839 offset += (parent.end - parent.start) - text.len();
16840 selections.push(Selection {
16841 id,
16842 start,
16843 end: start + text.len(),
16844 reversed: false,
16845 goal: Default::default(),
16846 });
16847 }
16848 s.select(selections);
16849 });
16850 });
16851 }
16852
16853 pub fn select_next_syntax_node(
16854 &mut self,
16855 _: &SelectNextSyntaxNode,
16856 window: &mut Window,
16857 cx: &mut Context<Self>,
16858 ) {
16859 let old_selections: Box<[_]> = self
16860 .selections
16861 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16862 .into();
16863 if old_selections.is_empty() {
16864 return;
16865 }
16866
16867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16868
16869 let buffer = self.buffer.read(cx).snapshot(cx);
16870 let mut selected_sibling = false;
16871
16872 let new_selections = old_selections
16873 .iter()
16874 .map(|selection| {
16875 let old_range = selection.start..selection.end;
16876
16877 let old_range =
16878 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16879 let excerpt = buffer.excerpt_containing(old_range.clone());
16880
16881 if let Some(mut excerpt) = excerpt
16882 && let Some(node) = excerpt
16883 .buffer()
16884 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16885 {
16886 let new_range = excerpt.map_range_from_buffer(
16887 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16888 );
16889 selected_sibling = true;
16890 Selection {
16891 id: selection.id,
16892 start: new_range.start,
16893 end: new_range.end,
16894 goal: SelectionGoal::None,
16895 reversed: selection.reversed,
16896 }
16897 } else {
16898 selection.clone()
16899 }
16900 })
16901 .collect::<Vec<_>>();
16902
16903 if selected_sibling {
16904 self.change_selections(
16905 SelectionEffects::scroll(Autoscroll::fit()),
16906 window,
16907 cx,
16908 |s| {
16909 s.select(new_selections);
16910 },
16911 );
16912 }
16913 }
16914
16915 pub fn select_prev_syntax_node(
16916 &mut self,
16917 _: &SelectPreviousSyntaxNode,
16918 window: &mut Window,
16919 cx: &mut Context<Self>,
16920 ) {
16921 let old_selections: Box<[_]> = self
16922 .selections
16923 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16924 .into();
16925 if old_selections.is_empty() {
16926 return;
16927 }
16928
16929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16930
16931 let buffer = self.buffer.read(cx).snapshot(cx);
16932 let mut selected_sibling = false;
16933
16934 let new_selections = old_selections
16935 .iter()
16936 .map(|selection| {
16937 let old_range = selection.start..selection.end;
16938 let old_range =
16939 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16940 let excerpt = buffer.excerpt_containing(old_range.clone());
16941
16942 if let Some(mut excerpt) = excerpt
16943 && let Some(node) = excerpt
16944 .buffer()
16945 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16946 {
16947 let new_range = excerpt.map_range_from_buffer(
16948 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16949 );
16950 selected_sibling = true;
16951 Selection {
16952 id: selection.id,
16953 start: new_range.start,
16954 end: new_range.end,
16955 goal: SelectionGoal::None,
16956 reversed: selection.reversed,
16957 }
16958 } else {
16959 selection.clone()
16960 }
16961 })
16962 .collect::<Vec<_>>();
16963
16964 if selected_sibling {
16965 self.change_selections(
16966 SelectionEffects::scroll(Autoscroll::fit()),
16967 window,
16968 cx,
16969 |s| {
16970 s.select(new_selections);
16971 },
16972 );
16973 }
16974 }
16975
16976 pub fn move_to_start_of_larger_syntax_node(
16977 &mut self,
16978 _: &MoveToStartOfLargerSyntaxNode,
16979 window: &mut Window,
16980 cx: &mut Context<Self>,
16981 ) {
16982 self.move_cursors_to_syntax_nodes(window, cx, false);
16983 }
16984
16985 pub fn move_to_end_of_larger_syntax_node(
16986 &mut self,
16987 _: &MoveToEndOfLargerSyntaxNode,
16988 window: &mut Window,
16989 cx: &mut Context<Self>,
16990 ) {
16991 self.move_cursors_to_syntax_nodes(window, cx, true);
16992 }
16993
16994 fn find_syntax_node_boundary(
16995 &self,
16996 selection_pos: MultiBufferOffset,
16997 move_to_end: bool,
16998 display_map: &DisplaySnapshot,
16999 buffer: &MultiBufferSnapshot,
17000 ) -> MultiBufferOffset {
17001 let old_range = selection_pos..selection_pos;
17002 let mut new_pos = selection_pos;
17003 let mut search_range = old_range;
17004 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17005 search_range = range.clone();
17006 if !node.is_named()
17007 || display_map.intersects_fold(range.start)
17008 || display_map.intersects_fold(range.end)
17009 // If cursor is already at the end of the syntax node, continue searching
17010 || (move_to_end && range.end == selection_pos)
17011 // If cursor is already at the start of the syntax node, continue searching
17012 || (!move_to_end && range.start == selection_pos)
17013 {
17014 continue;
17015 }
17016
17017 // If we found a string_content node, find the largest parent that is still string_content
17018 // Enables us to skip to the end of strings without taking multiple steps inside the string
17019 let (_, final_range) = if node.kind() == "string_content" {
17020 let mut current_node = node;
17021 let mut current_range = range;
17022 while let Some((parent, parent_range)) =
17023 buffer.syntax_ancestor(current_range.clone())
17024 {
17025 if parent.kind() == "string_content" {
17026 current_node = parent;
17027 current_range = parent_range;
17028 } else {
17029 break;
17030 }
17031 }
17032
17033 (current_node, current_range)
17034 } else {
17035 (node, range)
17036 };
17037
17038 new_pos = if move_to_end {
17039 final_range.end
17040 } else {
17041 final_range.start
17042 };
17043
17044 break;
17045 }
17046
17047 new_pos
17048 }
17049
17050 fn move_cursors_to_syntax_nodes(
17051 &mut self,
17052 window: &mut Window,
17053 cx: &mut Context<Self>,
17054 move_to_end: bool,
17055 ) -> bool {
17056 let old_selections: Box<[_]> = self
17057 .selections
17058 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17059 .into();
17060 if old_selections.is_empty() {
17061 return false;
17062 }
17063
17064 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17065
17066 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17067 let buffer = self.buffer.read(cx).snapshot(cx);
17068
17069 let mut any_cursor_moved = false;
17070 let new_selections = old_selections
17071 .iter()
17072 .map(|selection| {
17073 if !selection.is_empty() {
17074 return selection.clone();
17075 }
17076
17077 let selection_pos = selection.head();
17078 let new_pos = self.find_syntax_node_boundary(
17079 selection_pos,
17080 move_to_end,
17081 &display_map,
17082 &buffer,
17083 );
17084
17085 any_cursor_moved |= new_pos != selection_pos;
17086
17087 Selection {
17088 id: selection.id,
17089 start: new_pos,
17090 end: new_pos,
17091 goal: SelectionGoal::None,
17092 reversed: false,
17093 }
17094 })
17095 .collect::<Vec<_>>();
17096
17097 self.change_selections(Default::default(), window, cx, |s| {
17098 s.select(new_selections);
17099 });
17100 self.request_autoscroll(Autoscroll::newest(), cx);
17101
17102 any_cursor_moved
17103 }
17104
17105 pub fn select_to_start_of_larger_syntax_node(
17106 &mut self,
17107 _: &SelectToStartOfLargerSyntaxNode,
17108 window: &mut Window,
17109 cx: &mut Context<Self>,
17110 ) {
17111 self.select_to_syntax_nodes(window, cx, false);
17112 }
17113
17114 pub fn select_to_end_of_larger_syntax_node(
17115 &mut self,
17116 _: &SelectToEndOfLargerSyntaxNode,
17117 window: &mut Window,
17118 cx: &mut Context<Self>,
17119 ) {
17120 self.select_to_syntax_nodes(window, cx, true);
17121 }
17122
17123 fn select_to_syntax_nodes(
17124 &mut self,
17125 window: &mut Window,
17126 cx: &mut Context<Self>,
17127 move_to_end: bool,
17128 ) {
17129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17130
17131 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17132 let buffer = self.buffer.read(cx).snapshot(cx);
17133 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17134
17135 let new_selections = old_selections
17136 .iter()
17137 .map(|selection| {
17138 let new_pos = self.find_syntax_node_boundary(
17139 selection.head(),
17140 move_to_end,
17141 &display_map,
17142 &buffer,
17143 );
17144
17145 let mut new_selection = selection.clone();
17146 new_selection.set_head(new_pos, SelectionGoal::None);
17147 new_selection
17148 })
17149 .collect::<Vec<_>>();
17150
17151 self.change_selections(Default::default(), window, cx, |s| {
17152 s.select(new_selections);
17153 });
17154 }
17155
17156 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
17157 if !self.mode().is_full()
17158 || !EditorSettings::get_global(cx).gutter.runnables
17159 || !self.enable_runnables
17160 {
17161 self.clear_tasks();
17162 return Task::ready(());
17163 }
17164 let project = self.project().map(Entity::downgrade);
17165 let task_sources = self.lsp_task_sources(cx);
17166 let multi_buffer = self.buffer.downgrade();
17167 let lsp_data_enabled = self.lsp_data_enabled();
17168 cx.spawn_in(window, async move |editor, cx| {
17169 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
17170 let Some(project) = project.and_then(|p| p.upgrade()) else {
17171 return;
17172 };
17173 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
17174 this.display_map.update(cx, |map, cx| map.snapshot(cx))
17175 }) else {
17176 return;
17177 };
17178
17179 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
17180 if hide_runnables {
17181 return;
17182 }
17183 let new_rows = cx
17184 .background_spawn({
17185 let snapshot = display_snapshot.clone();
17186 async move {
17187 snapshot
17188 .buffer_snapshot()
17189 .runnable_ranges(Anchor::min()..Anchor::max())
17190 .collect()
17191 }
17192 })
17193 .await;
17194 let lsp_tasks = if lsp_data_enabled {
17195 let Ok(lsp_tasks) =
17196 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
17197 else {
17198 return;
17199 };
17200 lsp_tasks.await
17201 } else {
17202 Vec::new()
17203 };
17204
17205 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
17206 lsp_tasks
17207 .into_iter()
17208 .flat_map(|(kind, tasks)| {
17209 tasks.into_iter().filter_map(move |(location, task)| {
17210 Some((kind.clone(), location?, task))
17211 })
17212 })
17213 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
17214 let buffer = location.target.buffer;
17215 let buffer_snapshot = buffer.read(cx).snapshot();
17216 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
17217 |(excerpt_id, snapshot, _)| {
17218 if snapshot.remote_id() == buffer_snapshot.remote_id() {
17219 display_snapshot
17220 .buffer_snapshot()
17221 .anchor_in_excerpt(excerpt_id, location.target.range.start)
17222 } else {
17223 None
17224 }
17225 },
17226 );
17227 if let Some(offset) = offset {
17228 let task_buffer_range =
17229 location.target.range.to_point(&buffer_snapshot);
17230 let context_buffer_range =
17231 task_buffer_range.to_offset(&buffer_snapshot);
17232 let context_range = BufferOffset(context_buffer_range.start)
17233 ..BufferOffset(context_buffer_range.end);
17234
17235 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
17236 .or_insert_with(|| RunnableTasks {
17237 templates: Vec::new(),
17238 offset,
17239 column: task_buffer_range.start.column,
17240 extra_variables: HashMap::default(),
17241 context_range,
17242 })
17243 .templates
17244 .push((kind, task.original_task().clone()));
17245 }
17246
17247 acc
17248 })
17249 }) else {
17250 return;
17251 };
17252
17253 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
17254 buffer.language_settings(cx).tasks.prefer_lsp
17255 }) else {
17256 return;
17257 };
17258
17259 let rows = Self::runnable_rows(
17260 project,
17261 display_snapshot,
17262 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
17263 new_rows,
17264 cx.clone(),
17265 )
17266 .await;
17267 editor
17268 .update(cx, |editor, _| {
17269 editor.clear_tasks();
17270 for (key, mut value) in rows {
17271 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
17272 value.templates.extend(lsp_tasks.templates);
17273 }
17274
17275 editor.insert_tasks(key, value);
17276 }
17277 for (key, value) in lsp_tasks_by_rows {
17278 editor.insert_tasks(key, value);
17279 }
17280 })
17281 .ok();
17282 })
17283 }
17284
17285 fn runnable_rows(
17286 project: Entity<Project>,
17287 snapshot: DisplaySnapshot,
17288 prefer_lsp: bool,
17289 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
17290 cx: AsyncWindowContext,
17291 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
17292 cx.spawn(async move |cx| {
17293 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
17294 for (run_range, mut runnable) in runnable_ranges {
17295 let Some(tasks) = cx
17296 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
17297 .ok()
17298 else {
17299 continue;
17300 };
17301 let mut tasks = tasks.await;
17302
17303 if prefer_lsp {
17304 tasks.retain(|(task_kind, _)| {
17305 !matches!(task_kind, TaskSourceKind::Language { .. })
17306 });
17307 }
17308 if tasks.is_empty() {
17309 continue;
17310 }
17311
17312 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
17313 let Some(row) = snapshot
17314 .buffer_snapshot()
17315 .buffer_line_for_row(MultiBufferRow(point.row))
17316 .map(|(_, range)| range.start.row)
17317 else {
17318 continue;
17319 };
17320
17321 let context_range =
17322 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
17323 runnable_rows.push((
17324 (runnable.buffer_id, row),
17325 RunnableTasks {
17326 templates: tasks,
17327 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
17328 context_range,
17329 column: point.column,
17330 extra_variables: runnable.extra_captures,
17331 },
17332 ));
17333 }
17334 runnable_rows
17335 })
17336 }
17337
17338 fn templates_with_tags(
17339 project: &Entity<Project>,
17340 runnable: &mut Runnable,
17341 cx: &mut App,
17342 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
17343 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
17344 let (worktree_id, file) = project
17345 .buffer_for_id(runnable.buffer, cx)
17346 .and_then(|buffer| buffer.read(cx).file())
17347 .map(|file| (file.worktree_id(cx), file.clone()))
17348 .unzip();
17349
17350 (
17351 project.task_store().read(cx).task_inventory().cloned(),
17352 worktree_id,
17353 file,
17354 )
17355 });
17356
17357 let tags = mem::take(&mut runnable.tags);
17358 let language = runnable.language.clone();
17359 cx.spawn(async move |cx| {
17360 let mut templates_with_tags = Vec::new();
17361 if let Some(inventory) = inventory {
17362 for RunnableTag(tag) in tags {
17363 let new_tasks = inventory.update(cx, |inventory, cx| {
17364 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
17365 });
17366 templates_with_tags.extend(new_tasks.await.into_iter().filter(
17367 move |(_, template)| {
17368 template.tags.iter().any(|source_tag| source_tag == &tag)
17369 },
17370 ));
17371 }
17372 }
17373 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
17374
17375 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
17376 // Strongest source wins; if we have worktree tag binding, prefer that to
17377 // global and language bindings;
17378 // if we have a global binding, prefer that to language binding.
17379 let first_mismatch = templates_with_tags
17380 .iter()
17381 .position(|(tag_source, _)| tag_source != leading_tag_source);
17382 if let Some(index) = first_mismatch {
17383 templates_with_tags.truncate(index);
17384 }
17385 }
17386
17387 templates_with_tags
17388 })
17389 }
17390
17391 pub fn move_to_enclosing_bracket(
17392 &mut self,
17393 _: &MoveToEnclosingBracket,
17394 window: &mut Window,
17395 cx: &mut Context<Self>,
17396 ) {
17397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17398 self.change_selections(Default::default(), window, cx, |s| {
17399 s.move_offsets_with(&mut |snapshot, selection| {
17400 let Some(enclosing_bracket_ranges) =
17401 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17402 else {
17403 return;
17404 };
17405
17406 let mut best_length = usize::MAX;
17407 let mut best_inside = false;
17408 let mut best_in_bracket_range = false;
17409 let mut best_destination = None;
17410 for (open, close) in enclosing_bracket_ranges {
17411 let close = close.to_inclusive();
17412 let length = *close.end() - open.start;
17413 let inside = selection.start >= open.end && selection.end <= *close.start();
17414 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17415 || close.contains(&selection.head());
17416
17417 // If best is next to a bracket and current isn't, skip
17418 if !in_bracket_range && best_in_bracket_range {
17419 continue;
17420 }
17421
17422 // Prefer smaller lengths unless best is inside and current isn't
17423 if length > best_length && (best_inside || !inside) {
17424 continue;
17425 }
17426
17427 best_length = length;
17428 best_inside = inside;
17429 best_in_bracket_range = in_bracket_range;
17430 best_destination = Some(
17431 if close.contains(&selection.start) && close.contains(&selection.end) {
17432 if inside { open.end } else { open.start }
17433 } else if inside {
17434 *close.start()
17435 } else {
17436 *close.end()
17437 },
17438 );
17439 }
17440
17441 if let Some(destination) = best_destination {
17442 selection.collapse_to(destination, SelectionGoal::None);
17443 }
17444 })
17445 });
17446 }
17447
17448 pub fn undo_selection(
17449 &mut self,
17450 _: &UndoSelection,
17451 window: &mut Window,
17452 cx: &mut Context<Self>,
17453 ) {
17454 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17455 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17456 self.selection_history.mode = SelectionHistoryMode::Undoing;
17457 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17458 this.end_selection(window, cx);
17459 this.change_selections(
17460 SelectionEffects::scroll(Autoscroll::newest()),
17461 window,
17462 cx,
17463 |s| s.select_anchors(entry.selections.to_vec()),
17464 );
17465 });
17466 self.selection_history.mode = SelectionHistoryMode::Normal;
17467
17468 self.select_next_state = entry.select_next_state;
17469 self.select_prev_state = entry.select_prev_state;
17470 self.add_selections_state = entry.add_selections_state;
17471 }
17472 }
17473
17474 pub fn redo_selection(
17475 &mut self,
17476 _: &RedoSelection,
17477 window: &mut Window,
17478 cx: &mut Context<Self>,
17479 ) {
17480 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17481 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17482 self.selection_history.mode = SelectionHistoryMode::Redoing;
17483 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17484 this.end_selection(window, cx);
17485 this.change_selections(
17486 SelectionEffects::scroll(Autoscroll::newest()),
17487 window,
17488 cx,
17489 |s| s.select_anchors(entry.selections.to_vec()),
17490 );
17491 });
17492 self.selection_history.mode = SelectionHistoryMode::Normal;
17493
17494 self.select_next_state = entry.select_next_state;
17495 self.select_prev_state = entry.select_prev_state;
17496 self.add_selections_state = entry.add_selections_state;
17497 }
17498 }
17499
17500 pub fn expand_excerpts(
17501 &mut self,
17502 action: &ExpandExcerpts,
17503 _: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) {
17506 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17507 }
17508
17509 pub fn expand_excerpts_down(
17510 &mut self,
17511 action: &ExpandExcerptsDown,
17512 _: &mut Window,
17513 cx: &mut Context<Self>,
17514 ) {
17515 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17516 }
17517
17518 pub fn expand_excerpts_up(
17519 &mut self,
17520 action: &ExpandExcerptsUp,
17521 _: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) {
17524 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17525 }
17526
17527 pub fn expand_excerpts_for_direction(
17528 &mut self,
17529 lines: u32,
17530 direction: ExpandExcerptDirection,
17531 cx: &mut Context<Self>,
17532 ) {
17533 let selections = self.selections.disjoint_anchors_arc();
17534
17535 let lines = if lines == 0 {
17536 EditorSettings::get_global(cx).expand_excerpt_lines
17537 } else {
17538 lines
17539 };
17540
17541 let snapshot = self.buffer.read(cx).snapshot(cx);
17542 let excerpt_ids = selections
17543 .iter()
17544 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17545 .unique()
17546 .sorted()
17547 .collect::<Vec<_>>();
17548
17549 if self.delegate_expand_excerpts {
17550 cx.emit(EditorEvent::ExpandExcerptsRequested {
17551 excerpt_ids,
17552 lines,
17553 direction,
17554 });
17555 return;
17556 }
17557
17558 self.buffer.update(cx, |buffer, cx| {
17559 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17560 })
17561 }
17562
17563 pub fn expand_excerpt(
17564 &mut self,
17565 excerpt: ExcerptId,
17566 direction: ExpandExcerptDirection,
17567 window: &mut Window,
17568 cx: &mut Context<Self>,
17569 ) {
17570 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17571
17572 if self.delegate_expand_excerpts {
17573 cx.emit(EditorEvent::ExpandExcerptsRequested {
17574 excerpt_ids: vec![excerpt],
17575 lines: lines_to_expand,
17576 direction,
17577 });
17578 return;
17579 }
17580
17581 let current_scroll_position = self.scroll_position(cx);
17582 let mut scroll = None;
17583
17584 if direction == ExpandExcerptDirection::Down {
17585 let multi_buffer = self.buffer.read(cx);
17586 let snapshot = multi_buffer.snapshot(cx);
17587 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17588 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17589 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17590 {
17591 let buffer_snapshot = buffer.read(cx).snapshot();
17592 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17593 let last_row = buffer_snapshot.max_point().row;
17594 let lines_below = last_row.saturating_sub(excerpt_end_row);
17595 if lines_below >= lines_to_expand {
17596 scroll = Some(
17597 current_scroll_position
17598 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17599 );
17600 }
17601 }
17602 }
17603 if direction == ExpandExcerptDirection::Up
17604 && self
17605 .buffer
17606 .read(cx)
17607 .snapshot(cx)
17608 .excerpt_before(excerpt)
17609 .is_none()
17610 {
17611 scroll = Some(current_scroll_position);
17612 }
17613
17614 self.buffer.update(cx, |buffer, cx| {
17615 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17616 });
17617
17618 if let Some(new_scroll_position) = scroll {
17619 self.set_scroll_position(new_scroll_position, window, cx);
17620 }
17621 }
17622
17623 pub fn go_to_singleton_buffer_point(
17624 &mut self,
17625 point: Point,
17626 window: &mut Window,
17627 cx: &mut Context<Self>,
17628 ) {
17629 self.go_to_singleton_buffer_range(point..point, window, cx);
17630 }
17631
17632 pub fn go_to_singleton_buffer_range(
17633 &mut self,
17634 range: Range<Point>,
17635 window: &mut Window,
17636 cx: &mut Context<Self>,
17637 ) {
17638 let multibuffer = self.buffer().read(cx);
17639 let Some(buffer) = multibuffer.as_singleton() else {
17640 return;
17641 };
17642 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17643 return;
17644 };
17645 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17646 return;
17647 };
17648 self.change_selections(
17649 SelectionEffects::default().nav_history(true),
17650 window,
17651 cx,
17652 |s| s.select_anchor_ranges([start..end]),
17653 );
17654 }
17655
17656 pub fn go_to_diagnostic(
17657 &mut self,
17658 action: &GoToDiagnostic,
17659 window: &mut Window,
17660 cx: &mut Context<Self>,
17661 ) {
17662 if !self.diagnostics_enabled() {
17663 return;
17664 }
17665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17666 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17667 }
17668
17669 pub fn go_to_prev_diagnostic(
17670 &mut self,
17671 action: &GoToPreviousDiagnostic,
17672 window: &mut Window,
17673 cx: &mut Context<Self>,
17674 ) {
17675 if !self.diagnostics_enabled() {
17676 return;
17677 }
17678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17679 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17680 }
17681
17682 pub fn go_to_diagnostic_impl(
17683 &mut self,
17684 direction: Direction,
17685 severity: GoToDiagnosticSeverityFilter,
17686 window: &mut Window,
17687 cx: &mut Context<Self>,
17688 ) {
17689 let buffer = self.buffer.read(cx).snapshot(cx);
17690 let selection = self
17691 .selections
17692 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17693
17694 let mut active_group_id = None;
17695 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17696 && active_group.active_range.start.to_offset(&buffer) == selection.start
17697 {
17698 active_group_id = Some(active_group.group_id);
17699 }
17700
17701 fn filtered<'a>(
17702 severity: GoToDiagnosticSeverityFilter,
17703 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17704 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17705 diagnostics
17706 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17707 .filter(|entry| entry.range.start != entry.range.end)
17708 .filter(|entry| !entry.diagnostic.is_unnecessary)
17709 }
17710
17711 let before = filtered(
17712 severity,
17713 buffer
17714 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17715 .filter(|entry| entry.range.start <= selection.start),
17716 );
17717 let after = filtered(
17718 severity,
17719 buffer
17720 .diagnostics_in_range(selection.start..buffer.len())
17721 .filter(|entry| entry.range.start >= selection.start),
17722 );
17723
17724 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17725 if direction == Direction::Prev {
17726 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17727 {
17728 for diagnostic in prev_diagnostics.into_iter().rev() {
17729 if diagnostic.range.start != selection.start
17730 || active_group_id
17731 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17732 {
17733 found = Some(diagnostic);
17734 break 'outer;
17735 }
17736 }
17737 }
17738 } else {
17739 for diagnostic in after.chain(before) {
17740 if diagnostic.range.start != selection.start
17741 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17742 {
17743 found = Some(diagnostic);
17744 break;
17745 }
17746 }
17747 }
17748 let Some(next_diagnostic) = found else {
17749 return;
17750 };
17751
17752 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17753 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17754 return;
17755 };
17756 let snapshot = self.snapshot(window, cx);
17757 if snapshot.intersects_fold(next_diagnostic.range.start) {
17758 self.unfold_ranges(
17759 std::slice::from_ref(&next_diagnostic.range),
17760 true,
17761 false,
17762 cx,
17763 );
17764 }
17765 self.change_selections(Default::default(), window, cx, |s| {
17766 s.select_ranges(vec![
17767 next_diagnostic.range.start..next_diagnostic.range.start,
17768 ])
17769 });
17770 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17771 self.refresh_edit_prediction(false, true, window, cx);
17772 }
17773
17774 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17776 let snapshot = self.snapshot(window, cx);
17777 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17778 self.go_to_hunk_before_or_after_position(
17779 &snapshot,
17780 selection.head(),
17781 Direction::Next,
17782 true,
17783 window,
17784 cx,
17785 );
17786 }
17787
17788 pub fn go_to_hunk_before_or_after_position(
17789 &mut self,
17790 snapshot: &EditorSnapshot,
17791 position: Point,
17792 direction: Direction,
17793 wrap_around: bool,
17794 window: &mut Window,
17795 cx: &mut Context<Editor>,
17796 ) {
17797 let row = if direction == Direction::Next {
17798 self.hunk_after_position(snapshot, position, wrap_around)
17799 .map(|hunk| hunk.row_range.start)
17800 } else {
17801 self.hunk_before_position(snapshot, position, wrap_around)
17802 };
17803
17804 if let Some(row) = row {
17805 let destination = Point::new(row.0, 0);
17806 let autoscroll = Autoscroll::center();
17807
17808 self.unfold_ranges(&[destination..destination], false, false, cx);
17809 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17810 s.select_ranges([destination..destination]);
17811 });
17812 }
17813 }
17814
17815 fn hunk_after_position(
17816 &mut self,
17817 snapshot: &EditorSnapshot,
17818 position: Point,
17819 wrap_around: bool,
17820 ) -> Option<MultiBufferDiffHunk> {
17821 let result = snapshot
17822 .buffer_snapshot()
17823 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17824 .find(|hunk| hunk.row_range.start.0 > position.row);
17825
17826 if wrap_around {
17827 result.or_else(|| {
17828 snapshot
17829 .buffer_snapshot()
17830 .diff_hunks_in_range(Point::zero()..position)
17831 .find(|hunk| hunk.row_range.end.0 < position.row)
17832 })
17833 } else {
17834 result
17835 }
17836 }
17837
17838 fn go_to_prev_hunk(
17839 &mut self,
17840 _: &GoToPreviousHunk,
17841 window: &mut Window,
17842 cx: &mut Context<Self>,
17843 ) {
17844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17845 let snapshot = self.snapshot(window, cx);
17846 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17847 self.go_to_hunk_before_or_after_position(
17848 &snapshot,
17849 selection.head(),
17850 Direction::Prev,
17851 true,
17852 window,
17853 cx,
17854 );
17855 }
17856
17857 fn hunk_before_position(
17858 &mut self,
17859 snapshot: &EditorSnapshot,
17860 position: Point,
17861 wrap_around: bool,
17862 ) -> Option<MultiBufferRow> {
17863 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17864
17865 if wrap_around {
17866 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17867 } else {
17868 result
17869 }
17870 }
17871
17872 fn go_to_next_change(
17873 &mut self,
17874 _: &GoToNextChange,
17875 window: &mut Window,
17876 cx: &mut Context<Self>,
17877 ) {
17878 if let Some(selections) = self
17879 .change_list
17880 .next_change(1, Direction::Next)
17881 .map(|s| s.to_vec())
17882 {
17883 self.change_selections(Default::default(), window, cx, |s| {
17884 let map = s.display_snapshot();
17885 s.select_display_ranges(selections.iter().map(|a| {
17886 let point = a.to_display_point(&map);
17887 point..point
17888 }))
17889 })
17890 }
17891 }
17892
17893 fn go_to_previous_change(
17894 &mut self,
17895 _: &GoToPreviousChange,
17896 window: &mut Window,
17897 cx: &mut Context<Self>,
17898 ) {
17899 if let Some(selections) = self
17900 .change_list
17901 .next_change(1, Direction::Prev)
17902 .map(|s| s.to_vec())
17903 {
17904 self.change_selections(Default::default(), window, cx, |s| {
17905 let map = s.display_snapshot();
17906 s.select_display_ranges(selections.iter().map(|a| {
17907 let point = a.to_display_point(&map);
17908 point..point
17909 }))
17910 })
17911 }
17912 }
17913
17914 pub fn go_to_next_document_highlight(
17915 &mut self,
17916 _: &GoToNextDocumentHighlight,
17917 window: &mut Window,
17918 cx: &mut Context<Self>,
17919 ) {
17920 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17921 }
17922
17923 pub fn go_to_prev_document_highlight(
17924 &mut self,
17925 _: &GoToPreviousDocumentHighlight,
17926 window: &mut Window,
17927 cx: &mut Context<Self>,
17928 ) {
17929 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17930 }
17931
17932 pub fn go_to_document_highlight_before_or_after_position(
17933 &mut self,
17934 direction: Direction,
17935 window: &mut Window,
17936 cx: &mut Context<Editor>,
17937 ) {
17938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17939 let snapshot = self.snapshot(window, cx);
17940 let buffer = &snapshot.buffer_snapshot();
17941 let position = self
17942 .selections
17943 .newest::<Point>(&snapshot.display_snapshot)
17944 .head();
17945 let anchor_position = buffer.anchor_after(position);
17946
17947 // Get all document highlights (both read and write)
17948 let mut all_highlights = Vec::new();
17949
17950 if let Some((_, read_highlights)) = self
17951 .background_highlights
17952 .get(&HighlightKey::DocumentHighlightRead)
17953 {
17954 all_highlights.extend(read_highlights.iter());
17955 }
17956
17957 if let Some((_, write_highlights)) = self
17958 .background_highlights
17959 .get(&HighlightKey::DocumentHighlightWrite)
17960 {
17961 all_highlights.extend(write_highlights.iter());
17962 }
17963
17964 if all_highlights.is_empty() {
17965 return;
17966 }
17967
17968 // Sort highlights by position
17969 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17970
17971 let target_highlight = match direction {
17972 Direction::Next => {
17973 // Find the first highlight after the current position
17974 all_highlights
17975 .iter()
17976 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17977 }
17978 Direction::Prev => {
17979 // Find the last highlight before the current position
17980 all_highlights
17981 .iter()
17982 .rev()
17983 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17984 }
17985 };
17986
17987 if let Some(highlight) = target_highlight {
17988 let destination = highlight.start.to_point(buffer);
17989 let autoscroll = Autoscroll::center();
17990
17991 self.unfold_ranges(&[destination..destination], false, false, cx);
17992 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17993 s.select_ranges([destination..destination]);
17994 });
17995 }
17996 }
17997
17998 fn go_to_line<T: 'static>(
17999 &mut self,
18000 position: Anchor,
18001 highlight_color: Option<Hsla>,
18002 window: &mut Window,
18003 cx: &mut Context<Self>,
18004 ) {
18005 let snapshot = self.snapshot(window, cx).display_snapshot;
18006 let position = position.to_point(&snapshot.buffer_snapshot());
18007 let start = snapshot
18008 .buffer_snapshot()
18009 .clip_point(Point::new(position.row, 0), Bias::Left);
18010 let end = start + Point::new(1, 0);
18011 let start = snapshot.buffer_snapshot().anchor_before(start);
18012 let end = snapshot.buffer_snapshot().anchor_before(end);
18013
18014 self.highlight_rows::<T>(
18015 start..end,
18016 highlight_color
18017 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
18018 Default::default(),
18019 cx,
18020 );
18021
18022 if self.buffer.read(cx).is_singleton() {
18023 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
18024 }
18025 }
18026
18027 pub fn go_to_definition(
18028 &mut self,
18029 _: &GoToDefinition,
18030 window: &mut Window,
18031 cx: &mut Context<Self>,
18032 ) -> Task<Result<Navigated>> {
18033 let definition =
18034 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
18035 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
18036 cx.spawn_in(window, async move |editor, cx| {
18037 if definition.await? == Navigated::Yes {
18038 return Ok(Navigated::Yes);
18039 }
18040 match fallback_strategy {
18041 GoToDefinitionFallback::None => Ok(Navigated::No),
18042 GoToDefinitionFallback::FindAllReferences => {
18043 match editor.update_in(cx, |editor, window, cx| {
18044 editor.find_all_references(&FindAllReferences::default(), window, cx)
18045 })? {
18046 Some(references) => references.await,
18047 None => Ok(Navigated::No),
18048 }
18049 }
18050 }
18051 })
18052 }
18053
18054 pub fn go_to_declaration(
18055 &mut self,
18056 _: &GoToDeclaration,
18057 window: &mut Window,
18058 cx: &mut Context<Self>,
18059 ) -> Task<Result<Navigated>> {
18060 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
18061 }
18062
18063 pub fn go_to_declaration_split(
18064 &mut self,
18065 _: &GoToDeclaration,
18066 window: &mut Window,
18067 cx: &mut Context<Self>,
18068 ) -> Task<Result<Navigated>> {
18069 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
18070 }
18071
18072 pub fn go_to_implementation(
18073 &mut self,
18074 _: &GoToImplementation,
18075 window: &mut Window,
18076 cx: &mut Context<Self>,
18077 ) -> Task<Result<Navigated>> {
18078 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
18079 }
18080
18081 pub fn go_to_implementation_split(
18082 &mut self,
18083 _: &GoToImplementationSplit,
18084 window: &mut Window,
18085 cx: &mut Context<Self>,
18086 ) -> Task<Result<Navigated>> {
18087 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
18088 }
18089
18090 pub fn go_to_type_definition(
18091 &mut self,
18092 _: &GoToTypeDefinition,
18093 window: &mut Window,
18094 cx: &mut Context<Self>,
18095 ) -> Task<Result<Navigated>> {
18096 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18097 }
18098
18099 pub fn go_to_definition_split(
18100 &mut self,
18101 _: &GoToDefinitionSplit,
18102 window: &mut Window,
18103 cx: &mut Context<Self>,
18104 ) -> Task<Result<Navigated>> {
18105 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18106 }
18107
18108 pub fn go_to_type_definition_split(
18109 &mut self,
18110 _: &GoToTypeDefinitionSplit,
18111 window: &mut Window,
18112 cx: &mut Context<Self>,
18113 ) -> Task<Result<Navigated>> {
18114 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18115 }
18116
18117 fn go_to_definition_of_kind(
18118 &mut self,
18119 kind: GotoDefinitionKind,
18120 split: bool,
18121 window: &mut Window,
18122 cx: &mut Context<Self>,
18123 ) -> Task<Result<Navigated>> {
18124 let Some(provider) = self.semantics_provider.clone() else {
18125 return Task::ready(Ok(Navigated::No));
18126 };
18127 let head = self
18128 .selections
18129 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18130 .head();
18131 let buffer = self.buffer.read(cx);
18132 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18133 return Task::ready(Ok(Navigated::No));
18134 };
18135 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18136 return Task::ready(Ok(Navigated::No));
18137 };
18138
18139 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18140
18141 cx.spawn_in(window, async move |editor, cx| {
18142 let Some(definitions) = definitions.await? else {
18143 return Ok(Navigated::No);
18144 };
18145 let navigated = editor
18146 .update_in(cx, |editor, window, cx| {
18147 editor.navigate_to_hover_links(
18148 Some(kind),
18149 definitions
18150 .into_iter()
18151 .filter(|location| {
18152 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18153 })
18154 .map(HoverLink::Text)
18155 .collect::<Vec<_>>(),
18156 nav_entry,
18157 split,
18158 window,
18159 cx,
18160 )
18161 })?
18162 .await?;
18163 anyhow::Ok(navigated)
18164 })
18165 }
18166
18167 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18168 let selection = self.selections.newest_anchor();
18169 let head = selection.head();
18170 let tail = selection.tail();
18171
18172 let Some((buffer, start_position)) =
18173 self.buffer.read(cx).text_anchor_for_position(head, cx)
18174 else {
18175 return;
18176 };
18177
18178 let end_position = if head != tail {
18179 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18180 return;
18181 };
18182 Some(pos)
18183 } else {
18184 None
18185 };
18186
18187 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18188 let url = if let Some(end_pos) = end_position {
18189 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18190 } else {
18191 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18192 };
18193
18194 if let Some(url) = url {
18195 cx.update(|window, cx| {
18196 if parse_zed_link(&url, cx).is_some() {
18197 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18198 } else {
18199 cx.open_url(&url);
18200 }
18201 })?;
18202 }
18203
18204 anyhow::Ok(())
18205 });
18206
18207 url_finder.detach();
18208 }
18209
18210 pub fn open_selected_filename(
18211 &mut self,
18212 _: &OpenSelectedFilename,
18213 window: &mut Window,
18214 cx: &mut Context<Self>,
18215 ) {
18216 let Some(workspace) = self.workspace() else {
18217 return;
18218 };
18219
18220 let position = self.selections.newest_anchor().head();
18221
18222 let Some((buffer, buffer_position)) =
18223 self.buffer.read(cx).text_anchor_for_position(position, cx)
18224 else {
18225 return;
18226 };
18227
18228 let project = self.project.clone();
18229
18230 cx.spawn_in(window, async move |_, cx| {
18231 let result = find_file(&buffer, project, buffer_position, cx).await;
18232
18233 if let Some((_, path)) = result {
18234 workspace
18235 .update_in(cx, |workspace, window, cx| {
18236 workspace.open_resolved_path(path, window, cx)
18237 })?
18238 .await?;
18239 }
18240 anyhow::Ok(())
18241 })
18242 .detach();
18243 }
18244
18245 pub(crate) fn navigate_to_hover_links(
18246 &mut self,
18247 kind: Option<GotoDefinitionKind>,
18248 definitions: Vec<HoverLink>,
18249 origin: Option<NavigationEntry>,
18250 split: bool,
18251 window: &mut Window,
18252 cx: &mut Context<Editor>,
18253 ) -> Task<Result<Navigated>> {
18254 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18255 let mut first_url_or_file = None;
18256 let definitions: Vec<_> = definitions
18257 .into_iter()
18258 .filter_map(|def| match def {
18259 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18260 HoverLink::InlayHint(lsp_location, server_id) => {
18261 let computation =
18262 self.compute_target_location(lsp_location, server_id, window, cx);
18263 Some(cx.background_spawn(computation))
18264 }
18265 HoverLink::Url(url) => {
18266 first_url_or_file = Some(Either::Left(url));
18267 None
18268 }
18269 HoverLink::File(path) => {
18270 first_url_or_file = Some(Either::Right(path));
18271 None
18272 }
18273 })
18274 .collect();
18275
18276 let workspace = self.workspace();
18277
18278 cx.spawn_in(window, async move |editor, cx| {
18279 let locations: Vec<Location> = future::join_all(definitions)
18280 .await
18281 .into_iter()
18282 .filter_map(|location| location.transpose())
18283 .collect::<Result<_>>()
18284 .context("location tasks")?;
18285 let mut locations = cx.update(|_, cx| {
18286 locations
18287 .into_iter()
18288 .map(|location| {
18289 let buffer = location.buffer.read(cx);
18290 (location.buffer, location.range.to_point(buffer))
18291 })
18292 .into_group_map()
18293 })?;
18294 let mut num_locations = 0;
18295 for ranges in locations.values_mut() {
18296 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18297 ranges.dedup();
18298 num_locations += ranges.len();
18299 }
18300
18301 if num_locations > 1 {
18302 let tab_kind = match kind {
18303 Some(GotoDefinitionKind::Implementation) => "Implementations",
18304 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18305 Some(GotoDefinitionKind::Declaration) => "Declarations",
18306 Some(GotoDefinitionKind::Type) => "Types",
18307 };
18308 let title = editor
18309 .update_in(cx, |_, _, cx| {
18310 let target = locations
18311 .iter()
18312 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18313 .map(|(buffer, location)| {
18314 buffer
18315 .read(cx)
18316 .text_for_range(location.clone())
18317 .collect::<String>()
18318 })
18319 .filter(|text| !text.contains('\n'))
18320 .unique()
18321 .take(3)
18322 .join(", ");
18323 if target.is_empty() {
18324 tab_kind.to_owned()
18325 } else {
18326 format!("{tab_kind} for {target}")
18327 }
18328 })
18329 .context("buffer title")?;
18330
18331 let Some(workspace) = workspace else {
18332 return Ok(Navigated::No);
18333 };
18334
18335 let opened = workspace
18336 .update_in(cx, |workspace, window, cx| {
18337 let allow_preview = PreviewTabsSettings::get_global(cx)
18338 .enable_preview_multibuffer_from_code_navigation;
18339 if let Some((target_editor, target_pane)) =
18340 Self::open_locations_in_multibuffer(
18341 workspace,
18342 locations,
18343 title,
18344 split,
18345 allow_preview,
18346 MultibufferSelectionMode::First,
18347 window,
18348 cx,
18349 )
18350 {
18351 // We create our own nav history instead of using
18352 // `target_editor.nav_history` because `nav_history`
18353 // seems to be populated asynchronously when an item
18354 // is added to a pane
18355 let mut nav_history = target_pane
18356 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18357 target_editor.update(cx, |editor, cx| {
18358 let nav_data = editor
18359 .navigation_data(editor.selections.newest_anchor().head(), cx);
18360 let target =
18361 Some(nav_history.navigation_entry(Some(
18362 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18363 )));
18364 nav_history.push_tag(origin, target);
18365 })
18366 }
18367 })
18368 .is_ok();
18369
18370 anyhow::Ok(Navigated::from_bool(opened))
18371 } else if num_locations == 0 {
18372 // If there is one url or file, open it directly
18373 match first_url_or_file {
18374 Some(Either::Left(url)) => {
18375 cx.update(|window, cx| {
18376 if parse_zed_link(&url, cx).is_some() {
18377 window
18378 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18379 } else {
18380 cx.open_url(&url);
18381 }
18382 })?;
18383 Ok(Navigated::Yes)
18384 }
18385 Some(Either::Right(path)) => {
18386 // TODO(andrew): respect preview tab settings
18387 // `enable_keep_preview_on_code_navigation` and
18388 // `enable_preview_file_from_code_navigation`
18389 let Some(workspace) = workspace else {
18390 return Ok(Navigated::No);
18391 };
18392 workspace
18393 .update_in(cx, |workspace, window, cx| {
18394 workspace.open_resolved_path(path, window, cx)
18395 })?
18396 .await?;
18397 Ok(Navigated::Yes)
18398 }
18399 None => Ok(Navigated::No),
18400 }
18401 } else {
18402 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18403 let target_range = target_ranges.first().unwrap().clone();
18404
18405 editor.update_in(cx, |editor, window, cx| {
18406 let range = editor.range_for_match(&target_range);
18407 let range = collapse_multiline_range(range);
18408
18409 if !split
18410 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18411 {
18412 editor.go_to_singleton_buffer_range(range, window, cx);
18413
18414 let target =
18415 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18416 if let Some(mut nav_history) = editor.nav_history.clone() {
18417 nav_history.push_tag(origin, target);
18418 }
18419 } else {
18420 let Some(workspace) = workspace else {
18421 return Navigated::No;
18422 };
18423 let pane = workspace.read(cx).active_pane().clone();
18424 window.defer(cx, move |window, cx| {
18425 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18426 workspace.update(cx, |workspace, cx| {
18427 let pane = if split {
18428 workspace.adjacent_pane(window, cx)
18429 } else {
18430 workspace.active_pane().clone()
18431 };
18432
18433 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18434 let keep_old_preview = preview_tabs_settings
18435 .enable_keep_preview_on_code_navigation;
18436 let allow_new_preview = preview_tabs_settings
18437 .enable_preview_file_from_code_navigation;
18438
18439 let editor = workspace.open_project_item(
18440 pane.clone(),
18441 target_buffer.clone(),
18442 true,
18443 true,
18444 keep_old_preview,
18445 allow_new_preview,
18446 window,
18447 cx,
18448 );
18449 (editor, pane)
18450 });
18451 // We create our own nav history instead of using
18452 // `target_editor.nav_history` because `nav_history`
18453 // seems to be populated asynchronously when an item
18454 // is added to a pane
18455 let mut nav_history = target_pane
18456 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18457 target_editor.update(cx, |target_editor, cx| {
18458 // When selecting a definition in a different buffer, disable the nav history
18459 // to avoid creating a history entry at the previous cursor location.
18460 pane.update(cx, |pane, _| pane.disable_history());
18461 target_editor.go_to_singleton_buffer_range(range, window, cx);
18462
18463 let nav_data = target_editor.navigation_data(
18464 target_editor.selections.newest_anchor().head(),
18465 cx,
18466 );
18467 let target =
18468 Some(nav_history.navigation_entry(Some(
18469 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18470 )));
18471 nav_history.push_tag(origin, target);
18472 pane.update(cx, |pane, _| pane.enable_history());
18473 });
18474 });
18475 }
18476 Navigated::Yes
18477 })
18478 }
18479 })
18480 }
18481
18482 fn compute_target_location(
18483 &self,
18484 lsp_location: lsp::Location,
18485 server_id: LanguageServerId,
18486 window: &mut Window,
18487 cx: &mut Context<Self>,
18488 ) -> Task<anyhow::Result<Option<Location>>> {
18489 let Some(project) = self.project.clone() else {
18490 return Task::ready(Ok(None));
18491 };
18492
18493 cx.spawn_in(window, async move |editor, cx| {
18494 let location_task = editor.update(cx, |_, cx| {
18495 project.update(cx, |project, cx| {
18496 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18497 })
18498 })?;
18499 let location = Some({
18500 let target_buffer_handle = location_task.await.context("open local buffer")?;
18501 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18502 let target_start = target_buffer
18503 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18504 let target_end = target_buffer
18505 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18506 target_buffer.anchor_after(target_start)
18507 ..target_buffer.anchor_before(target_end)
18508 });
18509 Location {
18510 buffer: target_buffer_handle,
18511 range,
18512 }
18513 });
18514 Ok(location)
18515 })
18516 }
18517
18518 fn go_to_next_reference(
18519 &mut self,
18520 _: &GoToNextReference,
18521 window: &mut Window,
18522 cx: &mut Context<Self>,
18523 ) {
18524 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18525 if let Some(task) = task {
18526 task.detach();
18527 };
18528 }
18529
18530 fn go_to_prev_reference(
18531 &mut self,
18532 _: &GoToPreviousReference,
18533 window: &mut Window,
18534 cx: &mut Context<Self>,
18535 ) {
18536 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18537 if let Some(task) = task {
18538 task.detach();
18539 };
18540 }
18541
18542 pub fn go_to_reference_before_or_after_position(
18543 &mut self,
18544 direction: Direction,
18545 count: usize,
18546 window: &mut Window,
18547 cx: &mut Context<Self>,
18548 ) -> Option<Task<Result<()>>> {
18549 let selection = self.selections.newest_anchor();
18550 let head = selection.head();
18551
18552 let multi_buffer = self.buffer.read(cx);
18553
18554 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18555 let workspace = self.workspace()?;
18556 let project = workspace.read(cx).project().clone();
18557 let references =
18558 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18559 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18560 let Some(locations) = references.await? else {
18561 return Ok(());
18562 };
18563
18564 if locations.is_empty() {
18565 // totally normal - the cursor may be on something which is not
18566 // a symbol (e.g. a keyword)
18567 log::info!("no references found under cursor");
18568 return Ok(());
18569 }
18570
18571 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18572
18573 let (locations, current_location_index) =
18574 multi_buffer.update(cx, |multi_buffer, cx| {
18575 let mut locations = locations
18576 .into_iter()
18577 .filter_map(|loc| {
18578 let start = multi_buffer.buffer_anchor_to_anchor(
18579 &loc.buffer,
18580 loc.range.start,
18581 cx,
18582 )?;
18583 let end = multi_buffer.buffer_anchor_to_anchor(
18584 &loc.buffer,
18585 loc.range.end,
18586 cx,
18587 )?;
18588 Some(start..end)
18589 })
18590 .collect::<Vec<_>>();
18591
18592 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18593 // There is an O(n) implementation, but given this list will be
18594 // small (usually <100 items), the extra O(log(n)) factor isn't
18595 // worth the (surprisingly large amount of) extra complexity.
18596 locations
18597 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18598
18599 let head_offset = head.to_offset(&multi_buffer_snapshot);
18600
18601 let current_location_index = locations.iter().position(|loc| {
18602 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18603 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18604 });
18605
18606 (locations, current_location_index)
18607 });
18608
18609 let Some(current_location_index) = current_location_index else {
18610 // This indicates something has gone wrong, because we already
18611 // handle the "no references" case above
18612 log::error!(
18613 "failed to find current reference under cursor. Total references: {}",
18614 locations.len()
18615 );
18616 return Ok(());
18617 };
18618
18619 let destination_location_index = match direction {
18620 Direction::Next => (current_location_index + count) % locations.len(),
18621 Direction::Prev => {
18622 (current_location_index + locations.len() - count % locations.len())
18623 % locations.len()
18624 }
18625 };
18626
18627 // TODO(cameron): is this needed?
18628 // the thinking is to avoid "jumping to the current location" (avoid
18629 // polluting "jumplist" in vim terms)
18630 if current_location_index == destination_location_index {
18631 return Ok(());
18632 }
18633
18634 let Range { start, end } = locations[destination_location_index];
18635
18636 editor.update_in(cx, |editor, window, cx| {
18637 let effects = SelectionEffects::default();
18638
18639 editor.unfold_ranges(&[start..end], false, false, cx);
18640 editor.change_selections(effects, window, cx, |s| {
18641 s.select_ranges([start..start]);
18642 });
18643 })?;
18644
18645 Ok(())
18646 }))
18647 }
18648
18649 pub fn find_all_references(
18650 &mut self,
18651 action: &FindAllReferences,
18652 window: &mut Window,
18653 cx: &mut Context<Self>,
18654 ) -> Option<Task<Result<Navigated>>> {
18655 let always_open_multibuffer = action.always_open_multibuffer;
18656 let selection = self.selections.newest_anchor();
18657 let multi_buffer = self.buffer.read(cx);
18658 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18659 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18660 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18661 let head = selection_offset.head();
18662
18663 let head_anchor = multi_buffer_snapshot.anchor_at(
18664 head,
18665 if head < selection_offset.tail() {
18666 Bias::Right
18667 } else {
18668 Bias::Left
18669 },
18670 );
18671
18672 match self
18673 .find_all_references_task_sources
18674 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18675 {
18676 Ok(_) => {
18677 log::info!(
18678 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18679 );
18680 return None;
18681 }
18682 Err(i) => {
18683 self.find_all_references_task_sources.insert(i, head_anchor);
18684 }
18685 }
18686
18687 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18688 let workspace = self.workspace()?;
18689 let project = workspace.read(cx).project().clone();
18690 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18691 Some(cx.spawn_in(window, async move |editor, cx| {
18692 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18693 if let Ok(i) = editor
18694 .find_all_references_task_sources
18695 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18696 {
18697 editor.find_all_references_task_sources.remove(i);
18698 }
18699 });
18700
18701 let Some(locations) = references.await? else {
18702 return anyhow::Ok(Navigated::No);
18703 };
18704 let mut locations = cx.update(|_, cx| {
18705 locations
18706 .into_iter()
18707 .map(|location| {
18708 let buffer = location.buffer.read(cx);
18709 (location.buffer, location.range.to_point(buffer))
18710 })
18711 // if special-casing the single-match case, remove ranges
18712 // that intersect current selection
18713 .filter(|(location_buffer, location)| {
18714 if always_open_multibuffer || &buffer != location_buffer {
18715 return true;
18716 }
18717
18718 !location.contains_inclusive(&selection_point.range())
18719 })
18720 .into_group_map()
18721 })?;
18722 if locations.is_empty() {
18723 return anyhow::Ok(Navigated::No);
18724 }
18725 for ranges in locations.values_mut() {
18726 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18727 ranges.dedup();
18728 }
18729 let mut num_locations = 0;
18730 for ranges in locations.values_mut() {
18731 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18732 ranges.dedup();
18733 num_locations += ranges.len();
18734 }
18735
18736 if num_locations == 1 && !always_open_multibuffer {
18737 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18738 let target_range = target_ranges.first().unwrap().clone();
18739
18740 return editor.update_in(cx, |editor, window, cx| {
18741 let range = target_range.to_point(target_buffer.read(cx));
18742 let range = editor.range_for_match(&range);
18743 let range = range.start..range.start;
18744
18745 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18746 editor.go_to_singleton_buffer_range(range, window, cx);
18747 } else {
18748 let pane = workspace.read(cx).active_pane().clone();
18749 window.defer(cx, move |window, cx| {
18750 let target_editor: Entity<Self> =
18751 workspace.update(cx, |workspace, cx| {
18752 let pane = workspace.active_pane().clone();
18753
18754 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18755 let keep_old_preview = preview_tabs_settings
18756 .enable_keep_preview_on_code_navigation;
18757 let allow_new_preview = preview_tabs_settings
18758 .enable_preview_file_from_code_navigation;
18759
18760 workspace.open_project_item(
18761 pane,
18762 target_buffer.clone(),
18763 true,
18764 true,
18765 keep_old_preview,
18766 allow_new_preview,
18767 window,
18768 cx,
18769 )
18770 });
18771 target_editor.update(cx, |target_editor, cx| {
18772 // When selecting a definition in a different buffer, disable the nav history
18773 // to avoid creating a history entry at the previous cursor location.
18774 pane.update(cx, |pane, _| pane.disable_history());
18775 target_editor.go_to_singleton_buffer_range(range, window, cx);
18776 pane.update(cx, |pane, _| pane.enable_history());
18777 });
18778 });
18779 }
18780 Navigated::No
18781 });
18782 }
18783
18784 workspace.update_in(cx, |workspace, window, cx| {
18785 let target = locations
18786 .iter()
18787 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18788 .map(|(buffer, location)| {
18789 buffer
18790 .read(cx)
18791 .text_for_range(location.clone())
18792 .collect::<String>()
18793 })
18794 .filter(|text| !text.contains('\n'))
18795 .unique()
18796 .take(3)
18797 .join(", ");
18798 let title = if target.is_empty() {
18799 "References".to_owned()
18800 } else {
18801 format!("References to {target}")
18802 };
18803 let allow_preview = PreviewTabsSettings::get_global(cx)
18804 .enable_preview_multibuffer_from_code_navigation;
18805 Self::open_locations_in_multibuffer(
18806 workspace,
18807 locations,
18808 title,
18809 false,
18810 allow_preview,
18811 MultibufferSelectionMode::First,
18812 window,
18813 cx,
18814 );
18815 Navigated::Yes
18816 })
18817 }))
18818 }
18819
18820 /// Opens a multibuffer with the given project locations in it.
18821 pub fn open_locations_in_multibuffer(
18822 workspace: &mut Workspace,
18823 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18824 title: String,
18825 split: bool,
18826 allow_preview: bool,
18827 multibuffer_selection_mode: MultibufferSelectionMode,
18828 window: &mut Window,
18829 cx: &mut Context<Workspace>,
18830 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18831 if locations.is_empty() {
18832 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18833 return None;
18834 }
18835
18836 let capability = workspace.project().read(cx).capability();
18837 let mut ranges = <Vec<Range<Anchor>>>::new();
18838
18839 // a key to find existing multibuffer editors with the same set of locations
18840 // to prevent us from opening more and more multibuffer tabs for searches and the like
18841 let mut key = (title.clone(), vec![]);
18842 let excerpt_buffer = cx.new(|cx| {
18843 let key = &mut key.1;
18844 let mut multibuffer = MultiBuffer::new(capability);
18845 for (buffer, mut ranges_for_buffer) in locations {
18846 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18847 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18848 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18849 PathKey::for_buffer(&buffer, cx),
18850 buffer.clone(),
18851 ranges_for_buffer,
18852 multibuffer_context_lines(cx),
18853 cx,
18854 );
18855 ranges.extend(new_ranges)
18856 }
18857
18858 multibuffer.with_title(title)
18859 });
18860 let existing = workspace.active_pane().update(cx, |pane, cx| {
18861 pane.items()
18862 .filter_map(|item| item.downcast::<Editor>())
18863 .find(|editor| {
18864 editor
18865 .read(cx)
18866 .lookup_key
18867 .as_ref()
18868 .and_then(|it| {
18869 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18870 })
18871 .is_some_and(|it| *it == key)
18872 })
18873 });
18874 let was_existing = existing.is_some();
18875 let editor = existing.unwrap_or_else(|| {
18876 cx.new(|cx| {
18877 let mut editor = Editor::for_multibuffer(
18878 excerpt_buffer,
18879 Some(workspace.project().clone()),
18880 window,
18881 cx,
18882 );
18883 editor.lookup_key = Some(Box::new(key));
18884 editor
18885 })
18886 });
18887 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18888 MultibufferSelectionMode::First => {
18889 if let Some(first_range) = ranges.first() {
18890 editor.change_selections(
18891 SelectionEffects::no_scroll(),
18892 window,
18893 cx,
18894 |selections| {
18895 selections.clear_disjoint();
18896 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18897 },
18898 );
18899 }
18900 editor.highlight_background(
18901 HighlightKey::Editor,
18902 &ranges,
18903 |_, theme| theme.colors().editor_highlighted_line_background,
18904 cx,
18905 );
18906 }
18907 MultibufferSelectionMode::All => {
18908 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18909 selections.clear_disjoint();
18910 selections.select_anchor_ranges(ranges);
18911 });
18912 }
18913 });
18914
18915 let item = Box::new(editor.clone());
18916
18917 let pane = if split {
18918 workspace.adjacent_pane(window, cx)
18919 } else {
18920 workspace.active_pane().clone()
18921 };
18922 let activate_pane = split;
18923
18924 let mut destination_index = None;
18925 pane.update(cx, |pane, cx| {
18926 if allow_preview && !was_existing {
18927 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18928 }
18929 if was_existing && !allow_preview {
18930 pane.unpreview_item_if_preview(item.item_id());
18931 }
18932 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18933 });
18934
18935 Some((editor, pane))
18936 }
18937
18938 pub fn rename(
18939 &mut self,
18940 _: &Rename,
18941 window: &mut Window,
18942 cx: &mut Context<Self>,
18943 ) -> Option<Task<Result<()>>> {
18944 use language::ToOffset as _;
18945
18946 let provider = self.semantics_provider.clone()?;
18947 let selection = self.selections.newest_anchor().clone();
18948 let (cursor_buffer, cursor_buffer_position) = self
18949 .buffer
18950 .read(cx)
18951 .text_anchor_for_position(selection.head(), cx)?;
18952 let (tail_buffer, cursor_buffer_position_end) = self
18953 .buffer
18954 .read(cx)
18955 .text_anchor_for_position(selection.tail(), cx)?;
18956 if tail_buffer != cursor_buffer {
18957 return None;
18958 }
18959
18960 let snapshot = cursor_buffer.read(cx).snapshot();
18961 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18962 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18963 let prepare_rename = provider
18964 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18965 .unwrap_or_else(|| Task::ready(Ok(None)));
18966 drop(snapshot);
18967
18968 Some(cx.spawn_in(window, async move |this, cx| {
18969 let rename_range = if let Some(range) = prepare_rename.await? {
18970 Some(range)
18971 } else {
18972 this.update(cx, |this, cx| {
18973 let buffer = this.buffer.read(cx).snapshot(cx);
18974 let mut buffer_highlights = this
18975 .document_highlights_for_position(selection.head(), &buffer)
18976 .filter(|highlight| {
18977 highlight.start.excerpt_id == selection.head().excerpt_id
18978 && highlight.end.excerpt_id == selection.head().excerpt_id
18979 });
18980 buffer_highlights
18981 .next()
18982 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18983 })?
18984 };
18985 if let Some(rename_range) = rename_range {
18986 this.update_in(cx, |this, window, cx| {
18987 let snapshot = cursor_buffer.read(cx).snapshot();
18988 let rename_buffer_range = rename_range.to_offset(&snapshot);
18989 let cursor_offset_in_rename_range =
18990 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18991 let cursor_offset_in_rename_range_end =
18992 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18993
18994 this.take_rename(false, window, cx);
18995 let buffer = this.buffer.read(cx).read(cx);
18996 let cursor_offset = selection.head().to_offset(&buffer);
18997 let rename_start =
18998 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18999 let rename_end = rename_start + rename_buffer_range.len();
19000 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19001 let mut old_highlight_id = None;
19002 let old_name: Arc<str> = buffer
19003 .chunks(rename_start..rename_end, true)
19004 .map(|chunk| {
19005 if old_highlight_id.is_none() {
19006 old_highlight_id = chunk.syntax_highlight_id;
19007 }
19008 chunk.text
19009 })
19010 .collect::<String>()
19011 .into();
19012
19013 drop(buffer);
19014
19015 // Position the selection in the rename editor so that it matches the current selection.
19016 this.show_local_selections = false;
19017 let rename_editor = cx.new(|cx| {
19018 let mut editor = Editor::single_line(window, cx);
19019 editor.buffer.update(cx, |buffer, cx| {
19020 buffer.edit(
19021 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19022 None,
19023 cx,
19024 )
19025 });
19026 let cursor_offset_in_rename_range =
19027 MultiBufferOffset(cursor_offset_in_rename_range);
19028 let cursor_offset_in_rename_range_end =
19029 MultiBufferOffset(cursor_offset_in_rename_range_end);
19030 let rename_selection_range = match cursor_offset_in_rename_range
19031 .cmp(&cursor_offset_in_rename_range_end)
19032 {
19033 Ordering::Equal => {
19034 editor.select_all(&SelectAll, window, cx);
19035 return editor;
19036 }
19037 Ordering::Less => {
19038 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19039 }
19040 Ordering::Greater => {
19041 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19042 }
19043 };
19044 if rename_selection_range.end.0 > old_name.len() {
19045 editor.select_all(&SelectAll, window, cx);
19046 } else {
19047 editor.change_selections(Default::default(), window, cx, |s| {
19048 s.select_ranges([rename_selection_range]);
19049 });
19050 }
19051 editor
19052 });
19053 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19054 if e == &EditorEvent::Focused {
19055 cx.emit(EditorEvent::FocusedIn)
19056 }
19057 })
19058 .detach();
19059
19060 let write_highlights =
19061 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19062 let read_highlights =
19063 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19064 let ranges = write_highlights
19065 .iter()
19066 .flat_map(|(_, ranges)| ranges.iter())
19067 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19068 .cloned()
19069 .collect();
19070
19071 this.highlight_text(
19072 HighlightKey::Rename,
19073 ranges,
19074 HighlightStyle {
19075 fade_out: Some(0.6),
19076 ..Default::default()
19077 },
19078 cx,
19079 );
19080 let rename_focus_handle = rename_editor.focus_handle(cx);
19081 window.focus(&rename_focus_handle, cx);
19082 let block_id = this.insert_blocks(
19083 [BlockProperties {
19084 style: BlockStyle::Flex,
19085 placement: BlockPlacement::Below(range.start),
19086 height: Some(1),
19087 render: Arc::new({
19088 let rename_editor = rename_editor.clone();
19089 move |cx: &mut BlockContext| {
19090 let mut text_style = cx.editor_style.text.clone();
19091 if let Some(highlight_style) = old_highlight_id
19092 .and_then(|h| h.style(&cx.editor_style.syntax))
19093 {
19094 text_style = text_style.highlight(highlight_style);
19095 }
19096 div()
19097 .block_mouse_except_scroll()
19098 .pl(cx.anchor_x)
19099 .child(EditorElement::new(
19100 &rename_editor,
19101 EditorStyle {
19102 background: cx.theme().system().transparent,
19103 local_player: cx.editor_style.local_player,
19104 text: text_style,
19105 scrollbar_width: cx.editor_style.scrollbar_width,
19106 syntax: cx.editor_style.syntax.clone(),
19107 status: cx.editor_style.status.clone(),
19108 inlay_hints_style: HighlightStyle {
19109 font_weight: Some(FontWeight::BOLD),
19110 ..make_inlay_hints_style(cx.app)
19111 },
19112 edit_prediction_styles: make_suggestion_styles(
19113 cx.app,
19114 ),
19115 ..EditorStyle::default()
19116 },
19117 ))
19118 .into_any_element()
19119 }
19120 }),
19121 priority: 0,
19122 }],
19123 Some(Autoscroll::fit()),
19124 cx,
19125 )[0];
19126 this.pending_rename = Some(RenameState {
19127 range,
19128 old_name,
19129 editor: rename_editor,
19130 block_id,
19131 });
19132 })?;
19133 }
19134
19135 Ok(())
19136 }))
19137 }
19138
19139 pub fn confirm_rename(
19140 &mut self,
19141 _: &ConfirmRename,
19142 window: &mut Window,
19143 cx: &mut Context<Self>,
19144 ) -> Option<Task<Result<()>>> {
19145 let rename = self.take_rename(false, window, cx)?;
19146 let workspace = self.workspace()?.downgrade();
19147 let (buffer, start) = self
19148 .buffer
19149 .read(cx)
19150 .text_anchor_for_position(rename.range.start, cx)?;
19151 let (end_buffer, _) = self
19152 .buffer
19153 .read(cx)
19154 .text_anchor_for_position(rename.range.end, cx)?;
19155 if buffer != end_buffer {
19156 return None;
19157 }
19158
19159 let old_name = rename.old_name;
19160 let new_name = rename.editor.read(cx).text(cx);
19161
19162 let rename = self.semantics_provider.as_ref()?.perform_rename(
19163 &buffer,
19164 start,
19165 new_name.clone(),
19166 cx,
19167 )?;
19168
19169 Some(cx.spawn_in(window, async move |editor, cx| {
19170 let project_transaction = rename.await?;
19171 Self::open_project_transaction(
19172 &editor,
19173 workspace,
19174 project_transaction,
19175 format!("Rename: {} → {}", old_name, new_name),
19176 cx,
19177 )
19178 .await?;
19179
19180 editor.update(cx, |editor, cx| {
19181 editor.refresh_document_highlights(cx);
19182 })?;
19183 Ok(())
19184 }))
19185 }
19186
19187 fn take_rename(
19188 &mut self,
19189 moving_cursor: bool,
19190 window: &mut Window,
19191 cx: &mut Context<Self>,
19192 ) -> Option<RenameState> {
19193 let rename = self.pending_rename.take()?;
19194 if rename.editor.focus_handle(cx).is_focused(window) {
19195 window.focus(&self.focus_handle, cx);
19196 }
19197
19198 self.remove_blocks(
19199 [rename.block_id].into_iter().collect(),
19200 Some(Autoscroll::fit()),
19201 cx,
19202 );
19203 self.clear_highlights(HighlightKey::Rename, cx);
19204 self.show_local_selections = true;
19205
19206 if moving_cursor {
19207 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19208 editor
19209 .selections
19210 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19211 .head()
19212 });
19213
19214 // Update the selection to match the position of the selection inside
19215 // the rename editor.
19216 let snapshot = self.buffer.read(cx).read(cx);
19217 let rename_range = rename.range.to_offset(&snapshot);
19218 let cursor_in_editor = snapshot
19219 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19220 .min(rename_range.end);
19221 drop(snapshot);
19222
19223 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19224 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19225 });
19226 } else {
19227 self.refresh_document_highlights(cx);
19228 }
19229
19230 Some(rename)
19231 }
19232
19233 pub fn pending_rename(&self) -> Option<&RenameState> {
19234 self.pending_rename.as_ref()
19235 }
19236
19237 fn format(
19238 &mut self,
19239 _: &Format,
19240 window: &mut Window,
19241 cx: &mut Context<Self>,
19242 ) -> Option<Task<Result<()>>> {
19243 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19244
19245 let project = match &self.project {
19246 Some(project) => project.clone(),
19247 None => return None,
19248 };
19249
19250 Some(self.perform_format(
19251 project,
19252 FormatTrigger::Manual,
19253 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19254 window,
19255 cx,
19256 ))
19257 }
19258
19259 fn format_selections(
19260 &mut self,
19261 _: &FormatSelections,
19262 window: &mut Window,
19263 cx: &mut Context<Self>,
19264 ) -> Option<Task<Result<()>>> {
19265 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19266
19267 let project = match &self.project {
19268 Some(project) => project.clone(),
19269 None => return None,
19270 };
19271
19272 let ranges = self
19273 .selections
19274 .all_adjusted(&self.display_snapshot(cx))
19275 .into_iter()
19276 .map(|selection| selection.range())
19277 .collect_vec();
19278
19279 Some(self.perform_format(
19280 project,
19281 FormatTrigger::Manual,
19282 FormatTarget::Ranges(ranges),
19283 window,
19284 cx,
19285 ))
19286 }
19287
19288 fn perform_format(
19289 &mut self,
19290 project: Entity<Project>,
19291 trigger: FormatTrigger,
19292 target: FormatTarget,
19293 window: &mut Window,
19294 cx: &mut Context<Self>,
19295 ) -> Task<Result<()>> {
19296 let buffer = self.buffer.clone();
19297 let (buffers, target) = match target {
19298 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19299 FormatTarget::Ranges(selection_ranges) => {
19300 let multi_buffer = buffer.read(cx);
19301 let snapshot = multi_buffer.read(cx);
19302 let mut buffers = HashSet::default();
19303 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19304 BTreeMap::new();
19305 for selection_range in selection_ranges {
19306 for (buffer, buffer_range, _) in
19307 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19308 {
19309 let buffer_id = buffer.remote_id();
19310 let start = buffer.anchor_before(buffer_range.start);
19311 let end = buffer.anchor_after(buffer_range.end);
19312 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19313 buffer_id_to_ranges
19314 .entry(buffer_id)
19315 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19316 .or_insert_with(|| vec![start..end]);
19317 }
19318 }
19319 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19320 }
19321 };
19322
19323 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19324 let selections_prev = transaction_id_prev
19325 .and_then(|transaction_id_prev| {
19326 // default to selections as they were after the last edit, if we have them,
19327 // instead of how they are now.
19328 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19329 // will take you back to where you made the last edit, instead of staying where you scrolled
19330 self.selection_history
19331 .transaction(transaction_id_prev)
19332 .map(|t| t.0.clone())
19333 })
19334 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19335
19336 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19337 let format = project.update(cx, |project, cx| {
19338 project.format(buffers, target, true, trigger, cx)
19339 });
19340
19341 cx.spawn_in(window, async move |editor, cx| {
19342 let transaction = futures::select_biased! {
19343 transaction = format.log_err().fuse() => transaction,
19344 () = timeout => {
19345 log::warn!("timed out waiting for formatting");
19346 None
19347 }
19348 };
19349
19350 buffer.update(cx, |buffer, cx| {
19351 if let Some(transaction) = transaction
19352 && !buffer.is_singleton()
19353 {
19354 buffer.push_transaction(&transaction.0, cx);
19355 }
19356 cx.notify();
19357 });
19358
19359 if let Some(transaction_id_now) =
19360 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19361 {
19362 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19363 if has_new_transaction {
19364 editor
19365 .update(cx, |editor, _| {
19366 editor
19367 .selection_history
19368 .insert_transaction(transaction_id_now, selections_prev);
19369 })
19370 .ok();
19371 }
19372 }
19373
19374 Ok(())
19375 })
19376 }
19377
19378 fn organize_imports(
19379 &mut self,
19380 _: &OrganizeImports,
19381 window: &mut Window,
19382 cx: &mut Context<Self>,
19383 ) -> Option<Task<Result<()>>> {
19384 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19385 let project = match &self.project {
19386 Some(project) => project.clone(),
19387 None => return None,
19388 };
19389 Some(self.perform_code_action_kind(
19390 project,
19391 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19392 window,
19393 cx,
19394 ))
19395 }
19396
19397 fn perform_code_action_kind(
19398 &mut self,
19399 project: Entity<Project>,
19400 kind: CodeActionKind,
19401 window: &mut Window,
19402 cx: &mut Context<Self>,
19403 ) -> Task<Result<()>> {
19404 let buffer = self.buffer.clone();
19405 let buffers = buffer.read(cx).all_buffers();
19406 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19407 let apply_action = project.update(cx, |project, cx| {
19408 project.apply_code_action_kind(buffers, kind, true, cx)
19409 });
19410 cx.spawn_in(window, async move |_, cx| {
19411 let transaction = futures::select_biased! {
19412 () = timeout => {
19413 log::warn!("timed out waiting for executing code action");
19414 None
19415 }
19416 transaction = apply_action.log_err().fuse() => transaction,
19417 };
19418 buffer.update(cx, |buffer, cx| {
19419 // check if we need this
19420 if let Some(transaction) = transaction
19421 && !buffer.is_singleton()
19422 {
19423 buffer.push_transaction(&transaction.0, cx);
19424 }
19425 cx.notify();
19426 });
19427 Ok(())
19428 })
19429 }
19430
19431 pub fn restart_language_server(
19432 &mut self,
19433 _: &RestartLanguageServer,
19434 _: &mut Window,
19435 cx: &mut Context<Self>,
19436 ) {
19437 if let Some(project) = self.project.clone() {
19438 self.buffer.update(cx, |multi_buffer, cx| {
19439 project.update(cx, |project, cx| {
19440 project.restart_language_servers_for_buffers(
19441 multi_buffer.all_buffers().into_iter().collect(),
19442 HashSet::default(),
19443 cx,
19444 );
19445 });
19446 })
19447 }
19448 }
19449
19450 pub fn stop_language_server(
19451 &mut self,
19452 _: &StopLanguageServer,
19453 _: &mut Window,
19454 cx: &mut Context<Self>,
19455 ) {
19456 if let Some(project) = self.project.clone() {
19457 self.buffer.update(cx, |multi_buffer, cx| {
19458 project.update(cx, |project, cx| {
19459 project.stop_language_servers_for_buffers(
19460 multi_buffer.all_buffers().into_iter().collect(),
19461 HashSet::default(),
19462 cx,
19463 );
19464 });
19465 });
19466 }
19467 }
19468
19469 fn cancel_language_server_work(
19470 workspace: &mut Workspace,
19471 _: &actions::CancelLanguageServerWork,
19472 _: &mut Window,
19473 cx: &mut Context<Workspace>,
19474 ) {
19475 let project = workspace.project();
19476 let buffers = workspace
19477 .active_item(cx)
19478 .and_then(|item| item.act_as::<Editor>(cx))
19479 .map_or(HashSet::default(), |editor| {
19480 editor.read(cx).buffer.read(cx).all_buffers()
19481 });
19482 project.update(cx, |project, cx| {
19483 project.cancel_language_server_work_for_buffers(buffers, cx);
19484 });
19485 }
19486
19487 fn show_character_palette(
19488 &mut self,
19489 _: &ShowCharacterPalette,
19490 window: &mut Window,
19491 _: &mut Context<Self>,
19492 ) {
19493 window.show_character_palette();
19494 }
19495
19496 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19497 if !self.diagnostics_enabled() {
19498 return;
19499 }
19500
19501 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19502 let buffer = self.buffer.read(cx).snapshot(cx);
19503 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19504 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19505 let is_valid = buffer
19506 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19507 .any(|entry| {
19508 entry.diagnostic.is_primary
19509 && !entry.range.is_empty()
19510 && entry.range.start == primary_range_start
19511 && entry.diagnostic.message == active_diagnostics.active_message
19512 });
19513
19514 if !is_valid {
19515 self.dismiss_diagnostics(cx);
19516 }
19517 }
19518 }
19519
19520 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19521 match &self.active_diagnostics {
19522 ActiveDiagnostic::Group(group) => Some(group),
19523 _ => None,
19524 }
19525 }
19526
19527 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19528 if !self.diagnostics_enabled() {
19529 return;
19530 }
19531 self.dismiss_diagnostics(cx);
19532 self.active_diagnostics = ActiveDiagnostic::All;
19533 }
19534
19535 fn activate_diagnostics(
19536 &mut self,
19537 buffer_id: BufferId,
19538 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19539 window: &mut Window,
19540 cx: &mut Context<Self>,
19541 ) {
19542 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19543 return;
19544 }
19545 self.dismiss_diagnostics(cx);
19546 let snapshot = self.snapshot(window, cx);
19547 let buffer = self.buffer.read(cx).snapshot(cx);
19548 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19549 return;
19550 };
19551
19552 let diagnostic_group = buffer
19553 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19554 .collect::<Vec<_>>();
19555
19556 let language_registry = self
19557 .project()
19558 .map(|project| project.read(cx).languages().clone());
19559
19560 let blocks = renderer.render_group(
19561 diagnostic_group,
19562 buffer_id,
19563 snapshot,
19564 cx.weak_entity(),
19565 language_registry,
19566 cx,
19567 );
19568
19569 let blocks = self.display_map.update(cx, |display_map, cx| {
19570 display_map.insert_blocks(blocks, cx).into_iter().collect()
19571 });
19572 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19573 active_range: buffer.anchor_before(diagnostic.range.start)
19574 ..buffer.anchor_after(diagnostic.range.end),
19575 active_message: diagnostic.diagnostic.message.clone(),
19576 group_id: diagnostic.diagnostic.group_id,
19577 blocks,
19578 });
19579 cx.notify();
19580 }
19581
19582 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19583 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19584 return;
19585 };
19586
19587 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19588 if let ActiveDiagnostic::Group(group) = prev {
19589 self.display_map.update(cx, |display_map, cx| {
19590 display_map.remove_blocks(group.blocks, cx);
19591 });
19592 cx.notify();
19593 }
19594 }
19595
19596 /// Disable inline diagnostics rendering for this editor.
19597 pub fn disable_inline_diagnostics(&mut self) {
19598 self.inline_diagnostics_enabled = false;
19599 self.inline_diagnostics_update = Task::ready(());
19600 self.inline_diagnostics.clear();
19601 }
19602
19603 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19604 self.diagnostics_enabled = false;
19605 self.dismiss_diagnostics(cx);
19606 self.inline_diagnostics_update = Task::ready(());
19607 self.inline_diagnostics.clear();
19608 }
19609
19610 pub fn disable_word_completions(&mut self) {
19611 self.word_completions_enabled = false;
19612 }
19613
19614 pub fn diagnostics_enabled(&self) -> bool {
19615 self.diagnostics_enabled && self.lsp_data_enabled()
19616 }
19617
19618 pub fn inline_diagnostics_enabled(&self) -> bool {
19619 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19620 }
19621
19622 pub fn show_inline_diagnostics(&self) -> bool {
19623 self.show_inline_diagnostics
19624 }
19625
19626 pub fn toggle_inline_diagnostics(
19627 &mut self,
19628 _: &ToggleInlineDiagnostics,
19629 window: &mut Window,
19630 cx: &mut Context<Editor>,
19631 ) {
19632 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19633 self.refresh_inline_diagnostics(false, window, cx);
19634 }
19635
19636 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19637 self.diagnostics_max_severity = severity;
19638 self.display_map.update(cx, |display_map, _| {
19639 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19640 });
19641 }
19642
19643 pub fn toggle_diagnostics(
19644 &mut self,
19645 _: &ToggleDiagnostics,
19646 window: &mut Window,
19647 cx: &mut Context<Editor>,
19648 ) {
19649 if !self.diagnostics_enabled() {
19650 return;
19651 }
19652
19653 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19654 EditorSettings::get_global(cx)
19655 .diagnostics_max_severity
19656 .filter(|severity| severity != &DiagnosticSeverity::Off)
19657 .unwrap_or(DiagnosticSeverity::Hint)
19658 } else {
19659 DiagnosticSeverity::Off
19660 };
19661 self.set_max_diagnostics_severity(new_severity, cx);
19662 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19663 self.active_diagnostics = ActiveDiagnostic::None;
19664 self.inline_diagnostics_update = Task::ready(());
19665 self.inline_diagnostics.clear();
19666 } else {
19667 self.refresh_inline_diagnostics(false, window, cx);
19668 }
19669
19670 cx.notify();
19671 }
19672
19673 pub fn toggle_minimap(
19674 &mut self,
19675 _: &ToggleMinimap,
19676 window: &mut Window,
19677 cx: &mut Context<Editor>,
19678 ) {
19679 if self.supports_minimap(cx) {
19680 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19681 }
19682 }
19683
19684 fn refresh_inline_diagnostics(
19685 &mut self,
19686 debounce: bool,
19687 window: &mut Window,
19688 cx: &mut Context<Self>,
19689 ) {
19690 let max_severity = ProjectSettings::get_global(cx)
19691 .diagnostics
19692 .inline
19693 .max_severity
19694 .unwrap_or(self.diagnostics_max_severity);
19695
19696 if !self.inline_diagnostics_enabled()
19697 || !self.diagnostics_enabled()
19698 || !self.show_inline_diagnostics
19699 || max_severity == DiagnosticSeverity::Off
19700 {
19701 self.inline_diagnostics_update = Task::ready(());
19702 self.inline_diagnostics.clear();
19703 return;
19704 }
19705
19706 let debounce_ms = ProjectSettings::get_global(cx)
19707 .diagnostics
19708 .inline
19709 .update_debounce_ms;
19710 let debounce = if debounce && debounce_ms > 0 {
19711 Some(Duration::from_millis(debounce_ms))
19712 } else {
19713 None
19714 };
19715 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19716 if let Some(debounce) = debounce {
19717 cx.background_executor().timer(debounce).await;
19718 }
19719 let Some(snapshot) = editor.upgrade().map(|editor| {
19720 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19721 }) else {
19722 return;
19723 };
19724
19725 let new_inline_diagnostics = cx
19726 .background_spawn(async move {
19727 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19728 for diagnostic_entry in
19729 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19730 {
19731 let message = diagnostic_entry
19732 .diagnostic
19733 .message
19734 .split_once('\n')
19735 .map(|(line, _)| line)
19736 .map(SharedString::new)
19737 .unwrap_or_else(|| {
19738 SharedString::new(&*diagnostic_entry.diagnostic.message)
19739 });
19740 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19741 let (Ok(i) | Err(i)) = inline_diagnostics
19742 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19743 inline_diagnostics.insert(
19744 i,
19745 (
19746 start_anchor,
19747 InlineDiagnostic {
19748 message,
19749 group_id: diagnostic_entry.diagnostic.group_id,
19750 start: diagnostic_entry.range.start.to_point(&snapshot),
19751 is_primary: diagnostic_entry.diagnostic.is_primary,
19752 severity: diagnostic_entry.diagnostic.severity,
19753 },
19754 ),
19755 );
19756 }
19757 inline_diagnostics
19758 })
19759 .await;
19760
19761 editor
19762 .update(cx, |editor, cx| {
19763 editor.inline_diagnostics = new_inline_diagnostics;
19764 cx.notify();
19765 })
19766 .ok();
19767 });
19768 }
19769
19770 fn pull_diagnostics(
19771 &mut self,
19772 buffer_id: BufferId,
19773 _window: &Window,
19774 cx: &mut Context<Self>,
19775 ) -> Option<()> {
19776 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19777 // skip any LSP updates for it.
19778
19779 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19780 return None;
19781 }
19782 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19783 .diagnostics
19784 .lsp_pull_diagnostics;
19785 if !pull_diagnostics_settings.enabled {
19786 return None;
19787 }
19788 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19789 let project = self.project()?.downgrade();
19790 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19791
19792 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19793 cx.background_executor().timer(debounce).await;
19794 if let Ok(task) = project.update(cx, |project, cx| {
19795 project.lsp_store().update(cx, |lsp_store, cx| {
19796 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19797 })
19798 }) {
19799 task.await.log_err();
19800 }
19801 project
19802 .update(cx, |project, cx| {
19803 project.lsp_store().update(cx, |lsp_store, cx| {
19804 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19805 })
19806 })
19807 .log_err();
19808 });
19809
19810 Some(())
19811 }
19812
19813 pub fn set_selections_from_remote(
19814 &mut self,
19815 selections: Vec<Selection<Anchor>>,
19816 pending_selection: Option<Selection<Anchor>>,
19817 window: &mut Window,
19818 cx: &mut Context<Self>,
19819 ) {
19820 let old_cursor_position = self.selections.newest_anchor().head();
19821 self.selections
19822 .change_with(&self.display_snapshot(cx), |s| {
19823 s.select_anchors(selections);
19824 if let Some(pending_selection) = pending_selection {
19825 s.set_pending(pending_selection, SelectMode::Character);
19826 } else {
19827 s.clear_pending();
19828 }
19829 });
19830 self.selections_did_change(
19831 false,
19832 &old_cursor_position,
19833 SelectionEffects::default(),
19834 window,
19835 cx,
19836 );
19837 }
19838
19839 pub fn transact(
19840 &mut self,
19841 window: &mut Window,
19842 cx: &mut Context<Self>,
19843 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19844 ) -> Option<TransactionId> {
19845 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19846 this.start_transaction_at(Instant::now(), window, cx);
19847 update(this, window, cx);
19848 this.end_transaction_at(Instant::now(), cx)
19849 })
19850 }
19851
19852 pub fn start_transaction_at(
19853 &mut self,
19854 now: Instant,
19855 window: &mut Window,
19856 cx: &mut Context<Self>,
19857 ) -> Option<TransactionId> {
19858 self.end_selection(window, cx);
19859 if let Some(tx_id) = self
19860 .buffer
19861 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19862 {
19863 self.selection_history
19864 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19865 cx.emit(EditorEvent::TransactionBegun {
19866 transaction_id: tx_id,
19867 });
19868 Some(tx_id)
19869 } else {
19870 None
19871 }
19872 }
19873
19874 pub fn end_transaction_at(
19875 &mut self,
19876 now: Instant,
19877 cx: &mut Context<Self>,
19878 ) -> Option<TransactionId> {
19879 if let Some(transaction_id) = self
19880 .buffer
19881 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19882 {
19883 if let Some((_, end_selections)) =
19884 self.selection_history.transaction_mut(transaction_id)
19885 {
19886 *end_selections = Some(self.selections.disjoint_anchors_arc());
19887 } else {
19888 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19889 }
19890
19891 cx.emit(EditorEvent::Edited { transaction_id });
19892 Some(transaction_id)
19893 } else {
19894 None
19895 }
19896 }
19897
19898 pub fn modify_transaction_selection_history(
19899 &mut self,
19900 transaction_id: TransactionId,
19901 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19902 ) -> bool {
19903 self.selection_history
19904 .transaction_mut(transaction_id)
19905 .map(modify)
19906 .is_some()
19907 }
19908
19909 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19910 if self.selection_mark_mode {
19911 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19912 s.move_with(&mut |_, sel| {
19913 sel.collapse_to(sel.head(), SelectionGoal::None);
19914 });
19915 })
19916 }
19917 self.selection_mark_mode = true;
19918 cx.notify();
19919 }
19920
19921 pub fn swap_selection_ends(
19922 &mut self,
19923 _: &actions::SwapSelectionEnds,
19924 window: &mut Window,
19925 cx: &mut Context<Self>,
19926 ) {
19927 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19928 s.move_with(&mut |_, sel| {
19929 if sel.start != sel.end {
19930 sel.reversed = !sel.reversed
19931 }
19932 });
19933 });
19934 self.request_autoscroll(Autoscroll::newest(), cx);
19935 cx.notify();
19936 }
19937
19938 pub fn toggle_focus(
19939 workspace: &mut Workspace,
19940 _: &actions::ToggleFocus,
19941 window: &mut Window,
19942 cx: &mut Context<Workspace>,
19943 ) {
19944 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19945 return;
19946 };
19947 workspace.activate_item(&item, true, true, window, cx);
19948 }
19949
19950 pub fn toggle_fold(
19951 &mut self,
19952 _: &actions::ToggleFold,
19953 window: &mut Window,
19954 cx: &mut Context<Self>,
19955 ) {
19956 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19957 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19958 let selection = self.selections.newest::<Point>(&display_map);
19959
19960 let range = if selection.is_empty() {
19961 let point = selection.head().to_display_point(&display_map);
19962 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19963 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19964 .to_point(&display_map);
19965 start..end
19966 } else {
19967 selection.range()
19968 };
19969 if display_map.folds_in_range(range).next().is_some() {
19970 self.unfold_lines(&Default::default(), window, cx)
19971 } else {
19972 self.fold(&Default::default(), window, cx)
19973 }
19974 } else {
19975 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19976 let buffer_ids: HashSet<_> = self
19977 .selections
19978 .disjoint_anchor_ranges()
19979 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19980 .collect();
19981
19982 let should_unfold = buffer_ids
19983 .iter()
19984 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19985
19986 for buffer_id in buffer_ids {
19987 if should_unfold {
19988 self.unfold_buffer(buffer_id, cx);
19989 } else {
19990 self.fold_buffer(buffer_id, cx);
19991 }
19992 }
19993 }
19994 }
19995
19996 pub fn toggle_fold_recursive(
19997 &mut self,
19998 _: &actions::ToggleFoldRecursive,
19999 window: &mut Window,
20000 cx: &mut Context<Self>,
20001 ) {
20002 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20003
20004 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20005 let range = if selection.is_empty() {
20006 let point = selection.head().to_display_point(&display_map);
20007 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20008 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20009 .to_point(&display_map);
20010 start..end
20011 } else {
20012 selection.range()
20013 };
20014 if display_map.folds_in_range(range).next().is_some() {
20015 self.unfold_recursive(&Default::default(), window, cx)
20016 } else {
20017 self.fold_recursive(&Default::default(), window, cx)
20018 }
20019 }
20020
20021 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20022 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20023 let mut to_fold = Vec::new();
20024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20025 let selections = self.selections.all_adjusted(&display_map);
20026
20027 for selection in selections {
20028 let range = selection.range().sorted();
20029 let buffer_start_row = range.start.row;
20030
20031 if range.start.row != range.end.row {
20032 let mut found = false;
20033 let mut row = range.start.row;
20034 while row <= range.end.row {
20035 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20036 {
20037 found = true;
20038 row = crease.range().end.row + 1;
20039 to_fold.push(crease);
20040 } else {
20041 row += 1
20042 }
20043 }
20044 if found {
20045 continue;
20046 }
20047 }
20048
20049 for row in (0..=range.start.row).rev() {
20050 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20051 && crease.range().end.row >= buffer_start_row
20052 {
20053 to_fold.push(crease);
20054 if row <= range.start.row {
20055 break;
20056 }
20057 }
20058 }
20059 }
20060
20061 self.fold_creases(to_fold, true, window, cx);
20062 } else {
20063 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20064 let buffer_ids = self
20065 .selections
20066 .disjoint_anchor_ranges()
20067 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20068 .collect::<HashSet<_>>();
20069 for buffer_id in buffer_ids {
20070 self.fold_buffer(buffer_id, cx);
20071 }
20072 }
20073 }
20074
20075 pub fn toggle_fold_all(
20076 &mut self,
20077 _: &actions::ToggleFoldAll,
20078 window: &mut Window,
20079 cx: &mut Context<Self>,
20080 ) {
20081 let has_folds = if self.buffer.read(cx).is_singleton() {
20082 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20083 let has_folds = display_map
20084 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20085 .next()
20086 .is_some();
20087 has_folds
20088 } else {
20089 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
20090 let has_folds = buffer_ids
20091 .iter()
20092 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20093 has_folds
20094 };
20095
20096 if has_folds {
20097 self.unfold_all(&actions::UnfoldAll, window, cx);
20098 } else {
20099 self.fold_all(&actions::FoldAll, window, cx);
20100 }
20101 }
20102
20103 fn fold_at_level(
20104 &mut self,
20105 fold_at: &FoldAtLevel,
20106 window: &mut Window,
20107 cx: &mut Context<Self>,
20108 ) {
20109 if !self.buffer.read(cx).is_singleton() {
20110 return;
20111 }
20112
20113 let fold_at_level = fold_at.0;
20114 let snapshot = self.buffer.read(cx).snapshot(cx);
20115 let mut to_fold = Vec::new();
20116 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20117
20118 let row_ranges_to_keep: Vec<Range<u32>> = self
20119 .selections
20120 .all::<Point>(&self.display_snapshot(cx))
20121 .into_iter()
20122 .map(|sel| sel.start.row..sel.end.row)
20123 .collect();
20124
20125 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20126 while start_row < end_row {
20127 match self
20128 .snapshot(window, cx)
20129 .crease_for_buffer_row(MultiBufferRow(start_row))
20130 {
20131 Some(crease) => {
20132 let nested_start_row = crease.range().start.row + 1;
20133 let nested_end_row = crease.range().end.row;
20134
20135 if current_level < fold_at_level {
20136 stack.push((nested_start_row, nested_end_row, current_level + 1));
20137 } else if current_level == fold_at_level {
20138 // Fold iff there is no selection completely contained within the fold region
20139 if !row_ranges_to_keep.iter().any(|selection| {
20140 selection.end >= nested_start_row
20141 && selection.start <= nested_end_row
20142 }) {
20143 to_fold.push(crease);
20144 }
20145 }
20146
20147 start_row = nested_end_row + 1;
20148 }
20149 None => start_row += 1,
20150 }
20151 }
20152 }
20153
20154 self.fold_creases(to_fold, true, window, cx);
20155 }
20156
20157 pub fn fold_at_level_1(
20158 &mut self,
20159 _: &actions::FoldAtLevel1,
20160 window: &mut Window,
20161 cx: &mut Context<Self>,
20162 ) {
20163 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20164 }
20165
20166 pub fn fold_at_level_2(
20167 &mut self,
20168 _: &actions::FoldAtLevel2,
20169 window: &mut Window,
20170 cx: &mut Context<Self>,
20171 ) {
20172 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20173 }
20174
20175 pub fn fold_at_level_3(
20176 &mut self,
20177 _: &actions::FoldAtLevel3,
20178 window: &mut Window,
20179 cx: &mut Context<Self>,
20180 ) {
20181 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20182 }
20183
20184 pub fn fold_at_level_4(
20185 &mut self,
20186 _: &actions::FoldAtLevel4,
20187 window: &mut Window,
20188 cx: &mut Context<Self>,
20189 ) {
20190 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20191 }
20192
20193 pub fn fold_at_level_5(
20194 &mut self,
20195 _: &actions::FoldAtLevel5,
20196 window: &mut Window,
20197 cx: &mut Context<Self>,
20198 ) {
20199 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20200 }
20201
20202 pub fn fold_at_level_6(
20203 &mut self,
20204 _: &actions::FoldAtLevel6,
20205 window: &mut Window,
20206 cx: &mut Context<Self>,
20207 ) {
20208 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20209 }
20210
20211 pub fn fold_at_level_7(
20212 &mut self,
20213 _: &actions::FoldAtLevel7,
20214 window: &mut Window,
20215 cx: &mut Context<Self>,
20216 ) {
20217 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20218 }
20219
20220 pub fn fold_at_level_8(
20221 &mut self,
20222 _: &actions::FoldAtLevel8,
20223 window: &mut Window,
20224 cx: &mut Context<Self>,
20225 ) {
20226 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20227 }
20228
20229 pub fn fold_at_level_9(
20230 &mut self,
20231 _: &actions::FoldAtLevel9,
20232 window: &mut Window,
20233 cx: &mut Context<Self>,
20234 ) {
20235 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20236 }
20237
20238 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20239 if self.buffer.read(cx).is_singleton() {
20240 let mut fold_ranges = Vec::new();
20241 let snapshot = self.buffer.read(cx).snapshot(cx);
20242
20243 for row in 0..snapshot.max_row().0 {
20244 if let Some(foldable_range) = self
20245 .snapshot(window, cx)
20246 .crease_for_buffer_row(MultiBufferRow(row))
20247 {
20248 fold_ranges.push(foldable_range);
20249 }
20250 }
20251
20252 self.fold_creases(fold_ranges, true, window, cx);
20253 } else {
20254 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20255 editor
20256 .update_in(cx, |editor, _, cx| {
20257 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20258 editor.fold_buffer(buffer_id, cx);
20259 }
20260 })
20261 .ok();
20262 });
20263 }
20264 }
20265
20266 pub fn fold_function_bodies(
20267 &mut self,
20268 _: &actions::FoldFunctionBodies,
20269 window: &mut Window,
20270 cx: &mut Context<Self>,
20271 ) {
20272 let snapshot = self.buffer.read(cx).snapshot(cx);
20273
20274 let ranges = snapshot
20275 .text_object_ranges(
20276 MultiBufferOffset(0)..snapshot.len(),
20277 TreeSitterOptions::default(),
20278 )
20279 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20280 .collect::<Vec<_>>();
20281
20282 let creases = ranges
20283 .into_iter()
20284 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20285 .collect();
20286
20287 self.fold_creases(creases, true, window, cx);
20288 }
20289
20290 pub fn fold_recursive(
20291 &mut self,
20292 _: &actions::FoldRecursive,
20293 window: &mut Window,
20294 cx: &mut Context<Self>,
20295 ) {
20296 let mut to_fold = Vec::new();
20297 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20298 let selections = self.selections.all_adjusted(&display_map);
20299
20300 for selection in selections {
20301 let range = selection.range().sorted();
20302 let buffer_start_row = range.start.row;
20303
20304 if range.start.row != range.end.row {
20305 let mut found = false;
20306 for row in range.start.row..=range.end.row {
20307 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20308 found = true;
20309 to_fold.push(crease);
20310 }
20311 }
20312 if found {
20313 continue;
20314 }
20315 }
20316
20317 for row in (0..=range.start.row).rev() {
20318 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20319 if crease.range().end.row >= buffer_start_row {
20320 to_fold.push(crease);
20321 } else {
20322 break;
20323 }
20324 }
20325 }
20326 }
20327
20328 self.fold_creases(to_fold, true, window, cx);
20329 }
20330
20331 pub fn fold_at(
20332 &mut self,
20333 buffer_row: MultiBufferRow,
20334 window: &mut Window,
20335 cx: &mut Context<Self>,
20336 ) {
20337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20338
20339 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20340 let autoscroll = self
20341 .selections
20342 .all::<Point>(&display_map)
20343 .iter()
20344 .any(|selection| crease.range().overlaps(&selection.range()));
20345
20346 self.fold_creases(vec![crease], autoscroll, window, cx);
20347 }
20348 }
20349
20350 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20351 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20352 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20353 let buffer = display_map.buffer_snapshot();
20354 let selections = self.selections.all::<Point>(&display_map);
20355 let ranges = selections
20356 .iter()
20357 .map(|s| {
20358 let range = s.display_range(&display_map).sorted();
20359 let mut start = range.start.to_point(&display_map);
20360 let mut end = range.end.to_point(&display_map);
20361 start.column = 0;
20362 end.column = buffer.line_len(MultiBufferRow(end.row));
20363 start..end
20364 })
20365 .collect::<Vec<_>>();
20366
20367 self.unfold_ranges(&ranges, true, true, cx);
20368 } else {
20369 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20370 let buffer_ids = self
20371 .selections
20372 .disjoint_anchor_ranges()
20373 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20374 .collect::<HashSet<_>>();
20375 for buffer_id in buffer_ids {
20376 self.unfold_buffer(buffer_id, cx);
20377 }
20378 }
20379 }
20380
20381 pub fn unfold_recursive(
20382 &mut self,
20383 _: &UnfoldRecursive,
20384 _window: &mut Window,
20385 cx: &mut Context<Self>,
20386 ) {
20387 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20388 let selections = self.selections.all::<Point>(&display_map);
20389 let ranges = selections
20390 .iter()
20391 .map(|s| {
20392 let mut range = s.display_range(&display_map).sorted();
20393 *range.start.column_mut() = 0;
20394 *range.end.column_mut() = display_map.line_len(range.end.row());
20395 let start = range.start.to_point(&display_map);
20396 let end = range.end.to_point(&display_map);
20397 start..end
20398 })
20399 .collect::<Vec<_>>();
20400
20401 self.unfold_ranges(&ranges, true, true, cx);
20402 }
20403
20404 pub fn unfold_at(
20405 &mut self,
20406 buffer_row: MultiBufferRow,
20407 _window: &mut Window,
20408 cx: &mut Context<Self>,
20409 ) {
20410 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20411
20412 let intersection_range = Point::new(buffer_row.0, 0)
20413 ..Point::new(
20414 buffer_row.0,
20415 display_map.buffer_snapshot().line_len(buffer_row),
20416 );
20417
20418 let autoscroll = self
20419 .selections
20420 .all::<Point>(&display_map)
20421 .iter()
20422 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20423
20424 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20425 }
20426
20427 pub fn unfold_all(
20428 &mut self,
20429 _: &actions::UnfoldAll,
20430 _window: &mut Window,
20431 cx: &mut Context<Self>,
20432 ) {
20433 if self.buffer.read(cx).is_singleton() {
20434 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20435 self.unfold_ranges(
20436 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20437 true,
20438 true,
20439 cx,
20440 );
20441 } else {
20442 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20443 editor
20444 .update(cx, |editor, cx| {
20445 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20446 editor.unfold_buffer(buffer_id, cx);
20447 }
20448 })
20449 .ok();
20450 });
20451 }
20452 }
20453
20454 pub fn fold_selected_ranges(
20455 &mut self,
20456 _: &FoldSelectedRanges,
20457 window: &mut Window,
20458 cx: &mut Context<Self>,
20459 ) {
20460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20461 let selections = self.selections.all_adjusted(&display_map);
20462 let ranges = selections
20463 .into_iter()
20464 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20465 .collect::<Vec<_>>();
20466 self.fold_creases(ranges, true, window, cx);
20467 }
20468
20469 pub fn fold_ranges<T: ToOffset + Clone>(
20470 &mut self,
20471 ranges: Vec<Range<T>>,
20472 auto_scroll: bool,
20473 window: &mut Window,
20474 cx: &mut Context<Self>,
20475 ) {
20476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20477 let ranges = ranges
20478 .into_iter()
20479 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20480 .collect::<Vec<_>>();
20481 self.fold_creases(ranges, auto_scroll, window, cx);
20482 }
20483
20484 pub fn fold_creases<T: ToOffset + Clone>(
20485 &mut self,
20486 creases: Vec<Crease<T>>,
20487 auto_scroll: bool,
20488 _window: &mut Window,
20489 cx: &mut Context<Self>,
20490 ) {
20491 if creases.is_empty() {
20492 return;
20493 }
20494
20495 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20496
20497 if auto_scroll {
20498 self.request_autoscroll(Autoscroll::fit(), cx);
20499 }
20500
20501 cx.notify();
20502
20503 self.scrollbar_marker_state.dirty = true;
20504 self.folds_did_change(cx);
20505 }
20506
20507 /// Removes any folds whose ranges intersect any of the given ranges.
20508 pub fn unfold_ranges<T: ToOffset + Clone>(
20509 &mut self,
20510 ranges: &[Range<T>],
20511 inclusive: bool,
20512 auto_scroll: bool,
20513 cx: &mut Context<Self>,
20514 ) {
20515 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20516 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20517 });
20518 self.folds_did_change(cx);
20519 }
20520
20521 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20522 self.fold_buffers([buffer_id], cx);
20523 }
20524
20525 pub fn fold_buffers(
20526 &mut self,
20527 buffer_ids: impl IntoIterator<Item = BufferId>,
20528 cx: &mut Context<Self>,
20529 ) {
20530 if self.buffer().read(cx).is_singleton() {
20531 return;
20532 }
20533
20534 let ids_to_fold: Vec<BufferId> = buffer_ids
20535 .into_iter()
20536 .filter(|id| !self.is_buffer_folded(*id, cx))
20537 .collect();
20538
20539 if ids_to_fold.is_empty() {
20540 return;
20541 }
20542
20543 let mut all_folded_excerpt_ids = Vec::new();
20544 for buffer_id in &ids_to_fold {
20545 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20546 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20547 }
20548
20549 self.display_map.update(cx, |display_map, cx| {
20550 display_map.fold_buffers(ids_to_fold.clone(), cx)
20551 });
20552
20553 let snapshot = self.display_snapshot(cx);
20554 self.selections.change_with(&snapshot, |selections| {
20555 for buffer_id in ids_to_fold {
20556 selections.remove_selections_from_buffer(buffer_id);
20557 }
20558 });
20559
20560 cx.emit(EditorEvent::BufferFoldToggled {
20561 ids: all_folded_excerpt_ids,
20562 folded: true,
20563 });
20564 cx.notify();
20565 }
20566
20567 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20568 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20569 return;
20570 }
20571 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20572 self.display_map.update(cx, |display_map, cx| {
20573 display_map.unfold_buffers([buffer_id], cx);
20574 });
20575 cx.emit(EditorEvent::BufferFoldToggled {
20576 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20577 folded: false,
20578 });
20579 cx.notify();
20580 }
20581
20582 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20583 self.display_map.read(cx).is_buffer_folded(buffer)
20584 }
20585
20586 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20587 if self.buffer().read(cx).is_singleton() {
20588 return false;
20589 }
20590 !self.folded_buffers(cx).is_empty()
20591 }
20592
20593 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20594 self.display_map.read(cx).folded_buffers()
20595 }
20596
20597 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20598 self.display_map.update(cx, |display_map, cx| {
20599 display_map.disable_header_for_buffer(buffer_id, cx);
20600 });
20601 cx.notify();
20602 }
20603
20604 /// Removes any folds with the given ranges.
20605 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20606 &mut self,
20607 ranges: &[Range<T>],
20608 type_id: TypeId,
20609 auto_scroll: bool,
20610 cx: &mut Context<Self>,
20611 ) {
20612 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20613 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20614 });
20615 self.folds_did_change(cx);
20616 }
20617
20618 fn remove_folds_with<T: ToOffset + Clone>(
20619 &mut self,
20620 ranges: &[Range<T>],
20621 auto_scroll: bool,
20622 cx: &mut Context<Self>,
20623 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20624 ) {
20625 if ranges.is_empty() {
20626 return;
20627 }
20628
20629 let mut buffers_affected = HashSet::default();
20630 let multi_buffer = self.buffer().read(cx);
20631 for range in ranges {
20632 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20633 buffers_affected.insert(buffer.read(cx).remote_id());
20634 };
20635 }
20636
20637 self.display_map.update(cx, update);
20638
20639 if auto_scroll {
20640 self.request_autoscroll(Autoscroll::fit(), cx);
20641 }
20642
20643 cx.notify();
20644 self.scrollbar_marker_state.dirty = true;
20645 self.active_indent_guides_state.dirty = true;
20646 }
20647
20648 pub fn update_renderer_widths(
20649 &mut self,
20650 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20651 cx: &mut Context<Self>,
20652 ) -> bool {
20653 self.display_map
20654 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20655 }
20656
20657 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20658 self.display_map.read(cx).fold_placeholder.clone()
20659 }
20660
20661 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20662 self.buffer.update(cx, |buffer, cx| {
20663 buffer.set_all_diff_hunks_expanded(cx);
20664 });
20665 }
20666
20667 pub fn expand_all_diff_hunks(
20668 &mut self,
20669 _: &ExpandAllDiffHunks,
20670 _window: &mut Window,
20671 cx: &mut Context<Self>,
20672 ) {
20673 self.buffer.update(cx, |buffer, cx| {
20674 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20675 });
20676 }
20677
20678 pub fn collapse_all_diff_hunks(
20679 &mut self,
20680 _: &CollapseAllDiffHunks,
20681 _window: &mut Window,
20682 cx: &mut Context<Self>,
20683 ) {
20684 self.buffer.update(cx, |buffer, cx| {
20685 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20686 });
20687 }
20688
20689 pub fn toggle_selected_diff_hunks(
20690 &mut self,
20691 _: &ToggleSelectedDiffHunks,
20692 _window: &mut Window,
20693 cx: &mut Context<Self>,
20694 ) {
20695 let ranges: Vec<_> = self
20696 .selections
20697 .disjoint_anchors()
20698 .iter()
20699 .map(|s| s.range())
20700 .collect();
20701 self.toggle_diff_hunks_in_ranges(ranges, cx);
20702 }
20703
20704 pub fn diff_hunks_in_ranges<'a>(
20705 &'a self,
20706 ranges: &'a [Range<Anchor>],
20707 buffer: &'a MultiBufferSnapshot,
20708 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20709 ranges.iter().flat_map(move |range| {
20710 let end_excerpt_id = range.end.excerpt_id;
20711 let range = range.to_point(buffer);
20712 let mut peek_end = range.end;
20713 if range.end.row < buffer.max_row().0 {
20714 peek_end = Point::new(range.end.row + 1, 0);
20715 }
20716 buffer
20717 .diff_hunks_in_range(range.start..peek_end)
20718 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20719 })
20720 }
20721
20722 pub fn has_stageable_diff_hunks_in_ranges(
20723 &self,
20724 ranges: &[Range<Anchor>],
20725 snapshot: &MultiBufferSnapshot,
20726 ) -> bool {
20727 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20728 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20729 }
20730
20731 pub fn toggle_staged_selected_diff_hunks(
20732 &mut self,
20733 _: &::git::ToggleStaged,
20734 _: &mut Window,
20735 cx: &mut Context<Self>,
20736 ) {
20737 let snapshot = self.buffer.read(cx).snapshot(cx);
20738 let ranges: Vec<_> = self
20739 .selections
20740 .disjoint_anchors()
20741 .iter()
20742 .map(|s| s.range())
20743 .collect();
20744 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20745 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20746 }
20747
20748 pub fn set_render_diff_hunk_controls(
20749 &mut self,
20750 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20751 cx: &mut Context<Self>,
20752 ) {
20753 self.render_diff_hunk_controls = render_diff_hunk_controls;
20754 cx.notify();
20755 }
20756
20757 pub fn stage_and_next(
20758 &mut self,
20759 _: &::git::StageAndNext,
20760 window: &mut Window,
20761 cx: &mut Context<Self>,
20762 ) {
20763 self.do_stage_or_unstage_and_next(true, window, cx);
20764 }
20765
20766 pub fn unstage_and_next(
20767 &mut self,
20768 _: &::git::UnstageAndNext,
20769 window: &mut Window,
20770 cx: &mut Context<Self>,
20771 ) {
20772 self.do_stage_or_unstage_and_next(false, window, cx);
20773 }
20774
20775 pub fn stage_or_unstage_diff_hunks(
20776 &mut self,
20777 stage: bool,
20778 ranges: Vec<Range<Anchor>>,
20779 cx: &mut Context<Self>,
20780 ) {
20781 if self.delegate_stage_and_restore {
20782 let snapshot = self.buffer.read(cx).snapshot(cx);
20783 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20784 if !hunks.is_empty() {
20785 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20786 }
20787 return;
20788 }
20789 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20790 cx.spawn(async move |this, cx| {
20791 task.await?;
20792 this.update(cx, |this, cx| {
20793 let snapshot = this.buffer.read(cx).snapshot(cx);
20794 let chunk_by = this
20795 .diff_hunks_in_ranges(&ranges, &snapshot)
20796 .chunk_by(|hunk| hunk.buffer_id);
20797 for (buffer_id, hunks) in &chunk_by {
20798 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20799 }
20800 })
20801 })
20802 .detach_and_log_err(cx);
20803 }
20804
20805 fn save_buffers_for_ranges_if_needed(
20806 &mut self,
20807 ranges: &[Range<Anchor>],
20808 cx: &mut Context<Editor>,
20809 ) -> Task<Result<()>> {
20810 let multibuffer = self.buffer.read(cx);
20811 let snapshot = multibuffer.read(cx);
20812 let buffer_ids: HashSet<_> = ranges
20813 .iter()
20814 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20815 .collect();
20816 drop(snapshot);
20817
20818 let mut buffers = HashSet::default();
20819 for buffer_id in buffer_ids {
20820 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20821 let buffer = buffer_entity.read(cx);
20822 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20823 {
20824 buffers.insert(buffer_entity);
20825 }
20826 }
20827 }
20828
20829 if let Some(project) = &self.project {
20830 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20831 } else {
20832 Task::ready(Ok(()))
20833 }
20834 }
20835
20836 fn do_stage_or_unstage_and_next(
20837 &mut self,
20838 stage: bool,
20839 window: &mut Window,
20840 cx: &mut Context<Self>,
20841 ) {
20842 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20843
20844 if ranges.iter().any(|range| range.start != range.end) {
20845 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20846 return;
20847 }
20848
20849 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20850
20851 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20852 let wrap_around = !all_diff_hunks_expanded;
20853 let snapshot = self.snapshot(window, cx);
20854 let position = self
20855 .selections
20856 .newest::<Point>(&snapshot.display_snapshot)
20857 .head();
20858
20859 self.go_to_hunk_before_or_after_position(
20860 &snapshot,
20861 position,
20862 Direction::Next,
20863 wrap_around,
20864 window,
20865 cx,
20866 );
20867 }
20868
20869 pub(crate) fn do_stage_or_unstage(
20870 &self,
20871 stage: bool,
20872 buffer_id: BufferId,
20873 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20874 cx: &mut App,
20875 ) -> Option<()> {
20876 let project = self.project()?;
20877 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20878 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20879 let buffer_snapshot = buffer.read(cx).snapshot();
20880 let file_exists = buffer_snapshot
20881 .file()
20882 .is_some_and(|file| file.disk_state().exists());
20883 diff.update(cx, |diff, cx| {
20884 diff.stage_or_unstage_hunks(
20885 stage,
20886 &hunks
20887 .map(|hunk| buffer_diff::DiffHunk {
20888 buffer_range: hunk.buffer_range,
20889 // We don't need to pass in word diffs here because they're only used for rendering and
20890 // this function changes internal state
20891 base_word_diffs: Vec::default(),
20892 buffer_word_diffs: Vec::default(),
20893 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20894 ..hunk.diff_base_byte_range.end.0,
20895 secondary_status: hunk.status.secondary,
20896 range: Point::zero()..Point::zero(), // unused
20897 })
20898 .collect::<Vec<_>>(),
20899 &buffer_snapshot,
20900 file_exists,
20901 cx,
20902 )
20903 });
20904 None
20905 }
20906
20907 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20908 let ranges: Vec<_> = self
20909 .selections
20910 .disjoint_anchors()
20911 .iter()
20912 .map(|s| s.range())
20913 .collect();
20914 self.buffer
20915 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20916 }
20917
20918 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20919 self.buffer.update(cx, |buffer, cx| {
20920 let ranges = vec![Anchor::min()..Anchor::max()];
20921 if !buffer.all_diff_hunks_expanded()
20922 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20923 {
20924 buffer.collapse_diff_hunks(ranges, cx);
20925 true
20926 } else {
20927 false
20928 }
20929 })
20930 }
20931
20932 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20933 if self.buffer.read(cx).all_diff_hunks_expanded() {
20934 return true;
20935 }
20936 let ranges = vec![Anchor::min()..Anchor::max()];
20937 self.buffer
20938 .read(cx)
20939 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20940 }
20941
20942 fn toggle_diff_hunks_in_ranges(
20943 &mut self,
20944 ranges: Vec<Range<Anchor>>,
20945 cx: &mut Context<Editor>,
20946 ) {
20947 self.buffer.update(cx, |buffer, cx| {
20948 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20949 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20950 })
20951 }
20952
20953 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20954 self.buffer.update(cx, |buffer, cx| {
20955 buffer.toggle_single_diff_hunk(range, cx);
20956 })
20957 }
20958
20959 pub(crate) fn apply_all_diff_hunks(
20960 &mut self,
20961 _: &ApplyAllDiffHunks,
20962 window: &mut Window,
20963 cx: &mut Context<Self>,
20964 ) {
20965 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20966
20967 let buffers = self.buffer.read(cx).all_buffers();
20968 for branch_buffer in buffers {
20969 branch_buffer.update(cx, |branch_buffer, cx| {
20970 branch_buffer.merge_into_base(Vec::new(), cx);
20971 });
20972 }
20973
20974 if let Some(project) = self.project.clone() {
20975 self.save(
20976 SaveOptions {
20977 format: true,
20978 autosave: false,
20979 },
20980 project,
20981 window,
20982 cx,
20983 )
20984 .detach_and_log_err(cx);
20985 }
20986 }
20987
20988 pub(crate) fn apply_selected_diff_hunks(
20989 &mut self,
20990 _: &ApplyDiffHunk,
20991 window: &mut Window,
20992 cx: &mut Context<Self>,
20993 ) {
20994 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20995 let snapshot = self.snapshot(window, cx);
20996 let hunks = snapshot.hunks_for_ranges(
20997 self.selections
20998 .all(&snapshot.display_snapshot)
20999 .into_iter()
21000 .map(|selection| selection.range()),
21001 );
21002 let mut ranges_by_buffer = HashMap::default();
21003 self.transact(window, cx, |editor, _window, cx| {
21004 for hunk in hunks {
21005 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21006 ranges_by_buffer
21007 .entry(buffer.clone())
21008 .or_insert_with(Vec::new)
21009 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21010 }
21011 }
21012
21013 for (buffer, ranges) in ranges_by_buffer {
21014 buffer.update(cx, |buffer, cx| {
21015 buffer.merge_into_base(ranges, cx);
21016 });
21017 }
21018 });
21019
21020 if let Some(project) = self.project.clone() {
21021 self.save(
21022 SaveOptions {
21023 format: true,
21024 autosave: false,
21025 },
21026 project,
21027 window,
21028 cx,
21029 )
21030 .detach_and_log_err(cx);
21031 }
21032 }
21033
21034 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21035 if hovered != self.gutter_hovered {
21036 self.gutter_hovered = hovered;
21037 cx.notify();
21038 }
21039 }
21040
21041 pub fn insert_blocks(
21042 &mut self,
21043 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21044 autoscroll: Option<Autoscroll>,
21045 cx: &mut Context<Self>,
21046 ) -> Vec<CustomBlockId> {
21047 let blocks = self
21048 .display_map
21049 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21050 if let Some(autoscroll) = autoscroll {
21051 self.request_autoscroll(autoscroll, cx);
21052 }
21053 cx.notify();
21054 blocks
21055 }
21056
21057 pub fn resize_blocks(
21058 &mut self,
21059 heights: HashMap<CustomBlockId, u32>,
21060 autoscroll: Option<Autoscroll>,
21061 cx: &mut Context<Self>,
21062 ) {
21063 self.display_map
21064 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21065 if let Some(autoscroll) = autoscroll {
21066 self.request_autoscroll(autoscroll, cx);
21067 }
21068 cx.notify();
21069 }
21070
21071 pub fn replace_blocks(
21072 &mut self,
21073 renderers: HashMap<CustomBlockId, RenderBlock>,
21074 autoscroll: Option<Autoscroll>,
21075 cx: &mut Context<Self>,
21076 ) {
21077 self.display_map
21078 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21079 if let Some(autoscroll) = autoscroll {
21080 self.request_autoscroll(autoscroll, cx);
21081 }
21082 cx.notify();
21083 }
21084
21085 pub fn remove_blocks(
21086 &mut self,
21087 block_ids: HashSet<CustomBlockId>,
21088 autoscroll: Option<Autoscroll>,
21089 cx: &mut Context<Self>,
21090 ) {
21091 self.display_map.update(cx, |display_map, cx| {
21092 display_map.remove_blocks(block_ids, cx)
21093 });
21094 if let Some(autoscroll) = autoscroll {
21095 self.request_autoscroll(autoscroll, cx);
21096 }
21097 cx.notify();
21098 }
21099
21100 pub fn row_for_block(
21101 &self,
21102 block_id: CustomBlockId,
21103 cx: &mut Context<Self>,
21104 ) -> Option<DisplayRow> {
21105 self.display_map
21106 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21107 }
21108
21109 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21110 self.focused_block = Some(focused_block);
21111 }
21112
21113 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21114 self.focused_block.take()
21115 }
21116
21117 pub fn insert_creases(
21118 &mut self,
21119 creases: impl IntoIterator<Item = Crease<Anchor>>,
21120 cx: &mut Context<Self>,
21121 ) -> Vec<CreaseId> {
21122 self.display_map
21123 .update(cx, |map, cx| map.insert_creases(creases, cx))
21124 }
21125
21126 pub fn remove_creases(
21127 &mut self,
21128 ids: impl IntoIterator<Item = CreaseId>,
21129 cx: &mut Context<Self>,
21130 ) -> Vec<(CreaseId, Range<Anchor>)> {
21131 self.display_map
21132 .update(cx, |map, cx| map.remove_creases(ids, cx))
21133 }
21134
21135 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21136 self.display_map
21137 .update(cx, |map, cx| map.snapshot(cx))
21138 .longest_row()
21139 }
21140
21141 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21142 self.display_map
21143 .update(cx, |map, cx| map.snapshot(cx))
21144 .max_point()
21145 }
21146
21147 pub fn text(&self, cx: &App) -> String {
21148 self.buffer.read(cx).read(cx).text()
21149 }
21150
21151 pub fn is_empty(&self, cx: &App) -> bool {
21152 self.buffer.read(cx).read(cx).is_empty()
21153 }
21154
21155 pub fn text_option(&self, cx: &App) -> Option<String> {
21156 let text = self.text(cx);
21157 let text = text.trim();
21158
21159 if text.is_empty() {
21160 return None;
21161 }
21162
21163 Some(text.to_string())
21164 }
21165
21166 pub fn set_text(
21167 &mut self,
21168 text: impl Into<Arc<str>>,
21169 window: &mut Window,
21170 cx: &mut Context<Self>,
21171 ) {
21172 self.transact(window, cx, |this, _, cx| {
21173 this.buffer
21174 .read(cx)
21175 .as_singleton()
21176 .expect("you can only call set_text on editors for singleton buffers")
21177 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21178 });
21179 }
21180
21181 pub fn display_text(&self, cx: &mut App) -> String {
21182 self.display_map
21183 .update(cx, |map, cx| map.snapshot(cx))
21184 .text()
21185 }
21186
21187 fn create_minimap(
21188 &self,
21189 minimap_settings: MinimapSettings,
21190 window: &mut Window,
21191 cx: &mut Context<Self>,
21192 ) -> Option<Entity<Self>> {
21193 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21194 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21195 }
21196
21197 fn initialize_new_minimap(
21198 &self,
21199 minimap_settings: MinimapSettings,
21200 window: &mut Window,
21201 cx: &mut Context<Self>,
21202 ) -> Entity<Self> {
21203 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21204 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21205
21206 let mut minimap = Editor::new_internal(
21207 EditorMode::Minimap {
21208 parent: cx.weak_entity(),
21209 },
21210 self.buffer.clone(),
21211 None,
21212 Some(self.display_map.clone()),
21213 window,
21214 cx,
21215 );
21216 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21217 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21218 minimap.scroll_manager.clone_state(
21219 &self.scroll_manager,
21220 &my_snapshot,
21221 &minimap_snapshot,
21222 cx,
21223 );
21224 minimap.set_text_style_refinement(TextStyleRefinement {
21225 font_size: Some(MINIMAP_FONT_SIZE),
21226 font_weight: Some(MINIMAP_FONT_WEIGHT),
21227 font_family: Some(MINIMAP_FONT_FAMILY),
21228 ..Default::default()
21229 });
21230 minimap.update_minimap_configuration(minimap_settings, cx);
21231 cx.new(|_| minimap)
21232 }
21233
21234 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21235 let current_line_highlight = minimap_settings
21236 .current_line_highlight
21237 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21238 self.set_current_line_highlight(Some(current_line_highlight));
21239 }
21240
21241 pub fn minimap(&self) -> Option<&Entity<Self>> {
21242 self.minimap
21243 .as_ref()
21244 .filter(|_| self.minimap_visibility.visible())
21245 }
21246
21247 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21248 let mut wrap_guides = smallvec![];
21249
21250 if self.show_wrap_guides == Some(false) {
21251 return wrap_guides;
21252 }
21253
21254 let settings = self.buffer.read(cx).language_settings(cx);
21255 if settings.show_wrap_guides {
21256 match self.soft_wrap_mode(cx) {
21257 SoftWrap::Column(soft_wrap) => {
21258 wrap_guides.push((soft_wrap as usize, true));
21259 }
21260 SoftWrap::Bounded(soft_wrap) => {
21261 wrap_guides.push((soft_wrap as usize, true));
21262 }
21263 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21264 }
21265 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21266 }
21267
21268 wrap_guides
21269 }
21270
21271 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21272 let settings = self.buffer.read(cx).language_settings(cx);
21273 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21274 match mode {
21275 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21276 SoftWrap::None
21277 }
21278 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21279 language_settings::SoftWrap::PreferredLineLength => {
21280 SoftWrap::Column(settings.preferred_line_length)
21281 }
21282 language_settings::SoftWrap::Bounded => {
21283 SoftWrap::Bounded(settings.preferred_line_length)
21284 }
21285 }
21286 }
21287
21288 pub fn set_soft_wrap_mode(
21289 &mut self,
21290 mode: language_settings::SoftWrap,
21291 cx: &mut Context<Self>,
21292 ) {
21293 self.soft_wrap_mode_override = Some(mode);
21294 cx.notify();
21295 }
21296
21297 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21298 self.hard_wrap = hard_wrap;
21299 cx.notify();
21300 }
21301
21302 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21303 self.text_style_refinement = Some(style);
21304 }
21305
21306 /// called by the Element so we know what style we were most recently rendered with.
21307 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21308 // We intentionally do not inform the display map about the minimap style
21309 // so that wrapping is not recalculated and stays consistent for the editor
21310 // and its linked minimap.
21311 if !self.mode.is_minimap() {
21312 let font = style.text.font();
21313 let font_size = style.text.font_size.to_pixels(window.rem_size());
21314 let display_map = self
21315 .placeholder_display_map
21316 .as_ref()
21317 .filter(|_| self.is_empty(cx))
21318 .unwrap_or(&self.display_map);
21319
21320 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21321 }
21322 self.style = Some(style);
21323 }
21324
21325 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21326 if self.style.is_none() {
21327 self.style = Some(self.create_style(cx));
21328 }
21329 self.style.as_ref().unwrap()
21330 }
21331
21332 // Called by the element. This method is not designed to be called outside of the editor
21333 // element's layout code because it does not notify when rewrapping is computed synchronously.
21334 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21335 if self.is_empty(cx) {
21336 self.placeholder_display_map
21337 .as_ref()
21338 .map_or(false, |display_map| {
21339 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21340 })
21341 } else {
21342 self.display_map
21343 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21344 }
21345 }
21346
21347 pub fn set_soft_wrap(&mut self) {
21348 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21349 }
21350
21351 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21352 if self.soft_wrap_mode_override.is_some() {
21353 self.soft_wrap_mode_override.take();
21354 } else {
21355 let soft_wrap = match self.soft_wrap_mode(cx) {
21356 SoftWrap::GitDiff => return,
21357 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21358 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21359 language_settings::SoftWrap::None
21360 }
21361 };
21362 self.soft_wrap_mode_override = Some(soft_wrap);
21363 }
21364 cx.notify();
21365 }
21366
21367 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21368 let Some(workspace) = self.workspace() else {
21369 return;
21370 };
21371 let fs = workspace.read(cx).app_state().fs.clone();
21372 let current_show = TabBarSettings::get_global(cx).show;
21373 update_settings_file(fs, cx, move |setting, _| {
21374 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21375 });
21376 }
21377
21378 pub fn toggle_indent_guides(
21379 &mut self,
21380 _: &ToggleIndentGuides,
21381 _: &mut Window,
21382 cx: &mut Context<Self>,
21383 ) {
21384 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21385 self.buffer
21386 .read(cx)
21387 .language_settings(cx)
21388 .indent_guides
21389 .enabled
21390 });
21391 self.show_indent_guides = Some(!currently_enabled);
21392 cx.notify();
21393 }
21394
21395 fn should_show_indent_guides(&self) -> Option<bool> {
21396 self.show_indent_guides
21397 }
21398
21399 pub fn disable_indent_guides_for_buffer(
21400 &mut self,
21401 buffer_id: BufferId,
21402 cx: &mut Context<Self>,
21403 ) {
21404 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21405 cx.notify();
21406 }
21407
21408 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21409 self.buffers_with_disabled_indent_guides
21410 .contains(&buffer_id)
21411 }
21412
21413 pub fn toggle_line_numbers(
21414 &mut self,
21415 _: &ToggleLineNumbers,
21416 _: &mut Window,
21417 cx: &mut Context<Self>,
21418 ) {
21419 let mut editor_settings = EditorSettings::get_global(cx).clone();
21420 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21421 EditorSettings::override_global(editor_settings, cx);
21422 }
21423
21424 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21425 if let Some(show_line_numbers) = self.show_line_numbers {
21426 return show_line_numbers;
21427 }
21428 EditorSettings::get_global(cx).gutter.line_numbers
21429 }
21430
21431 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21432 match (
21433 self.use_relative_line_numbers,
21434 EditorSettings::get_global(cx).relative_line_numbers,
21435 ) {
21436 (None, setting) => setting,
21437 (Some(false), _) => RelativeLineNumbers::Disabled,
21438 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21439 (Some(true), _) => RelativeLineNumbers::Enabled,
21440 }
21441 }
21442
21443 pub fn toggle_relative_line_numbers(
21444 &mut self,
21445 _: &ToggleRelativeLineNumbers,
21446 _: &mut Window,
21447 cx: &mut Context<Self>,
21448 ) {
21449 let is_relative = self.relative_line_numbers(cx);
21450 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21451 }
21452
21453 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21454 self.use_relative_line_numbers = is_relative;
21455 cx.notify();
21456 }
21457
21458 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21459 self.show_gutter = show_gutter;
21460 cx.notify();
21461 }
21462
21463 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21464 self.show_scrollbars = ScrollbarAxes {
21465 horizontal: show,
21466 vertical: show,
21467 };
21468 cx.notify();
21469 }
21470
21471 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21472 self.show_scrollbars.vertical = show;
21473 cx.notify();
21474 }
21475
21476 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21477 self.show_scrollbars.horizontal = show;
21478 cx.notify();
21479 }
21480
21481 pub fn set_minimap_visibility(
21482 &mut self,
21483 minimap_visibility: MinimapVisibility,
21484 window: &mut Window,
21485 cx: &mut Context<Self>,
21486 ) {
21487 if self.minimap_visibility != minimap_visibility {
21488 if minimap_visibility.visible() && self.minimap.is_none() {
21489 let minimap_settings = EditorSettings::get_global(cx).minimap;
21490 self.minimap =
21491 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21492 }
21493 self.minimap_visibility = minimap_visibility;
21494 cx.notify();
21495 }
21496 }
21497
21498 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21499 self.set_show_scrollbars(false, cx);
21500 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21501 }
21502
21503 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21504 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21505 }
21506
21507 /// Normally the text in full mode and auto height editors is padded on the
21508 /// left side by roughly half a character width for improved hit testing.
21509 ///
21510 /// Use this method to disable this for cases where this is not wanted (e.g.
21511 /// if you want to align the editor text with some other text above or below)
21512 /// or if you want to add this padding to single-line editors.
21513 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21514 self.offset_content = offset_content;
21515 cx.notify();
21516 }
21517
21518 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21519 self.show_line_numbers = Some(show_line_numbers);
21520 cx.notify();
21521 }
21522
21523 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21524 self.disable_expand_excerpt_buttons = true;
21525 cx.notify();
21526 }
21527
21528 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21529 self.number_deleted_lines = number;
21530 cx.notify();
21531 }
21532
21533 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21534 self.delegate_expand_excerpts = delegate;
21535 }
21536
21537 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21538 self.delegate_stage_and_restore = delegate;
21539 }
21540
21541 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21542 self.delegate_open_excerpts = delegate;
21543 }
21544
21545 pub fn set_on_local_selections_changed(
21546 &mut self,
21547 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21548 ) {
21549 self.on_local_selections_changed = callback;
21550 }
21551
21552 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21553 self.suppress_selection_callback = suppress;
21554 }
21555
21556 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21557 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21558 cx.notify();
21559 }
21560
21561 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21562 self.show_code_actions = Some(show_code_actions);
21563 cx.notify();
21564 }
21565
21566 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21567 self.show_runnables = Some(show_runnables);
21568 cx.notify();
21569 }
21570
21571 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21572 self.show_breakpoints = Some(show_breakpoints);
21573 cx.notify();
21574 }
21575
21576 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21577 self.show_diff_review_button = show;
21578 cx.notify();
21579 }
21580
21581 pub fn show_diff_review_button(&self) -> bool {
21582 self.show_diff_review_button
21583 }
21584
21585 pub fn render_diff_review_button(
21586 &self,
21587 display_row: DisplayRow,
21588 width: Pixels,
21589 cx: &mut Context<Self>,
21590 ) -> impl IntoElement {
21591 let text_color = cx.theme().colors().text;
21592 let icon_color = cx.theme().colors().icon_accent;
21593
21594 h_flex()
21595 .id("diff_review_button")
21596 .cursor_pointer()
21597 .w(width - px(1.))
21598 .h(relative(0.9))
21599 .justify_center()
21600 .rounded_sm()
21601 .border_1()
21602 .border_color(text_color.opacity(0.1))
21603 .bg(text_color.opacity(0.15))
21604 .hover(|s| {
21605 s.bg(icon_color.opacity(0.4))
21606 .border_color(icon_color.opacity(0.5))
21607 })
21608 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21609 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21610 .on_mouse_down(
21611 gpui::MouseButton::Left,
21612 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21613 editor.start_diff_review_drag(display_row, window, cx);
21614 }),
21615 )
21616 }
21617
21618 pub fn start_diff_review_drag(
21619 &mut self,
21620 display_row: DisplayRow,
21621 window: &mut Window,
21622 cx: &mut Context<Self>,
21623 ) {
21624 let snapshot = self.snapshot(window, cx);
21625 let point = snapshot
21626 .display_snapshot
21627 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21628 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21629 self.diff_review_drag_state = Some(DiffReviewDragState {
21630 start_anchor: anchor,
21631 current_anchor: anchor,
21632 });
21633 cx.notify();
21634 }
21635
21636 pub fn update_diff_review_drag(
21637 &mut self,
21638 display_row: DisplayRow,
21639 window: &mut Window,
21640 cx: &mut Context<Self>,
21641 ) {
21642 if self.diff_review_drag_state.is_none() {
21643 return;
21644 }
21645 let snapshot = self.snapshot(window, cx);
21646 let point = snapshot
21647 .display_snapshot
21648 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21649 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21650 if let Some(drag_state) = &mut self.diff_review_drag_state {
21651 drag_state.current_anchor = anchor;
21652 cx.notify();
21653 }
21654 }
21655
21656 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21657 if let Some(drag_state) = self.diff_review_drag_state.take() {
21658 let snapshot = self.snapshot(window, cx);
21659 let range = drag_state.row_range(&snapshot.display_snapshot);
21660 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21661 }
21662 cx.notify();
21663 }
21664
21665 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21666 self.diff_review_drag_state = None;
21667 cx.notify();
21668 }
21669
21670 /// Calculates the appropriate block height for the diff review overlay.
21671 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21672 /// and 2 lines per comment when expanded.
21673 fn calculate_overlay_height(
21674 &self,
21675 hunk_key: &DiffHunkKey,
21676 comments_expanded: bool,
21677 snapshot: &MultiBufferSnapshot,
21678 ) -> u32 {
21679 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21680 let base_height: u32 = 2; // Input row with avatar and buttons
21681
21682 if comment_count == 0 {
21683 base_height
21684 } else if comments_expanded {
21685 // Header (1 line) + 2 lines per comment
21686 base_height + 1 + (comment_count as u32 * 2)
21687 } else {
21688 // Just header when collapsed
21689 base_height + 1
21690 }
21691 }
21692
21693 pub fn show_diff_review_overlay(
21694 &mut self,
21695 display_range: Range<DisplayRow>,
21696 window: &mut Window,
21697 cx: &mut Context<Self>,
21698 ) {
21699 let Range { start, end } = display_range.sorted();
21700
21701 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21702 let editor_snapshot = self.snapshot(window, cx);
21703
21704 // Convert display rows to multibuffer points
21705 let start_point = editor_snapshot
21706 .display_snapshot
21707 .display_point_to_point(start.as_display_point(), Bias::Left);
21708 let end_point = editor_snapshot
21709 .display_snapshot
21710 .display_point_to_point(end.as_display_point(), Bias::Left);
21711 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21712
21713 // Create anchor range for the selected lines (start of first line to end of last line)
21714 let line_end = Point::new(
21715 end_point.row,
21716 buffer_snapshot.line_len(end_multi_buffer_row),
21717 );
21718 let anchor_range =
21719 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21720
21721 // Compute the hunk key for this display row
21722 let file_path = buffer_snapshot
21723 .file_at(start_point)
21724 .map(|file: &Arc<dyn language::File>| file.path().clone())
21725 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21726 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21727 let new_hunk_key = DiffHunkKey {
21728 file_path,
21729 hunk_start_anchor,
21730 };
21731
21732 // Check if we already have an overlay for this hunk
21733 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21734 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21735 }) {
21736 // Just focus the existing overlay's prompt editor
21737 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21738 window.focus(&focus_handle, cx);
21739 return;
21740 }
21741
21742 // Dismiss overlays that have no comments for their hunks
21743 self.dismiss_overlays_without_comments(cx);
21744
21745 // Get the current user's avatar URI from the project's user_store
21746 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21747 let user_store = project.read(cx).user_store();
21748 user_store
21749 .read(cx)
21750 .current_user()
21751 .map(|user| user.avatar_uri.clone())
21752 });
21753
21754 // Create anchor at the end of the last row so the block appears immediately below it
21755 // Use multibuffer coordinates for anchor creation
21756 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21757 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21758
21759 // Use the hunk key we already computed
21760 let hunk_key = new_hunk_key;
21761
21762 // Create the prompt editor for the review input
21763 let prompt_editor = cx.new(|cx| {
21764 let mut editor = Editor::single_line(window, cx);
21765 editor.set_placeholder_text("Add a review comment...", window, cx);
21766 editor
21767 });
21768
21769 // Register the Newline action on the prompt editor to submit the review
21770 let parent_editor = cx.entity().downgrade();
21771 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21772 prompt_editor.register_action({
21773 let parent_editor = parent_editor.clone();
21774 move |_: &crate::actions::Newline, window, cx| {
21775 if let Some(editor) = parent_editor.upgrade() {
21776 editor.update(cx, |editor, cx| {
21777 editor.submit_diff_review_comment(window, cx);
21778 });
21779 }
21780 }
21781 })
21782 });
21783
21784 // Calculate initial height based on existing comments for this hunk
21785 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21786
21787 // Create the overlay block
21788 let prompt_editor_for_render = prompt_editor.clone();
21789 let hunk_key_for_render = hunk_key.clone();
21790 let editor_handle = cx.entity().downgrade();
21791 let block = BlockProperties {
21792 style: BlockStyle::Sticky,
21793 placement: BlockPlacement::Below(anchor),
21794 height: Some(initial_height),
21795 render: Arc::new(move |cx| {
21796 Self::render_diff_review_overlay(
21797 &prompt_editor_for_render,
21798 &hunk_key_for_render,
21799 &editor_handle,
21800 cx,
21801 )
21802 }),
21803 priority: 0,
21804 };
21805
21806 let block_ids = self.insert_blocks([block], None, cx);
21807 let Some(block_id) = block_ids.into_iter().next() else {
21808 log::error!("Failed to insert diff review overlay block");
21809 return;
21810 };
21811
21812 self.diff_review_overlays.push(DiffReviewOverlay {
21813 anchor_range,
21814 block_id,
21815 prompt_editor: prompt_editor.clone(),
21816 hunk_key,
21817 comments_expanded: true,
21818 inline_edit_editors: HashMap::default(),
21819 inline_edit_subscriptions: HashMap::default(),
21820 user_avatar_uri,
21821 _subscription: subscription,
21822 });
21823
21824 // Focus the prompt editor
21825 let focus_handle = prompt_editor.focus_handle(cx);
21826 window.focus(&focus_handle, cx);
21827
21828 cx.notify();
21829 }
21830
21831 /// Dismisses all diff review overlays.
21832 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21833 if self.diff_review_overlays.is_empty() {
21834 return;
21835 }
21836 let block_ids: HashSet<_> = self
21837 .diff_review_overlays
21838 .drain(..)
21839 .map(|overlay| overlay.block_id)
21840 .collect();
21841 self.remove_blocks(block_ids, None, cx);
21842 cx.notify();
21843 }
21844
21845 /// Dismisses overlays that have no comments stored for their hunks.
21846 /// Keeps overlays that have at least one comment.
21847 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21848 let snapshot = self.buffer.read(cx).snapshot(cx);
21849
21850 // First, compute which overlays have comments (to avoid borrow issues with retain)
21851 let overlays_with_comments: Vec<bool> = self
21852 .diff_review_overlays
21853 .iter()
21854 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21855 .collect();
21856
21857 // Now collect block IDs to remove and retain overlays
21858 let mut block_ids_to_remove = HashSet::default();
21859 let mut index = 0;
21860 self.diff_review_overlays.retain(|overlay| {
21861 let has_comments = overlays_with_comments[index];
21862 index += 1;
21863 if !has_comments {
21864 block_ids_to_remove.insert(overlay.block_id);
21865 }
21866 has_comments
21867 });
21868
21869 if !block_ids_to_remove.is_empty() {
21870 self.remove_blocks(block_ids_to_remove, None, cx);
21871 cx.notify();
21872 }
21873 }
21874
21875 /// Refreshes the diff review overlay block to update its height and render function.
21876 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21877 fn refresh_diff_review_overlay_height(
21878 &mut self,
21879 hunk_key: &DiffHunkKey,
21880 _window: &mut Window,
21881 cx: &mut Context<Self>,
21882 ) {
21883 // Extract all needed data from overlay first to avoid borrow conflicts
21884 let snapshot = self.buffer.read(cx).snapshot(cx);
21885 let (comments_expanded, block_id, prompt_editor) = {
21886 let Some(overlay) = self
21887 .diff_review_overlays
21888 .iter()
21889 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21890 else {
21891 return;
21892 };
21893
21894 (
21895 overlay.comments_expanded,
21896 overlay.block_id,
21897 overlay.prompt_editor.clone(),
21898 )
21899 };
21900
21901 // Calculate new height
21902 let snapshot = self.buffer.read(cx).snapshot(cx);
21903 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21904
21905 // Update the block height using resize_blocks (avoids flicker)
21906 let mut heights = HashMap::default();
21907 heights.insert(block_id, new_height);
21908 self.resize_blocks(heights, None, cx);
21909
21910 // Update the render function using replace_blocks (avoids flicker)
21911 let hunk_key_for_render = hunk_key.clone();
21912 let editor_handle = cx.entity().downgrade();
21913 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21914 Arc::new(move |cx| {
21915 Self::render_diff_review_overlay(
21916 &prompt_editor,
21917 &hunk_key_for_render,
21918 &editor_handle,
21919 cx,
21920 )
21921 });
21922
21923 let mut renderers = HashMap::default();
21924 renderers.insert(block_id, render);
21925 self.replace_blocks(renderers, None, cx);
21926 }
21927
21928 /// Action handler for SubmitDiffReviewComment.
21929 pub fn submit_diff_review_comment_action(
21930 &mut self,
21931 _: &SubmitDiffReviewComment,
21932 window: &mut Window,
21933 cx: &mut Context<Self>,
21934 ) {
21935 self.submit_diff_review_comment(window, cx);
21936 }
21937
21938 /// Stores the diff review comment locally.
21939 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21940 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21941 // Find the overlay that currently has focus
21942 let overlay_index = self
21943 .diff_review_overlays
21944 .iter()
21945 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21946 let Some(overlay_index) = overlay_index else {
21947 return;
21948 };
21949 let overlay = &self.diff_review_overlays[overlay_index];
21950
21951 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21952 if comment_text.is_empty() {
21953 return;
21954 }
21955
21956 let anchor_range = overlay.anchor_range.clone();
21957 let hunk_key = overlay.hunk_key.clone();
21958
21959 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21960
21961 // Clear the prompt editor but keep the overlay open
21962 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21963 overlay.prompt_editor.update(cx, |editor, cx| {
21964 editor.clear(window, cx);
21965 });
21966 }
21967
21968 // Refresh the overlay to update the block height for the new comment
21969 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21970
21971 cx.notify();
21972 }
21973
21974 /// Returns the prompt editor for the diff review overlay, if one is active.
21975 /// This is primarily used for testing.
21976 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21977 self.diff_review_overlays
21978 .first()
21979 .map(|overlay| &overlay.prompt_editor)
21980 }
21981
21982 /// Returns the line range for the first diff review overlay, if one is active.
21983 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21984 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21985 let overlay = self.diff_review_overlays.first()?;
21986 let snapshot = self.buffer.read(cx).snapshot(cx);
21987 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21988 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21989 let start_row = snapshot
21990 .point_to_buffer_point(start_point)
21991 .map(|(_, p, _)| p.row)
21992 .unwrap_or(start_point.row);
21993 let end_row = snapshot
21994 .point_to_buffer_point(end_point)
21995 .map(|(_, p, _)| p.row)
21996 .unwrap_or(end_point.row);
21997 Some((start_row, end_row))
21998 }
21999
22000 /// Sets whether the comments section is expanded in the diff review overlay.
22001 /// This is primarily used for testing.
22002 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22003 for overlay in &mut self.diff_review_overlays {
22004 overlay.comments_expanded = expanded;
22005 }
22006 cx.notify();
22007 }
22008
22009 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22010 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22011 a.file_path == b.file_path
22012 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22013 }
22014
22015 /// Returns comments for a specific hunk, ordered by creation time.
22016 pub fn comments_for_hunk<'a>(
22017 &'a self,
22018 key: &DiffHunkKey,
22019 snapshot: &MultiBufferSnapshot,
22020 ) -> &'a [StoredReviewComment] {
22021 let key_point = key.hunk_start_anchor.to_point(snapshot);
22022 self.stored_review_comments
22023 .iter()
22024 .find(|(k, _)| {
22025 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22026 })
22027 .map(|(_, comments)| comments.as_slice())
22028 .unwrap_or(&[])
22029 }
22030
22031 /// Returns the total count of stored review comments across all hunks.
22032 pub fn total_review_comment_count(&self) -> usize {
22033 self.stored_review_comments
22034 .iter()
22035 .map(|(_, v)| v.len())
22036 .sum()
22037 }
22038
22039 /// Returns the count of comments for a specific hunk.
22040 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22041 let key_point = key.hunk_start_anchor.to_point(snapshot);
22042 self.stored_review_comments
22043 .iter()
22044 .find(|(k, _)| {
22045 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22046 })
22047 .map(|(_, v)| v.len())
22048 .unwrap_or(0)
22049 }
22050
22051 /// Adds a new review comment to a specific hunk.
22052 pub fn add_review_comment(
22053 &mut self,
22054 hunk_key: DiffHunkKey,
22055 comment: String,
22056 anchor_range: Range<Anchor>,
22057 cx: &mut Context<Self>,
22058 ) -> usize {
22059 let id = self.next_review_comment_id;
22060 self.next_review_comment_id += 1;
22061
22062 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22063
22064 let snapshot = self.buffer.read(cx).snapshot(cx);
22065 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22066
22067 // Find existing entry for this hunk or add a new one
22068 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22069 k.file_path == hunk_key.file_path
22070 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22071 }) {
22072 comments.push(stored_comment);
22073 } else {
22074 self.stored_review_comments
22075 .push((hunk_key, vec![stored_comment]));
22076 }
22077
22078 cx.emit(EditorEvent::ReviewCommentsChanged {
22079 total_count: self.total_review_comment_count(),
22080 });
22081 cx.notify();
22082 id
22083 }
22084
22085 /// Removes a review comment by ID from any hunk.
22086 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22087 for (_, comments) in self.stored_review_comments.iter_mut() {
22088 if let Some(index) = comments.iter().position(|c| c.id == id) {
22089 comments.remove(index);
22090 cx.emit(EditorEvent::ReviewCommentsChanged {
22091 total_count: self.total_review_comment_count(),
22092 });
22093 cx.notify();
22094 return true;
22095 }
22096 }
22097 false
22098 }
22099
22100 /// Updates a review comment's text by ID.
22101 pub fn update_review_comment(
22102 &mut self,
22103 id: usize,
22104 new_comment: String,
22105 cx: &mut Context<Self>,
22106 ) -> bool {
22107 for (_, comments) in self.stored_review_comments.iter_mut() {
22108 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22109 comment.comment = new_comment;
22110 comment.is_editing = false;
22111 cx.emit(EditorEvent::ReviewCommentsChanged {
22112 total_count: self.total_review_comment_count(),
22113 });
22114 cx.notify();
22115 return true;
22116 }
22117 }
22118 false
22119 }
22120
22121 /// Sets a comment's editing state.
22122 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22123 for (_, comments) in self.stored_review_comments.iter_mut() {
22124 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22125 comment.is_editing = is_editing;
22126 cx.notify();
22127 return;
22128 }
22129 }
22130 }
22131
22132 /// Takes all stored comments from all hunks, clearing the storage.
22133 /// Returns a Vec of (hunk_key, comments) pairs.
22134 pub fn take_all_review_comments(
22135 &mut self,
22136 cx: &mut Context<Self>,
22137 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22138 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22139 self.dismiss_all_diff_review_overlays(cx);
22140 let comments = std::mem::take(&mut self.stored_review_comments);
22141 // Reset the ID counter since all comments have been taken
22142 self.next_review_comment_id = 0;
22143 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22144 cx.notify();
22145 comments
22146 }
22147
22148 /// Removes review comments whose anchors are no longer valid or whose
22149 /// associated diff hunks no longer exist.
22150 ///
22151 /// This should be called when the buffer changes to prevent orphaned comments
22152 /// from accumulating.
22153 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22154 let snapshot = self.buffer.read(cx).snapshot(cx);
22155 let original_count = self.total_review_comment_count();
22156
22157 // Remove comments with invalid hunk anchors
22158 self.stored_review_comments
22159 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22160
22161 // Also clean up individual comments with invalid anchor ranges
22162 for (_, comments) in &mut self.stored_review_comments {
22163 comments.retain(|comment| {
22164 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22165 });
22166 }
22167
22168 // Remove empty hunk entries
22169 self.stored_review_comments
22170 .retain(|(_, comments)| !comments.is_empty());
22171
22172 let new_count = self.total_review_comment_count();
22173 if new_count != original_count {
22174 cx.emit(EditorEvent::ReviewCommentsChanged {
22175 total_count: new_count,
22176 });
22177 cx.notify();
22178 }
22179 }
22180
22181 /// Toggles the expanded state of the comments section in the overlay.
22182 pub fn toggle_review_comments_expanded(
22183 &mut self,
22184 _: &ToggleReviewCommentsExpanded,
22185 window: &mut Window,
22186 cx: &mut Context<Self>,
22187 ) {
22188 // Find the overlay that currently has focus, or use the first one
22189 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22190 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22191 overlay.comments_expanded = !overlay.comments_expanded;
22192 Some(overlay.hunk_key.clone())
22193 } else {
22194 None
22195 }
22196 });
22197
22198 // If no focused overlay found, toggle the first one
22199 let hunk_key = overlay_info.or_else(|| {
22200 self.diff_review_overlays.first_mut().map(|overlay| {
22201 overlay.comments_expanded = !overlay.comments_expanded;
22202 overlay.hunk_key.clone()
22203 })
22204 });
22205
22206 if let Some(hunk_key) = hunk_key {
22207 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22208 cx.notify();
22209 }
22210 }
22211
22212 /// Handles the EditReviewComment action - sets a comment into editing mode.
22213 pub fn edit_review_comment(
22214 &mut self,
22215 action: &EditReviewComment,
22216 window: &mut Window,
22217 cx: &mut Context<Self>,
22218 ) {
22219 let comment_id = action.id;
22220
22221 // Set the comment to editing mode
22222 self.set_comment_editing(comment_id, true, cx);
22223
22224 // Find the overlay that contains this comment and create an inline editor if needed
22225 // First, find which hunk this comment belongs to
22226 let hunk_key = self
22227 .stored_review_comments
22228 .iter()
22229 .find_map(|(key, comments)| {
22230 if comments.iter().any(|c| c.id == comment_id) {
22231 Some(key.clone())
22232 } else {
22233 None
22234 }
22235 });
22236
22237 let snapshot = self.buffer.read(cx).snapshot(cx);
22238 if let Some(hunk_key) = hunk_key {
22239 if let Some(overlay) = self
22240 .diff_review_overlays
22241 .iter_mut()
22242 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22243 {
22244 if let std::collections::hash_map::Entry::Vacant(entry) =
22245 overlay.inline_edit_editors.entry(comment_id)
22246 {
22247 // Find the comment text
22248 let comment_text = self
22249 .stored_review_comments
22250 .iter()
22251 .flat_map(|(_, comments)| comments)
22252 .find(|c| c.id == comment_id)
22253 .map(|c| c.comment.clone())
22254 .unwrap_or_default();
22255
22256 // Create inline editor
22257 let parent_editor = cx.entity().downgrade();
22258 let inline_editor = cx.new(|cx| {
22259 let mut editor = Editor::single_line(window, cx);
22260 editor.set_text(&*comment_text, window, cx);
22261 // Select all text for easy replacement
22262 editor.select_all(&crate::actions::SelectAll, window, cx);
22263 editor
22264 });
22265
22266 // Register the Newline action to confirm the edit
22267 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22268 inline_editor.register_action({
22269 let parent_editor = parent_editor.clone();
22270 move |_: &crate::actions::Newline, window, cx| {
22271 if let Some(editor) = parent_editor.upgrade() {
22272 editor.update(cx, |editor, cx| {
22273 editor.confirm_edit_review_comment(comment_id, window, cx);
22274 });
22275 }
22276 }
22277 })
22278 });
22279
22280 // Store the subscription to keep the action handler alive
22281 overlay
22282 .inline_edit_subscriptions
22283 .insert(comment_id, subscription);
22284
22285 // Focus the inline editor
22286 let focus_handle = inline_editor.focus_handle(cx);
22287 window.focus(&focus_handle, cx);
22288
22289 entry.insert(inline_editor);
22290 }
22291 }
22292 }
22293
22294 cx.notify();
22295 }
22296
22297 /// Confirms an inline edit of a review comment.
22298 pub fn confirm_edit_review_comment(
22299 &mut self,
22300 comment_id: usize,
22301 _window: &mut Window,
22302 cx: &mut Context<Self>,
22303 ) {
22304 // Get the new text from the inline editor
22305 // Find the overlay containing this comment's inline editor
22306 let snapshot = self.buffer.read(cx).snapshot(cx);
22307 let hunk_key = self
22308 .stored_review_comments
22309 .iter()
22310 .find_map(|(key, comments)| {
22311 if comments.iter().any(|c| c.id == comment_id) {
22312 Some(key.clone())
22313 } else {
22314 None
22315 }
22316 });
22317
22318 let new_text = hunk_key
22319 .as_ref()
22320 .and_then(|hunk_key| {
22321 self.diff_review_overlays
22322 .iter()
22323 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22324 })
22325 .as_ref()
22326 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22327 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22328
22329 if let Some(new_text) = new_text {
22330 if !new_text.is_empty() {
22331 self.update_review_comment(comment_id, new_text, cx);
22332 }
22333 }
22334
22335 // Remove the inline editor and its subscription
22336 if let Some(hunk_key) = hunk_key {
22337 if let Some(overlay) = self
22338 .diff_review_overlays
22339 .iter_mut()
22340 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22341 {
22342 overlay.inline_edit_editors.remove(&comment_id);
22343 overlay.inline_edit_subscriptions.remove(&comment_id);
22344 }
22345 }
22346
22347 // Clear editing state
22348 self.set_comment_editing(comment_id, false, cx);
22349 }
22350
22351 /// Cancels an inline edit of a review comment.
22352 pub fn cancel_edit_review_comment(
22353 &mut self,
22354 comment_id: usize,
22355 _window: &mut Window,
22356 cx: &mut Context<Self>,
22357 ) {
22358 // Find which hunk this comment belongs to
22359 let hunk_key = self
22360 .stored_review_comments
22361 .iter()
22362 .find_map(|(key, comments)| {
22363 if comments.iter().any(|c| c.id == comment_id) {
22364 Some(key.clone())
22365 } else {
22366 None
22367 }
22368 });
22369
22370 // Remove the inline editor and its subscription
22371 if let Some(hunk_key) = hunk_key {
22372 let snapshot = self.buffer.read(cx).snapshot(cx);
22373 if let Some(overlay) = self
22374 .diff_review_overlays
22375 .iter_mut()
22376 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22377 {
22378 overlay.inline_edit_editors.remove(&comment_id);
22379 overlay.inline_edit_subscriptions.remove(&comment_id);
22380 }
22381 }
22382
22383 // Clear editing state
22384 self.set_comment_editing(comment_id, false, cx);
22385 }
22386
22387 /// Action handler for ConfirmEditReviewComment.
22388 pub fn confirm_edit_review_comment_action(
22389 &mut self,
22390 action: &ConfirmEditReviewComment,
22391 window: &mut Window,
22392 cx: &mut Context<Self>,
22393 ) {
22394 self.confirm_edit_review_comment(action.id, window, cx);
22395 }
22396
22397 /// Action handler for CancelEditReviewComment.
22398 pub fn cancel_edit_review_comment_action(
22399 &mut self,
22400 action: &CancelEditReviewComment,
22401 window: &mut Window,
22402 cx: &mut Context<Self>,
22403 ) {
22404 self.cancel_edit_review_comment(action.id, window, cx);
22405 }
22406
22407 /// Handles the DeleteReviewComment action - removes a comment.
22408 pub fn delete_review_comment(
22409 &mut self,
22410 action: &DeleteReviewComment,
22411 window: &mut Window,
22412 cx: &mut Context<Self>,
22413 ) {
22414 // Get the hunk key before removing the comment
22415 // Find the hunk key from the comment itself
22416 let comment_id = action.id;
22417 let hunk_key = self
22418 .stored_review_comments
22419 .iter()
22420 .find_map(|(key, comments)| {
22421 if comments.iter().any(|c| c.id == comment_id) {
22422 Some(key.clone())
22423 } else {
22424 None
22425 }
22426 });
22427
22428 // Also get it from the overlay for refresh purposes
22429 let overlay_hunk_key = self
22430 .diff_review_overlays
22431 .first()
22432 .map(|o| o.hunk_key.clone());
22433
22434 self.remove_review_comment(action.id, cx);
22435
22436 // Refresh the overlay height after removing a comment
22437 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22438 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22439 }
22440 }
22441
22442 fn render_diff_review_overlay(
22443 prompt_editor: &Entity<Editor>,
22444 hunk_key: &DiffHunkKey,
22445 editor_handle: &WeakEntity<Editor>,
22446 cx: &mut BlockContext,
22447 ) -> AnyElement {
22448 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22449 if ranges.is_empty() {
22450 return None;
22451 }
22452 let formatted: Vec<String> = ranges
22453 .iter()
22454 .map(|(start, end)| {
22455 let start_line = start + 1;
22456 let end_line = end + 1;
22457 if start_line == end_line {
22458 format!("Line {start_line}")
22459 } else {
22460 format!("Lines {start_line}-{end_line}")
22461 }
22462 })
22463 .collect();
22464 // Don't show label for single line in single excerpt
22465 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22466 return None;
22467 }
22468 Some(formatted.join(" ⋯ "))
22469 }
22470
22471 let theme = cx.theme();
22472 let colors = theme.colors();
22473
22474 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22475 editor_handle
22476 .upgrade()
22477 .map(|editor| {
22478 let editor = editor.read(cx);
22479 let snapshot = editor.buffer().read(cx).snapshot(cx);
22480 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22481 let (expanded, editors, avatar_uri, line_ranges) = editor
22482 .diff_review_overlays
22483 .iter()
22484 .find(|overlay| {
22485 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22486 })
22487 .map(|o| {
22488 let start_point = o.anchor_range.start.to_point(&snapshot);
22489 let end_point = o.anchor_range.end.to_point(&snapshot);
22490 // Get line ranges per excerpt to detect discontinuities
22491 let buffer_ranges =
22492 snapshot.range_to_buffer_ranges(start_point..end_point);
22493 let ranges: Vec<(u32, u32)> = buffer_ranges
22494 .iter()
22495 .map(|(buffer, range, _)| {
22496 let start = buffer.offset_to_point(range.start.0).row;
22497 let end = buffer.offset_to_point(range.end.0).row;
22498 (start, end)
22499 })
22500 .collect();
22501 (
22502 o.comments_expanded,
22503 o.inline_edit_editors.clone(),
22504 o.user_avatar_uri.clone(),
22505 if ranges.is_empty() {
22506 None
22507 } else {
22508 Some(ranges)
22509 },
22510 )
22511 })
22512 .unwrap_or((true, HashMap::default(), None, None));
22513 (comments, expanded, editors, avatar_uri, line_ranges)
22514 })
22515 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22516
22517 let comment_count = comments.len();
22518 let avatar_size = px(20.);
22519 let action_icon_size = IconSize::XSmall;
22520
22521 v_flex()
22522 .w_full()
22523 .bg(colors.editor_background)
22524 .border_b_1()
22525 .border_color(colors.border)
22526 .px_2()
22527 .pb_2()
22528 .gap_2()
22529 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22530 .when_some(line_ranges, |el, ranges| {
22531 let label = format_line_ranges(&ranges);
22532 if let Some(label) = label {
22533 el.child(
22534 h_flex()
22535 .w_full()
22536 .px_2()
22537 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22538 )
22539 } else {
22540 el
22541 }
22542 })
22543 // Top row: editable input with user's avatar
22544 .child(
22545 h_flex()
22546 .w_full()
22547 .items_center()
22548 .gap_2()
22549 .px_2()
22550 .py_1p5()
22551 .rounded_md()
22552 .bg(colors.surface_background)
22553 .child(
22554 div()
22555 .size(avatar_size)
22556 .flex_shrink_0()
22557 .rounded_full()
22558 .overflow_hidden()
22559 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22560 Avatar::new(avatar_uri.clone())
22561 .size(avatar_size)
22562 .into_any_element()
22563 } else {
22564 Icon::new(IconName::Person)
22565 .size(IconSize::Small)
22566 .color(ui::Color::Muted)
22567 .into_any_element()
22568 }),
22569 )
22570 .child(
22571 div()
22572 .flex_1()
22573 .border_1()
22574 .border_color(colors.border)
22575 .rounded_md()
22576 .bg(colors.editor_background)
22577 .px_2()
22578 .py_1()
22579 .child(prompt_editor.clone()),
22580 )
22581 .child(
22582 h_flex()
22583 .flex_shrink_0()
22584 .gap_1()
22585 .child(
22586 IconButton::new("diff-review-close", IconName::Close)
22587 .icon_color(ui::Color::Muted)
22588 .icon_size(action_icon_size)
22589 .tooltip(Tooltip::text("Close"))
22590 .on_click(|_, window, cx| {
22591 window
22592 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22593 }),
22594 )
22595 .child(
22596 IconButton::new("diff-review-add", IconName::Return)
22597 .icon_color(ui::Color::Muted)
22598 .icon_size(action_icon_size)
22599 .tooltip(Tooltip::text("Add comment"))
22600 .on_click(|_, window, cx| {
22601 window.dispatch_action(
22602 Box::new(crate::actions::SubmitDiffReviewComment),
22603 cx,
22604 );
22605 }),
22606 ),
22607 ),
22608 )
22609 // Expandable comments section (only shown when there are comments)
22610 .when(comment_count > 0, |el| {
22611 el.child(Self::render_comments_section(
22612 comments,
22613 comments_expanded,
22614 inline_editors,
22615 user_avatar_uri,
22616 avatar_size,
22617 action_icon_size,
22618 colors,
22619 ))
22620 })
22621 .into_any_element()
22622 }
22623
22624 fn render_comments_section(
22625 comments: Vec<StoredReviewComment>,
22626 expanded: bool,
22627 inline_editors: HashMap<usize, Entity<Editor>>,
22628 user_avatar_uri: Option<SharedUri>,
22629 avatar_size: Pixels,
22630 action_icon_size: IconSize,
22631 colors: &theme::ThemeColors,
22632 ) -> impl IntoElement {
22633 let comment_count = comments.len();
22634
22635 v_flex()
22636 .w_full()
22637 .gap_1()
22638 // Header with expand/collapse toggle
22639 .child(
22640 h_flex()
22641 .id("review-comments-header")
22642 .w_full()
22643 .items_center()
22644 .gap_1()
22645 .px_2()
22646 .py_1()
22647 .cursor_pointer()
22648 .rounded_md()
22649 .hover(|style| style.bg(colors.ghost_element_hover))
22650 .on_click(|_, window: &mut Window, cx| {
22651 window.dispatch_action(
22652 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22653 cx,
22654 );
22655 })
22656 .child(
22657 Icon::new(if expanded {
22658 IconName::ChevronDown
22659 } else {
22660 IconName::ChevronRight
22661 })
22662 .size(IconSize::Small)
22663 .color(ui::Color::Muted),
22664 )
22665 .child(
22666 Label::new(format!(
22667 "{} Comment{}",
22668 comment_count,
22669 if comment_count == 1 { "" } else { "s" }
22670 ))
22671 .size(LabelSize::Small)
22672 .color(Color::Muted),
22673 ),
22674 )
22675 // Comments list (when expanded)
22676 .when(expanded, |el| {
22677 el.children(comments.into_iter().map(|comment| {
22678 let inline_editor = inline_editors.get(&comment.id).cloned();
22679 Self::render_comment_row(
22680 comment,
22681 inline_editor,
22682 user_avatar_uri.clone(),
22683 avatar_size,
22684 action_icon_size,
22685 colors,
22686 )
22687 }))
22688 })
22689 }
22690
22691 fn render_comment_row(
22692 comment: StoredReviewComment,
22693 inline_editor: Option<Entity<Editor>>,
22694 user_avatar_uri: Option<SharedUri>,
22695 avatar_size: Pixels,
22696 action_icon_size: IconSize,
22697 colors: &theme::ThemeColors,
22698 ) -> impl IntoElement {
22699 let comment_id = comment.id;
22700 let is_editing = inline_editor.is_some();
22701
22702 h_flex()
22703 .w_full()
22704 .items_center()
22705 .gap_2()
22706 .px_2()
22707 .py_1p5()
22708 .rounded_md()
22709 .bg(colors.surface_background)
22710 .child(
22711 div()
22712 .size(avatar_size)
22713 .flex_shrink_0()
22714 .rounded_full()
22715 .overflow_hidden()
22716 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22717 Avatar::new(avatar_uri.clone())
22718 .size(avatar_size)
22719 .into_any_element()
22720 } else {
22721 Icon::new(IconName::Person)
22722 .size(IconSize::Small)
22723 .color(ui::Color::Muted)
22724 .into_any_element()
22725 }),
22726 )
22727 .child(if let Some(editor) = inline_editor {
22728 // Inline edit mode: show an editable text field
22729 div()
22730 .flex_1()
22731 .border_1()
22732 .border_color(colors.border)
22733 .rounded_md()
22734 .bg(colors.editor_background)
22735 .px_2()
22736 .py_1()
22737 .child(editor)
22738 .into_any_element()
22739 } else {
22740 // Display mode: show the comment text
22741 div()
22742 .flex_1()
22743 .text_sm()
22744 .text_color(colors.text)
22745 .child(comment.comment)
22746 .into_any_element()
22747 })
22748 .child(if is_editing {
22749 // Editing mode: show close and confirm buttons
22750 h_flex()
22751 .gap_1()
22752 .child(
22753 IconButton::new(
22754 format!("diff-review-cancel-edit-{comment_id}"),
22755 IconName::Close,
22756 )
22757 .icon_color(ui::Color::Muted)
22758 .icon_size(action_icon_size)
22759 .tooltip(Tooltip::text("Cancel"))
22760 .on_click(move |_, window, cx| {
22761 window.dispatch_action(
22762 Box::new(crate::actions::CancelEditReviewComment {
22763 id: comment_id,
22764 }),
22765 cx,
22766 );
22767 }),
22768 )
22769 .child(
22770 IconButton::new(
22771 format!("diff-review-confirm-edit-{comment_id}"),
22772 IconName::Return,
22773 )
22774 .icon_color(ui::Color::Muted)
22775 .icon_size(action_icon_size)
22776 .tooltip(Tooltip::text("Confirm"))
22777 .on_click(move |_, window, cx| {
22778 window.dispatch_action(
22779 Box::new(crate::actions::ConfirmEditReviewComment {
22780 id: comment_id,
22781 }),
22782 cx,
22783 );
22784 }),
22785 )
22786 .into_any_element()
22787 } else {
22788 // Display mode: no action buttons for now (edit/delete not yet implemented)
22789 gpui::Empty.into_any_element()
22790 })
22791 }
22792
22793 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22794 if self.display_map.read(cx).masked != masked {
22795 self.display_map.update(cx, |map, _| map.masked = masked);
22796 }
22797 cx.notify()
22798 }
22799
22800 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22801 self.show_wrap_guides = Some(show_wrap_guides);
22802 cx.notify();
22803 }
22804
22805 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22806 self.show_indent_guides = Some(show_indent_guides);
22807 cx.notify();
22808 }
22809
22810 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22811 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22812 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22813 && let Some(dir) = file.abs_path(cx).parent()
22814 {
22815 return Some(dir.to_owned());
22816 }
22817 }
22818
22819 None
22820 }
22821
22822 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22823 self.active_excerpt(cx)?
22824 .1
22825 .read(cx)
22826 .file()
22827 .and_then(|f| f.as_local())
22828 }
22829
22830 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22831 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22832 let buffer = buffer.read(cx);
22833 if let Some(project_path) = buffer.project_path(cx) {
22834 let project = self.project()?.read(cx);
22835 project.absolute_path(&project_path, cx)
22836 } else {
22837 buffer
22838 .file()
22839 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22840 }
22841 })
22842 }
22843
22844 pub fn reveal_in_finder(
22845 &mut self,
22846 _: &RevealInFileManager,
22847 _window: &mut Window,
22848 cx: &mut Context<Self>,
22849 ) {
22850 if let Some(path) = self.target_file_abs_path(cx) {
22851 if let Some(project) = self.project() {
22852 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22853 } else {
22854 cx.reveal_path(&path);
22855 }
22856 }
22857 }
22858
22859 pub fn copy_path(
22860 &mut self,
22861 _: &zed_actions::workspace::CopyPath,
22862 _window: &mut Window,
22863 cx: &mut Context<Self>,
22864 ) {
22865 if let Some(path) = self.target_file_abs_path(cx)
22866 && let Some(path) = path.to_str()
22867 {
22868 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22869 } else {
22870 cx.propagate();
22871 }
22872 }
22873
22874 pub fn copy_relative_path(
22875 &mut self,
22876 _: &zed_actions::workspace::CopyRelativePath,
22877 _window: &mut Window,
22878 cx: &mut Context<Self>,
22879 ) {
22880 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22881 let project = self.project()?.read(cx);
22882 let path = buffer.read(cx).file()?.path();
22883 let path = path.display(project.path_style(cx));
22884 Some(path)
22885 }) {
22886 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22887 } else {
22888 cx.propagate();
22889 }
22890 }
22891
22892 /// Returns the project path for the editor's buffer, if any buffer is
22893 /// opened in the editor.
22894 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22895 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22896 buffer.read(cx).project_path(cx)
22897 } else {
22898 None
22899 }
22900 }
22901
22902 // Returns true if the editor handled a go-to-line request
22903 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22904 maybe!({
22905 let breakpoint_store = self.breakpoint_store.as_ref()?;
22906
22907 let (active_stack_frame, debug_line_pane_id) = {
22908 let store = breakpoint_store.read(cx);
22909 let active_stack_frame = store.active_position().cloned();
22910 let debug_line_pane_id = store.active_debug_line_pane_id();
22911 (active_stack_frame, debug_line_pane_id)
22912 };
22913
22914 let Some(active_stack_frame) = active_stack_frame else {
22915 self.clear_row_highlights::<ActiveDebugLine>();
22916 return None;
22917 };
22918
22919 if let Some(debug_line_pane_id) = debug_line_pane_id {
22920 if let Some(workspace) = self
22921 .workspace
22922 .as_ref()
22923 .and_then(|(workspace, _)| workspace.upgrade())
22924 {
22925 let editor_pane_id = workspace
22926 .read(cx)
22927 .pane_for_item_id(cx.entity_id())
22928 .map(|pane| pane.entity_id());
22929
22930 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22931 self.clear_row_highlights::<ActiveDebugLine>();
22932 return None;
22933 }
22934 }
22935 }
22936
22937 let position = active_stack_frame.position;
22938 let buffer_id = position.buffer_id?;
22939 let snapshot = self
22940 .project
22941 .as_ref()?
22942 .read(cx)
22943 .buffer_for_id(buffer_id, cx)?
22944 .read(cx)
22945 .snapshot();
22946
22947 let mut handled = false;
22948 for (id, _, ExcerptRange { context, .. }) in
22949 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22950 {
22951 if context.start.cmp(&position, &snapshot).is_ge()
22952 || context.end.cmp(&position, &snapshot).is_lt()
22953 {
22954 continue;
22955 }
22956 let snapshot = self.buffer.read(cx).snapshot(cx);
22957 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22958
22959 handled = true;
22960 self.clear_row_highlights::<ActiveDebugLine>();
22961
22962 self.go_to_line::<ActiveDebugLine>(
22963 multibuffer_anchor,
22964 Some(cx.theme().colors().editor_debugger_active_line_background),
22965 window,
22966 cx,
22967 );
22968
22969 cx.notify();
22970 }
22971
22972 handled.then_some(())
22973 })
22974 .is_some()
22975 }
22976
22977 pub fn copy_file_name_without_extension(
22978 &mut self,
22979 _: &CopyFileNameWithoutExtension,
22980 _: &mut Window,
22981 cx: &mut Context<Self>,
22982 ) {
22983 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22984 let file = buffer.read(cx).file()?;
22985 file.path().file_stem()
22986 }) {
22987 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22988 }
22989 }
22990
22991 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22992 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22993 let file = buffer.read(cx).file()?;
22994 Some(file.file_name(cx))
22995 }) {
22996 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22997 }
22998 }
22999
23000 pub fn toggle_git_blame(
23001 &mut self,
23002 _: &::git::Blame,
23003 window: &mut Window,
23004 cx: &mut Context<Self>,
23005 ) {
23006 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23007
23008 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23009 self.start_git_blame(true, window, cx);
23010 }
23011
23012 cx.notify();
23013 }
23014
23015 pub fn toggle_git_blame_inline(
23016 &mut self,
23017 _: &ToggleGitBlameInline,
23018 window: &mut Window,
23019 cx: &mut Context<Self>,
23020 ) {
23021 self.toggle_git_blame_inline_internal(true, window, cx);
23022 cx.notify();
23023 }
23024
23025 pub fn open_git_blame_commit(
23026 &mut self,
23027 _: &OpenGitBlameCommit,
23028 window: &mut Window,
23029 cx: &mut Context<Self>,
23030 ) {
23031 self.open_git_blame_commit_internal(window, cx);
23032 }
23033
23034 fn open_git_blame_commit_internal(
23035 &mut self,
23036 window: &mut Window,
23037 cx: &mut Context<Self>,
23038 ) -> Option<()> {
23039 let blame = self.blame.as_ref()?;
23040 let snapshot = self.snapshot(window, cx);
23041 let cursor = self
23042 .selections
23043 .newest::<Point>(&snapshot.display_snapshot)
23044 .head();
23045 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23046 let (_, blame_entry) = blame
23047 .update(cx, |blame, cx| {
23048 blame
23049 .blame_for_rows(
23050 &[RowInfo {
23051 buffer_id: Some(buffer.remote_id()),
23052 buffer_row: Some(point.row),
23053 ..Default::default()
23054 }],
23055 cx,
23056 )
23057 .next()
23058 })
23059 .flatten()?;
23060 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23061 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23062 let workspace = self.workspace()?.downgrade();
23063 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23064 None
23065 }
23066
23067 pub fn git_blame_inline_enabled(&self) -> bool {
23068 self.git_blame_inline_enabled
23069 }
23070
23071 pub fn toggle_selection_menu(
23072 &mut self,
23073 _: &ToggleSelectionMenu,
23074 _: &mut Window,
23075 cx: &mut Context<Self>,
23076 ) {
23077 self.show_selection_menu = self
23078 .show_selection_menu
23079 .map(|show_selections_menu| !show_selections_menu)
23080 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23081
23082 cx.notify();
23083 }
23084
23085 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23086 self.show_selection_menu
23087 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23088 }
23089
23090 fn start_git_blame(
23091 &mut self,
23092 user_triggered: bool,
23093 window: &mut Window,
23094 cx: &mut Context<Self>,
23095 ) {
23096 if let Some(project) = self.project() {
23097 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23098 && buffer.read(cx).file().is_none()
23099 {
23100 return;
23101 }
23102
23103 let focused = self.focus_handle(cx).contains_focused(window, cx);
23104
23105 let project = project.clone();
23106 let blame = cx
23107 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23108 self.blame_subscription =
23109 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23110 self.blame = Some(blame);
23111 }
23112 }
23113
23114 fn toggle_git_blame_inline_internal(
23115 &mut self,
23116 user_triggered: bool,
23117 window: &mut Window,
23118 cx: &mut Context<Self>,
23119 ) {
23120 if self.git_blame_inline_enabled {
23121 self.git_blame_inline_enabled = false;
23122 self.show_git_blame_inline = false;
23123 self.show_git_blame_inline_delay_task.take();
23124 } else {
23125 self.git_blame_inline_enabled = true;
23126 self.start_git_blame_inline(user_triggered, window, cx);
23127 }
23128
23129 cx.notify();
23130 }
23131
23132 fn start_git_blame_inline(
23133 &mut self,
23134 user_triggered: bool,
23135 window: &mut Window,
23136 cx: &mut Context<Self>,
23137 ) {
23138 self.start_git_blame(user_triggered, window, cx);
23139
23140 if ProjectSettings::get_global(cx)
23141 .git
23142 .inline_blame_delay()
23143 .is_some()
23144 {
23145 self.start_inline_blame_timer(window, cx);
23146 } else {
23147 self.show_git_blame_inline = true
23148 }
23149 }
23150
23151 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23152 self.blame.as_ref()
23153 }
23154
23155 pub fn show_git_blame_gutter(&self) -> bool {
23156 self.show_git_blame_gutter
23157 }
23158
23159 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23160 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23161 }
23162
23163 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23164 self.show_git_blame_inline
23165 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23166 && !self.newest_selection_head_on_empty_line(cx)
23167 && self.has_blame_entries(cx)
23168 }
23169
23170 fn has_blame_entries(&self, cx: &App) -> bool {
23171 self.blame()
23172 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23173 }
23174
23175 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23176 let cursor_anchor = self.selections.newest_anchor().head();
23177
23178 let snapshot = self.buffer.read(cx).snapshot(cx);
23179 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23180
23181 snapshot.line_len(buffer_row) == 0
23182 }
23183
23184 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23185 let buffer_and_selection = maybe!({
23186 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23187 let selection_range = selection.range();
23188
23189 let multi_buffer = self.buffer().read(cx);
23190 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23191 let buffer_ranges = multi_buffer_snapshot
23192 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23193
23194 let (buffer, range, _) = if selection.reversed {
23195 buffer_ranges.first()
23196 } else {
23197 buffer_ranges.last()
23198 }?;
23199
23200 let buffer_range = range.to_point(buffer);
23201
23202 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23203 return Some((
23204 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23205 buffer_range.start.row..buffer_range.end.row,
23206 ));
23207 };
23208
23209 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23210 let start =
23211 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23212 let end =
23213 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23214
23215 Some((
23216 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23217 start.row..end.row,
23218 ))
23219 });
23220
23221 let Some((buffer, selection)) = buffer_and_selection else {
23222 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23223 };
23224
23225 let Some(project) = self.project() else {
23226 return Task::ready(Err(anyhow!("editor does not have project")));
23227 };
23228
23229 project.update(cx, |project, cx| {
23230 project.get_permalink_to_line(&buffer, selection, cx)
23231 })
23232 }
23233
23234 pub fn copy_permalink_to_line(
23235 &mut self,
23236 _: &CopyPermalinkToLine,
23237 window: &mut Window,
23238 cx: &mut Context<Self>,
23239 ) {
23240 let permalink_task = self.get_permalink_to_line(cx);
23241 let workspace = self.workspace();
23242
23243 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23244 Ok(permalink) => {
23245 cx.update(|_, cx| {
23246 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23247 })
23248 .ok();
23249 }
23250 Err(err) => {
23251 let message = format!("Failed to copy permalink: {err}");
23252
23253 anyhow::Result::<()>::Err(err).log_err();
23254
23255 if let Some(workspace) = workspace {
23256 workspace
23257 .update_in(cx, |workspace, _, cx| {
23258 struct CopyPermalinkToLine;
23259
23260 workspace.show_toast(
23261 Toast::new(
23262 NotificationId::unique::<CopyPermalinkToLine>(),
23263 message,
23264 ),
23265 cx,
23266 )
23267 })
23268 .ok();
23269 }
23270 }
23271 })
23272 .detach();
23273 }
23274
23275 pub fn copy_file_location(
23276 &mut self,
23277 _: &CopyFileLocation,
23278 _: &mut Window,
23279 cx: &mut Context<Self>,
23280 ) {
23281 let selection = self
23282 .selections
23283 .newest::<Point>(&self.display_snapshot(cx))
23284 .start
23285 .row
23286 + 1;
23287 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23288 let project = self.project()?.read(cx);
23289 let file = buffer.read(cx).file()?;
23290 let path = file.path().display(project.path_style(cx));
23291
23292 Some(format!("{path}:{selection}"))
23293 }) {
23294 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23295 }
23296 }
23297
23298 pub fn open_permalink_to_line(
23299 &mut self,
23300 _: &OpenPermalinkToLine,
23301 window: &mut Window,
23302 cx: &mut Context<Self>,
23303 ) {
23304 let permalink_task = self.get_permalink_to_line(cx);
23305 let workspace = self.workspace();
23306
23307 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23308 Ok(permalink) => {
23309 cx.update(|_, cx| {
23310 cx.open_url(permalink.as_ref());
23311 })
23312 .ok();
23313 }
23314 Err(err) => {
23315 let message = format!("Failed to open permalink: {err}");
23316
23317 anyhow::Result::<()>::Err(err).log_err();
23318
23319 if let Some(workspace) = workspace {
23320 workspace.update(cx, |workspace, cx| {
23321 struct OpenPermalinkToLine;
23322
23323 workspace.show_toast(
23324 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23325 cx,
23326 )
23327 });
23328 }
23329 }
23330 })
23331 .detach();
23332 }
23333
23334 pub fn insert_uuid_v4(
23335 &mut self,
23336 _: &InsertUuidV4,
23337 window: &mut Window,
23338 cx: &mut Context<Self>,
23339 ) {
23340 self.insert_uuid(UuidVersion::V4, window, cx);
23341 }
23342
23343 pub fn insert_uuid_v7(
23344 &mut self,
23345 _: &InsertUuidV7,
23346 window: &mut Window,
23347 cx: &mut Context<Self>,
23348 ) {
23349 self.insert_uuid(UuidVersion::V7, window, cx);
23350 }
23351
23352 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23353 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23354 self.transact(window, cx, |this, window, cx| {
23355 let edits = this
23356 .selections
23357 .all::<Point>(&this.display_snapshot(cx))
23358 .into_iter()
23359 .map(|selection| {
23360 let uuid = match version {
23361 UuidVersion::V4 => uuid::Uuid::new_v4(),
23362 UuidVersion::V7 => uuid::Uuid::now_v7(),
23363 };
23364
23365 (selection.range(), uuid.to_string())
23366 });
23367 this.edit(edits, cx);
23368 this.refresh_edit_prediction(true, false, window, cx);
23369 });
23370 }
23371
23372 pub fn open_selections_in_multibuffer(
23373 &mut self,
23374 _: &OpenSelectionsInMultibuffer,
23375 window: &mut Window,
23376 cx: &mut Context<Self>,
23377 ) {
23378 let multibuffer = self.buffer.read(cx);
23379
23380 let Some(buffer) = multibuffer.as_singleton() else {
23381 return;
23382 };
23383
23384 let Some(workspace) = self.workspace() else {
23385 return;
23386 };
23387
23388 let title = multibuffer.title(cx).to_string();
23389
23390 let locations = self
23391 .selections
23392 .all_anchors(&self.display_snapshot(cx))
23393 .iter()
23394 .map(|selection| {
23395 (
23396 buffer.clone(),
23397 (selection.start.text_anchor..selection.end.text_anchor)
23398 .to_point(buffer.read(cx)),
23399 )
23400 })
23401 .into_group_map();
23402
23403 cx.spawn_in(window, async move |_, cx| {
23404 workspace.update_in(cx, |workspace, window, cx| {
23405 Self::open_locations_in_multibuffer(
23406 workspace,
23407 locations,
23408 format!("Selections for '{title}'"),
23409 false,
23410 false,
23411 MultibufferSelectionMode::All,
23412 window,
23413 cx,
23414 );
23415 })
23416 })
23417 .detach();
23418 }
23419
23420 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23421 /// last highlight added will be used.
23422 ///
23423 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23424 pub fn highlight_rows<T: 'static>(
23425 &mut self,
23426 range: Range<Anchor>,
23427 color: Hsla,
23428 options: RowHighlightOptions,
23429 cx: &mut Context<Self>,
23430 ) {
23431 let snapshot = self.buffer().read(cx).snapshot(cx);
23432 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23433 let ix = row_highlights.binary_search_by(|highlight| {
23434 Ordering::Equal
23435 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23436 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23437 });
23438
23439 if let Err(mut ix) = ix {
23440 let index = post_inc(&mut self.highlight_order);
23441
23442 // If this range intersects with the preceding highlight, then merge it with
23443 // the preceding highlight. Otherwise insert a new highlight.
23444 let mut merged = false;
23445 if ix > 0 {
23446 let prev_highlight = &mut row_highlights[ix - 1];
23447 if prev_highlight
23448 .range
23449 .end
23450 .cmp(&range.start, &snapshot)
23451 .is_ge()
23452 {
23453 ix -= 1;
23454 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23455 prev_highlight.range.end = range.end;
23456 }
23457 merged = true;
23458 prev_highlight.index = index;
23459 prev_highlight.color = color;
23460 prev_highlight.options = options;
23461 }
23462 }
23463
23464 if !merged {
23465 row_highlights.insert(
23466 ix,
23467 RowHighlight {
23468 range,
23469 index,
23470 color,
23471 options,
23472 type_id: TypeId::of::<T>(),
23473 },
23474 );
23475 }
23476
23477 // If any of the following highlights intersect with this one, merge them.
23478 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23479 let highlight = &row_highlights[ix];
23480 if next_highlight
23481 .range
23482 .start
23483 .cmp(&highlight.range.end, &snapshot)
23484 .is_le()
23485 {
23486 if next_highlight
23487 .range
23488 .end
23489 .cmp(&highlight.range.end, &snapshot)
23490 .is_gt()
23491 {
23492 row_highlights[ix].range.end = next_highlight.range.end;
23493 }
23494 row_highlights.remove(ix + 1);
23495 } else {
23496 break;
23497 }
23498 }
23499 }
23500 }
23501
23502 /// Remove any highlighted row ranges of the given type that intersect the
23503 /// given ranges.
23504 pub fn remove_highlighted_rows<T: 'static>(
23505 &mut self,
23506 ranges_to_remove: Vec<Range<Anchor>>,
23507 cx: &mut Context<Self>,
23508 ) {
23509 let snapshot = self.buffer().read(cx).snapshot(cx);
23510 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23511 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23512 row_highlights.retain(|highlight| {
23513 while let Some(range_to_remove) = ranges_to_remove.peek() {
23514 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23515 Ordering::Less | Ordering::Equal => {
23516 ranges_to_remove.next();
23517 }
23518 Ordering::Greater => {
23519 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23520 Ordering::Less | Ordering::Equal => {
23521 return false;
23522 }
23523 Ordering::Greater => break,
23524 }
23525 }
23526 }
23527 }
23528
23529 true
23530 })
23531 }
23532
23533 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23534 pub fn clear_row_highlights<T: 'static>(&mut self) {
23535 self.highlighted_rows.remove(&TypeId::of::<T>());
23536 }
23537
23538 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23539 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23540 self.highlighted_rows
23541 .get(&TypeId::of::<T>())
23542 .map_or(&[] as &[_], |vec| vec.as_slice())
23543 .iter()
23544 .map(|highlight| (highlight.range.clone(), highlight.color))
23545 }
23546
23547 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23548 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23549 /// Allows to ignore certain kinds of highlights.
23550 pub fn highlighted_display_rows(
23551 &self,
23552 window: &mut Window,
23553 cx: &mut App,
23554 ) -> BTreeMap<DisplayRow, LineHighlight> {
23555 let snapshot = self.snapshot(window, cx);
23556 let mut used_highlight_orders = HashMap::default();
23557 self.highlighted_rows
23558 .iter()
23559 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23560 .fold(
23561 BTreeMap::<DisplayRow, LineHighlight>::new(),
23562 |mut unique_rows, highlight| {
23563 let start = highlight.range.start.to_display_point(&snapshot);
23564 let end = highlight.range.end.to_display_point(&snapshot);
23565 let start_row = start.row().0;
23566 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23567 {
23568 end.row().0.saturating_sub(1)
23569 } else {
23570 end.row().0
23571 };
23572 for row in start_row..=end_row {
23573 let used_index =
23574 used_highlight_orders.entry(row).or_insert(highlight.index);
23575 if highlight.index >= *used_index {
23576 *used_index = highlight.index;
23577 unique_rows.insert(
23578 DisplayRow(row),
23579 LineHighlight {
23580 include_gutter: highlight.options.include_gutter,
23581 border: None,
23582 background: highlight.color.into(),
23583 type_id: Some(highlight.type_id),
23584 },
23585 );
23586 }
23587 }
23588 unique_rows
23589 },
23590 )
23591 }
23592
23593 pub fn highlighted_display_row_for_autoscroll(
23594 &self,
23595 snapshot: &DisplaySnapshot,
23596 ) -> Option<DisplayRow> {
23597 self.highlighted_rows
23598 .values()
23599 .flat_map(|highlighted_rows| highlighted_rows.iter())
23600 .filter_map(|highlight| {
23601 if highlight.options.autoscroll {
23602 Some(highlight.range.start.to_display_point(snapshot).row())
23603 } else {
23604 None
23605 }
23606 })
23607 .min()
23608 }
23609
23610 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23611 self.highlight_background(
23612 HighlightKey::SearchWithinRange,
23613 ranges,
23614 |_, colors| colors.colors().editor_document_highlight_read_background,
23615 cx,
23616 )
23617 }
23618
23619 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23620 self.breadcrumb_header = Some(new_header);
23621 }
23622
23623 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23624 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23625 }
23626
23627 pub fn highlight_background(
23628 &mut self,
23629 key: HighlightKey,
23630 ranges: &[Range<Anchor>],
23631 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23632 cx: &mut Context<Self>,
23633 ) {
23634 self.background_highlights
23635 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23636 self.scrollbar_marker_state.dirty = true;
23637 cx.notify();
23638 }
23639
23640 pub fn clear_background_highlights(
23641 &mut self,
23642 key: HighlightKey,
23643 cx: &mut Context<Self>,
23644 ) -> Option<BackgroundHighlight> {
23645 let text_highlights = self.background_highlights.remove(&key)?;
23646 if !text_highlights.1.is_empty() {
23647 self.scrollbar_marker_state.dirty = true;
23648 cx.notify();
23649 }
23650 Some(text_highlights)
23651 }
23652
23653 pub fn highlight_gutter<T: 'static>(
23654 &mut self,
23655 ranges: impl Into<Vec<Range<Anchor>>>,
23656 color_fetcher: fn(&App) -> Hsla,
23657 cx: &mut Context<Self>,
23658 ) {
23659 self.gutter_highlights
23660 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23661 cx.notify();
23662 }
23663
23664 pub fn clear_gutter_highlights<T: 'static>(
23665 &mut self,
23666 cx: &mut Context<Self>,
23667 ) -> Option<GutterHighlight> {
23668 cx.notify();
23669 self.gutter_highlights.remove(&TypeId::of::<T>())
23670 }
23671
23672 pub fn insert_gutter_highlight<T: 'static>(
23673 &mut self,
23674 range: Range<Anchor>,
23675 color_fetcher: fn(&App) -> Hsla,
23676 cx: &mut Context<Self>,
23677 ) {
23678 let snapshot = self.buffer().read(cx).snapshot(cx);
23679 let mut highlights = self
23680 .gutter_highlights
23681 .remove(&TypeId::of::<T>())
23682 .map(|(_, highlights)| highlights)
23683 .unwrap_or_default();
23684 let ix = highlights.binary_search_by(|highlight| {
23685 Ordering::Equal
23686 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23687 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23688 });
23689 if let Err(ix) = ix {
23690 highlights.insert(ix, range);
23691 }
23692 self.gutter_highlights
23693 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23694 }
23695
23696 pub fn remove_gutter_highlights<T: 'static>(
23697 &mut self,
23698 ranges_to_remove: Vec<Range<Anchor>>,
23699 cx: &mut Context<Self>,
23700 ) {
23701 let snapshot = self.buffer().read(cx).snapshot(cx);
23702 let Some((color_fetcher, mut gutter_highlights)) =
23703 self.gutter_highlights.remove(&TypeId::of::<T>())
23704 else {
23705 return;
23706 };
23707 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23708 gutter_highlights.retain(|highlight| {
23709 while let Some(range_to_remove) = ranges_to_remove.peek() {
23710 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23711 Ordering::Less | Ordering::Equal => {
23712 ranges_to_remove.next();
23713 }
23714 Ordering::Greater => {
23715 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23716 Ordering::Less | Ordering::Equal => {
23717 return false;
23718 }
23719 Ordering::Greater => break,
23720 }
23721 }
23722 }
23723 }
23724
23725 true
23726 });
23727 self.gutter_highlights
23728 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23729 }
23730
23731 #[cfg(any(test, feature = "test-support"))]
23732 pub fn all_text_highlights(
23733 &self,
23734 window: &mut Window,
23735 cx: &mut Context<Self>,
23736 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23737 let snapshot = self.snapshot(window, cx);
23738 self.display_map.update(cx, |display_map, _| {
23739 display_map
23740 .all_text_highlights()
23741 .map(|(_, highlight)| {
23742 let (style, ranges) = highlight.as_ref();
23743 (
23744 *style,
23745 ranges
23746 .iter()
23747 .map(|range| range.clone().to_display_points(&snapshot))
23748 .collect(),
23749 )
23750 })
23751 .collect()
23752 })
23753 }
23754
23755 #[cfg(any(test, feature = "test-support"))]
23756 pub fn all_text_background_highlights(
23757 &self,
23758 window: &mut Window,
23759 cx: &mut Context<Self>,
23760 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23761 let snapshot = self.snapshot(window, cx);
23762 let buffer = &snapshot.buffer_snapshot();
23763 let start = buffer.anchor_before(MultiBufferOffset(0));
23764 let end = buffer.anchor_after(buffer.len());
23765 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23766 }
23767
23768 #[cfg(any(test, feature = "test-support"))]
23769 pub fn sorted_background_highlights_in_range(
23770 &self,
23771 search_range: Range<Anchor>,
23772 display_snapshot: &DisplaySnapshot,
23773 theme: &Theme,
23774 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23775 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23776 res.sort_by(|a, b| {
23777 a.0.start
23778 .cmp(&b.0.start)
23779 .then_with(|| a.0.end.cmp(&b.0.end))
23780 .then_with(|| a.1.cmp(&b.1))
23781 });
23782 res
23783 }
23784
23785 #[cfg(any(test, feature = "test-support"))]
23786 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23787 let snapshot = self.buffer().read(cx).snapshot(cx);
23788
23789 let highlights = self
23790 .background_highlights
23791 .get(&HighlightKey::BufferSearchHighlights);
23792
23793 if let Some((_color, ranges)) = highlights {
23794 ranges
23795 .iter()
23796 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23797 .collect_vec()
23798 } else {
23799 vec![]
23800 }
23801 }
23802
23803 fn document_highlights_for_position<'a>(
23804 &'a self,
23805 position: Anchor,
23806 buffer: &'a MultiBufferSnapshot,
23807 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23808 let read_highlights = self
23809 .background_highlights
23810 .get(&HighlightKey::DocumentHighlightRead)
23811 .map(|h| &h.1);
23812 let write_highlights = self
23813 .background_highlights
23814 .get(&HighlightKey::DocumentHighlightWrite)
23815 .map(|h| &h.1);
23816 let left_position = position.bias_left(buffer);
23817 let right_position = position.bias_right(buffer);
23818 read_highlights
23819 .into_iter()
23820 .chain(write_highlights)
23821 .flat_map(move |ranges| {
23822 let start_ix = match ranges.binary_search_by(|probe| {
23823 let cmp = probe.end.cmp(&left_position, buffer);
23824 if cmp.is_ge() {
23825 Ordering::Greater
23826 } else {
23827 Ordering::Less
23828 }
23829 }) {
23830 Ok(i) | Err(i) => i,
23831 };
23832
23833 ranges[start_ix..]
23834 .iter()
23835 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23836 })
23837 }
23838
23839 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23840 self.background_highlights
23841 .get(&key)
23842 .is_some_and(|(_, highlights)| !highlights.is_empty())
23843 }
23844
23845 /// Returns all background highlights for a given range.
23846 ///
23847 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23848 pub fn background_highlights_in_range(
23849 &self,
23850 search_range: Range<Anchor>,
23851 display_snapshot: &DisplaySnapshot,
23852 theme: &Theme,
23853 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23854 let mut results = Vec::new();
23855 for (color_fetcher, ranges) in self.background_highlights.values() {
23856 let start_ix = match ranges.binary_search_by(|probe| {
23857 let cmp = probe
23858 .end
23859 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23860 if cmp.is_gt() {
23861 Ordering::Greater
23862 } else {
23863 Ordering::Less
23864 }
23865 }) {
23866 Ok(i) | Err(i) => i,
23867 };
23868 for (index, range) in ranges[start_ix..].iter().enumerate() {
23869 if range
23870 .start
23871 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23872 .is_ge()
23873 {
23874 break;
23875 }
23876
23877 let color = color_fetcher(&(start_ix + index), theme);
23878 let start = range.start.to_display_point(display_snapshot);
23879 let end = range.end.to_display_point(display_snapshot);
23880 results.push((start..end, color))
23881 }
23882 }
23883 results
23884 }
23885
23886 pub fn gutter_highlights_in_range(
23887 &self,
23888 search_range: Range<Anchor>,
23889 display_snapshot: &DisplaySnapshot,
23890 cx: &App,
23891 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23892 let mut results = Vec::new();
23893 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23894 let color = color_fetcher(cx);
23895 let start_ix = match ranges.binary_search_by(|probe| {
23896 let cmp = probe
23897 .end
23898 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23899 if cmp.is_gt() {
23900 Ordering::Greater
23901 } else {
23902 Ordering::Less
23903 }
23904 }) {
23905 Ok(i) | Err(i) => i,
23906 };
23907 for range in &ranges[start_ix..] {
23908 if range
23909 .start
23910 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23911 .is_ge()
23912 {
23913 break;
23914 }
23915
23916 let start = range.start.to_display_point(display_snapshot);
23917 let end = range.end.to_display_point(display_snapshot);
23918 results.push((start..end, color))
23919 }
23920 }
23921 results
23922 }
23923
23924 /// Get the text ranges corresponding to the redaction query
23925 pub fn redacted_ranges(
23926 &self,
23927 search_range: Range<Anchor>,
23928 display_snapshot: &DisplaySnapshot,
23929 cx: &App,
23930 ) -> Vec<Range<DisplayPoint>> {
23931 display_snapshot
23932 .buffer_snapshot()
23933 .redacted_ranges(search_range, |file| {
23934 if let Some(file) = file {
23935 file.is_private()
23936 && EditorSettings::get(
23937 Some(SettingsLocation {
23938 worktree_id: file.worktree_id(cx),
23939 path: file.path().as_ref(),
23940 }),
23941 cx,
23942 )
23943 .redact_private_values
23944 } else {
23945 false
23946 }
23947 })
23948 .map(|range| {
23949 range.start.to_display_point(display_snapshot)
23950 ..range.end.to_display_point(display_snapshot)
23951 })
23952 .collect()
23953 }
23954
23955 pub fn highlight_text_key(
23956 &mut self,
23957 key: HighlightKey,
23958 ranges: Vec<Range<Anchor>>,
23959 style: HighlightStyle,
23960 merge: bool,
23961 cx: &mut Context<Self>,
23962 ) {
23963 self.display_map.update(cx, |map, cx| {
23964 map.highlight_text(key, ranges, style, merge, cx);
23965 });
23966 cx.notify();
23967 }
23968
23969 pub fn highlight_text(
23970 &mut self,
23971 key: HighlightKey,
23972 ranges: Vec<Range<Anchor>>,
23973 style: HighlightStyle,
23974 cx: &mut Context<Self>,
23975 ) {
23976 self.display_map.update(cx, |map, cx| {
23977 map.highlight_text(key, ranges, style, false, cx)
23978 });
23979 cx.notify();
23980 }
23981
23982 pub fn text_highlights<'a>(
23983 &'a self,
23984 key: HighlightKey,
23985 cx: &'a App,
23986 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23987 self.display_map.read(cx).text_highlights(key)
23988 }
23989
23990 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23991 let cleared = self
23992 .display_map
23993 .update(cx, |map, _| map.clear_highlights(key));
23994 if cleared {
23995 cx.notify();
23996 }
23997 }
23998
23999 pub fn clear_highlights_with(
24000 &mut self,
24001 f: &mut dyn FnMut(&HighlightKey) -> bool,
24002 cx: &mut Context<Self>,
24003 ) {
24004 let cleared = self
24005 .display_map
24006 .update(cx, |map, _| map.clear_highlights_with(f));
24007 if cleared {
24008 cx.notify();
24009 }
24010 }
24011
24012 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24013 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24014 && self.focus_handle.is_focused(window)
24015 }
24016
24017 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24018 self.show_cursor_when_unfocused = is_enabled;
24019 cx.notify();
24020 }
24021
24022 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24023 cx.notify();
24024 }
24025
24026 fn on_debug_session_event(
24027 &mut self,
24028 _session: Entity<Session>,
24029 event: &SessionEvent,
24030 cx: &mut Context<Self>,
24031 ) {
24032 if let SessionEvent::InvalidateInlineValue = event {
24033 self.refresh_inline_values(cx);
24034 }
24035 }
24036
24037 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24038 let Some(semantics) = self.semantics_provider.clone() else {
24039 return;
24040 };
24041
24042 if !self.inline_value_cache.enabled {
24043 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24044 self.splice_inlays(&inlays, Vec::new(), cx);
24045 return;
24046 }
24047
24048 let current_execution_position = self
24049 .highlighted_rows
24050 .get(&TypeId::of::<ActiveDebugLine>())
24051 .and_then(|lines| lines.last().map(|line| line.range.end));
24052
24053 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24054 let inline_values = editor
24055 .update(cx, |editor, cx| {
24056 let Some(current_execution_position) = current_execution_position else {
24057 return Some(Task::ready(Ok(Vec::new())));
24058 };
24059
24060 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
24061 let snapshot = buffer.snapshot(cx);
24062
24063 let excerpt = snapshot.excerpt_containing(
24064 current_execution_position..current_execution_position,
24065 )?;
24066
24067 editor.buffer.read(cx).buffer(excerpt.buffer_id())
24068 })?;
24069
24070 let range =
24071 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
24072
24073 semantics.inline_values(buffer, range, cx)
24074 })
24075 .ok()
24076 .flatten()?
24077 .await
24078 .context("refreshing debugger inlays")
24079 .log_err()?;
24080
24081 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24082
24083 for (buffer_id, inline_value) in inline_values
24084 .into_iter()
24085 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
24086 {
24087 buffer_inline_values
24088 .entry(buffer_id)
24089 .or_default()
24090 .push(inline_value);
24091 }
24092
24093 editor
24094 .update(cx, |editor, cx| {
24095 let snapshot = editor.buffer.read(cx).snapshot(cx);
24096 let mut new_inlays = Vec::default();
24097
24098 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
24099 let buffer_id = buffer_snapshot.remote_id();
24100 buffer_inline_values
24101 .get(&buffer_id)
24102 .into_iter()
24103 .flatten()
24104 .for_each(|hint| {
24105 let inlay = Inlay::debugger(
24106 post_inc(&mut editor.next_inlay_id),
24107 Anchor::in_buffer(excerpt_id, hint.position),
24108 hint.text(),
24109 );
24110 if !inlay.text().chars().contains(&'\n') {
24111 new_inlays.push(inlay);
24112 }
24113 });
24114 }
24115
24116 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24117 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24118
24119 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24120 })
24121 .ok()?;
24122 Some(())
24123 });
24124 }
24125
24126 fn on_buffer_event(
24127 &mut self,
24128 multibuffer: &Entity<MultiBuffer>,
24129 event: &multi_buffer::Event,
24130 window: &mut Window,
24131 cx: &mut Context<Self>,
24132 ) {
24133 match event {
24134 multi_buffer::Event::Edited {
24135 edited_buffer,
24136 is_local,
24137 } => {
24138 self.scrollbar_marker_state.dirty = true;
24139 self.active_indent_guides_state.dirty = true;
24140 self.refresh_active_diagnostics(cx);
24141 self.refresh_code_actions(window, cx);
24142 self.refresh_single_line_folds(window, cx);
24143 let snapshot = self.snapshot(window, cx);
24144 self.refresh_matching_bracket_highlights(&snapshot, cx);
24145 self.refresh_outline_symbols_at_cursor(cx);
24146 self.refresh_sticky_headers(&snapshot, cx);
24147 if *is_local && self.has_active_edit_prediction() {
24148 self.update_visible_edit_prediction(window, cx);
24149 }
24150
24151 // Clean up orphaned review comments after edits
24152 self.cleanup_orphaned_review_comments(cx);
24153
24154 if let Some(buffer) = edited_buffer {
24155 if buffer.read(cx).file().is_none() {
24156 cx.emit(EditorEvent::TitleChanged);
24157 }
24158
24159 if self.project.is_some() {
24160 let buffer_id = buffer.read(cx).remote_id();
24161 self.register_buffer(buffer_id, cx);
24162 self.update_lsp_data(Some(buffer_id), window, cx);
24163 self.refresh_inlay_hints(
24164 InlayHintRefreshReason::BufferEdited(buffer_id),
24165 cx,
24166 );
24167 }
24168 }
24169
24170 cx.emit(EditorEvent::BufferEdited);
24171 cx.emit(SearchEvent::MatchesInvalidated);
24172
24173 let Some(project) = &self.project else { return };
24174 let (telemetry, is_via_ssh) = {
24175 let project = project.read(cx);
24176 let telemetry = project.client().telemetry().clone();
24177 let is_via_ssh = project.is_via_remote_server();
24178 (telemetry, is_via_ssh)
24179 };
24180 telemetry.log_edit_event("editor", is_via_ssh);
24181 }
24182 multi_buffer::Event::ExcerptsAdded {
24183 buffer,
24184 predecessor,
24185 excerpts,
24186 } => {
24187 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24188 let buffer_id = buffer.read(cx).remote_id();
24189 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24190 && let Some(project) = &self.project
24191 {
24192 update_uncommitted_diff_for_buffer(
24193 cx.entity(),
24194 project,
24195 [buffer.clone()],
24196 self.buffer.clone(),
24197 cx,
24198 )
24199 .detach();
24200 }
24201 self.semantic_token_state
24202 .invalidate_buffer(&buffer.read(cx).remote_id());
24203 self.update_lsp_data(Some(buffer_id), window, cx);
24204 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24205 self.colorize_brackets(false, cx);
24206 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24207 cx.emit(EditorEvent::ExcerptsAdded {
24208 buffer: buffer.clone(),
24209 predecessor: *predecessor,
24210 excerpts: excerpts.clone(),
24211 });
24212 }
24213 multi_buffer::Event::ExcerptsRemoved {
24214 ids,
24215 removed_buffer_ids,
24216 } => {
24217 if let Some(inlay_hints) = &mut self.inlay_hints {
24218 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24219 }
24220 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24221 for buffer_id in removed_buffer_ids {
24222 self.registered_buffers.remove(buffer_id);
24223 self.tasks
24224 .retain(|(task_buffer_id, _), _| task_buffer_id != buffer_id);
24225 self.semantic_token_state.invalidate_buffer(buffer_id);
24226 self.display_map.update(cx, |display_map, cx| {
24227 display_map.invalidate_semantic_highlights(*buffer_id);
24228 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24229 });
24230 }
24231
24232 self.display_map.update(cx, |display_map, cx| {
24233 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24234 });
24235
24236 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24237 cx.emit(EditorEvent::ExcerptsRemoved {
24238 ids: ids.clone(),
24239 removed_buffer_ids: removed_buffer_ids.clone(),
24240 });
24241 }
24242 multi_buffer::Event::ExcerptsEdited {
24243 excerpt_ids,
24244 buffer_ids,
24245 } => {
24246 self.display_map.update(cx, |map, cx| {
24247 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24248 });
24249 cx.emit(EditorEvent::ExcerptsEdited {
24250 ids: excerpt_ids.clone(),
24251 });
24252 }
24253 multi_buffer::Event::ExcerptsExpanded { ids } => {
24254 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24255 self.refresh_document_highlights(cx);
24256 let snapshot = multibuffer.read(cx).snapshot(cx);
24257 for id in ids {
24258 self.bracket_fetched_tree_sitter_chunks.remove(id);
24259 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24260 self.semantic_token_state
24261 .invalidate_buffer(&buffer.remote_id());
24262 }
24263 }
24264 self.colorize_brackets(false, cx);
24265 self.update_lsp_data(None, window, cx);
24266 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24267 }
24268 multi_buffer::Event::Reparsed(buffer_id) => {
24269 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24270 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24271 self.colorize_brackets(true, cx);
24272 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24273
24274 cx.emit(EditorEvent::Reparsed(*buffer_id));
24275 }
24276 multi_buffer::Event::DiffHunksToggled => {
24277 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24278 }
24279 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24280 if !is_fresh_language {
24281 self.registered_buffers.remove(&buffer_id);
24282 }
24283 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24284 cx.emit(EditorEvent::Reparsed(*buffer_id));
24285 cx.notify();
24286 }
24287 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24288 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24289 multi_buffer::Event::FileHandleChanged
24290 | multi_buffer::Event::Reloaded
24291 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24292 multi_buffer::Event::DiagnosticsUpdated => {
24293 self.update_diagnostics_state(window, cx);
24294 }
24295 _ => {}
24296 };
24297 }
24298
24299 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24300 if !self.diagnostics_enabled() {
24301 return;
24302 }
24303 self.refresh_active_diagnostics(cx);
24304 self.refresh_inline_diagnostics(true, window, cx);
24305 self.scrollbar_marker_state.dirty = true;
24306 cx.notify();
24307 }
24308
24309 pub fn start_temporary_diff_override(&mut self) {
24310 self.load_diff_task.take();
24311 self.temporary_diff_override = true;
24312 }
24313
24314 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24315 self.temporary_diff_override = false;
24316 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24317 self.buffer.update(cx, |buffer, cx| {
24318 buffer.set_all_diff_hunks_collapsed(cx);
24319 });
24320
24321 if let Some(project) = self.project.clone() {
24322 self.load_diff_task = Some(
24323 update_uncommitted_diff_for_buffer(
24324 cx.entity(),
24325 &project,
24326 self.buffer.read(cx).all_buffers(),
24327 self.buffer.clone(),
24328 cx,
24329 )
24330 .shared(),
24331 );
24332 }
24333 }
24334
24335 fn on_display_map_changed(
24336 &mut self,
24337 _: Entity<DisplayMap>,
24338 _: &mut Window,
24339 cx: &mut Context<Self>,
24340 ) {
24341 cx.notify();
24342 }
24343
24344 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24345 if !self.mode.is_full() {
24346 return None;
24347 }
24348
24349 let theme_settings = theme::ThemeSettings::get_global(cx);
24350 let theme = cx.theme();
24351 let accent_colors = theme.accents().clone();
24352
24353 let accent_overrides = theme_settings
24354 .theme_overrides
24355 .get(theme.name.as_ref())
24356 .map(|theme_style| &theme_style.accents)
24357 .into_iter()
24358 .flatten()
24359 .chain(
24360 theme_settings
24361 .experimental_theme_overrides
24362 .as_ref()
24363 .map(|overrides| &overrides.accents)
24364 .into_iter()
24365 .flatten(),
24366 )
24367 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24368 .collect();
24369
24370 Some(AccentData {
24371 colors: accent_colors,
24372 overrides: accent_overrides,
24373 })
24374 }
24375
24376 fn fetch_applicable_language_settings(
24377 &self,
24378 cx: &App,
24379 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24380 if !self.mode.is_full() {
24381 return HashMap::default();
24382 }
24383
24384 self.buffer().read(cx).all_buffers().into_iter().fold(
24385 HashMap::default(),
24386 |mut acc, buffer| {
24387 let buffer = buffer.read(cx);
24388 let language = buffer.language().map(|language| language.name());
24389 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24390 let file = buffer.file();
24391 v.insert(language_settings(language, file, cx).into_owned());
24392 }
24393 acc
24394 },
24395 )
24396 }
24397
24398 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24399 let new_language_settings = self.fetch_applicable_language_settings(cx);
24400 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24401 self.applicable_language_settings = new_language_settings;
24402
24403 let new_accents = self.fetch_accent_data(cx);
24404 let accents_changed = new_accents != self.accent_data;
24405 self.accent_data = new_accents;
24406
24407 if self.diagnostics_enabled() {
24408 let new_severity = EditorSettings::get_global(cx)
24409 .diagnostics_max_severity
24410 .unwrap_or(DiagnosticSeverity::Hint);
24411 self.set_max_diagnostics_severity(new_severity, cx);
24412 }
24413 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24414 self.update_edit_prediction_settings(cx);
24415 self.refresh_edit_prediction(true, false, window, cx);
24416 self.refresh_inline_values(cx);
24417
24418 let old_cursor_shape = self.cursor_shape;
24419 let old_show_breadcrumbs = self.show_breadcrumbs;
24420
24421 {
24422 let editor_settings = EditorSettings::get_global(cx);
24423 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24424 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24425 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24426 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24427 }
24428
24429 if old_cursor_shape != self.cursor_shape {
24430 cx.emit(EditorEvent::CursorShapeChanged);
24431 }
24432
24433 if old_show_breadcrumbs != self.show_breadcrumbs {
24434 cx.emit(EditorEvent::BreadcrumbsChanged);
24435 }
24436
24437 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24438 let project_settings = ProjectSettings::get_global(cx);
24439 (
24440 project_settings.session.restore_unsaved_buffers,
24441 project_settings.diagnostics.inline.enabled,
24442 project_settings.git.inline_blame.enabled,
24443 )
24444 };
24445 self.buffer_serialization = self
24446 .should_serialize_buffer()
24447 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24448
24449 if self.mode.is_full() {
24450 if self.show_inline_diagnostics != show_inline_diagnostics {
24451 self.show_inline_diagnostics = show_inline_diagnostics;
24452 self.refresh_inline_diagnostics(false, window, cx);
24453 }
24454
24455 if self.git_blame_inline_enabled != inline_blame_enabled {
24456 self.toggle_git_blame_inline_internal(false, window, cx);
24457 }
24458
24459 let minimap_settings = EditorSettings::get_global(cx).minimap;
24460 if self.minimap_visibility != MinimapVisibility::Disabled {
24461 if self.minimap_visibility.settings_visibility()
24462 != minimap_settings.minimap_enabled()
24463 {
24464 self.set_minimap_visibility(
24465 MinimapVisibility::for_mode(self.mode(), cx),
24466 window,
24467 cx,
24468 );
24469 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24470 minimap_entity.update(cx, |minimap_editor, cx| {
24471 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24472 })
24473 }
24474 }
24475
24476 if language_settings_changed || accents_changed {
24477 self.colorize_brackets(true, cx);
24478 }
24479
24480 if language_settings_changed {
24481 self.clear_disabled_lsp_folding_ranges(window, cx);
24482 self.refresh_document_symbols(None, cx);
24483 }
24484
24485 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24486 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24487 }) {
24488 if !inlay_splice.is_empty() {
24489 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24490 }
24491 self.refresh_document_colors(None, window, cx);
24492 }
24493
24494 self.refresh_inlay_hints(
24495 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24496 self.selections.newest_anchor().head(),
24497 &self.buffer.read(cx).snapshot(cx),
24498 cx,
24499 )),
24500 cx,
24501 );
24502
24503 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24504 .global_lsp_settings
24505 .semantic_token_rules
24506 .clone();
24507 let semantic_token_rules_changed = self
24508 .semantic_token_state
24509 .update_rules(new_semantic_token_rules);
24510 if language_settings_changed || semantic_token_rules_changed {
24511 self.invalidate_semantic_tokens(None);
24512 self.refresh_semantic_tokens(None, None, cx);
24513 }
24514 }
24515
24516 cx.notify();
24517 }
24518
24519 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24520 if !self.mode.is_full() {
24521 return;
24522 }
24523
24524 let new_accents = self.fetch_accent_data(cx);
24525 if new_accents != self.accent_data {
24526 self.accent_data = new_accents;
24527 self.colorize_brackets(true, cx);
24528 }
24529
24530 self.invalidate_semantic_tokens(None);
24531 self.refresh_semantic_tokens(None, None, cx);
24532 }
24533
24534 pub fn set_searchable(&mut self, searchable: bool) {
24535 self.searchable = searchable;
24536 }
24537
24538 pub fn searchable(&self) -> bool {
24539 self.searchable
24540 }
24541
24542 pub fn open_excerpts_in_split(
24543 &mut self,
24544 _: &OpenExcerptsSplit,
24545 window: &mut Window,
24546 cx: &mut Context<Self>,
24547 ) {
24548 self.open_excerpts_common(None, true, window, cx)
24549 }
24550
24551 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24552 self.open_excerpts_common(None, false, window, cx)
24553 }
24554
24555 pub(crate) fn open_excerpts_common(
24556 &mut self,
24557 jump_data: Option<JumpData>,
24558 split: bool,
24559 window: &mut Window,
24560 cx: &mut Context<Self>,
24561 ) {
24562 if self.buffer.read(cx).is_singleton() {
24563 cx.propagate();
24564 return;
24565 }
24566
24567 let mut new_selections_by_buffer = HashMap::default();
24568 match &jump_data {
24569 Some(JumpData::MultiBufferPoint {
24570 excerpt_id,
24571 position,
24572 anchor,
24573 line_offset_from_top,
24574 }) => {
24575 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24576 if let Some(buffer) = multi_buffer_snapshot
24577 .buffer_id_for_excerpt(*excerpt_id)
24578 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24579 {
24580 let buffer_snapshot = buffer.read(cx).snapshot();
24581 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24582 language::ToPoint::to_point(anchor, &buffer_snapshot)
24583 } else {
24584 buffer_snapshot.clip_point(*position, Bias::Left)
24585 };
24586 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24587 new_selections_by_buffer.insert(
24588 buffer,
24589 (
24590 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24591 Some(*line_offset_from_top),
24592 ),
24593 );
24594 }
24595 }
24596 Some(JumpData::MultiBufferRow {
24597 row,
24598 line_offset_from_top,
24599 }) => {
24600 let point = MultiBufferPoint::new(row.0, 0);
24601 if let Some((buffer, buffer_point, _)) =
24602 self.buffer.read(cx).point_to_buffer_point(point, cx)
24603 {
24604 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24605 new_selections_by_buffer
24606 .entry(buffer)
24607 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24608 .0
24609 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24610 }
24611 }
24612 None => {
24613 let selections = self
24614 .selections
24615 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24616 let multi_buffer = self.buffer.read(cx);
24617 for selection in selections {
24618 for (snapshot, range, _, anchor) in multi_buffer
24619 .snapshot(cx)
24620 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24621 {
24622 if let Some(anchor) = anchor {
24623 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24624 else {
24625 continue;
24626 };
24627 let offset = text::ToOffset::to_offset(
24628 &anchor.text_anchor,
24629 &buffer_handle.read(cx).snapshot(),
24630 );
24631 let range = BufferOffset(offset)..BufferOffset(offset);
24632 new_selections_by_buffer
24633 .entry(buffer_handle)
24634 .or_insert((Vec::new(), None))
24635 .0
24636 .push(range)
24637 } else {
24638 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24639 else {
24640 continue;
24641 };
24642 new_selections_by_buffer
24643 .entry(buffer_handle)
24644 .or_insert((Vec::new(), None))
24645 .0
24646 .push(range)
24647 }
24648 }
24649 }
24650 }
24651 }
24652
24653 if self.delegate_open_excerpts {
24654 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24655 .into_iter()
24656 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24657 .collect();
24658 if !selections_by_buffer.is_empty() {
24659 cx.emit(EditorEvent::OpenExcerptsRequested {
24660 selections_by_buffer,
24661 split,
24662 });
24663 }
24664 return;
24665 }
24666
24667 let Some(workspace) = self.workspace() else {
24668 cx.propagate();
24669 return;
24670 };
24671
24672 new_selections_by_buffer
24673 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24674
24675 if new_selections_by_buffer.is_empty() {
24676 return;
24677 }
24678
24679 Self::open_buffers_in_workspace(
24680 workspace.downgrade(),
24681 new_selections_by_buffer,
24682 split,
24683 window,
24684 cx,
24685 );
24686 }
24687
24688 pub(crate) fn open_buffers_in_workspace(
24689 workspace: WeakEntity<Workspace>,
24690 new_selections_by_buffer: HashMap<
24691 Entity<language::Buffer>,
24692 (Vec<Range<BufferOffset>>, Option<u32>),
24693 >,
24694 split: bool,
24695 window: &mut Window,
24696 cx: &mut App,
24697 ) {
24698 // We defer the pane interaction because we ourselves are a workspace item
24699 // and activating a new item causes the pane to call a method on us reentrantly,
24700 // which panics if we're on the stack.
24701 window.defer(cx, move |window, cx| {
24702 workspace
24703 .update(cx, |workspace, cx| {
24704 let pane = if split {
24705 workspace.adjacent_pane(window, cx)
24706 } else {
24707 workspace.active_pane().clone()
24708 };
24709
24710 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24711 let buffer_read = buffer.read(cx);
24712 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24713 (true, project::File::from_dyn(Some(file)).is_some())
24714 } else {
24715 (false, false)
24716 };
24717
24718 // If project file is none workspace.open_project_item will fail to open the excerpt
24719 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24720 // so we check if there's a tab match in that case first
24721 let editor = (!has_file || !is_project_file)
24722 .then(|| {
24723 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24724 // so `workspace.open_project_item` will never find them, always opening a new editor.
24725 // Instead, we try to activate the existing editor in the pane first.
24726 let (editor, pane_item_index, pane_item_id) =
24727 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24728 let editor = item.downcast::<Editor>()?;
24729 let singleton_buffer =
24730 editor.read(cx).buffer().read(cx).as_singleton()?;
24731 if singleton_buffer == buffer {
24732 Some((editor, i, item.item_id()))
24733 } else {
24734 None
24735 }
24736 })?;
24737 pane.update(cx, |pane, cx| {
24738 pane.activate_item(pane_item_index, true, true, window, cx);
24739 if !PreviewTabsSettings::get_global(cx)
24740 .enable_preview_from_multibuffer
24741 {
24742 pane.unpreview_item_if_preview(pane_item_id);
24743 }
24744 });
24745 Some(editor)
24746 })
24747 .flatten()
24748 .unwrap_or_else(|| {
24749 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24750 .enable_keep_preview_on_code_navigation;
24751 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24752 .enable_preview_from_multibuffer;
24753 workspace.open_project_item::<Self>(
24754 pane.clone(),
24755 buffer,
24756 true,
24757 true,
24758 keep_old_preview,
24759 allow_new_preview,
24760 window,
24761 cx,
24762 )
24763 });
24764
24765 editor.update(cx, |editor, cx| {
24766 if has_file && !is_project_file {
24767 editor.set_read_only(true);
24768 }
24769 let autoscroll = match scroll_offset {
24770 Some(scroll_offset) => {
24771 Autoscroll::top_relative(scroll_offset as usize)
24772 }
24773 None => Autoscroll::newest(),
24774 };
24775 let nav_history = editor.nav_history.take();
24776 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24777 let Some((excerpt_id, _, buffer_snapshot)) =
24778 multibuffer_snapshot.as_singleton()
24779 else {
24780 return;
24781 };
24782 editor.change_selections(
24783 SelectionEffects::scroll(autoscroll),
24784 window,
24785 cx,
24786 |s| {
24787 s.select_ranges(ranges.into_iter().map(|range| {
24788 let range = buffer_snapshot.anchor_before(range.start)
24789 ..buffer_snapshot.anchor_after(range.end);
24790 multibuffer_snapshot
24791 .anchor_range_in_excerpt(excerpt_id, range)
24792 .unwrap()
24793 }));
24794 },
24795 );
24796 editor.nav_history = nav_history;
24797 });
24798 }
24799 })
24800 .ok();
24801 });
24802 }
24803
24804 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24805 let snapshot = self.buffer.read(cx).read(cx);
24806 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24807 Some(
24808 ranges
24809 .iter()
24810 .map(move |range| {
24811 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24812 })
24813 .collect(),
24814 )
24815 }
24816
24817 fn selection_replacement_ranges(
24818 &self,
24819 range: Range<MultiBufferOffsetUtf16>,
24820 cx: &mut App,
24821 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24822 let selections = self
24823 .selections
24824 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24825 let newest_selection = selections
24826 .iter()
24827 .max_by_key(|selection| selection.id)
24828 .unwrap();
24829 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24830 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24831 let snapshot = self.buffer.read(cx).read(cx);
24832 selections
24833 .into_iter()
24834 .map(|mut selection| {
24835 selection.start.0.0 =
24836 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24837 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24838 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24839 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24840 })
24841 .collect()
24842 }
24843
24844 fn report_editor_event(
24845 &self,
24846 reported_event: ReportEditorEvent,
24847 file_extension: Option<String>,
24848 cx: &App,
24849 ) {
24850 if cfg!(any(test, feature = "test-support")) {
24851 return;
24852 }
24853
24854 let Some(project) = &self.project else { return };
24855
24856 // If None, we are in a file without an extension
24857 let file = self
24858 .buffer
24859 .read(cx)
24860 .as_singleton()
24861 .and_then(|b| b.read(cx).file());
24862 let file_extension = file_extension.or(file
24863 .as_ref()
24864 .and_then(|file| Path::new(file.file_name(cx)).extension())
24865 .and_then(|e| e.to_str())
24866 .map(|a| a.to_string()));
24867
24868 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24869 .map(|vim_mode| vim_mode.0)
24870 .unwrap_or(false);
24871
24872 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24873 let copilot_enabled = edit_predictions_provider
24874 == language::language_settings::EditPredictionProvider::Copilot;
24875 let copilot_enabled_for_language = self
24876 .buffer
24877 .read(cx)
24878 .language_settings(cx)
24879 .show_edit_predictions;
24880
24881 let project = project.read(cx);
24882 let event_type = reported_event.event_type();
24883
24884 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24885 telemetry::event!(
24886 event_type,
24887 type = if auto_saved {"autosave"} else {"manual"},
24888 file_extension,
24889 vim_mode,
24890 copilot_enabled,
24891 copilot_enabled_for_language,
24892 edit_predictions_provider,
24893 is_via_ssh = project.is_via_remote_server(),
24894 );
24895 } else {
24896 telemetry::event!(
24897 event_type,
24898 file_extension,
24899 vim_mode,
24900 copilot_enabled,
24901 copilot_enabled_for_language,
24902 edit_predictions_provider,
24903 is_via_ssh = project.is_via_remote_server(),
24904 );
24905 };
24906 }
24907
24908 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24909 /// with each line being an array of {text, highlight} objects.
24910 fn copy_highlight_json(
24911 &mut self,
24912 _: &CopyHighlightJson,
24913 window: &mut Window,
24914 cx: &mut Context<Self>,
24915 ) {
24916 #[derive(Serialize)]
24917 struct Chunk<'a> {
24918 text: String,
24919 highlight: Option<&'a str>,
24920 }
24921
24922 let snapshot = self.buffer.read(cx).snapshot(cx);
24923 let range = self
24924 .selected_text_range(false, window, cx)
24925 .and_then(|selection| {
24926 if selection.range.is_empty() {
24927 None
24928 } else {
24929 Some(
24930 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24931 selection.range.start,
24932 )))
24933 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24934 selection.range.end,
24935 ))),
24936 )
24937 }
24938 })
24939 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24940
24941 let chunks = snapshot.chunks(range, true);
24942 let mut lines = Vec::new();
24943 let mut line: VecDeque<Chunk> = VecDeque::new();
24944
24945 let Some(style) = self.style.as_ref() else {
24946 return;
24947 };
24948
24949 for chunk in chunks {
24950 let highlight = chunk
24951 .syntax_highlight_id
24952 .and_then(|id| id.name(&style.syntax));
24953 let mut chunk_lines = chunk.text.split('\n').peekable();
24954 while let Some(text) = chunk_lines.next() {
24955 let mut merged_with_last_token = false;
24956 if let Some(last_token) = line.back_mut()
24957 && last_token.highlight == highlight
24958 {
24959 last_token.text.push_str(text);
24960 merged_with_last_token = true;
24961 }
24962
24963 if !merged_with_last_token {
24964 line.push_back(Chunk {
24965 text: text.into(),
24966 highlight,
24967 });
24968 }
24969
24970 if chunk_lines.peek().is_some() {
24971 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24972 line.pop_front();
24973 }
24974 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24975 line.pop_back();
24976 }
24977
24978 lines.push(mem::take(&mut line));
24979 }
24980 }
24981 }
24982
24983 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24984 return;
24985 };
24986 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24987 }
24988
24989 pub fn open_context_menu(
24990 &mut self,
24991 _: &OpenContextMenu,
24992 window: &mut Window,
24993 cx: &mut Context<Self>,
24994 ) {
24995 self.request_autoscroll(Autoscroll::newest(), cx);
24996 let position = self
24997 .selections
24998 .newest_display(&self.display_snapshot(cx))
24999 .start;
25000 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25001 }
25002
25003 pub fn replay_insert_event(
25004 &mut self,
25005 text: &str,
25006 relative_utf16_range: Option<Range<isize>>,
25007 window: &mut Window,
25008 cx: &mut Context<Self>,
25009 ) {
25010 if !self.input_enabled {
25011 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25012 return;
25013 }
25014 if let Some(relative_utf16_range) = relative_utf16_range {
25015 let selections = self
25016 .selections
25017 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25018 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25019 let new_ranges = selections.into_iter().map(|range| {
25020 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25021 range
25022 .head()
25023 .0
25024 .0
25025 .saturating_add_signed(relative_utf16_range.start),
25026 ));
25027 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25028 range
25029 .head()
25030 .0
25031 .0
25032 .saturating_add_signed(relative_utf16_range.end),
25033 ));
25034 start..end
25035 });
25036 s.select_ranges(new_ranges);
25037 });
25038 }
25039
25040 self.handle_input(text, window, cx);
25041 }
25042
25043 pub fn is_focused(&self, window: &Window) -> bool {
25044 self.focus_handle.is_focused(window)
25045 }
25046
25047 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25048 cx.emit(EditorEvent::Focused);
25049
25050 if let Some(descendant) = self
25051 .last_focused_descendant
25052 .take()
25053 .and_then(|descendant| descendant.upgrade())
25054 {
25055 window.focus(&descendant, cx);
25056 } else {
25057 if let Some(blame) = self.blame.as_ref() {
25058 blame.update(cx, GitBlame::focus)
25059 }
25060
25061 self.blink_manager.update(cx, BlinkManager::enable);
25062 self.show_cursor_names(window, cx);
25063 self.buffer.update(cx, |buffer, cx| {
25064 buffer.finalize_last_transaction(cx);
25065 if self.leader_id.is_none() {
25066 buffer.set_active_selections(
25067 &self.selections.disjoint_anchors_arc(),
25068 self.selections.line_mode(),
25069 self.cursor_shape,
25070 cx,
25071 );
25072 }
25073 });
25074
25075 if let Some(position_map) = self.last_position_map.clone() {
25076 EditorElement::mouse_moved(
25077 self,
25078 &MouseMoveEvent {
25079 position: window.mouse_position(),
25080 pressed_button: None,
25081 modifiers: window.modifiers(),
25082 },
25083 &position_map,
25084 None,
25085 window,
25086 cx,
25087 );
25088 }
25089 }
25090 }
25091
25092 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25093 cx.emit(EditorEvent::FocusedIn)
25094 }
25095
25096 fn handle_focus_out(
25097 &mut self,
25098 event: FocusOutEvent,
25099 _window: &mut Window,
25100 cx: &mut Context<Self>,
25101 ) {
25102 if event.blurred != self.focus_handle {
25103 self.last_focused_descendant = Some(event.blurred);
25104 }
25105 self.selection_drag_state = SelectionDragState::None;
25106 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25107 }
25108
25109 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25110 self.blink_manager.update(cx, BlinkManager::disable);
25111 self.buffer
25112 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25113
25114 if let Some(blame) = self.blame.as_ref() {
25115 blame.update(cx, GitBlame::blur)
25116 }
25117 if !self.hover_state.focused(window, cx) {
25118 hide_hover(self, cx);
25119 }
25120 if !self
25121 .context_menu
25122 .borrow()
25123 .as_ref()
25124 .is_some_and(|context_menu| context_menu.focused(window, cx))
25125 {
25126 self.hide_context_menu(window, cx);
25127 }
25128 self.take_active_edit_prediction(cx);
25129 cx.emit(EditorEvent::Blurred);
25130 cx.notify();
25131 }
25132
25133 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25134 let mut pending: String = window
25135 .pending_input_keystrokes()
25136 .into_iter()
25137 .flatten()
25138 .filter_map(|keystroke| keystroke.key_char.clone())
25139 .collect();
25140
25141 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25142 pending = "".to_string();
25143 }
25144
25145 let existing_pending = self
25146 .text_highlights(HighlightKey::PendingInput, cx)
25147 .map(|(_, ranges)| ranges.to_vec());
25148 if existing_pending.is_none() && pending.is_empty() {
25149 return;
25150 }
25151 let transaction =
25152 self.transact(window, cx, |this, window, cx| {
25153 let selections = this
25154 .selections
25155 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25156 let edits = selections
25157 .iter()
25158 .map(|selection| (selection.end..selection.end, pending.clone()));
25159 this.edit(edits, cx);
25160 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25161 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25162 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25163 }));
25164 });
25165 if let Some(existing_ranges) = existing_pending {
25166 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25167 this.edit(edits, cx);
25168 }
25169 });
25170
25171 let snapshot = self.snapshot(window, cx);
25172 let ranges = self
25173 .selections
25174 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25175 .into_iter()
25176 .map(|selection| {
25177 snapshot.buffer_snapshot().anchor_after(selection.end)
25178 ..snapshot
25179 .buffer_snapshot()
25180 .anchor_before(selection.end + pending.len())
25181 })
25182 .collect();
25183
25184 if pending.is_empty() {
25185 self.clear_highlights(HighlightKey::PendingInput, cx);
25186 } else {
25187 self.highlight_text(
25188 HighlightKey::PendingInput,
25189 ranges,
25190 HighlightStyle {
25191 underline: Some(UnderlineStyle {
25192 thickness: px(1.),
25193 color: None,
25194 wavy: false,
25195 }),
25196 ..Default::default()
25197 },
25198 cx,
25199 );
25200 }
25201
25202 self.ime_transaction = self.ime_transaction.or(transaction);
25203 if let Some(transaction) = self.ime_transaction {
25204 self.buffer.update(cx, |buffer, cx| {
25205 buffer.group_until_transaction(transaction, cx);
25206 });
25207 }
25208
25209 if self
25210 .text_highlights(HighlightKey::PendingInput, cx)
25211 .is_none()
25212 {
25213 self.ime_transaction.take();
25214 }
25215 }
25216
25217 pub fn register_action_renderer(
25218 &mut self,
25219 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25220 ) -> Subscription {
25221 let id = self.next_editor_action_id.post_inc();
25222 self.editor_actions
25223 .borrow_mut()
25224 .insert(id, Box::new(listener));
25225
25226 let editor_actions = self.editor_actions.clone();
25227 Subscription::new(move || {
25228 editor_actions.borrow_mut().remove(&id);
25229 })
25230 }
25231
25232 pub fn register_action<A: Action>(
25233 &mut self,
25234 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25235 ) -> Subscription {
25236 let id = self.next_editor_action_id.post_inc();
25237 let listener = Arc::new(listener);
25238 self.editor_actions.borrow_mut().insert(
25239 id,
25240 Box::new(move |_, window, _| {
25241 let listener = listener.clone();
25242 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25243 let action = action.downcast_ref().unwrap();
25244 if phase == DispatchPhase::Bubble {
25245 listener(action, window, cx)
25246 }
25247 })
25248 }),
25249 );
25250
25251 let editor_actions = self.editor_actions.clone();
25252 Subscription::new(move || {
25253 editor_actions.borrow_mut().remove(&id);
25254 })
25255 }
25256
25257 pub fn file_header_size(&self) -> u32 {
25258 FILE_HEADER_HEIGHT
25259 }
25260
25261 pub fn restore(
25262 &mut self,
25263 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25264 window: &mut Window,
25265 cx: &mut Context<Self>,
25266 ) {
25267 self.buffer().update(cx, |multi_buffer, cx| {
25268 for (buffer_id, changes) in revert_changes {
25269 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25270 buffer.update(cx, |buffer, cx| {
25271 buffer.edit(
25272 changes
25273 .into_iter()
25274 .map(|(range, text)| (range, text.to_string())),
25275 None,
25276 cx,
25277 );
25278 });
25279 }
25280 }
25281 });
25282 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25283 selections.refresh()
25284 });
25285 }
25286
25287 pub fn to_pixel_point(
25288 &mut self,
25289 source: Anchor,
25290 editor_snapshot: &EditorSnapshot,
25291 window: &mut Window,
25292 cx: &mut App,
25293 ) -> Option<gpui::Point<Pixels>> {
25294 let source_point = source.to_display_point(editor_snapshot);
25295 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25296 }
25297
25298 pub fn display_to_pixel_point(
25299 &mut self,
25300 source: DisplayPoint,
25301 editor_snapshot: &EditorSnapshot,
25302 window: &mut Window,
25303 cx: &mut App,
25304 ) -> Option<gpui::Point<Pixels>> {
25305 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25306 let text_layout_details = self.text_layout_details(window, cx);
25307 let scroll_top = text_layout_details
25308 .scroll_anchor
25309 .scroll_position(editor_snapshot)
25310 .y;
25311
25312 if source.row().as_f64() < scroll_top.floor() {
25313 return None;
25314 }
25315 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25316 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25317 Some(gpui::Point::new(source_x, source_y))
25318 }
25319
25320 pub fn has_visible_completions_menu(&self) -> bool {
25321 !self.edit_prediction_preview_is_active()
25322 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25323 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25324 })
25325 }
25326
25327 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25328 if self.mode.is_minimap() {
25329 return;
25330 }
25331 self.addons
25332 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25333 }
25334
25335 pub fn unregister_addon<T: Addon>(&mut self) {
25336 self.addons.remove(&std::any::TypeId::of::<T>());
25337 }
25338
25339 pub fn addon<T: Addon>(&self) -> Option<&T> {
25340 let type_id = std::any::TypeId::of::<T>();
25341 self.addons
25342 .get(&type_id)
25343 .and_then(|item| item.to_any().downcast_ref::<T>())
25344 }
25345
25346 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25347 let type_id = std::any::TypeId::of::<T>();
25348 self.addons
25349 .get_mut(&type_id)
25350 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25351 }
25352
25353 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25354 let text_layout_details = self.text_layout_details(window, cx);
25355 let style = &text_layout_details.editor_style;
25356 let font_id = window.text_system().resolve_font(&style.text.font());
25357 let font_size = style.text.font_size.to_pixels(window.rem_size());
25358 let line_height = style.text.line_height_in_pixels(window.rem_size());
25359 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25360 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25361
25362 CharacterDimensions {
25363 em_width,
25364 em_advance,
25365 line_height,
25366 }
25367 }
25368
25369 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25370 self.load_diff_task.clone()
25371 }
25372
25373 fn read_metadata_from_db(
25374 &mut self,
25375 item_id: u64,
25376 workspace_id: WorkspaceId,
25377 window: &mut Window,
25378 cx: &mut Context<Editor>,
25379 ) {
25380 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25381 && !self.mode.is_minimap()
25382 && WorkspaceSettings::get(None, cx).restore_on_startup
25383 != RestoreOnStartupBehavior::EmptyTab
25384 {
25385 let buffer_snapshot = OnceCell::new();
25386
25387 // Get file path for path-based fold lookup
25388 let file_path: Option<Arc<Path>> =
25389 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25390 project::File::from_dyn(buffer.read(cx).file())
25391 .map(|file| Arc::from(file.abs_path(cx)))
25392 });
25393
25394 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25395 let (folds, needs_migration) = if let Some(ref path) = file_path {
25396 if let Some(folds) = DB.get_file_folds(workspace_id, path).log_err()
25397 && !folds.is_empty()
25398 {
25399 (Some(folds), false)
25400 } else if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
25401 && !folds.is_empty()
25402 {
25403 // Found old editor_folds data, will migrate to file_folds
25404 (Some(folds), true)
25405 } else {
25406 (None, false)
25407 }
25408 } else {
25409 // No file path, try editor_folds as fallback
25410 let folds = DB.get_editor_folds(item_id, workspace_id).log_err();
25411 (folds.filter(|f| !f.is_empty()), false)
25412 };
25413
25414 if let Some(folds) = folds {
25415 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25416 let snapshot_len = snapshot.len().0;
25417
25418 // Helper: search for fingerprint in buffer, return offset if found
25419 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25420 // Ensure we start at a character boundary (defensive)
25421 let search_start = snapshot
25422 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25423 .0;
25424 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25425
25426 let mut byte_offset = search_start;
25427 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25428 if byte_offset > search_end {
25429 break;
25430 }
25431 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25432 return Some(byte_offset);
25433 }
25434 byte_offset += ch.len_utf8();
25435 }
25436 None
25437 };
25438
25439 // Track search position to handle duplicate fingerprints correctly.
25440 // Folds are stored in document order, so we advance after each match.
25441 let mut search_start = 0usize;
25442
25443 // Collect db_folds for migration (only folds with valid fingerprints)
25444 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25445
25446 let valid_folds: Vec<_> = folds
25447 .into_iter()
25448 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25449 // Skip folds without fingerprints (old data before migration)
25450 let sfp = start_fp?;
25451 let efp = end_fp?;
25452 let efp_len = efp.len();
25453
25454 // Fast path: check if fingerprints match at stored offsets
25455 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25456 let start_matches = stored_start < snapshot_len
25457 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25458 let efp_check_pos = stored_end.saturating_sub(efp_len);
25459 let end_matches = efp_check_pos >= stored_start
25460 && stored_end <= snapshot_len
25461 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25462
25463 let (new_start, new_end) = if start_matches && end_matches {
25464 // Offsets unchanged, use stored values
25465 (stored_start, stored_end)
25466 } else if sfp == efp {
25467 // Short fold: identical fingerprints can only match once per search
25468 // Use stored fold length to compute new_end
25469 let new_start = find_fingerprint(&sfp, search_start)?;
25470 let fold_len = stored_end - stored_start;
25471 let new_end = new_start + fold_len;
25472 (new_start, new_end)
25473 } else {
25474 // Slow path: search for fingerprints in buffer
25475 let new_start = find_fingerprint(&sfp, search_start)?;
25476 // Search for end_fp after start, then add efp_len to get actual fold end
25477 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25478 let new_end = efp_pos + efp_len;
25479 (new_start, new_end)
25480 };
25481
25482 // Advance search position for next fold
25483 search_start = new_end;
25484
25485 // Validate fold makes sense (end must be after start)
25486 if new_end <= new_start {
25487 return None;
25488 }
25489
25490 // Collect for migration if needed
25491 if needs_migration {
25492 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25493 }
25494
25495 Some(
25496 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25497 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25498 )
25499 })
25500 .collect();
25501
25502 if !valid_folds.is_empty() {
25503 self.fold_ranges(valid_folds, false, window, cx);
25504
25505 // Migrate from editor_folds to file_folds if we loaded from old table
25506 if needs_migration {
25507 if let Some(ref path) = file_path {
25508 let path = path.clone();
25509 cx.spawn(async move |_, _| {
25510 DB.save_file_folds(workspace_id, path, db_folds_for_migration)
25511 .await
25512 .log_err();
25513 })
25514 .detach();
25515 }
25516 }
25517 }
25518 }
25519
25520 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25521 && !selections.is_empty()
25522 {
25523 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25524 // skip adding the initial selection to selection history
25525 self.selection_history.mode = SelectionHistoryMode::Skipping;
25526 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25527 s.select_ranges(selections.into_iter().map(|(start, end)| {
25528 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25529 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25530 }));
25531 });
25532 self.selection_history.mode = SelectionHistoryMode::Normal;
25533 };
25534 }
25535
25536 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25537 }
25538
25539 /// Load folds from the file_folds database table by file path.
25540 /// Used when manually opening a file that was previously closed.
25541 fn load_folds_from_db(
25542 &mut self,
25543 workspace_id: WorkspaceId,
25544 file_path: PathBuf,
25545 window: &mut Window,
25546 cx: &mut Context<Editor>,
25547 ) {
25548 if self.mode.is_minimap()
25549 || WorkspaceSettings::get(None, cx).restore_on_startup
25550 == RestoreOnStartupBehavior::EmptyTab
25551 {
25552 return;
25553 }
25554
25555 let Some(folds) = DB.get_file_folds(workspace_id, &file_path).log_err() else {
25556 return;
25557 };
25558 if folds.is_empty() {
25559 return;
25560 }
25561
25562 let snapshot = self.buffer.read(cx).snapshot(cx);
25563 let snapshot_len = snapshot.len().0;
25564
25565 // Helper: search for fingerprint in buffer, return offset if found
25566 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25567 let search_start = snapshot
25568 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25569 .0;
25570 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25571
25572 let mut byte_offset = search_start;
25573 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25574 if byte_offset > search_end {
25575 break;
25576 }
25577 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25578 return Some(byte_offset);
25579 }
25580 byte_offset += ch.len_utf8();
25581 }
25582 None
25583 };
25584
25585 let mut search_start = 0usize;
25586
25587 let valid_folds: Vec<_> = folds
25588 .into_iter()
25589 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25590 let sfp = start_fp?;
25591 let efp = end_fp?;
25592 let efp_len = efp.len();
25593
25594 let start_matches = stored_start < snapshot_len
25595 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25596 let efp_check_pos = stored_end.saturating_sub(efp_len);
25597 let end_matches = efp_check_pos >= stored_start
25598 && stored_end <= snapshot_len
25599 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25600
25601 let (new_start, new_end) = if start_matches && end_matches {
25602 (stored_start, stored_end)
25603 } else if sfp == efp {
25604 let new_start = find_fingerprint(&sfp, search_start)?;
25605 let fold_len = stored_end - stored_start;
25606 let new_end = new_start + fold_len;
25607 (new_start, new_end)
25608 } else {
25609 let new_start = find_fingerprint(&sfp, search_start)?;
25610 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25611 let new_end = efp_pos + efp_len;
25612 (new_start, new_end)
25613 };
25614
25615 search_start = new_end;
25616
25617 if new_end <= new_start {
25618 return None;
25619 }
25620
25621 Some(
25622 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25623 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25624 )
25625 })
25626 .collect();
25627
25628 if !valid_folds.is_empty() {
25629 self.fold_ranges(valid_folds, false, window, cx);
25630 }
25631 }
25632
25633 fn lsp_data_enabled(&self) -> bool {
25634 self.enable_lsp_data && self.mode().is_full()
25635 }
25636
25637 fn update_lsp_data(
25638 &mut self,
25639 for_buffer: Option<BufferId>,
25640 window: &mut Window,
25641 cx: &mut Context<'_, Self>,
25642 ) {
25643 if !self.lsp_data_enabled() {
25644 return;
25645 }
25646
25647 if let Some(buffer_id) = for_buffer {
25648 self.pull_diagnostics(buffer_id, window, cx);
25649 }
25650 self.refresh_semantic_tokens(for_buffer, None, cx);
25651 self.refresh_document_colors(for_buffer, window, cx);
25652 self.refresh_folding_ranges(for_buffer, window, cx);
25653 self.refresh_document_symbols(for_buffer, cx);
25654 }
25655
25656 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25657 if !self.lsp_data_enabled() {
25658 return;
25659 }
25660 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25661 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25662 }
25663 }
25664
25665 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25666 if !self.lsp_data_enabled() {
25667 return;
25668 }
25669
25670 if !self.registered_buffers.contains_key(&buffer_id)
25671 && let Some(project) = self.project.as_ref()
25672 {
25673 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25674 project.update(cx, |project, cx| {
25675 self.registered_buffers.insert(
25676 buffer_id,
25677 project.register_buffer_with_language_servers(&buffer, cx),
25678 );
25679 });
25680 } else {
25681 self.registered_buffers.remove(&buffer_id);
25682 }
25683 }
25684 }
25685
25686 fn create_style(&self, cx: &App) -> EditorStyle {
25687 let settings = ThemeSettings::get_global(cx);
25688
25689 let mut text_style = match self.mode {
25690 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25691 color: cx.theme().colors().editor_foreground,
25692 font_family: settings.ui_font.family.clone(),
25693 font_features: settings.ui_font.features.clone(),
25694 font_fallbacks: settings.ui_font.fallbacks.clone(),
25695 font_size: rems(0.875).into(),
25696 font_weight: settings.ui_font.weight,
25697 line_height: relative(settings.buffer_line_height.value()),
25698 ..Default::default()
25699 },
25700 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25701 color: cx.theme().colors().editor_foreground,
25702 font_family: settings.buffer_font.family.clone(),
25703 font_features: settings.buffer_font.features.clone(),
25704 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25705 font_size: settings.buffer_font_size(cx).into(),
25706 font_weight: settings.buffer_font.weight,
25707 line_height: relative(settings.buffer_line_height.value()),
25708 ..Default::default()
25709 },
25710 };
25711 if let Some(text_style_refinement) = &self.text_style_refinement {
25712 text_style.refine(text_style_refinement)
25713 }
25714
25715 let background = match self.mode {
25716 EditorMode::SingleLine => cx.theme().system().transparent,
25717 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25718 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25719 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25720 };
25721
25722 EditorStyle {
25723 background,
25724 border: cx.theme().colors().border,
25725 local_player: cx.theme().players().local(),
25726 text: text_style,
25727 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25728 syntax: cx.theme().syntax().clone(),
25729 status: cx.theme().status().clone(),
25730 inlay_hints_style: make_inlay_hints_style(cx),
25731 edit_prediction_styles: make_suggestion_styles(cx),
25732 unnecessary_code_fade: settings.unnecessary_code_fade,
25733 show_underlines: self.diagnostics_enabled(),
25734 }
25735 }
25736
25737 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
25738 let multibuffer = self.buffer().read(cx);
25739 let is_singleton = multibuffer.is_singleton();
25740 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25741 let buffer = multibuffer.buffer(*buffer_id)?;
25742
25743 let buffer = buffer.read(cx);
25744 let settings = ThemeSettings::get_global(cx);
25745 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25746 let mut breadcrumbs = if is_singleton {
25747 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25748 buffer
25749 .snapshot()
25750 .resolve_file_path(
25751 self.project
25752 .as_ref()
25753 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25754 .unwrap_or_default(),
25755 cx,
25756 )
25757 .unwrap_or_else(|| {
25758 if multibuffer.is_singleton() {
25759 multibuffer.title(cx).to_string()
25760 } else {
25761 "untitled".to_string()
25762 }
25763 })
25764 });
25765 vec![BreadcrumbText {
25766 text,
25767 highlights: None,
25768 font: Some(settings.buffer_font.clone()),
25769 }]
25770 } else {
25771 vec![]
25772 };
25773
25774 breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
25775 text: symbol.text.clone(),
25776 highlights: Some(symbol.highlight_ranges.clone()),
25777 font: Some(settings.buffer_font.clone()),
25778 }));
25779 Some(breadcrumbs)
25780 }
25781
25782 fn disable_lsp_data(&mut self) {
25783 self.enable_lsp_data = false;
25784 }
25785
25786 fn disable_runnables(&mut self) {
25787 self.enable_runnables = false;
25788 }
25789}
25790
25791fn edit_for_markdown_paste<'a>(
25792 buffer: &MultiBufferSnapshot,
25793 range: Range<MultiBufferOffset>,
25794 to_insert: &'a str,
25795 url: Option<url::Url>,
25796) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25797 if url.is_none() {
25798 return (range, Cow::Borrowed(to_insert));
25799 };
25800
25801 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25802
25803 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25804 Cow::Borrowed(to_insert)
25805 } else {
25806 Cow::Owned(format!("[{old_text}]({to_insert})"))
25807 };
25808 (range, new_text)
25809}
25810
25811fn process_completion_for_edit(
25812 completion: &Completion,
25813 intent: CompletionIntent,
25814 buffer: &Entity<Buffer>,
25815 cursor_position: &text::Anchor,
25816 cx: &mut Context<Editor>,
25817) -> CompletionEdit {
25818 let buffer = buffer.read(cx);
25819 let buffer_snapshot = buffer.snapshot();
25820 let (snippet, new_text) = if completion.is_snippet() {
25821 let mut snippet_source = completion.new_text.clone();
25822 // Workaround for typescript language server issues so that methods don't expand within
25823 // strings and functions with type expressions. The previous point is used because the query
25824 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25825 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25826 let previous_point = if previous_point.column > 0 {
25827 cursor_position.to_previous_offset(&buffer_snapshot)
25828 } else {
25829 cursor_position.to_offset(&buffer_snapshot)
25830 };
25831 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25832 && scope.prefers_label_for_snippet_in_completion()
25833 && let Some(label) = completion.label()
25834 && matches!(
25835 completion.kind(),
25836 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25837 )
25838 {
25839 snippet_source = label;
25840 }
25841 match Snippet::parse(&snippet_source).log_err() {
25842 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25843 None => (None, completion.new_text.clone()),
25844 }
25845 } else {
25846 (None, completion.new_text.clone())
25847 };
25848
25849 let mut range_to_replace = {
25850 let replace_range = &completion.replace_range;
25851 if let CompletionSource::Lsp {
25852 insert_range: Some(insert_range),
25853 ..
25854 } = &completion.source
25855 {
25856 debug_assert_eq!(
25857 insert_range.start, replace_range.start,
25858 "insert_range and replace_range should start at the same position"
25859 );
25860 debug_assert!(
25861 insert_range
25862 .start
25863 .cmp(cursor_position, &buffer_snapshot)
25864 .is_le(),
25865 "insert_range should start before or at cursor position"
25866 );
25867 debug_assert!(
25868 replace_range
25869 .start
25870 .cmp(cursor_position, &buffer_snapshot)
25871 .is_le(),
25872 "replace_range should start before or at cursor position"
25873 );
25874
25875 let should_replace = match intent {
25876 CompletionIntent::CompleteWithInsert => false,
25877 CompletionIntent::CompleteWithReplace => true,
25878 CompletionIntent::Complete | CompletionIntent::Compose => {
25879 let insert_mode =
25880 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25881 .completions
25882 .lsp_insert_mode;
25883 match insert_mode {
25884 LspInsertMode::Insert => false,
25885 LspInsertMode::Replace => true,
25886 LspInsertMode::ReplaceSubsequence => {
25887 let mut text_to_replace = buffer.chars_for_range(
25888 buffer.anchor_before(replace_range.start)
25889 ..buffer.anchor_after(replace_range.end),
25890 );
25891 let mut current_needle = text_to_replace.next();
25892 for haystack_ch in completion.label.text.chars() {
25893 if let Some(needle_ch) = current_needle
25894 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25895 {
25896 current_needle = text_to_replace.next();
25897 }
25898 }
25899 current_needle.is_none()
25900 }
25901 LspInsertMode::ReplaceSuffix => {
25902 if replace_range
25903 .end
25904 .cmp(cursor_position, &buffer_snapshot)
25905 .is_gt()
25906 {
25907 let range_after_cursor = *cursor_position..replace_range.end;
25908 let text_after_cursor = buffer
25909 .text_for_range(
25910 buffer.anchor_before(range_after_cursor.start)
25911 ..buffer.anchor_after(range_after_cursor.end),
25912 )
25913 .collect::<String>()
25914 .to_ascii_lowercase();
25915 completion
25916 .label
25917 .text
25918 .to_ascii_lowercase()
25919 .ends_with(&text_after_cursor)
25920 } else {
25921 true
25922 }
25923 }
25924 }
25925 }
25926 };
25927
25928 if should_replace {
25929 replace_range.clone()
25930 } else {
25931 insert_range.clone()
25932 }
25933 } else {
25934 replace_range.clone()
25935 }
25936 };
25937
25938 if range_to_replace
25939 .end
25940 .cmp(cursor_position, &buffer_snapshot)
25941 .is_lt()
25942 {
25943 range_to_replace.end = *cursor_position;
25944 }
25945
25946 let replace_range = range_to_replace.to_offset(buffer);
25947 CompletionEdit {
25948 new_text,
25949 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25950 snippet,
25951 }
25952}
25953
25954struct CompletionEdit {
25955 new_text: String,
25956 replace_range: Range<BufferOffset>,
25957 snippet: Option<Snippet>,
25958}
25959
25960fn comment_delimiter_for_newline(
25961 start_point: &Point,
25962 buffer: &MultiBufferSnapshot,
25963 language: &LanguageScope,
25964) -> Option<Arc<str>> {
25965 let delimiters = language.line_comment_prefixes();
25966 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25967 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25968
25969 let num_of_whitespaces = snapshot
25970 .chars_for_range(range.clone())
25971 .take_while(|c| c.is_whitespace())
25972 .count();
25973 let comment_candidate = snapshot
25974 .chars_for_range(range.clone())
25975 .skip(num_of_whitespaces)
25976 .take(max_len_of_delimiter + 2)
25977 .collect::<String>();
25978 let (delimiter, trimmed_len, is_repl) = delimiters
25979 .iter()
25980 .filter_map(|delimiter| {
25981 let prefix = delimiter.trim_end();
25982 if comment_candidate.starts_with(prefix) {
25983 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
25984 {
25985 stripped_comment.starts_with(" %%")
25986 } else {
25987 false
25988 };
25989 Some((delimiter, prefix.len(), is_repl))
25990 } else {
25991 None
25992 }
25993 })
25994 .max_by_key(|(_, len, _)| *len)?;
25995
25996 if let Some(BlockCommentConfig {
25997 start: block_start, ..
25998 }) = language.block_comment()
25999 {
26000 let block_start_trimmed = block_start.trim_end();
26001 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26002 let line_content = snapshot
26003 .chars_for_range(range.clone())
26004 .skip(num_of_whitespaces)
26005 .take(block_start_trimmed.len())
26006 .collect::<String>();
26007
26008 if line_content.starts_with(block_start_trimmed) {
26009 return None;
26010 }
26011 }
26012 }
26013
26014 let cursor_is_placed_after_comment_marker =
26015 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26016 if cursor_is_placed_after_comment_marker {
26017 if !is_repl {
26018 return Some(delimiter.clone());
26019 }
26020
26021 let line_content_after_cursor: String = snapshot
26022 .chars_for_range(range)
26023 .skip(start_point.column as usize)
26024 .collect();
26025
26026 if line_content_after_cursor.trim().is_empty() {
26027 return None;
26028 } else {
26029 return Some(delimiter.clone());
26030 }
26031 } else {
26032 None
26033 }
26034}
26035
26036fn documentation_delimiter_for_newline(
26037 start_point: &Point,
26038 buffer: &MultiBufferSnapshot,
26039 language: &LanguageScope,
26040 newline_config: &mut NewlineConfig,
26041) -> Option<Arc<str>> {
26042 let BlockCommentConfig {
26043 start: start_tag,
26044 end: end_tag,
26045 prefix: delimiter,
26046 tab_size: len,
26047 } = language.documentation_comment()?;
26048 let is_within_block_comment = buffer
26049 .language_scope_at(*start_point)
26050 .is_some_and(|scope| scope.override_name() == Some("comment"));
26051 if !is_within_block_comment {
26052 return None;
26053 }
26054
26055 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26056
26057 let num_of_whitespaces = snapshot
26058 .chars_for_range(range.clone())
26059 .take_while(|c| c.is_whitespace())
26060 .count();
26061
26062 // 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.
26063 let column = start_point.column;
26064 let cursor_is_after_start_tag = {
26065 let start_tag_len = start_tag.len();
26066 let start_tag_line = snapshot
26067 .chars_for_range(range.clone())
26068 .skip(num_of_whitespaces)
26069 .take(start_tag_len)
26070 .collect::<String>();
26071 if start_tag_line.starts_with(start_tag.as_ref()) {
26072 num_of_whitespaces + start_tag_len <= column as usize
26073 } else {
26074 false
26075 }
26076 };
26077
26078 let cursor_is_after_delimiter = {
26079 let delimiter_trim = delimiter.trim_end();
26080 let delimiter_line = snapshot
26081 .chars_for_range(range.clone())
26082 .skip(num_of_whitespaces)
26083 .take(delimiter_trim.len())
26084 .collect::<String>();
26085 if delimiter_line.starts_with(delimiter_trim) {
26086 num_of_whitespaces + delimiter_trim.len() <= column as usize
26087 } else {
26088 false
26089 }
26090 };
26091
26092 let mut needs_extra_line = false;
26093 let mut extra_line_additional_indent = IndentSize::spaces(0);
26094
26095 let cursor_is_before_end_tag_if_exists = {
26096 let mut char_position = 0u32;
26097 let mut end_tag_offset = None;
26098
26099 'outer: for chunk in snapshot.text_for_range(range) {
26100 if let Some(byte_pos) = chunk.find(&**end_tag) {
26101 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26102 end_tag_offset = Some(char_position + chars_before_match);
26103 break 'outer;
26104 }
26105 char_position += chunk.chars().count() as u32;
26106 }
26107
26108 if let Some(end_tag_offset) = end_tag_offset {
26109 let cursor_is_before_end_tag = column <= end_tag_offset;
26110 if cursor_is_after_start_tag {
26111 if cursor_is_before_end_tag {
26112 needs_extra_line = true;
26113 }
26114 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26115 if cursor_is_at_start_of_end_tag {
26116 extra_line_additional_indent.len = *len;
26117 }
26118 }
26119 cursor_is_before_end_tag
26120 } else {
26121 true
26122 }
26123 };
26124
26125 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26126 && cursor_is_before_end_tag_if_exists
26127 {
26128 let additional_indent = if cursor_is_after_start_tag {
26129 IndentSize::spaces(*len)
26130 } else {
26131 IndentSize::spaces(0)
26132 };
26133
26134 *newline_config = NewlineConfig::Newline {
26135 additional_indent,
26136 extra_line_additional_indent: if needs_extra_line {
26137 Some(extra_line_additional_indent)
26138 } else {
26139 None
26140 },
26141 prevent_auto_indent: true,
26142 };
26143 Some(delimiter.clone())
26144 } else {
26145 None
26146 }
26147}
26148
26149const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26150
26151fn list_delimiter_for_newline(
26152 start_point: &Point,
26153 buffer: &MultiBufferSnapshot,
26154 language: &LanguageScope,
26155 newline_config: &mut NewlineConfig,
26156) -> Option<Arc<str>> {
26157 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26158
26159 let num_of_whitespaces = snapshot
26160 .chars_for_range(range.clone())
26161 .take_while(|c| c.is_whitespace())
26162 .count();
26163
26164 let task_list_entries: Vec<_> = language
26165 .task_list()
26166 .into_iter()
26167 .flat_map(|config| {
26168 config
26169 .prefixes
26170 .iter()
26171 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26172 })
26173 .collect();
26174 let unordered_list_entries: Vec<_> = language
26175 .unordered_list()
26176 .iter()
26177 .map(|marker| (marker.as_ref(), marker.as_ref()))
26178 .collect();
26179
26180 let all_entries: Vec<_> = task_list_entries
26181 .into_iter()
26182 .chain(unordered_list_entries)
26183 .collect();
26184
26185 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26186 let candidate: String = snapshot
26187 .chars_for_range(range.clone())
26188 .skip(num_of_whitespaces)
26189 .take(max_prefix_len)
26190 .collect();
26191
26192 if let Some((prefix, continuation)) = all_entries
26193 .iter()
26194 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26195 .max_by_key(|(prefix, _)| prefix.len())
26196 {
26197 let end_of_prefix = num_of_whitespaces + prefix.len();
26198 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26199 let has_content_after_marker = snapshot
26200 .chars_for_range(range)
26201 .skip(end_of_prefix)
26202 .any(|c| !c.is_whitespace());
26203
26204 if has_content_after_marker && cursor_is_after_prefix {
26205 return Some((*continuation).into());
26206 }
26207
26208 if start_point.column as usize == end_of_prefix {
26209 if num_of_whitespaces == 0 {
26210 *newline_config = NewlineConfig::ClearCurrentLine;
26211 } else {
26212 *newline_config = NewlineConfig::UnindentCurrentLine {
26213 continuation: (*continuation).into(),
26214 };
26215 }
26216 }
26217
26218 return None;
26219 }
26220 }
26221
26222 let candidate: String = snapshot
26223 .chars_for_range(range.clone())
26224 .skip(num_of_whitespaces)
26225 .take(ORDERED_LIST_MAX_MARKER_LEN)
26226 .collect();
26227
26228 for ordered_config in language.ordered_list() {
26229 let regex = match Regex::new(&ordered_config.pattern) {
26230 Ok(r) => r,
26231 Err(_) => continue,
26232 };
26233
26234 if let Some(captures) = regex.captures(&candidate) {
26235 let full_match = captures.get(0)?;
26236 let marker_len = full_match.len();
26237 let end_of_prefix = num_of_whitespaces + marker_len;
26238 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26239
26240 let has_content_after_marker = snapshot
26241 .chars_for_range(range)
26242 .skip(end_of_prefix)
26243 .any(|c| !c.is_whitespace());
26244
26245 if has_content_after_marker && cursor_is_after_prefix {
26246 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26247 let continuation = ordered_config
26248 .format
26249 .replace("{1}", &(number + 1).to_string());
26250 return Some(continuation.into());
26251 }
26252
26253 if start_point.column as usize == end_of_prefix {
26254 let continuation = ordered_config.format.replace("{1}", "1");
26255 if num_of_whitespaces == 0 {
26256 *newline_config = NewlineConfig::ClearCurrentLine;
26257 } else {
26258 *newline_config = NewlineConfig::UnindentCurrentLine {
26259 continuation: continuation.into(),
26260 };
26261 }
26262 }
26263
26264 return None;
26265 }
26266 }
26267
26268 None
26269}
26270
26271fn is_list_prefix_row(
26272 row: MultiBufferRow,
26273 buffer: &MultiBufferSnapshot,
26274 language: &LanguageScope,
26275) -> bool {
26276 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26277 return false;
26278 };
26279
26280 let num_of_whitespaces = snapshot
26281 .chars_for_range(range.clone())
26282 .take_while(|c| c.is_whitespace())
26283 .count();
26284
26285 let task_list_prefixes: Vec<_> = language
26286 .task_list()
26287 .into_iter()
26288 .flat_map(|config| {
26289 config
26290 .prefixes
26291 .iter()
26292 .map(|p| p.as_ref())
26293 .collect::<Vec<_>>()
26294 })
26295 .collect();
26296 let unordered_list_markers: Vec<_> = language
26297 .unordered_list()
26298 .iter()
26299 .map(|marker| marker.as_ref())
26300 .collect();
26301 let all_prefixes: Vec<_> = task_list_prefixes
26302 .into_iter()
26303 .chain(unordered_list_markers)
26304 .collect();
26305 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26306 let candidate: String = snapshot
26307 .chars_for_range(range.clone())
26308 .skip(num_of_whitespaces)
26309 .take(max_prefix_len)
26310 .collect();
26311 if all_prefixes
26312 .iter()
26313 .any(|prefix| candidate.starts_with(*prefix))
26314 {
26315 return true;
26316 }
26317 }
26318
26319 let ordered_list_candidate: String = snapshot
26320 .chars_for_range(range)
26321 .skip(num_of_whitespaces)
26322 .take(ORDERED_LIST_MAX_MARKER_LEN)
26323 .collect();
26324 for ordered_config in language.ordered_list() {
26325 let regex = match Regex::new(&ordered_config.pattern) {
26326 Ok(r) => r,
26327 Err(_) => continue,
26328 };
26329 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26330 return captures.get(0).is_some();
26331 }
26332 }
26333
26334 false
26335}
26336
26337#[derive(Debug)]
26338enum NewlineConfig {
26339 /// Insert newline with optional additional indent and optional extra blank line
26340 Newline {
26341 additional_indent: IndentSize,
26342 extra_line_additional_indent: Option<IndentSize>,
26343 prevent_auto_indent: bool,
26344 },
26345 /// Clear the current line
26346 ClearCurrentLine,
26347 /// Unindent the current line and add continuation
26348 UnindentCurrentLine { continuation: Arc<str> },
26349}
26350
26351impl NewlineConfig {
26352 fn has_extra_line(&self) -> bool {
26353 matches!(
26354 self,
26355 Self::Newline {
26356 extra_line_additional_indent: Some(_),
26357 ..
26358 }
26359 )
26360 }
26361
26362 fn insert_extra_newline_brackets(
26363 buffer: &MultiBufferSnapshot,
26364 range: Range<MultiBufferOffset>,
26365 language: &language::LanguageScope,
26366 ) -> bool {
26367 let leading_whitespace_len = buffer
26368 .reversed_chars_at(range.start)
26369 .take_while(|c| c.is_whitespace() && *c != '\n')
26370 .map(|c| c.len_utf8())
26371 .sum::<usize>();
26372 let trailing_whitespace_len = buffer
26373 .chars_at(range.end)
26374 .take_while(|c| c.is_whitespace() && *c != '\n')
26375 .map(|c| c.len_utf8())
26376 .sum::<usize>();
26377 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26378
26379 language.brackets().any(|(pair, enabled)| {
26380 let pair_start = pair.start.trim_end();
26381 let pair_end = pair.end.trim_start();
26382
26383 enabled
26384 && pair.newline
26385 && buffer.contains_str_at(range.end, pair_end)
26386 && buffer.contains_str_at(
26387 range.start.saturating_sub_usize(pair_start.len()),
26388 pair_start,
26389 )
26390 })
26391 }
26392
26393 fn insert_extra_newline_tree_sitter(
26394 buffer: &MultiBufferSnapshot,
26395 range: Range<MultiBufferOffset>,
26396 ) -> bool {
26397 let (buffer, range) = match buffer
26398 .range_to_buffer_ranges(range.start..=range.end)
26399 .as_slice()
26400 {
26401 [(buffer, range, _)] => (*buffer, range.clone()),
26402 _ => return false,
26403 };
26404 let pair = {
26405 let mut result: Option<BracketMatch<usize>> = None;
26406
26407 for pair in buffer
26408 .all_bracket_ranges(range.start.0..range.end.0)
26409 .filter(move |pair| {
26410 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26411 })
26412 {
26413 let len = pair.close_range.end - pair.open_range.start;
26414
26415 if let Some(existing) = &result {
26416 let existing_len = existing.close_range.end - existing.open_range.start;
26417 if len > existing_len {
26418 continue;
26419 }
26420 }
26421
26422 result = Some(pair);
26423 }
26424
26425 result
26426 };
26427 let Some(pair) = pair else {
26428 return false;
26429 };
26430 pair.newline_only
26431 && buffer
26432 .chars_for_range(pair.open_range.end..range.start.0)
26433 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26434 .all(|c| c.is_whitespace() && c != '\n')
26435 }
26436}
26437
26438fn update_uncommitted_diff_for_buffer(
26439 editor: Entity<Editor>,
26440 project: &Entity<Project>,
26441 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26442 buffer: Entity<MultiBuffer>,
26443 cx: &mut App,
26444) -> Task<()> {
26445 let mut tasks = Vec::new();
26446 project.update(cx, |project, cx| {
26447 for buffer in buffers {
26448 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26449 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26450 }
26451 }
26452 });
26453 cx.spawn(async move |cx| {
26454 let diffs = future::join_all(tasks).await;
26455 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26456 return;
26457 }
26458
26459 buffer.update(cx, |buffer, cx| {
26460 for diff in diffs.into_iter().flatten() {
26461 buffer.add_diff(diff, cx);
26462 }
26463 });
26464 })
26465}
26466
26467fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26468 let tab_size = tab_size.get() as usize;
26469 let mut width = offset;
26470
26471 for ch in text.chars() {
26472 width += if ch == '\t' {
26473 tab_size - (width % tab_size)
26474 } else {
26475 1
26476 };
26477 }
26478
26479 width - offset
26480}
26481
26482#[cfg(test)]
26483mod tests {
26484 use super::*;
26485
26486 #[test]
26487 fn test_string_size_with_expanded_tabs() {
26488 let nz = |val| NonZeroU32::new(val).unwrap();
26489 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26490 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26491 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26492 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26493 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26494 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26495 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26496 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26497 }
26498}
26499
26500/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26501struct WordBreakingTokenizer<'a> {
26502 input: &'a str,
26503}
26504
26505impl<'a> WordBreakingTokenizer<'a> {
26506 fn new(input: &'a str) -> Self {
26507 Self { input }
26508 }
26509}
26510
26511fn is_char_ideographic(ch: char) -> bool {
26512 use unicode_script::Script::*;
26513 use unicode_script::UnicodeScript;
26514 matches!(ch.script(), Han | Tangut | Yi)
26515}
26516
26517fn is_grapheme_ideographic(text: &str) -> bool {
26518 text.chars().any(is_char_ideographic)
26519}
26520
26521fn is_grapheme_whitespace(text: &str) -> bool {
26522 text.chars().any(|x| x.is_whitespace())
26523}
26524
26525fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26526 text.chars()
26527 .next()
26528 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26529}
26530
26531#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26532enum WordBreakToken<'a> {
26533 Word { token: &'a str, grapheme_len: usize },
26534 InlineWhitespace { token: &'a str, grapheme_len: usize },
26535 Newline,
26536}
26537
26538impl<'a> Iterator for WordBreakingTokenizer<'a> {
26539 /// Yields a span, the count of graphemes in the token, and whether it was
26540 /// whitespace. Note that it also breaks at word boundaries.
26541 type Item = WordBreakToken<'a>;
26542
26543 fn next(&mut self) -> Option<Self::Item> {
26544 use unicode_segmentation::UnicodeSegmentation;
26545 if self.input.is_empty() {
26546 return None;
26547 }
26548
26549 let mut iter = self.input.graphemes(true).peekable();
26550 let mut offset = 0;
26551 let mut grapheme_len = 0;
26552 if let Some(first_grapheme) = iter.next() {
26553 let is_newline = first_grapheme == "\n";
26554 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26555 offset += first_grapheme.len();
26556 grapheme_len += 1;
26557 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26558 if let Some(grapheme) = iter.peek().copied()
26559 && should_stay_with_preceding_ideograph(grapheme)
26560 {
26561 offset += grapheme.len();
26562 grapheme_len += 1;
26563 }
26564 } else {
26565 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26566 let mut next_word_bound = words.peek().copied();
26567 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26568 next_word_bound = words.next();
26569 }
26570 while let Some(grapheme) = iter.peek().copied() {
26571 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26572 break;
26573 };
26574 if is_grapheme_whitespace(grapheme) != is_whitespace
26575 || (grapheme == "\n") != is_newline
26576 {
26577 break;
26578 };
26579 offset += grapheme.len();
26580 grapheme_len += 1;
26581 iter.next();
26582 }
26583 }
26584 let token = &self.input[..offset];
26585 self.input = &self.input[offset..];
26586 if token == "\n" {
26587 Some(WordBreakToken::Newline)
26588 } else if is_whitespace {
26589 Some(WordBreakToken::InlineWhitespace {
26590 token,
26591 grapheme_len,
26592 })
26593 } else {
26594 Some(WordBreakToken::Word {
26595 token,
26596 grapheme_len,
26597 })
26598 }
26599 } else {
26600 None
26601 }
26602 }
26603}
26604
26605#[test]
26606fn test_word_breaking_tokenizer() {
26607 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26608 ("", &[]),
26609 (" ", &[whitespace(" ", 2)]),
26610 ("Ʒ", &[word("Ʒ", 1)]),
26611 ("Ǽ", &[word("Ǽ", 1)]),
26612 ("⋑", &[word("⋑", 1)]),
26613 ("⋑⋑", &[word("⋑⋑", 2)]),
26614 (
26615 "原理,进而",
26616 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26617 ),
26618 (
26619 "hello world",
26620 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26621 ),
26622 (
26623 "hello, world",
26624 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26625 ),
26626 (
26627 " hello world",
26628 &[
26629 whitespace(" ", 2),
26630 word("hello", 5),
26631 whitespace(" ", 1),
26632 word("world", 5),
26633 ],
26634 ),
26635 (
26636 "这是什么 \n 钢笔",
26637 &[
26638 word("这", 1),
26639 word("是", 1),
26640 word("什", 1),
26641 word("么", 1),
26642 whitespace(" ", 1),
26643 newline(),
26644 whitespace(" ", 1),
26645 word("钢", 1),
26646 word("笔", 1),
26647 ],
26648 ),
26649 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26650 ];
26651
26652 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26653 WordBreakToken::Word {
26654 token,
26655 grapheme_len,
26656 }
26657 }
26658
26659 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26660 WordBreakToken::InlineWhitespace {
26661 token,
26662 grapheme_len,
26663 }
26664 }
26665
26666 fn newline() -> WordBreakToken<'static> {
26667 WordBreakToken::Newline
26668 }
26669
26670 for (input, result) in tests {
26671 assert_eq!(
26672 WordBreakingTokenizer::new(input)
26673 .collect::<Vec<_>>()
26674 .as_slice(),
26675 *result,
26676 );
26677 }
26678}
26679
26680fn wrap_with_prefix(
26681 first_line_prefix: String,
26682 subsequent_lines_prefix: String,
26683 unwrapped_text: String,
26684 wrap_column: usize,
26685 tab_size: NonZeroU32,
26686 preserve_existing_whitespace: bool,
26687) -> String {
26688 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26689 let subsequent_lines_prefix_len =
26690 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26691 let mut wrapped_text = String::new();
26692 let mut current_line = first_line_prefix;
26693 let mut is_first_line = true;
26694
26695 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26696 let mut current_line_len = first_line_prefix_len;
26697 let mut in_whitespace = false;
26698 for token in tokenizer {
26699 let have_preceding_whitespace = in_whitespace;
26700 match token {
26701 WordBreakToken::Word {
26702 token,
26703 grapheme_len,
26704 } => {
26705 in_whitespace = false;
26706 let current_prefix_len = if is_first_line {
26707 first_line_prefix_len
26708 } else {
26709 subsequent_lines_prefix_len
26710 };
26711 if current_line_len + grapheme_len > wrap_column
26712 && current_line_len != current_prefix_len
26713 {
26714 wrapped_text.push_str(current_line.trim_end());
26715 wrapped_text.push('\n');
26716 is_first_line = false;
26717 current_line = subsequent_lines_prefix.clone();
26718 current_line_len = subsequent_lines_prefix_len;
26719 }
26720 current_line.push_str(token);
26721 current_line_len += grapheme_len;
26722 }
26723 WordBreakToken::InlineWhitespace {
26724 mut token,
26725 mut grapheme_len,
26726 } => {
26727 in_whitespace = true;
26728 if have_preceding_whitespace && !preserve_existing_whitespace {
26729 continue;
26730 }
26731 if !preserve_existing_whitespace {
26732 // Keep a single whitespace grapheme as-is
26733 if let Some(first) =
26734 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26735 {
26736 token = first;
26737 } else {
26738 token = " ";
26739 }
26740 grapheme_len = 1;
26741 }
26742 let current_prefix_len = if is_first_line {
26743 first_line_prefix_len
26744 } else {
26745 subsequent_lines_prefix_len
26746 };
26747 if current_line_len + grapheme_len > wrap_column {
26748 wrapped_text.push_str(current_line.trim_end());
26749 wrapped_text.push('\n');
26750 is_first_line = false;
26751 current_line = subsequent_lines_prefix.clone();
26752 current_line_len = subsequent_lines_prefix_len;
26753 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26754 current_line.push_str(token);
26755 current_line_len += grapheme_len;
26756 }
26757 }
26758 WordBreakToken::Newline => {
26759 in_whitespace = true;
26760 let current_prefix_len = if is_first_line {
26761 first_line_prefix_len
26762 } else {
26763 subsequent_lines_prefix_len
26764 };
26765 if preserve_existing_whitespace {
26766 wrapped_text.push_str(current_line.trim_end());
26767 wrapped_text.push('\n');
26768 is_first_line = false;
26769 current_line = subsequent_lines_prefix.clone();
26770 current_line_len = subsequent_lines_prefix_len;
26771 } else if have_preceding_whitespace {
26772 continue;
26773 } else if current_line_len + 1 > wrap_column
26774 && current_line_len != current_prefix_len
26775 {
26776 wrapped_text.push_str(current_line.trim_end());
26777 wrapped_text.push('\n');
26778 is_first_line = false;
26779 current_line = subsequent_lines_prefix.clone();
26780 current_line_len = subsequent_lines_prefix_len;
26781 } else if current_line_len != current_prefix_len {
26782 current_line.push(' ');
26783 current_line_len += 1;
26784 }
26785 }
26786 }
26787 }
26788
26789 if !current_line.is_empty() {
26790 wrapped_text.push_str(¤t_line);
26791 }
26792 wrapped_text
26793}
26794
26795#[test]
26796fn test_wrap_with_prefix() {
26797 assert_eq!(
26798 wrap_with_prefix(
26799 "# ".to_string(),
26800 "# ".to_string(),
26801 "abcdefg".to_string(),
26802 4,
26803 NonZeroU32::new(4).unwrap(),
26804 false,
26805 ),
26806 "# abcdefg"
26807 );
26808 assert_eq!(
26809 wrap_with_prefix(
26810 "".to_string(),
26811 "".to_string(),
26812 "\thello world".to_string(),
26813 8,
26814 NonZeroU32::new(4).unwrap(),
26815 false,
26816 ),
26817 "hello\nworld"
26818 );
26819 assert_eq!(
26820 wrap_with_prefix(
26821 "// ".to_string(),
26822 "// ".to_string(),
26823 "xx \nyy zz aa bb cc".to_string(),
26824 12,
26825 NonZeroU32::new(4).unwrap(),
26826 false,
26827 ),
26828 "// xx yy zz\n// aa bb cc"
26829 );
26830 assert_eq!(
26831 wrap_with_prefix(
26832 String::new(),
26833 String::new(),
26834 "这是什么 \n 钢笔".to_string(),
26835 3,
26836 NonZeroU32::new(4).unwrap(),
26837 false,
26838 ),
26839 "这是什\n么 钢\n笔"
26840 );
26841 assert_eq!(
26842 wrap_with_prefix(
26843 String::new(),
26844 String::new(),
26845 format!("foo{}bar", '\u{2009}'), // thin space
26846 80,
26847 NonZeroU32::new(4).unwrap(),
26848 false,
26849 ),
26850 format!("foo{}bar", '\u{2009}')
26851 );
26852}
26853
26854pub trait CollaborationHub {
26855 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26856 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26857 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26858}
26859
26860impl CollaborationHub for Entity<Project> {
26861 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26862 self.read(cx).collaborators()
26863 }
26864
26865 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26866 self.read(cx).user_store().read(cx).participant_indices()
26867 }
26868
26869 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26870 let this = self.read(cx);
26871 let user_ids = this.collaborators().values().map(|c| c.user_id);
26872 this.user_store().read(cx).participant_names(user_ids, cx)
26873 }
26874}
26875
26876pub trait SemanticsProvider {
26877 fn hover(
26878 &self,
26879 buffer: &Entity<Buffer>,
26880 position: text::Anchor,
26881 cx: &mut App,
26882 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26883
26884 fn inline_values(
26885 &self,
26886 buffer_handle: Entity<Buffer>,
26887 range: Range<text::Anchor>,
26888 cx: &mut App,
26889 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26890
26891 fn applicable_inlay_chunks(
26892 &self,
26893 buffer: &Entity<Buffer>,
26894 ranges: &[Range<text::Anchor>],
26895 cx: &mut App,
26896 ) -> Vec<Range<BufferRow>>;
26897
26898 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26899
26900 fn inlay_hints(
26901 &self,
26902 invalidate: InvalidationStrategy,
26903 buffer: Entity<Buffer>,
26904 ranges: Vec<Range<text::Anchor>>,
26905 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26906 cx: &mut App,
26907 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26908
26909 fn semantic_tokens(
26910 &self,
26911 buffer: Entity<Buffer>,
26912 refresh: Option<RefreshForServer>,
26913 cx: &mut App,
26914 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
26915
26916 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26917
26918 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26919
26920 fn document_highlights(
26921 &self,
26922 buffer: &Entity<Buffer>,
26923 position: text::Anchor,
26924 cx: &mut App,
26925 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26926
26927 fn definitions(
26928 &self,
26929 buffer: &Entity<Buffer>,
26930 position: text::Anchor,
26931 kind: GotoDefinitionKind,
26932 cx: &mut App,
26933 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26934
26935 fn range_for_rename(
26936 &self,
26937 buffer: &Entity<Buffer>,
26938 position: text::Anchor,
26939 cx: &mut App,
26940 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26941
26942 fn perform_rename(
26943 &self,
26944 buffer: &Entity<Buffer>,
26945 position: text::Anchor,
26946 new_name: String,
26947 cx: &mut App,
26948 ) -> Option<Task<Result<ProjectTransaction>>>;
26949}
26950
26951pub trait CompletionProvider {
26952 fn completions(
26953 &self,
26954 excerpt_id: ExcerptId,
26955 buffer: &Entity<Buffer>,
26956 buffer_position: text::Anchor,
26957 trigger: CompletionContext,
26958 window: &mut Window,
26959 cx: &mut Context<Editor>,
26960 ) -> Task<Result<Vec<CompletionResponse>>>;
26961
26962 fn resolve_completions(
26963 &self,
26964 _buffer: Entity<Buffer>,
26965 _completion_indices: Vec<usize>,
26966 _completions: Rc<RefCell<Box<[Completion]>>>,
26967 _cx: &mut Context<Editor>,
26968 ) -> Task<Result<bool>> {
26969 Task::ready(Ok(false))
26970 }
26971
26972 fn apply_additional_edits_for_completion(
26973 &self,
26974 _buffer: Entity<Buffer>,
26975 _completions: Rc<RefCell<Box<[Completion]>>>,
26976 _completion_index: usize,
26977 _push_to_history: bool,
26978 _cx: &mut Context<Editor>,
26979 ) -> Task<Result<Option<language::Transaction>>> {
26980 Task::ready(Ok(None))
26981 }
26982
26983 fn is_completion_trigger(
26984 &self,
26985 buffer: &Entity<Buffer>,
26986 position: language::Anchor,
26987 text: &str,
26988 trigger_in_words: bool,
26989 cx: &mut Context<Editor>,
26990 ) -> bool;
26991
26992 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26993
26994 fn sort_completions(&self) -> bool {
26995 true
26996 }
26997
26998 fn filter_completions(&self) -> bool {
26999 true
27000 }
27001
27002 fn show_snippets(&self) -> bool {
27003 false
27004 }
27005}
27006
27007pub trait CodeActionProvider {
27008 fn id(&self) -> Arc<str>;
27009
27010 fn code_actions(
27011 &self,
27012 buffer: &Entity<Buffer>,
27013 range: Range<text::Anchor>,
27014 window: &mut Window,
27015 cx: &mut App,
27016 ) -> Task<Result<Vec<CodeAction>>>;
27017
27018 fn apply_code_action(
27019 &self,
27020 buffer_handle: Entity<Buffer>,
27021 action: CodeAction,
27022 excerpt_id: ExcerptId,
27023 push_to_history: bool,
27024 window: &mut Window,
27025 cx: &mut App,
27026 ) -> Task<Result<ProjectTransaction>>;
27027}
27028
27029impl CodeActionProvider for Entity<Project> {
27030 fn id(&self) -> Arc<str> {
27031 "project".into()
27032 }
27033
27034 fn code_actions(
27035 &self,
27036 buffer: &Entity<Buffer>,
27037 range: Range<text::Anchor>,
27038 _window: &mut Window,
27039 cx: &mut App,
27040 ) -> Task<Result<Vec<CodeAction>>> {
27041 self.update(cx, |project, cx| {
27042 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27043 let code_actions = project.code_actions(buffer, range, None, cx);
27044 cx.background_spawn(async move {
27045 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27046 Ok(code_lens_actions
27047 .context("code lens fetch")?
27048 .into_iter()
27049 .flatten()
27050 .chain(
27051 code_actions
27052 .context("code action fetch")?
27053 .into_iter()
27054 .flatten(),
27055 )
27056 .collect())
27057 })
27058 })
27059 }
27060
27061 fn apply_code_action(
27062 &self,
27063 buffer_handle: Entity<Buffer>,
27064 action: CodeAction,
27065 _excerpt_id: ExcerptId,
27066 push_to_history: bool,
27067 _window: &mut Window,
27068 cx: &mut App,
27069 ) -> Task<Result<ProjectTransaction>> {
27070 self.update(cx, |project, cx| {
27071 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27072 })
27073 }
27074}
27075
27076fn snippet_completions(
27077 project: &Project,
27078 buffer: &Entity<Buffer>,
27079 buffer_anchor: text::Anchor,
27080 classifier: CharClassifier,
27081 cx: &mut App,
27082) -> Task<Result<CompletionResponse>> {
27083 let languages = buffer.read(cx).languages_at(buffer_anchor);
27084 let snippet_store = project.snippets().read(cx);
27085
27086 let scopes: Vec<_> = languages
27087 .iter()
27088 .filter_map(|language| {
27089 let language_name = language.lsp_id();
27090 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27091
27092 if snippets.is_empty() {
27093 None
27094 } else {
27095 Some((language.default_scope(), snippets))
27096 }
27097 })
27098 .collect();
27099
27100 if scopes.is_empty() {
27101 return Task::ready(Ok(CompletionResponse {
27102 completions: vec![],
27103 display_options: CompletionDisplayOptions::default(),
27104 is_incomplete: false,
27105 }));
27106 }
27107
27108 let snapshot = buffer.read(cx).text_snapshot();
27109 let executor = cx.background_executor().clone();
27110
27111 cx.background_spawn(async move {
27112 let is_word_char = |c| classifier.is_word(c);
27113
27114 let mut is_incomplete = false;
27115 let mut completions: Vec<Completion> = Vec::new();
27116
27117 const MAX_PREFIX_LEN: usize = 128;
27118 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27119 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27120 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27121
27122 let max_buffer_window: String = snapshot
27123 .text_for_range(window_start..buffer_offset)
27124 .collect();
27125
27126 if max_buffer_window.is_empty() {
27127 return Ok(CompletionResponse {
27128 completions: vec![],
27129 display_options: CompletionDisplayOptions::default(),
27130 is_incomplete: true,
27131 });
27132 }
27133
27134 for (_scope, snippets) in scopes.into_iter() {
27135 // Sort snippets by word count to match longer snippet prefixes first.
27136 let mut sorted_snippet_candidates = snippets
27137 .iter()
27138 .enumerate()
27139 .flat_map(|(snippet_ix, snippet)| {
27140 snippet
27141 .prefix
27142 .iter()
27143 .enumerate()
27144 .map(move |(prefix_ix, prefix)| {
27145 let word_count =
27146 snippet_candidate_suffixes(prefix, &is_word_char).count();
27147 ((snippet_ix, prefix_ix), prefix, word_count)
27148 })
27149 })
27150 .collect_vec();
27151 sorted_snippet_candidates
27152 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27153
27154 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27155
27156 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27157 .take(
27158 sorted_snippet_candidates
27159 .first()
27160 .map(|(_, _, word_count)| *word_count)
27161 .unwrap_or_default(),
27162 )
27163 .collect_vec();
27164
27165 const MAX_RESULTS: usize = 100;
27166 // Each match also remembers how many characters from the buffer it consumed
27167 let mut matches: Vec<(StringMatch, usize)> = vec![];
27168
27169 let mut snippet_list_cutoff_index = 0;
27170 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27171 let word_count = buffer_index + 1;
27172 // Increase `snippet_list_cutoff_index` until we have all of the
27173 // snippets with sufficiently many words.
27174 while sorted_snippet_candidates
27175 .get(snippet_list_cutoff_index)
27176 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27177 *snippet_word_count >= word_count
27178 })
27179 {
27180 snippet_list_cutoff_index += 1;
27181 }
27182
27183 // Take only the candidates with at least `word_count` many words
27184 let snippet_candidates_at_word_len =
27185 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27186
27187 let candidates = snippet_candidates_at_word_len
27188 .iter()
27189 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27190 .enumerate() // index in `sorted_snippet_candidates`
27191 // First char must match
27192 .filter(|(_ix, prefix)| {
27193 itertools::equal(
27194 prefix
27195 .chars()
27196 .next()
27197 .into_iter()
27198 .flat_map(|c| c.to_lowercase()),
27199 buffer_window
27200 .chars()
27201 .next()
27202 .into_iter()
27203 .flat_map(|c| c.to_lowercase()),
27204 )
27205 })
27206 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27207 .collect::<Vec<StringMatchCandidate>>();
27208
27209 matches.extend(
27210 fuzzy::match_strings(
27211 &candidates,
27212 &buffer_window,
27213 buffer_window.chars().any(|c| c.is_uppercase()),
27214 true,
27215 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27216 &Default::default(),
27217 executor.clone(),
27218 )
27219 .await
27220 .into_iter()
27221 .map(|string_match| (string_match, buffer_window.len())),
27222 );
27223
27224 if matches.len() >= MAX_RESULTS {
27225 break;
27226 }
27227 }
27228
27229 let to_lsp = |point: &text::Anchor| {
27230 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27231 point_to_lsp(end)
27232 };
27233 let lsp_end = to_lsp(&buffer_anchor);
27234
27235 if matches.len() >= MAX_RESULTS {
27236 is_incomplete = true;
27237 }
27238
27239 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27240 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27241 sorted_snippet_candidates[string_match.candidate_id];
27242 let snippet = &snippets[snippet_index];
27243 let start = buffer_offset - buffer_window_len;
27244 let start = snapshot.anchor_before(start);
27245 let range = start..buffer_anchor;
27246 let lsp_start = to_lsp(&start);
27247 let lsp_range = lsp::Range {
27248 start: lsp_start,
27249 end: lsp_end,
27250 };
27251 Completion {
27252 replace_range: range,
27253 new_text: snippet.body.clone(),
27254 source: CompletionSource::Lsp {
27255 insert_range: None,
27256 server_id: LanguageServerId(usize::MAX),
27257 resolved: true,
27258 lsp_completion: Box::new(lsp::CompletionItem {
27259 label: snippet.prefix.first().unwrap().clone(),
27260 kind: Some(CompletionItemKind::SNIPPET),
27261 label_details: snippet.description.as_ref().map(|description| {
27262 lsp::CompletionItemLabelDetails {
27263 detail: Some(description.clone()),
27264 description: None,
27265 }
27266 }),
27267 insert_text_format: Some(InsertTextFormat::SNIPPET),
27268 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27269 lsp::InsertReplaceEdit {
27270 new_text: snippet.body.clone(),
27271 insert: lsp_range,
27272 replace: lsp_range,
27273 },
27274 )),
27275 filter_text: Some(snippet.body.clone()),
27276 sort_text: Some(char::MAX.to_string()),
27277 ..lsp::CompletionItem::default()
27278 }),
27279 lsp_defaults: None,
27280 },
27281 label: CodeLabel {
27282 text: matching_prefix.clone(),
27283 runs: Vec::new(),
27284 filter_range: 0..matching_prefix.len(),
27285 },
27286 icon_path: None,
27287 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27288 single_line: snippet.name.clone().into(),
27289 plain_text: snippet
27290 .description
27291 .clone()
27292 .map(|description| description.into()),
27293 }),
27294 insert_text_mode: None,
27295 confirm: None,
27296 match_start: Some(start),
27297 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27298 }
27299 }));
27300 }
27301
27302 Ok(CompletionResponse {
27303 completions,
27304 display_options: CompletionDisplayOptions::default(),
27305 is_incomplete,
27306 })
27307 })
27308}
27309
27310impl CompletionProvider for Entity<Project> {
27311 fn completions(
27312 &self,
27313 _excerpt_id: ExcerptId,
27314 buffer: &Entity<Buffer>,
27315 buffer_position: text::Anchor,
27316 options: CompletionContext,
27317 _window: &mut Window,
27318 cx: &mut Context<Editor>,
27319 ) -> Task<Result<Vec<CompletionResponse>>> {
27320 self.update(cx, |project, cx| {
27321 let task = project.completions(buffer, buffer_position, options, cx);
27322 cx.background_spawn(task)
27323 })
27324 }
27325
27326 fn resolve_completions(
27327 &self,
27328 buffer: Entity<Buffer>,
27329 completion_indices: Vec<usize>,
27330 completions: Rc<RefCell<Box<[Completion]>>>,
27331 cx: &mut Context<Editor>,
27332 ) -> Task<Result<bool>> {
27333 self.update(cx, |project, cx| {
27334 project.lsp_store().update(cx, |lsp_store, cx| {
27335 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27336 })
27337 })
27338 }
27339
27340 fn apply_additional_edits_for_completion(
27341 &self,
27342 buffer: Entity<Buffer>,
27343 completions: Rc<RefCell<Box<[Completion]>>>,
27344 completion_index: usize,
27345 push_to_history: bool,
27346 cx: &mut Context<Editor>,
27347 ) -> Task<Result<Option<language::Transaction>>> {
27348 self.update(cx, |project, cx| {
27349 project.lsp_store().update(cx, |lsp_store, cx| {
27350 lsp_store.apply_additional_edits_for_completion(
27351 buffer,
27352 completions,
27353 completion_index,
27354 push_to_history,
27355 cx,
27356 )
27357 })
27358 })
27359 }
27360
27361 fn is_completion_trigger(
27362 &self,
27363 buffer: &Entity<Buffer>,
27364 position: language::Anchor,
27365 text: &str,
27366 trigger_in_words: bool,
27367 cx: &mut Context<Editor>,
27368 ) -> bool {
27369 let mut chars = text.chars();
27370 let char = if let Some(char) = chars.next() {
27371 char
27372 } else {
27373 return false;
27374 };
27375 if chars.next().is_some() {
27376 return false;
27377 }
27378
27379 let buffer = buffer.read(cx);
27380 let snapshot = buffer.snapshot();
27381 let classifier = snapshot
27382 .char_classifier_at(position)
27383 .scope_context(Some(CharScopeContext::Completion));
27384 if trigger_in_words && classifier.is_word(char) {
27385 return true;
27386 }
27387
27388 buffer.completion_triggers().contains(text)
27389 }
27390
27391 fn show_snippets(&self) -> bool {
27392 true
27393 }
27394}
27395
27396impl SemanticsProvider for WeakEntity<Project> {
27397 fn hover(
27398 &self,
27399 buffer: &Entity<Buffer>,
27400 position: text::Anchor,
27401 cx: &mut App,
27402 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27403 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27404 .ok()
27405 }
27406
27407 fn document_highlights(
27408 &self,
27409 buffer: &Entity<Buffer>,
27410 position: text::Anchor,
27411 cx: &mut App,
27412 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27413 self.update(cx, |project, cx| {
27414 project.document_highlights(buffer, position, cx)
27415 })
27416 .ok()
27417 }
27418
27419 fn definitions(
27420 &self,
27421 buffer: &Entity<Buffer>,
27422 position: text::Anchor,
27423 kind: GotoDefinitionKind,
27424 cx: &mut App,
27425 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27426 self.update(cx, |project, cx| match kind {
27427 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27428 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27429 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27430 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27431 })
27432 .ok()
27433 }
27434
27435 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27436 self.update(cx, |project, cx| {
27437 if project
27438 .active_debug_session(cx)
27439 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27440 {
27441 return true;
27442 }
27443
27444 buffer.update(cx, |buffer, cx| {
27445 project.any_language_server_supports_inlay_hints(buffer, cx)
27446 })
27447 })
27448 .unwrap_or(false)
27449 }
27450
27451 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27452 self.update(cx, |project, cx| {
27453 buffer.update(cx, |buffer, cx| {
27454 project.any_language_server_supports_semantic_tokens(buffer, cx)
27455 })
27456 })
27457 .unwrap_or(false)
27458 }
27459
27460 fn inline_values(
27461 &self,
27462 buffer_handle: Entity<Buffer>,
27463 range: Range<text::Anchor>,
27464 cx: &mut App,
27465 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27466 self.update(cx, |project, cx| {
27467 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27468
27469 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27470 })
27471 .ok()
27472 .flatten()
27473 }
27474
27475 fn applicable_inlay_chunks(
27476 &self,
27477 buffer: &Entity<Buffer>,
27478 ranges: &[Range<text::Anchor>],
27479 cx: &mut App,
27480 ) -> Vec<Range<BufferRow>> {
27481 self.update(cx, |project, cx| {
27482 project.lsp_store().update(cx, |lsp_store, cx| {
27483 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27484 })
27485 })
27486 .unwrap_or_default()
27487 }
27488
27489 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27490 self.update(cx, |project, cx| {
27491 project.lsp_store().update(cx, |lsp_store, _| {
27492 lsp_store.invalidate_inlay_hints(for_buffers)
27493 })
27494 })
27495 .ok();
27496 }
27497
27498 fn inlay_hints(
27499 &self,
27500 invalidate: InvalidationStrategy,
27501 buffer: Entity<Buffer>,
27502 ranges: Vec<Range<text::Anchor>>,
27503 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27504 cx: &mut App,
27505 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27506 self.update(cx, |project, cx| {
27507 project.lsp_store().update(cx, |lsp_store, cx| {
27508 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27509 })
27510 })
27511 .ok()
27512 }
27513
27514 fn semantic_tokens(
27515 &self,
27516 buffer: Entity<Buffer>,
27517 refresh: Option<RefreshForServer>,
27518 cx: &mut App,
27519 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27520 self.update(cx, |this, cx| {
27521 this.lsp_store().update(cx, |lsp_store, cx| {
27522 lsp_store.semantic_tokens(buffer, refresh, cx)
27523 })
27524 })
27525 .ok()
27526 }
27527
27528 fn range_for_rename(
27529 &self,
27530 buffer: &Entity<Buffer>,
27531 position: text::Anchor,
27532 cx: &mut App,
27533 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27534 self.update(cx, |project, cx| {
27535 let buffer = buffer.clone();
27536 let task = project.prepare_rename(buffer.clone(), position, cx);
27537 cx.spawn(async move |_, cx| {
27538 Ok(match task.await? {
27539 PrepareRenameResponse::Success(range) => Some(range),
27540 PrepareRenameResponse::InvalidPosition => None,
27541 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27542 // Fallback on using TreeSitter info to determine identifier range
27543 buffer.read_with(cx, |buffer, _| {
27544 let snapshot = buffer.snapshot();
27545 let (range, kind) = snapshot.surrounding_word(position, None);
27546 if kind != Some(CharKind::Word) {
27547 return None;
27548 }
27549 Some(
27550 snapshot.anchor_before(range.start)
27551 ..snapshot.anchor_after(range.end),
27552 )
27553 })
27554 }
27555 })
27556 })
27557 })
27558 .ok()
27559 }
27560
27561 fn perform_rename(
27562 &self,
27563 buffer: &Entity<Buffer>,
27564 position: text::Anchor,
27565 new_name: String,
27566 cx: &mut App,
27567 ) -> Option<Task<Result<ProjectTransaction>>> {
27568 self.update(cx, |project, cx| {
27569 project.perform_rename(buffer.clone(), position, new_name, cx)
27570 })
27571 .ok()
27572 }
27573}
27574
27575fn consume_contiguous_rows(
27576 contiguous_row_selections: &mut Vec<Selection<Point>>,
27577 selection: &Selection<Point>,
27578 display_map: &DisplaySnapshot,
27579 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27580) -> (MultiBufferRow, MultiBufferRow) {
27581 contiguous_row_selections.push(selection.clone());
27582 let start_row = starting_row(selection, display_map);
27583 let mut end_row = ending_row(selection, display_map);
27584
27585 while let Some(next_selection) = selections.peek() {
27586 if next_selection.start.row <= end_row.0 {
27587 end_row = ending_row(next_selection, display_map);
27588 contiguous_row_selections.push(selections.next().unwrap().clone());
27589 } else {
27590 break;
27591 }
27592 }
27593 (start_row, end_row)
27594}
27595
27596fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27597 if selection.start.column > 0 {
27598 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27599 } else {
27600 MultiBufferRow(selection.start.row)
27601 }
27602}
27603
27604fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27605 if next_selection.end.column > 0 || next_selection.is_empty() {
27606 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27607 } else {
27608 MultiBufferRow(next_selection.end.row)
27609 }
27610}
27611
27612impl EditorSnapshot {
27613 pub fn remote_selections_in_range<'a>(
27614 &'a self,
27615 range: &'a Range<Anchor>,
27616 collaboration_hub: &dyn CollaborationHub,
27617 cx: &'a App,
27618 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27619 let participant_names = collaboration_hub.user_names(cx);
27620 let participant_indices = collaboration_hub.user_participant_indices(cx);
27621 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27622 let collaborators_by_replica_id = collaborators_by_peer_id
27623 .values()
27624 .map(|collaborator| (collaborator.replica_id, collaborator))
27625 .collect::<HashMap<_, _>>();
27626 self.buffer_snapshot()
27627 .selections_in_range(range, false)
27628 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27629 if replica_id == ReplicaId::AGENT {
27630 Some(RemoteSelection {
27631 replica_id,
27632 selection,
27633 cursor_shape,
27634 line_mode,
27635 collaborator_id: CollaboratorId::Agent,
27636 user_name: Some("Agent".into()),
27637 color: cx.theme().players().agent(),
27638 })
27639 } else {
27640 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27641 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27642 let user_name = participant_names.get(&collaborator.user_id).cloned();
27643 Some(RemoteSelection {
27644 replica_id,
27645 selection,
27646 cursor_shape,
27647 line_mode,
27648 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27649 user_name,
27650 color: if let Some(index) = participant_index {
27651 cx.theme().players().color_for_participant(index.0)
27652 } else {
27653 cx.theme().players().absent()
27654 },
27655 })
27656 }
27657 })
27658 }
27659
27660 pub fn hunks_for_ranges(
27661 &self,
27662 ranges: impl IntoIterator<Item = Range<Point>>,
27663 ) -> Vec<MultiBufferDiffHunk> {
27664 let mut hunks = Vec::new();
27665 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27666 HashMap::default();
27667 for query_range in ranges {
27668 let query_rows =
27669 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27670 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27671 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27672 ) {
27673 // Include deleted hunks that are adjacent to the query range, because
27674 // otherwise they would be missed.
27675 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27676 if hunk.status().is_deleted() {
27677 intersects_range |= hunk.row_range.start == query_rows.end;
27678 intersects_range |= hunk.row_range.end == query_rows.start;
27679 }
27680 if intersects_range {
27681 if !processed_buffer_rows
27682 .entry(hunk.buffer_id)
27683 .or_default()
27684 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27685 {
27686 continue;
27687 }
27688 hunks.push(hunk);
27689 }
27690 }
27691 }
27692
27693 hunks
27694 }
27695
27696 fn display_diff_hunks_for_rows<'a>(
27697 &'a self,
27698 display_rows: Range<DisplayRow>,
27699 folded_buffers: &'a HashSet<BufferId>,
27700 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27701 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27702 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27703
27704 self.buffer_snapshot()
27705 .diff_hunks_in_range(buffer_start..buffer_end)
27706 .filter_map(|hunk| {
27707 if folded_buffers.contains(&hunk.buffer_id)
27708 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27709 {
27710 return None;
27711 }
27712
27713 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27714 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27715 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27716 let line_len = self.buffer_snapshot().line_len(last_row);
27717 Point::new(last_row.0, line_len)
27718 } else {
27719 Point::new(hunk.row_range.end.0, 0)
27720 };
27721
27722 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27723 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27724
27725 let display_hunk = if hunk_display_start.column() != 0 {
27726 DisplayDiffHunk::Folded {
27727 display_row: hunk_display_start.row(),
27728 }
27729 } else {
27730 let mut end_row = hunk_display_end.row();
27731 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27732 end_row.0 += 1;
27733 }
27734 let is_created_file = hunk.is_created_file();
27735
27736 DisplayDiffHunk::Unfolded {
27737 status: hunk.status(),
27738 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27739 ..hunk.diff_base_byte_range.end.0,
27740 word_diffs: hunk.word_diffs,
27741 display_row_range: hunk_display_start.row()..end_row,
27742 multi_buffer_range: Anchor::range_in_buffer(
27743 hunk.excerpt_id,
27744 hunk.buffer_range,
27745 ),
27746 is_created_file,
27747 }
27748 };
27749
27750 Some(display_hunk)
27751 })
27752 }
27753
27754 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27755 self.display_snapshot
27756 .buffer_snapshot()
27757 .language_at(position)
27758 }
27759
27760 pub fn is_focused(&self) -> bool {
27761 self.is_focused
27762 }
27763
27764 pub fn placeholder_text(&self) -> Option<String> {
27765 self.placeholder_display_snapshot
27766 .as_ref()
27767 .map(|display_map| display_map.text())
27768 }
27769
27770 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27771 self.scroll_anchor.scroll_position(&self.display_snapshot)
27772 }
27773
27774 pub fn gutter_dimensions(
27775 &self,
27776 font_id: FontId,
27777 font_size: Pixels,
27778 style: &EditorStyle,
27779 window: &mut Window,
27780 cx: &App,
27781 ) -> GutterDimensions {
27782 if self.show_gutter
27783 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27784 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27785 {
27786 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27787 matches!(
27788 ProjectSettings::get_global(cx).git.git_gutter,
27789 GitGutterSetting::TrackedFiles
27790 )
27791 });
27792 let gutter_settings = EditorSettings::get_global(cx).gutter;
27793 let show_line_numbers = self
27794 .show_line_numbers
27795 .unwrap_or(gutter_settings.line_numbers);
27796 let line_gutter_width = if show_line_numbers {
27797 // Avoid flicker-like gutter resizes when the line number gains another digit by
27798 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27799 let min_width_for_number_on_gutter =
27800 ch_advance * gutter_settings.min_line_number_digits as f32;
27801 self.max_line_number_width(style, window)
27802 .max(min_width_for_number_on_gutter)
27803 } else {
27804 0.0.into()
27805 };
27806
27807 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27808 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27809
27810 let git_blame_entries_width =
27811 self.git_blame_gutter_max_author_length
27812 .map(|max_author_length| {
27813 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27814 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27815
27816 /// The number of characters to dedicate to gaps and margins.
27817 const SPACING_WIDTH: usize = 4;
27818
27819 let max_char_count = max_author_length.min(renderer.max_author_length())
27820 + ::git::SHORT_SHA_LENGTH
27821 + MAX_RELATIVE_TIMESTAMP.len()
27822 + SPACING_WIDTH;
27823
27824 ch_advance * max_char_count
27825 });
27826
27827 let is_singleton = self.buffer_snapshot().is_singleton();
27828
27829 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27830 left_padding += if !is_singleton {
27831 ch_width * 4.0
27832 } else if show_runnables || show_breakpoints {
27833 ch_width * 3.0
27834 } else if show_git_gutter && show_line_numbers {
27835 ch_width * 2.0
27836 } else if show_git_gutter || show_line_numbers {
27837 ch_width
27838 } else {
27839 px(0.)
27840 };
27841
27842 let shows_folds = is_singleton && gutter_settings.folds;
27843
27844 let right_padding = if shows_folds && show_line_numbers {
27845 ch_width * 4.0
27846 } else if shows_folds || (!is_singleton && show_line_numbers) {
27847 ch_width * 3.0
27848 } else if show_line_numbers {
27849 ch_width
27850 } else {
27851 px(0.)
27852 };
27853
27854 GutterDimensions {
27855 left_padding,
27856 right_padding,
27857 width: line_gutter_width + left_padding + right_padding,
27858 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27859 git_blame_entries_width,
27860 }
27861 } else if self.offset_content {
27862 GutterDimensions::default_with_margin(font_id, font_size, cx)
27863 } else {
27864 GutterDimensions::default()
27865 }
27866 }
27867
27868 pub fn render_crease_toggle(
27869 &self,
27870 buffer_row: MultiBufferRow,
27871 row_contains_cursor: bool,
27872 editor: Entity<Editor>,
27873 window: &mut Window,
27874 cx: &mut App,
27875 ) -> Option<AnyElement> {
27876 let folded = self.is_line_folded(buffer_row);
27877 let mut is_foldable = false;
27878
27879 if let Some(crease) = self
27880 .crease_snapshot
27881 .query_row(buffer_row, self.buffer_snapshot())
27882 {
27883 is_foldable = true;
27884 match crease {
27885 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27886 if let Some(render_toggle) = render_toggle {
27887 let toggle_callback =
27888 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27889 if folded {
27890 editor.update(cx, |editor, cx| {
27891 editor.fold_at(buffer_row, window, cx)
27892 });
27893 } else {
27894 editor.update(cx, |editor, cx| {
27895 editor.unfold_at(buffer_row, window, cx)
27896 });
27897 }
27898 });
27899 return Some((render_toggle)(
27900 buffer_row,
27901 folded,
27902 toggle_callback,
27903 window,
27904 cx,
27905 ));
27906 }
27907 }
27908 }
27909 }
27910
27911 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27912
27913 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27914 Some(
27915 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27916 .toggle_state(folded)
27917 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27918 if folded {
27919 this.unfold_at(buffer_row, window, cx);
27920 } else {
27921 this.fold_at(buffer_row, window, cx);
27922 }
27923 }))
27924 .into_any_element(),
27925 )
27926 } else {
27927 None
27928 }
27929 }
27930
27931 pub fn render_crease_trailer(
27932 &self,
27933 buffer_row: MultiBufferRow,
27934 window: &mut Window,
27935 cx: &mut App,
27936 ) -> Option<AnyElement> {
27937 let folded = self.is_line_folded(buffer_row);
27938 if let Crease::Inline { render_trailer, .. } = self
27939 .crease_snapshot
27940 .query_row(buffer_row, self.buffer_snapshot())?
27941 {
27942 let render_trailer = render_trailer.as_ref()?;
27943 Some(render_trailer(buffer_row, folded, window, cx))
27944 } else {
27945 None
27946 }
27947 }
27948
27949 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27950 let digit_count = self.widest_line_number().ilog10() + 1;
27951 column_pixels(style, digit_count as usize, window)
27952 }
27953
27954 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27955 ///
27956 /// This is positive if `base` is before `line`.
27957 fn relative_line_delta(
27958 &self,
27959 current_selection_head: DisplayRow,
27960 first_visible_row: DisplayRow,
27961 consider_wrapped_lines: bool,
27962 ) -> i64 {
27963 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27964 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27965
27966 if consider_wrapped_lines {
27967 let wrap_snapshot = self.wrap_snapshot();
27968 let base_wrap_row = wrap_snapshot
27969 .make_wrap_point(current_selection_head, Bias::Left)
27970 .row();
27971 let wrap_row = wrap_snapshot
27972 .make_wrap_point(first_visible_row, Bias::Left)
27973 .row();
27974
27975 wrap_row.0 as i64 - base_wrap_row.0 as i64
27976 } else {
27977 let fold_snapshot = self.fold_snapshot();
27978 let base_fold_row = fold_snapshot
27979 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27980 .row();
27981 let fold_row = fold_snapshot
27982 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27983 .row();
27984
27985 fold_row as i64 - base_fold_row as i64
27986 }
27987 }
27988
27989 /// Returns the unsigned relative line number to display for each row in `rows`.
27990 ///
27991 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27992 pub fn calculate_relative_line_numbers(
27993 &self,
27994 rows: &Range<DisplayRow>,
27995 current_selection_head: DisplayRow,
27996 count_wrapped_lines: bool,
27997 ) -> HashMap<DisplayRow, u32> {
27998 let initial_offset =
27999 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28000
28001 self.row_infos(rows.start)
28002 .take(rows.len())
28003 .enumerate()
28004 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28005 .filter(|(_row, row_info)| {
28006 row_info.buffer_row.is_some()
28007 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28008 })
28009 .enumerate()
28010 .filter_map(|(i, (row, row_info))| {
28011 // We want to ensure here that the current line has absolute
28012 // numbering, even if we are in a soft-wrapped line. With the
28013 // exception that if we are in a deleted line, we should number this
28014 // relative with 0, as otherwise it would have no line number at all
28015 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28016
28017 (relative_line_number != 0
28018 || row_info
28019 .diff_status
28020 .is_some_and(|status| status.is_deleted()))
28021 .then_some((row, relative_line_number))
28022 })
28023 .collect()
28024 }
28025}
28026
28027pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28028 let font_size = style.text.font_size.to_pixels(window.rem_size());
28029 let layout = window.text_system().shape_line(
28030 SharedString::from(" ".repeat(column)),
28031 font_size,
28032 &[TextRun {
28033 len: column,
28034 font: style.text.font(),
28035 color: Hsla::default(),
28036 ..Default::default()
28037 }],
28038 None,
28039 );
28040
28041 layout.width
28042}
28043
28044impl Deref for EditorSnapshot {
28045 type Target = DisplaySnapshot;
28046
28047 fn deref(&self) -> &Self::Target {
28048 &self.display_snapshot
28049 }
28050}
28051
28052#[derive(Clone, Debug, PartialEq, Eq)]
28053pub enum EditorEvent {
28054 /// Emitted when the stored review comments change (added, removed, or updated).
28055 ReviewCommentsChanged {
28056 /// The new total count of review comments.
28057 total_count: usize,
28058 },
28059 InputIgnored {
28060 text: Arc<str>,
28061 },
28062 InputHandled {
28063 utf16_range_to_replace: Option<Range<isize>>,
28064 text: Arc<str>,
28065 },
28066 ExcerptsAdded {
28067 buffer: Entity<Buffer>,
28068 predecessor: ExcerptId,
28069 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
28070 },
28071 ExcerptsRemoved {
28072 ids: Vec<ExcerptId>,
28073 removed_buffer_ids: Vec<BufferId>,
28074 },
28075 BufferFoldToggled {
28076 ids: Vec<ExcerptId>,
28077 folded: bool,
28078 },
28079 ExcerptsEdited {
28080 ids: Vec<ExcerptId>,
28081 },
28082 ExcerptsExpanded {
28083 ids: Vec<ExcerptId>,
28084 },
28085 ExpandExcerptsRequested {
28086 excerpt_ids: Vec<ExcerptId>,
28087 lines: u32,
28088 direction: ExpandExcerptDirection,
28089 },
28090 StageOrUnstageRequested {
28091 stage: bool,
28092 hunks: Vec<MultiBufferDiffHunk>,
28093 },
28094 OpenExcerptsRequested {
28095 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28096 split: bool,
28097 },
28098 RestoreRequested {
28099 hunks: Vec<MultiBufferDiffHunk>,
28100 },
28101 BufferEdited,
28102 Edited {
28103 transaction_id: clock::Lamport,
28104 },
28105 Reparsed(BufferId),
28106 Focused,
28107 FocusedIn,
28108 Blurred,
28109 DirtyChanged,
28110 Saved,
28111 TitleChanged,
28112 SelectionsChanged {
28113 local: bool,
28114 },
28115 ScrollPositionChanged {
28116 local: bool,
28117 autoscroll: bool,
28118 },
28119 TransactionUndone {
28120 transaction_id: clock::Lamport,
28121 },
28122 TransactionBegun {
28123 transaction_id: clock::Lamport,
28124 },
28125 CursorShapeChanged,
28126 BreadcrumbsChanged,
28127 OutlineSymbolsChanged,
28128 PushedToNavHistory {
28129 anchor: Anchor,
28130 is_deactivate: bool,
28131 },
28132}
28133
28134impl EventEmitter<EditorEvent> for Editor {}
28135
28136impl Focusable for Editor {
28137 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28138 self.focus_handle.clone()
28139 }
28140}
28141
28142impl Render for Editor {
28143 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28144 EditorElement::new(&cx.entity(), self.create_style(cx))
28145 }
28146}
28147
28148impl EntityInputHandler for Editor {
28149 fn text_for_range(
28150 &mut self,
28151 range_utf16: Range<usize>,
28152 adjusted_range: &mut Option<Range<usize>>,
28153 _: &mut Window,
28154 cx: &mut Context<Self>,
28155 ) -> Option<String> {
28156 let snapshot = self.buffer.read(cx).read(cx);
28157 let start = snapshot.clip_offset_utf16(
28158 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28159 Bias::Left,
28160 );
28161 let end = snapshot.clip_offset_utf16(
28162 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28163 Bias::Right,
28164 );
28165 if (start.0.0..end.0.0) != range_utf16 {
28166 adjusted_range.replace(start.0.0..end.0.0);
28167 }
28168 Some(snapshot.text_for_range(start..end).collect())
28169 }
28170
28171 fn selected_text_range(
28172 &mut self,
28173 ignore_disabled_input: bool,
28174 _: &mut Window,
28175 cx: &mut Context<Self>,
28176 ) -> Option<UTF16Selection> {
28177 // Prevent the IME menu from appearing when holding down an alphabetic key
28178 // while input is disabled.
28179 if !ignore_disabled_input && !self.input_enabled {
28180 return None;
28181 }
28182
28183 let selection = self
28184 .selections
28185 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28186 let range = selection.range();
28187
28188 Some(UTF16Selection {
28189 range: range.start.0.0..range.end.0.0,
28190 reversed: selection.reversed,
28191 })
28192 }
28193
28194 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28195 let snapshot = self.buffer.read(cx).read(cx);
28196 let range = self
28197 .text_highlights(HighlightKey::InputComposition, cx)?
28198 .1
28199 .first()?;
28200 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28201 }
28202
28203 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28204 self.clear_highlights(HighlightKey::InputComposition, cx);
28205 self.ime_transaction.take();
28206 }
28207
28208 fn replace_text_in_range(
28209 &mut self,
28210 range_utf16: Option<Range<usize>>,
28211 text: &str,
28212 window: &mut Window,
28213 cx: &mut Context<Self>,
28214 ) {
28215 if !self.input_enabled {
28216 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28217 return;
28218 }
28219
28220 self.transact(window, cx, |this, window, cx| {
28221 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28222 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28223 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28224 Some(this.selection_replacement_ranges(range_utf16, cx))
28225 } else {
28226 this.marked_text_ranges(cx)
28227 };
28228
28229 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28230 let newest_selection_id = this.selections.newest_anchor().id;
28231 this.selections
28232 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28233 .iter()
28234 .zip(ranges_to_replace.iter())
28235 .find_map(|(selection, range)| {
28236 if selection.id == newest_selection_id {
28237 Some(
28238 (range.start.0.0 as isize - selection.head().0.0 as isize)
28239 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28240 )
28241 } else {
28242 None
28243 }
28244 })
28245 });
28246
28247 cx.emit(EditorEvent::InputHandled {
28248 utf16_range_to_replace: range_to_replace,
28249 text: text.into(),
28250 });
28251
28252 if let Some(new_selected_ranges) = new_selected_ranges {
28253 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28254 selections.select_ranges(new_selected_ranges)
28255 });
28256 this.backspace(&Default::default(), window, cx);
28257 }
28258
28259 this.handle_input(text, window, cx);
28260 });
28261
28262 if let Some(transaction) = self.ime_transaction {
28263 self.buffer.update(cx, |buffer, cx| {
28264 buffer.group_until_transaction(transaction, cx);
28265 });
28266 }
28267
28268 self.unmark_text(window, cx);
28269 }
28270
28271 fn replace_and_mark_text_in_range(
28272 &mut self,
28273 range_utf16: Option<Range<usize>>,
28274 text: &str,
28275 new_selected_range_utf16: Option<Range<usize>>,
28276 window: &mut Window,
28277 cx: &mut Context<Self>,
28278 ) {
28279 if !self.input_enabled {
28280 return;
28281 }
28282
28283 let transaction = self.transact(window, cx, |this, window, cx| {
28284 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28285 let snapshot = this.buffer.read(cx).read(cx);
28286 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28287 for marked_range in &mut marked_ranges {
28288 marked_range.end = marked_range.start + relative_range_utf16.end;
28289 marked_range.start += relative_range_utf16.start;
28290 marked_range.start =
28291 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28292 marked_range.end =
28293 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28294 }
28295 }
28296 Some(marked_ranges)
28297 } else if let Some(range_utf16) = range_utf16 {
28298 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28299 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28300 Some(this.selection_replacement_ranges(range_utf16, cx))
28301 } else {
28302 None
28303 };
28304
28305 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28306 let newest_selection_id = this.selections.newest_anchor().id;
28307 this.selections
28308 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28309 .iter()
28310 .zip(ranges_to_replace.iter())
28311 .find_map(|(selection, range)| {
28312 if selection.id == newest_selection_id {
28313 Some(
28314 (range.start.0.0 as isize - selection.head().0.0 as isize)
28315 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28316 )
28317 } else {
28318 None
28319 }
28320 })
28321 });
28322
28323 cx.emit(EditorEvent::InputHandled {
28324 utf16_range_to_replace: range_to_replace,
28325 text: text.into(),
28326 });
28327
28328 if let Some(ranges) = ranges_to_replace {
28329 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28330 s.select_ranges(ranges)
28331 });
28332 }
28333
28334 let marked_ranges = {
28335 let snapshot = this.buffer.read(cx).read(cx);
28336 this.selections
28337 .disjoint_anchors_arc()
28338 .iter()
28339 .map(|selection| {
28340 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28341 })
28342 .collect::<Vec<_>>()
28343 };
28344
28345 if text.is_empty() {
28346 this.unmark_text(window, cx);
28347 } else {
28348 this.highlight_text(
28349 HighlightKey::InputComposition,
28350 marked_ranges.clone(),
28351 HighlightStyle {
28352 underline: Some(UnderlineStyle {
28353 thickness: px(1.),
28354 color: None,
28355 wavy: false,
28356 }),
28357 ..Default::default()
28358 },
28359 cx,
28360 );
28361 }
28362
28363 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28364 let use_autoclose = this.use_autoclose;
28365 let use_auto_surround = this.use_auto_surround;
28366 this.set_use_autoclose(false);
28367 this.set_use_auto_surround(false);
28368 this.handle_input(text, window, cx);
28369 this.set_use_autoclose(use_autoclose);
28370 this.set_use_auto_surround(use_auto_surround);
28371
28372 if let Some(new_selected_range) = new_selected_range_utf16 {
28373 let snapshot = this.buffer.read(cx).read(cx);
28374 let new_selected_ranges = marked_ranges
28375 .into_iter()
28376 .map(|marked_range| {
28377 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28378 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28379 insertion_start.0 + new_selected_range.start,
28380 ));
28381 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28382 insertion_start.0 + new_selected_range.end,
28383 ));
28384 snapshot.clip_offset_utf16(new_start, Bias::Left)
28385 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28386 })
28387 .collect::<Vec<_>>();
28388
28389 drop(snapshot);
28390 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28391 selections.select_ranges(new_selected_ranges)
28392 });
28393 }
28394 });
28395
28396 self.ime_transaction = self.ime_transaction.or(transaction);
28397 if let Some(transaction) = self.ime_transaction {
28398 self.buffer.update(cx, |buffer, cx| {
28399 buffer.group_until_transaction(transaction, cx);
28400 });
28401 }
28402
28403 if self
28404 .text_highlights(HighlightKey::InputComposition, cx)
28405 .is_none()
28406 {
28407 self.ime_transaction.take();
28408 }
28409 }
28410
28411 fn bounds_for_range(
28412 &mut self,
28413 range_utf16: Range<usize>,
28414 element_bounds: gpui::Bounds<Pixels>,
28415 window: &mut Window,
28416 cx: &mut Context<Self>,
28417 ) -> Option<gpui::Bounds<Pixels>> {
28418 let text_layout_details = self.text_layout_details(window, cx);
28419 let CharacterDimensions {
28420 em_width,
28421 em_advance,
28422 line_height,
28423 } = self.character_dimensions(window, cx);
28424
28425 let snapshot = self.snapshot(window, cx);
28426 let scroll_position = snapshot.scroll_position();
28427 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28428
28429 let start =
28430 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28431 let x = Pixels::from(
28432 ScrollOffset::from(
28433 snapshot.x_for_display_point(start, &text_layout_details)
28434 + self.gutter_dimensions.full_width(),
28435 ) - scroll_left,
28436 );
28437 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28438
28439 Some(Bounds {
28440 origin: element_bounds.origin + point(x, y),
28441 size: size(em_width, line_height),
28442 })
28443 }
28444
28445 fn character_index_for_point(
28446 &mut self,
28447 point: gpui::Point<Pixels>,
28448 _window: &mut Window,
28449 _cx: &mut Context<Self>,
28450 ) -> Option<usize> {
28451 let position_map = self.last_position_map.as_ref()?;
28452 if !position_map.text_hitbox.contains(&point) {
28453 return None;
28454 }
28455 let display_point = position_map.point_for_position(point).previous_valid;
28456 let anchor = position_map
28457 .snapshot
28458 .display_point_to_anchor(display_point, Bias::Left);
28459 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28460 Some(utf16_offset.0.0)
28461 }
28462
28463 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28464 self.expects_character_input
28465 }
28466}
28467
28468trait SelectionExt {
28469 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28470 fn spanned_rows(
28471 &self,
28472 include_end_if_at_line_start: bool,
28473 map: &DisplaySnapshot,
28474 ) -> Range<MultiBufferRow>;
28475}
28476
28477impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28478 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28479 let start = self
28480 .start
28481 .to_point(map.buffer_snapshot())
28482 .to_display_point(map);
28483 let end = self
28484 .end
28485 .to_point(map.buffer_snapshot())
28486 .to_display_point(map);
28487 if self.reversed {
28488 end..start
28489 } else {
28490 start..end
28491 }
28492 }
28493
28494 fn spanned_rows(
28495 &self,
28496 include_end_if_at_line_start: bool,
28497 map: &DisplaySnapshot,
28498 ) -> Range<MultiBufferRow> {
28499 let start = self.start.to_point(map.buffer_snapshot());
28500 let mut end = self.end.to_point(map.buffer_snapshot());
28501 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28502 end.row -= 1;
28503 }
28504
28505 let buffer_start = map.prev_line_boundary(start).0;
28506 let buffer_end = map.next_line_boundary(end).0;
28507 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28508 }
28509}
28510
28511impl<T: InvalidationRegion> InvalidationStack<T> {
28512 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28513 where
28514 S: Clone + ToOffset,
28515 {
28516 while let Some(region) = self.last() {
28517 let all_selections_inside_invalidation_ranges =
28518 if selections.len() == region.ranges().len() {
28519 selections
28520 .iter()
28521 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28522 .all(|(selection, invalidation_range)| {
28523 let head = selection.head().to_offset(buffer);
28524 invalidation_range.start <= head && invalidation_range.end >= head
28525 })
28526 } else {
28527 false
28528 };
28529
28530 if all_selections_inside_invalidation_ranges {
28531 break;
28532 } else {
28533 self.pop();
28534 }
28535 }
28536 }
28537}
28538
28539#[derive(Clone)]
28540struct ErasedEditorImpl(Entity<Editor>);
28541
28542impl ui_input::ErasedEditor for ErasedEditorImpl {
28543 fn text(&self, cx: &App) -> String {
28544 self.0.read(cx).text(cx)
28545 }
28546
28547 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28548 self.0.update(cx, |this, cx| {
28549 this.set_text(text, window, cx);
28550 })
28551 }
28552
28553 fn clear(&self, window: &mut Window, cx: &mut App) {
28554 self.0.update(cx, |this, cx| this.clear(window, cx));
28555 }
28556
28557 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28558 self.0.update(cx, |this, cx| {
28559 this.set_placeholder_text(text, window, cx);
28560 });
28561 }
28562
28563 fn focus_handle(&self, cx: &App) -> FocusHandle {
28564 self.0.read(cx).focus_handle(cx)
28565 }
28566
28567 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28568 let settings = ThemeSettings::get_global(cx);
28569 let theme_color = cx.theme().colors();
28570
28571 let text_style = TextStyle {
28572 font_family: settings.ui_font.family.clone(),
28573 font_features: settings.ui_font.features.clone(),
28574 font_size: rems(0.875).into(),
28575 font_weight: settings.ui_font.weight,
28576 font_style: FontStyle::Normal,
28577 line_height: relative(1.2),
28578 color: theme_color.text,
28579 ..Default::default()
28580 };
28581 let editor_style = EditorStyle {
28582 background: theme_color.ghost_element_background,
28583 local_player: cx.theme().players().local(),
28584 syntax: cx.theme().syntax().clone(),
28585 text: text_style,
28586 ..Default::default()
28587 };
28588 EditorElement::new(&self.0, editor_style).into_any()
28589 }
28590
28591 fn as_any(&self) -> &dyn Any {
28592 &self.0
28593 }
28594
28595 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28596 self.0.update(cx, |editor, cx| {
28597 let editor_offset = editor.buffer().read(cx).len(cx);
28598 editor.change_selections(
28599 SelectionEffects::scroll(Autoscroll::Next),
28600 window,
28601 cx,
28602 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28603 );
28604 });
28605 }
28606
28607 fn subscribe(
28608 &self,
28609 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28610 window: &mut Window,
28611 cx: &mut App,
28612 ) -> Subscription {
28613 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28614 let event = match event {
28615 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28616 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28617 _ => return,
28618 };
28619 (callback)(event, window, cx);
28620 })
28621 }
28622
28623 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28624 self.0.update(cx, |editor, cx| {
28625 editor.set_masked(masked, cx);
28626 });
28627 }
28628}
28629impl<T> Default for InvalidationStack<T> {
28630 fn default() -> Self {
28631 Self(Default::default())
28632 }
28633}
28634
28635impl<T> Deref for InvalidationStack<T> {
28636 type Target = Vec<T>;
28637
28638 fn deref(&self) -> &Self::Target {
28639 &self.0
28640 }
28641}
28642
28643impl<T> DerefMut for InvalidationStack<T> {
28644 fn deref_mut(&mut self) -> &mut Self::Target {
28645 &mut self.0
28646 }
28647}
28648
28649impl InvalidationRegion for SnippetState {
28650 fn ranges(&self) -> &[Range<Anchor>] {
28651 &self.ranges[self.active_index]
28652 }
28653}
28654
28655fn edit_prediction_edit_text(
28656 current_snapshot: &BufferSnapshot,
28657 edits: &[(Range<Anchor>, impl AsRef<str>)],
28658 edit_preview: &EditPreview,
28659 include_deletions: bool,
28660 cx: &App,
28661) -> HighlightedText {
28662 let edits = edits
28663 .iter()
28664 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28665 .collect::<Vec<_>>();
28666
28667 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28668}
28669
28670fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28671 // Fallback for providers that don't provide edit_preview (like Copilot)
28672 // Just show the raw edit text with basic styling
28673 let mut text = String::new();
28674 let mut highlights = Vec::new();
28675
28676 let insertion_highlight_style = HighlightStyle {
28677 color: Some(cx.theme().colors().text),
28678 ..Default::default()
28679 };
28680
28681 for (_, edit_text) in edits {
28682 let start_offset = text.len();
28683 text.push_str(edit_text);
28684 let end_offset = text.len();
28685
28686 if start_offset < end_offset {
28687 highlights.push((start_offset..end_offset, insertion_highlight_style));
28688 }
28689 }
28690
28691 HighlightedText {
28692 text: text.into(),
28693 highlights,
28694 }
28695}
28696
28697pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28698 match severity {
28699 lsp::DiagnosticSeverity::ERROR => colors.error,
28700 lsp::DiagnosticSeverity::WARNING => colors.warning,
28701 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28702 lsp::DiagnosticSeverity::HINT => colors.info,
28703 _ => colors.ignored,
28704 }
28705}
28706
28707pub fn styled_runs_for_code_label<'a>(
28708 label: &'a CodeLabel,
28709 syntax_theme: &'a theme::SyntaxTheme,
28710 local_player: &'a theme::PlayerColor,
28711) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28712 let fade_out = HighlightStyle {
28713 fade_out: Some(0.35),
28714 ..Default::default()
28715 };
28716
28717 let mut prev_end = label.filter_range.end;
28718 label
28719 .runs
28720 .iter()
28721 .enumerate()
28722 .flat_map(move |(ix, (range, highlight_id))| {
28723 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28724 HighlightStyle {
28725 color: Some(local_player.cursor),
28726 ..Default::default()
28727 }
28728 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28729 HighlightStyle {
28730 background_color: Some(local_player.selection),
28731 ..Default::default()
28732 }
28733 } else if let Some(style) = highlight_id.style(syntax_theme) {
28734 style
28735 } else {
28736 return Default::default();
28737 };
28738 let muted_style = style.highlight(fade_out);
28739
28740 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28741 if range.start >= label.filter_range.end {
28742 if range.start > prev_end {
28743 runs.push((prev_end..range.start, fade_out));
28744 }
28745 runs.push((range.clone(), muted_style));
28746 } else if range.end <= label.filter_range.end {
28747 runs.push((range.clone(), style));
28748 } else {
28749 runs.push((range.start..label.filter_range.end, style));
28750 runs.push((label.filter_range.end..range.end, muted_style));
28751 }
28752 prev_end = cmp::max(prev_end, range.end);
28753
28754 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28755 runs.push((prev_end..label.text.len(), fade_out));
28756 }
28757
28758 runs
28759 })
28760}
28761
28762pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28763 let mut prev_index = 0;
28764 let mut prev_codepoint: Option<char> = None;
28765 text.char_indices()
28766 .chain([(text.len(), '\0')])
28767 .filter_map(move |(index, codepoint)| {
28768 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28769 let is_boundary = index == text.len()
28770 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28771 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28772 if is_boundary {
28773 let chunk = &text[prev_index..index];
28774 prev_index = index;
28775 Some(chunk)
28776 } else {
28777 None
28778 }
28779 })
28780}
28781
28782/// Given a string of text immediately before the cursor, iterates over possible
28783/// strings a snippet could match to. More precisely: returns an iterator over
28784/// suffixes of `text` created by splitting at word boundaries (before & after
28785/// every non-word character).
28786///
28787/// Shorter suffixes are returned first.
28788pub(crate) fn snippet_candidate_suffixes<'a>(
28789 text: &'a str,
28790 is_word_char: &'a dyn Fn(char) -> bool,
28791) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28792 let mut prev_index = text.len();
28793 let mut prev_codepoint = None;
28794 text.char_indices()
28795 .rev()
28796 .chain([(0, '\0')])
28797 .filter_map(move |(index, codepoint)| {
28798 let prev_index = std::mem::replace(&mut prev_index, index);
28799 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28800 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28801 None
28802 } else {
28803 let chunk = &text[prev_index..]; // go to end of string
28804 Some(chunk)
28805 }
28806 })
28807}
28808
28809pub trait RangeToAnchorExt: Sized {
28810 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28811
28812 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28813 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28814 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28815 }
28816}
28817
28818impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28819 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28820 let start_offset = self.start.to_offset(snapshot);
28821 let end_offset = self.end.to_offset(snapshot);
28822 if start_offset == end_offset {
28823 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28824 } else {
28825 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28826 }
28827 }
28828}
28829
28830pub trait RowExt {
28831 fn as_f64(&self) -> f64;
28832
28833 fn next_row(&self) -> Self;
28834
28835 fn previous_row(&self) -> Self;
28836
28837 fn minus(&self, other: Self) -> u32;
28838}
28839
28840impl RowExt for DisplayRow {
28841 fn as_f64(&self) -> f64 {
28842 self.0 as _
28843 }
28844
28845 fn next_row(&self) -> Self {
28846 Self(self.0 + 1)
28847 }
28848
28849 fn previous_row(&self) -> Self {
28850 Self(self.0.saturating_sub(1))
28851 }
28852
28853 fn minus(&self, other: Self) -> u32 {
28854 self.0 - other.0
28855 }
28856}
28857
28858impl RowExt for MultiBufferRow {
28859 fn as_f64(&self) -> f64 {
28860 self.0 as _
28861 }
28862
28863 fn next_row(&self) -> Self {
28864 Self(self.0 + 1)
28865 }
28866
28867 fn previous_row(&self) -> Self {
28868 Self(self.0.saturating_sub(1))
28869 }
28870
28871 fn minus(&self, other: Self) -> u32 {
28872 self.0 - other.0
28873 }
28874}
28875
28876trait RowRangeExt {
28877 type Row;
28878
28879 fn len(&self) -> usize;
28880
28881 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28882}
28883
28884impl RowRangeExt for Range<MultiBufferRow> {
28885 type Row = MultiBufferRow;
28886
28887 fn len(&self) -> usize {
28888 (self.end.0 - self.start.0) as usize
28889 }
28890
28891 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28892 (self.start.0..self.end.0).map(MultiBufferRow)
28893 }
28894}
28895
28896impl RowRangeExt for Range<DisplayRow> {
28897 type Row = DisplayRow;
28898
28899 fn len(&self) -> usize {
28900 (self.end.0 - self.start.0) as usize
28901 }
28902
28903 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28904 (self.start.0..self.end.0).map(DisplayRow)
28905 }
28906}
28907
28908/// If select range has more than one line, we
28909/// just point the cursor to range.start.
28910fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28911 if range.start.row == range.end.row {
28912 range
28913 } else {
28914 range.start..range.start
28915 }
28916}
28917pub struct KillRing(ClipboardItem);
28918impl Global for KillRing {}
28919
28920const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28921
28922enum BreakpointPromptEditAction {
28923 Log,
28924 Condition,
28925 HitCondition,
28926}
28927
28928struct BreakpointPromptEditor {
28929 pub(crate) prompt: Entity<Editor>,
28930 editor: WeakEntity<Editor>,
28931 breakpoint_anchor: Anchor,
28932 breakpoint: Breakpoint,
28933 edit_action: BreakpointPromptEditAction,
28934 block_ids: HashSet<CustomBlockId>,
28935 editor_margins: Arc<Mutex<EditorMargins>>,
28936 _subscriptions: Vec<Subscription>,
28937}
28938
28939impl BreakpointPromptEditor {
28940 const MAX_LINES: u8 = 4;
28941
28942 fn new(
28943 editor: WeakEntity<Editor>,
28944 breakpoint_anchor: Anchor,
28945 breakpoint: Breakpoint,
28946 edit_action: BreakpointPromptEditAction,
28947 window: &mut Window,
28948 cx: &mut Context<Self>,
28949 ) -> Self {
28950 let base_text = match edit_action {
28951 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28952 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28953 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28954 }
28955 .map(|msg| msg.to_string())
28956 .unwrap_or_default();
28957
28958 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28959 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28960
28961 let prompt = cx.new(|cx| {
28962 let mut prompt = Editor::new(
28963 EditorMode::AutoHeight {
28964 min_lines: 1,
28965 max_lines: Some(Self::MAX_LINES as usize),
28966 },
28967 buffer,
28968 None,
28969 window,
28970 cx,
28971 );
28972 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28973 prompt.set_show_cursor_when_unfocused(false, cx);
28974 prompt.set_placeholder_text(
28975 match edit_action {
28976 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28977 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28978 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28979 },
28980 window,
28981 cx,
28982 );
28983
28984 prompt
28985 });
28986
28987 Self {
28988 prompt,
28989 editor,
28990 breakpoint_anchor,
28991 breakpoint,
28992 edit_action,
28993 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28994 block_ids: Default::default(),
28995 _subscriptions: vec![],
28996 }
28997 }
28998
28999 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29000 self.block_ids.extend(block_ids)
29001 }
29002
29003 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29004 if let Some(editor) = self.editor.upgrade() {
29005 let message = self
29006 .prompt
29007 .read(cx)
29008 .buffer
29009 .read(cx)
29010 .as_singleton()
29011 .expect("A multi buffer in breakpoint prompt isn't possible")
29012 .read(cx)
29013 .as_rope()
29014 .to_string();
29015
29016 editor.update(cx, |editor, cx| {
29017 editor.edit_breakpoint_at_anchor(
29018 self.breakpoint_anchor,
29019 self.breakpoint.clone(),
29020 match self.edit_action {
29021 BreakpointPromptEditAction::Log => {
29022 BreakpointEditAction::EditLogMessage(message.into())
29023 }
29024 BreakpointPromptEditAction::Condition => {
29025 BreakpointEditAction::EditCondition(message.into())
29026 }
29027 BreakpointPromptEditAction::HitCondition => {
29028 BreakpointEditAction::EditHitCondition(message.into())
29029 }
29030 },
29031 cx,
29032 );
29033
29034 editor.remove_blocks(self.block_ids.clone(), None, cx);
29035 cx.focus_self(window);
29036 });
29037 }
29038 }
29039
29040 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29041 self.editor
29042 .update(cx, |editor, cx| {
29043 editor.remove_blocks(self.block_ids.clone(), None, cx);
29044 window.focus(&editor.focus_handle, cx);
29045 })
29046 .log_err();
29047 }
29048
29049 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29050 let settings = ThemeSettings::get_global(cx);
29051 let text_style = TextStyle {
29052 color: if self.prompt.read(cx).read_only(cx) {
29053 cx.theme().colors().text_disabled
29054 } else {
29055 cx.theme().colors().text
29056 },
29057 font_family: settings.buffer_font.family.clone(),
29058 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29059 font_size: settings.buffer_font_size(cx).into(),
29060 font_weight: settings.buffer_font.weight,
29061 line_height: relative(settings.buffer_line_height.value()),
29062 ..Default::default()
29063 };
29064 EditorElement::new(
29065 &self.prompt,
29066 EditorStyle {
29067 background: cx.theme().colors().editor_background,
29068 local_player: cx.theme().players().local(),
29069 text: text_style,
29070 ..Default::default()
29071 },
29072 )
29073 }
29074
29075 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29076 let focus_handle = self.prompt.focus_handle(cx);
29077 IconButton::new("cancel", IconName::Close)
29078 .icon_color(Color::Muted)
29079 .shape(IconButtonShape::Square)
29080 .tooltip(move |_window, cx| {
29081 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29082 })
29083 .on_click(cx.listener(|this, _, window, cx| {
29084 this.cancel(&menu::Cancel, window, cx);
29085 }))
29086 }
29087
29088 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29089 let focus_handle = self.prompt.focus_handle(cx);
29090 IconButton::new("confirm", IconName::Return)
29091 .icon_color(Color::Muted)
29092 .shape(IconButtonShape::Square)
29093 .tooltip(move |_window, cx| {
29094 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29095 })
29096 .on_click(cx.listener(|this, _, window, cx| {
29097 this.confirm(&menu::Confirm, window, cx);
29098 }))
29099 }
29100}
29101
29102impl Render for BreakpointPromptEditor {
29103 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29104 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29105 let editor_margins = *self.editor_margins.lock();
29106 let gutter_dimensions = editor_margins.gutter;
29107 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29108 let right_padding = editor_margins.right + px(9.);
29109 h_flex()
29110 .key_context("Editor")
29111 .bg(cx.theme().colors().editor_background)
29112 .border_y_1()
29113 .border_color(cx.theme().status().info_border)
29114 .size_full()
29115 .py(window.line_height() / 2.5)
29116 .pr(right_padding)
29117 .on_action(cx.listener(Self::confirm))
29118 .on_action(cx.listener(Self::cancel))
29119 .child(
29120 WithRemSize::new(ui_font_size)
29121 .h_full()
29122 .w(left_gutter_width)
29123 .flex()
29124 .flex_row()
29125 .flex_shrink_0()
29126 .items_center()
29127 .justify_center()
29128 .gap_1()
29129 .child(self.render_close_button(cx)),
29130 )
29131 .child(
29132 h_flex()
29133 .w_full()
29134 .justify_between()
29135 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29136 .child(
29137 WithRemSize::new(ui_font_size)
29138 .flex()
29139 .flex_row()
29140 .items_center()
29141 .child(self.render_confirm_button(cx)),
29142 ),
29143 )
29144 }
29145}
29146
29147impl Focusable for BreakpointPromptEditor {
29148 fn focus_handle(&self, cx: &App) -> FocusHandle {
29149 self.prompt.focus_handle(cx)
29150 }
29151}
29152
29153fn all_edits_insertions_or_deletions(
29154 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29155 snapshot: &MultiBufferSnapshot,
29156) -> bool {
29157 let mut all_insertions = true;
29158 let mut all_deletions = true;
29159
29160 for (range, new_text) in edits.iter() {
29161 let range_is_empty = range.to_offset(snapshot).is_empty();
29162 let text_is_empty = new_text.is_empty();
29163
29164 if range_is_empty != text_is_empty {
29165 if range_is_empty {
29166 all_deletions = false;
29167 } else {
29168 all_insertions = false;
29169 }
29170 } else {
29171 return false;
29172 }
29173
29174 if !all_insertions && !all_deletions {
29175 return false;
29176 }
29177 }
29178 all_insertions || all_deletions
29179}
29180
29181struct MissingEditPredictionKeybindingTooltip;
29182
29183impl Render for MissingEditPredictionKeybindingTooltip {
29184 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29185 ui::tooltip_container(cx, |container, cx| {
29186 container
29187 .flex_shrink_0()
29188 .max_w_80()
29189 .min_h(rems_from_px(124.))
29190 .justify_between()
29191 .child(
29192 v_flex()
29193 .flex_1()
29194 .text_ui_sm(cx)
29195 .child(Label::new("Conflict with Accept Keybinding"))
29196 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29197 )
29198 .child(
29199 h_flex()
29200 .pb_1()
29201 .gap_1()
29202 .items_end()
29203 .w_full()
29204 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29205 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29206 }))
29207 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29208 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29209 })),
29210 )
29211 })
29212 }
29213}
29214
29215#[derive(Debug, Clone, Copy, PartialEq)]
29216pub struct LineHighlight {
29217 pub background: Background,
29218 pub border: Option<gpui::Hsla>,
29219 pub include_gutter: bool,
29220 pub type_id: Option<TypeId>,
29221}
29222
29223struct LineManipulationResult {
29224 pub new_text: String,
29225 pub line_count_before: usize,
29226 pub line_count_after: usize,
29227}
29228
29229fn render_diff_hunk_controls(
29230 row: u32,
29231 status: &DiffHunkStatus,
29232 hunk_range: Range<Anchor>,
29233 is_created_file: bool,
29234 line_height: Pixels,
29235 editor: &Entity<Editor>,
29236 _window: &mut Window,
29237 cx: &mut App,
29238) -> AnyElement {
29239 h_flex()
29240 .h(line_height)
29241 .mr_1()
29242 .gap_1()
29243 .px_0p5()
29244 .pb_1()
29245 .border_x_1()
29246 .border_b_1()
29247 .border_color(cx.theme().colors().border_variant)
29248 .rounded_b_lg()
29249 .bg(cx.theme().colors().editor_background)
29250 .gap_1()
29251 .block_mouse_except_scroll()
29252 .shadow_md()
29253 .child(if status.has_secondary_hunk() {
29254 Button::new(("stage", row as u64), "Stage")
29255 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29256 .tooltip({
29257 let focus_handle = editor.focus_handle(cx);
29258 move |_window, cx| {
29259 Tooltip::for_action_in(
29260 "Stage Hunk",
29261 &::git::ToggleStaged,
29262 &focus_handle,
29263 cx,
29264 )
29265 }
29266 })
29267 .on_click({
29268 let editor = editor.clone();
29269 move |_event, _window, cx| {
29270 editor.update(cx, |editor, cx| {
29271 editor.stage_or_unstage_diff_hunks(
29272 true,
29273 vec![hunk_range.start..hunk_range.start],
29274 cx,
29275 );
29276 });
29277 }
29278 })
29279 } else {
29280 Button::new(("unstage", row as u64), "Unstage")
29281 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29282 .tooltip({
29283 let focus_handle = editor.focus_handle(cx);
29284 move |_window, cx| {
29285 Tooltip::for_action_in(
29286 "Unstage Hunk",
29287 &::git::ToggleStaged,
29288 &focus_handle,
29289 cx,
29290 )
29291 }
29292 })
29293 .on_click({
29294 let editor = editor.clone();
29295 move |_event, _window, cx| {
29296 editor.update(cx, |editor, cx| {
29297 editor.stage_or_unstage_diff_hunks(
29298 false,
29299 vec![hunk_range.start..hunk_range.start],
29300 cx,
29301 );
29302 });
29303 }
29304 })
29305 })
29306 .child(
29307 Button::new(("restore", row as u64), "Restore")
29308 .tooltip({
29309 let focus_handle = editor.focus_handle(cx);
29310 move |_window, cx| {
29311 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29312 }
29313 })
29314 .on_click({
29315 let editor = editor.clone();
29316 move |_event, window, cx| {
29317 editor.update(cx, |editor, cx| {
29318 let snapshot = editor.snapshot(window, cx);
29319 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29320 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29321 });
29322 }
29323 })
29324 .disabled(is_created_file),
29325 )
29326 .when(
29327 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29328 |el| {
29329 el.child(
29330 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29331 .shape(IconButtonShape::Square)
29332 .icon_size(IconSize::Small)
29333 // .disabled(!has_multiple_hunks)
29334 .tooltip({
29335 let focus_handle = editor.focus_handle(cx);
29336 move |_window, cx| {
29337 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29338 }
29339 })
29340 .on_click({
29341 let editor = editor.clone();
29342 move |_event, window, cx| {
29343 editor.update(cx, |editor, cx| {
29344 let snapshot = editor.snapshot(window, cx);
29345 let position =
29346 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29347 editor.go_to_hunk_before_or_after_position(
29348 &snapshot,
29349 position,
29350 Direction::Next,
29351 true,
29352 window,
29353 cx,
29354 );
29355 editor.expand_selected_diff_hunks(cx);
29356 });
29357 }
29358 }),
29359 )
29360 .child(
29361 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29362 .shape(IconButtonShape::Square)
29363 .icon_size(IconSize::Small)
29364 // .disabled(!has_multiple_hunks)
29365 .tooltip({
29366 let focus_handle = editor.focus_handle(cx);
29367 move |_window, cx| {
29368 Tooltip::for_action_in(
29369 "Previous Hunk",
29370 &GoToPreviousHunk,
29371 &focus_handle,
29372 cx,
29373 )
29374 }
29375 })
29376 .on_click({
29377 let editor = editor.clone();
29378 move |_event, window, cx| {
29379 editor.update(cx, |editor, cx| {
29380 let snapshot = editor.snapshot(window, cx);
29381 let point =
29382 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29383 editor.go_to_hunk_before_or_after_position(
29384 &snapshot,
29385 point,
29386 Direction::Prev,
29387 true,
29388 window,
29389 cx,
29390 );
29391 editor.expand_selected_diff_hunks(cx);
29392 });
29393 }
29394 }),
29395 )
29396 },
29397 )
29398 .into_any_element()
29399}
29400
29401pub fn multibuffer_context_lines(cx: &App) -> u32 {
29402 EditorSettings::try_get(cx)
29403 .map(|settings| settings.excerpt_context_lines)
29404 .unwrap_or(2)
29405 .min(32)
29406}