1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod runnables;
39mod rust_analyzer_ext;
40pub mod scroll;
41mod selections_collection;
42pub mod semantic_tokens;
43mod split;
44pub mod split_editor_view;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
65 ShowMinimap,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
80 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
81 ToPoint,
82};
83pub use split::{SplittableEditor, ToggleSplitDiff};
84pub use split_editor_view::SplitEditorView;
85pub use text::Bias;
86
87use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
88use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
89use anyhow::{Context as _, Result, anyhow, bail};
90use blink_manager::BlinkManager;
91use buffer_diff::DiffHunkStatus;
92use client::{Collaborator, ParticipantIndex, parse_zed_link};
93use clock::ReplicaId;
94use code_context_menus::{
95 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
96 CompletionsMenu, ContextMenuOrigin,
97};
98use collections::{BTreeMap, HashMap, HashSet, VecDeque};
99use convert_case::{Case, Casing};
100use dap::TelemetrySpawnLocation;
101use display_map::*;
102use document_colors::LspColorData;
103use edit_prediction_types::{
104 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
105 EditPredictionGranularity, SuggestionDisplayType,
106};
107use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
108use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
109use futures::{
110 FutureExt,
111 future::{self, Shared, join},
112};
113use fuzzy::{StringMatch, StringMatchCandidate};
114use git::blame::{GitBlame, GlobalBlameRenderer};
115use gpui::{
116 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
117 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
118 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
119 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
120 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
121 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
122 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
123 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
124 pulsating_between, px, relative, size,
125};
126use hover_links::{HoverLink, HoveredLinkState, find_file};
127use hover_popover::{HoverState, hide_hover};
128use indent_guides::ActiveIndentGuidesState;
129use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
130use itertools::{Either, Itertools};
131use language::{
132 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
133 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
134 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
135 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, LocalFile, OffsetRangeExt,
136 OutlineItem, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
137 WordsQuery,
138 language_settings::{
139 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
140 all_language_settings, language_settings,
141 },
142 point_from_lsp, point_to_lsp, text_diff_with_options,
143};
144use linked_editing_ranges::refresh_linked_ranges;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId,
148};
149use markdown::Markdown;
150use mouse_context_menu::MouseContextMenu;
151use movement::TextLayoutDetails;
152use multi_buffer::{
153 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
154};
155use parking_lot::Mutex;
156use persistence::DB;
157use project::{
158 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
159 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
160 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
161 ProjectItem, ProjectPath, ProjectTransaction,
162 debugger::{
163 breakpoint_store::{
164 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
165 BreakpointStore, BreakpointStoreEvent,
166 },
167 session::{Session, SessionEvent},
168 },
169 git_store::GitStoreEvent,
170 lsp_store::{
171 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
172 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
173 },
174 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
175};
176use rand::seq::SliceRandom;
177use regex::Regex;
178use rpc::{ErrorCode, ErrorExt, proto::PeerId};
179use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
180use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
181use serde::{Deserialize, Serialize};
182use settings::{
183 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
184 update_settings_file,
185};
186use smallvec::{SmallVec, smallvec};
187use snippet::Snippet;
188use std::{
189 any::{Any, TypeId},
190 borrow::Cow,
191 cell::{OnceCell, RefCell},
192 cmp::{self, Ordering, Reverse},
193 collections::hash_map,
194 iter::{self, Peekable},
195 mem,
196 num::NonZeroU32,
197 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
198 path::{Path, PathBuf},
199 rc::Rc,
200 sync::Arc,
201 time::{Duration, Instant},
202};
203use task::TaskVariables;
204use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
205use theme::{
206 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
207 ThemeSettings, observe_buffer_font_size_adjustment,
208};
209use ui::{
210 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
211 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
212 utils::WithRemSize,
213};
214use ui_input::ErasedEditor;
215use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
216use workspace::{
217 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
218 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
219 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
220 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
221 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
222 searchable::SearchEvent,
223};
224use zed_actions::editor::{MoveDown, MoveUp};
225
226use crate::{
227 code_context_menus::CompletionsMenuSource,
228 editor_settings::MultiCursorModifier,
229 hover_links::{find_url, find_url_from_range},
230 inlays::{
231 InlineValueCache,
232 inlay_hints::{LspInlayHintData, inlay_hint_settings},
233 },
234 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
235 scroll::{ScrollOffset, ScrollPixelOffset},
236 selections_collection::resolve_selections_wrapping_blocks,
237 semantic_tokens::SemanticTokenState,
238 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
239};
240
241pub const FILE_HEADER_HEIGHT: u32 = 2;
242pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
243pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
244const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
245const MAX_LINE_LEN: usize = 1024;
246const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
247const MAX_SELECTION_HISTORY_LEN: usize = 1024;
248pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
249#[doc(hidden)]
250pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
251pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
252
253pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
254pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
255pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
256pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
257
258pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
259pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
260pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
261
262pub type RenderDiffHunkControlsFn = Arc<
263 dyn Fn(
264 u32,
265 &DiffHunkStatus,
266 Range<Anchor>,
267 bool,
268 Pixels,
269 &Entity<Editor>,
270 &mut Window,
271 &mut App,
272 ) -> AnyElement,
273>;
274
275enum ReportEditorEvent {
276 Saved { auto_saved: bool },
277 EditorOpened,
278 Closed,
279}
280
281impl ReportEditorEvent {
282 pub fn event_type(&self) -> &'static str {
283 match self {
284 Self::Saved { .. } => "Editor Saved",
285 Self::EditorOpened => "Editor Opened",
286 Self::Closed => "Editor Closed",
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293
294pub enum ConflictsOuter {}
295pub enum ConflictsOurs {}
296pub enum ConflictsTheirs {}
297pub enum ConflictsOursMarker {}
298pub enum ConflictsTheirsMarker {}
299
300pub struct HunkAddedColor;
301pub struct HunkRemovedColor;
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 word_diffs: Vec<Range<MultiBufferOffset>>,
327 },
328}
329
330pub enum HideMouseCursorOrigin {
331 TypingAction,
332 MovementAction,
333}
334
335pub fn init(cx: &mut App) {
336 cx.set_global(GlobalBlameRenderer(Arc::new(())));
337 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
338
339 workspace::register_project_item::<Editor>(cx);
340 workspace::FollowableViewRegistry::register::<Editor>(cx);
341 workspace::register_serializable_item::<Editor>(cx);
342
343 cx.observe_new(
344 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
345 workspace.register_action(Editor::new_file);
346 workspace.register_action(Editor::new_file_split);
347 workspace.register_action(Editor::new_file_vertical);
348 workspace.register_action(Editor::new_file_horizontal);
349 workspace.register_action(Editor::cancel_language_server_work);
350 workspace.register_action(Editor::toggle_focus);
351 },
352 )
353 .detach();
354
355 cx.on_action(move |_: &workspace::NewFile, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 Editor::new_file(workspace, &Default::default(), window, cx)
364 },
365 )
366 .detach_and_log_err(cx);
367 }
368 })
369 .on_action(move |_: &workspace::NewWindow, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 cx.activate(true);
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach_and_log_err(cx);
382 }
383 });
384 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
385 Arc::new(ErasedEditorImpl(
386 cx.new(|cx| Editor::single_line(window, cx)),
387 )) as Arc<dyn ErasedEditor>
388 });
389 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
390}
391
392pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
393 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
394}
395
396pub trait DiagnosticRenderer {
397 fn render_group(
398 &self,
399 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
400 buffer_id: BufferId,
401 snapshot: EditorSnapshot,
402 editor: WeakEntity<Editor>,
403 language_registry: Option<Arc<LanguageRegistry>>,
404 cx: &mut App,
405 ) -> Vec<BlockProperties<Anchor>>;
406
407 fn render_hover(
408 &self,
409 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
410 range: Range<Point>,
411 buffer_id: BufferId,
412 language_registry: Option<Arc<LanguageRegistry>>,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
484pub enum SizingBehavior {
485 /// The editor will layout itself using `size_full` and will include the vertical
486 /// scroll margin as requested by user settings.
487 #[default]
488 Default,
489 /// The editor will layout itself using `size_full`, but will not have any
490 /// vertical overscroll.
491 ExcludeOverscrollMargin,
492 /// The editor will request a vertical size according to its content and will be
493 /// layouted without a vertical scroll margin.
494 SizeByContent,
495}
496
497#[derive(Clone, PartialEq, Eq, Debug)]
498pub enum EditorMode {
499 SingleLine,
500 AutoHeight {
501 min_lines: usize,
502 max_lines: Option<usize>,
503 },
504 Full {
505 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
506 scale_ui_elements_with_buffer_font_size: bool,
507 /// When set to `true`, the editor will render a background for the active line.
508 show_active_line_background: bool,
509 /// Determines the sizing behavior for this editor
510 sizing_behavior: SizingBehavior,
511 },
512 Minimap {
513 parent: WeakEntity<Editor>,
514 },
515}
516
517impl EditorMode {
518 pub fn full() -> Self {
519 Self::Full {
520 scale_ui_elements_with_buffer_font_size: true,
521 show_active_line_background: true,
522 sizing_behavior: SizingBehavior::Default,
523 }
524 }
525
526 #[inline]
527 pub fn is_full(&self) -> bool {
528 matches!(self, Self::Full { .. })
529 }
530
531 #[inline]
532 pub fn is_single_line(&self) -> bool {
533 matches!(self, Self::SingleLine { .. })
534 }
535
536 #[inline]
537 fn is_minimap(&self) -> bool {
538 matches!(self, Self::Minimap { .. })
539 }
540}
541
542#[derive(Copy, Clone, Debug)]
543pub enum SoftWrap {
544 /// Prefer not to wrap at all.
545 ///
546 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
547 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
548 GitDiff,
549 /// Prefer a single line generally, unless an overly long line is encountered.
550 None,
551 /// Soft wrap lines that exceed the editor width.
552 EditorWidth,
553 /// Soft wrap lines at the preferred line length.
554 Column(u32),
555 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
556 Bounded(u32),
557}
558
559#[derive(Clone)]
560pub struct EditorStyle {
561 pub background: Hsla,
562 pub border: Hsla,
563 pub local_player: PlayerColor,
564 pub text: TextStyle,
565 pub scrollbar_width: Pixels,
566 pub syntax: Arc<SyntaxTheme>,
567 pub status: StatusColors,
568 pub inlay_hints_style: HighlightStyle,
569 pub edit_prediction_styles: EditPredictionStyles,
570 pub unnecessary_code_fade: f32,
571 pub show_underlines: bool,
572}
573
574impl Default for EditorStyle {
575 fn default() -> Self {
576 Self {
577 background: Hsla::default(),
578 border: Hsla::default(),
579 local_player: PlayerColor::default(),
580 text: TextStyle::default(),
581 scrollbar_width: Pixels::default(),
582 syntax: Default::default(),
583 // HACK: Status colors don't have a real default.
584 // We should look into removing the status colors from the editor
585 // style and retrieve them directly from the theme.
586 status: StatusColors::dark(),
587 inlay_hints_style: HighlightStyle::default(),
588 edit_prediction_styles: EditPredictionStyles {
589 insertion: HighlightStyle::default(),
590 whitespace: HighlightStyle::default(),
591 },
592 unnecessary_code_fade: Default::default(),
593 show_underlines: true,
594 }
595 }
596}
597
598pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
599 let show_background = language_settings::language_settings(None, None, cx)
600 .inlay_hints
601 .show_background;
602
603 let mut style = cx.theme().syntax().get("hint");
604
605 if style.color.is_none() {
606 style.color = Some(cx.theme().status().hint);
607 }
608
609 if !show_background {
610 style.background_color = None;
611 return style;
612 }
613
614 if style.background_color.is_none() {
615 style.background_color = Some(cx.theme().status().hint_background);
616 }
617
618 style
619}
620
621pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
622 EditPredictionStyles {
623 insertion: HighlightStyle {
624 color: Some(cx.theme().status().predictive),
625 ..HighlightStyle::default()
626 },
627 whitespace: HighlightStyle {
628 background_color: Some(cx.theme().status().created_background),
629 ..HighlightStyle::default()
630 },
631 }
632}
633
634type CompletionId = usize;
635
636pub(crate) enum EditDisplayMode {
637 TabAccept,
638 DiffPopover,
639 Inline,
640}
641
642enum EditPrediction {
643 Edit {
644 edits: Vec<(Range<Anchor>, Arc<str>)>,
645 /// Predicted cursor position as (anchor, offset_from_anchor).
646 /// The anchor is in multibuffer coordinates; after applying edits,
647 /// resolve the anchor and add the offset to get the final cursor position.
648 cursor_position: Option<(Anchor, usize)>,
649 edit_preview: Option<EditPreview>,
650 display_mode: EditDisplayMode,
651 snapshot: BufferSnapshot,
652 },
653 /// Move to a specific location in the active editor
654 MoveWithin {
655 target: Anchor,
656 snapshot: BufferSnapshot,
657 },
658 /// Move to a specific location in a different editor (not the active one)
659 MoveOutside {
660 target: language::Anchor,
661 snapshot: BufferSnapshot,
662 },
663}
664
665struct EditPredictionState {
666 inlay_ids: Vec<InlayId>,
667 completion: EditPrediction,
668 completion_id: Option<SharedString>,
669 invalidation_range: Option<Range<Anchor>>,
670}
671
672enum EditPredictionSettings {
673 Disabled,
674 Enabled {
675 show_in_menu: bool,
676 preview_requires_modifier: bool,
677 },
678}
679
680#[derive(Debug, Clone)]
681struct InlineDiagnostic {
682 message: SharedString,
683 group_id: usize,
684 is_primary: bool,
685 start: Point,
686 severity: lsp::DiagnosticSeverity,
687}
688
689pub enum MenuEditPredictionsPolicy {
690 Never,
691 ByProvider,
692}
693
694pub enum EditPredictionPreview {
695 /// Modifier is not pressed
696 Inactive { released_too_fast: bool },
697 /// Modifier pressed
698 Active {
699 since: Instant,
700 previous_scroll_position: Option<SharedScrollAnchor>,
701 },
702}
703
704impl EditPredictionPreview {
705 pub fn released_too_fast(&self) -> bool {
706 match self {
707 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
708 EditPredictionPreview::Active { .. } => false,
709 }
710 }
711
712 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
713 if let EditPredictionPreview::Active {
714 previous_scroll_position,
715 ..
716 } = self
717 {
718 *previous_scroll_position = scroll_position;
719 }
720 }
721}
722
723pub struct ContextMenuOptions {
724 pub min_entries_visible: usize,
725 pub max_entries_visible: usize,
726 pub placement: Option<ContextMenuPlacement>,
727}
728
729#[derive(Debug, Clone, PartialEq, Eq)]
730pub enum ContextMenuPlacement {
731 Above,
732 Below,
733}
734
735#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
736struct EditorActionId(usize);
737
738impl EditorActionId {
739 pub fn post_inc(&mut self) -> Self {
740 let answer = self.0;
741
742 *self = Self(answer + 1);
743
744 Self(answer)
745 }
746}
747
748// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
749// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
750
751type BackgroundHighlight = (
752 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
753 Arc<[Range<Anchor>]>,
754);
755type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
756
757#[derive(Default)]
758struct ScrollbarMarkerState {
759 scrollbar_size: Size<Pixels>,
760 dirty: bool,
761 markers: Arc<[PaintQuad]>,
762 pending_refresh: Option<Task<Result<()>>>,
763}
764
765impl ScrollbarMarkerState {
766 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
767 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
768 }
769}
770
771#[derive(Clone, Copy, PartialEq, Eq)]
772pub enum MinimapVisibility {
773 Disabled,
774 Enabled {
775 /// The configuration currently present in the users settings.
776 setting_configuration: bool,
777 /// Whether to override the currently set visibility from the users setting.
778 toggle_override: bool,
779 },
780}
781
782impl MinimapVisibility {
783 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
784 if mode.is_full() {
785 Self::Enabled {
786 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
787 toggle_override: false,
788 }
789 } else {
790 Self::Disabled
791 }
792 }
793
794 fn hidden(&self) -> Self {
795 match *self {
796 Self::Enabled {
797 setting_configuration,
798 ..
799 } => Self::Enabled {
800 setting_configuration,
801 toggle_override: setting_configuration,
802 },
803 Self::Disabled => Self::Disabled,
804 }
805 }
806
807 fn disabled(&self) -> bool {
808 matches!(*self, Self::Disabled)
809 }
810
811 fn settings_visibility(&self) -> bool {
812 match *self {
813 Self::Enabled {
814 setting_configuration,
815 ..
816 } => setting_configuration,
817 _ => false,
818 }
819 }
820
821 fn visible(&self) -> bool {
822 match *self {
823 Self::Enabled {
824 setting_configuration,
825 toggle_override,
826 } => setting_configuration ^ toggle_override,
827 _ => false,
828 }
829 }
830
831 fn toggle_visibility(&self) -> Self {
832 match *self {
833 Self::Enabled {
834 toggle_override,
835 setting_configuration,
836 } => Self::Enabled {
837 setting_configuration,
838 toggle_override: !toggle_override,
839 },
840 Self::Disabled => Self::Disabled,
841 }
842 }
843}
844
845#[derive(Debug, Clone, Copy, PartialEq, Eq)]
846pub enum BufferSerialization {
847 All,
848 NonDirtyBuffers,
849}
850
851impl BufferSerialization {
852 fn new(restore_unsaved_buffers: bool) -> Self {
853 if restore_unsaved_buffers {
854 Self::All
855 } else {
856 Self::NonDirtyBuffers
857 }
858 }
859}
860
861/// Addons allow storing per-editor state in other crates (e.g. Vim)
862pub trait Addon: 'static {
863 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
864
865 fn render_buffer_header_controls(
866 &self,
867 _: &ExcerptInfo,
868 _: &Window,
869 _: &App,
870 ) -> Option<AnyElement> {
871 None
872 }
873
874 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
875 None
876 }
877
878 fn to_any(&self) -> &dyn std::any::Any;
879
880 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
881 None
882 }
883}
884
885struct ChangeLocation {
886 current: Option<Vec<Anchor>>,
887 original: Vec<Anchor>,
888}
889impl ChangeLocation {
890 fn locations(&self) -> &[Anchor] {
891 self.current.as_ref().unwrap_or(&self.original)
892 }
893}
894
895/// A set of caret positions, registered when the editor was edited.
896pub struct ChangeList {
897 changes: Vec<ChangeLocation>,
898 /// Currently "selected" change.
899 position: Option<usize>,
900}
901
902impl ChangeList {
903 pub fn new() -> Self {
904 Self {
905 changes: Vec::new(),
906 position: None,
907 }
908 }
909
910 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
911 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
912 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
913 if self.changes.is_empty() {
914 return None;
915 }
916
917 let prev = self.position.unwrap_or(self.changes.len());
918 let next = if direction == Direction::Prev {
919 prev.saturating_sub(count)
920 } else {
921 (prev + count).min(self.changes.len() - 1)
922 };
923 self.position = Some(next);
924 self.changes.get(next).map(|change| change.locations())
925 }
926
927 /// Adds a new change to the list, resetting the change list position.
928 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
929 self.position.take();
930 if let Some(last) = self.changes.last_mut()
931 && group
932 {
933 last.current = Some(new_positions)
934 } else {
935 self.changes.push(ChangeLocation {
936 original: new_positions,
937 current: None,
938 });
939 }
940 }
941
942 pub fn last(&self) -> Option<&[Anchor]> {
943 self.changes.last().map(|change| change.locations())
944 }
945
946 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
947 self.changes.last().map(|change| change.original.as_slice())
948 }
949
950 pub fn invert_last_group(&mut self) {
951 if let Some(last) = self.changes.last_mut()
952 && let Some(current) = last.current.as_mut()
953 {
954 mem::swap(&mut last.original, current);
955 }
956 }
957}
958
959#[derive(Clone)]
960struct InlineBlamePopoverState {
961 scroll_handle: ScrollHandle,
962 commit_message: Option<ParsedCommitMessage>,
963 markdown: Entity<Markdown>,
964}
965
966struct InlineBlamePopover {
967 position: gpui::Point<Pixels>,
968 hide_task: Option<Task<()>>,
969 popover_bounds: Option<Bounds<Pixels>>,
970 popover_state: InlineBlamePopoverState,
971 keyboard_grace: bool,
972}
973
974enum SelectionDragState {
975 /// State when no drag related activity is detected.
976 None,
977 /// State when the mouse is down on a selection that is about to be dragged.
978 ReadyToDrag {
979 selection: Selection<Anchor>,
980 click_position: gpui::Point<Pixels>,
981 mouse_down_time: Instant,
982 },
983 /// State when the mouse is dragging the selection in the editor.
984 Dragging {
985 selection: Selection<Anchor>,
986 drop_cursor: Selection<Anchor>,
987 hide_drop_cursor: bool,
988 },
989}
990
991enum ColumnarSelectionState {
992 FromMouse {
993 selection_tail: Anchor,
994 display_point: Option<DisplayPoint>,
995 },
996 FromSelection {
997 selection_tail: Anchor,
998 },
999}
1000
1001/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1002/// a breakpoint on them.
1003#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1004struct PhantomBreakpointIndicator {
1005 display_row: DisplayRow,
1006 /// There's a small debounce between hovering over the line and showing the indicator.
1007 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1008 is_active: bool,
1009 collides_with_existing_breakpoint: bool,
1010}
1011
1012/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1013/// in diff view mode.
1014#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1015pub(crate) struct PhantomDiffReviewIndicator {
1016 /// The starting anchor of the selection (or the only row if not dragging).
1017 pub start: Anchor,
1018 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1019 pub end: Anchor,
1020 /// There's a small debounce between hovering over the line and showing the indicator.
1021 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1022 pub is_active: bool,
1023}
1024
1025#[derive(Clone, Debug)]
1026pub(crate) struct DiffReviewDragState {
1027 pub start_anchor: Anchor,
1028 pub current_anchor: Anchor,
1029}
1030
1031impl DiffReviewDragState {
1032 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1033 let start = self.start_anchor.to_display_point(snapshot).row();
1034 let current = self.current_anchor.to_display_point(snapshot).row();
1035
1036 (start..=current).sorted()
1037 }
1038}
1039
1040/// Identifies a specific hunk in the diff buffer.
1041/// Used as a key to group comments by their location.
1042#[derive(Clone, Debug)]
1043pub struct DiffHunkKey {
1044 /// The file path (relative to worktree) this hunk belongs to.
1045 pub file_path: Arc<util::rel_path::RelPath>,
1046 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1047 pub hunk_start_anchor: Anchor,
1048}
1049
1050/// A review comment stored locally before being sent to the Agent panel.
1051#[derive(Clone)]
1052pub struct StoredReviewComment {
1053 /// Unique identifier for this comment (for edit/delete operations).
1054 pub id: usize,
1055 /// The comment text entered by the user.
1056 pub comment: String,
1057 /// Anchors for the code range being reviewed.
1058 pub range: Range<Anchor>,
1059 /// Timestamp when the comment was created (for chronological ordering).
1060 pub created_at: Instant,
1061 /// Whether this comment is currently being edited inline.
1062 pub is_editing: bool,
1063}
1064
1065impl StoredReviewComment {
1066 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1067 Self {
1068 id,
1069 comment,
1070 range: anchor_range,
1071 created_at: Instant::now(),
1072 is_editing: false,
1073 }
1074 }
1075}
1076
1077/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1078pub(crate) struct DiffReviewOverlay {
1079 pub anchor_range: Range<Anchor>,
1080 /// The block ID for the overlay.
1081 pub block_id: CustomBlockId,
1082 /// The editor entity for the review input.
1083 pub prompt_editor: Entity<Editor>,
1084 /// The hunk key this overlay belongs to.
1085 pub hunk_key: DiffHunkKey,
1086 /// Whether the comments section is expanded.
1087 pub comments_expanded: bool,
1088 /// Editors for comments currently being edited inline.
1089 /// Key: comment ID, Value: Editor entity for inline editing.
1090 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1091 /// Subscriptions for inline edit editors' action handlers.
1092 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1093 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1094 /// The current user's avatar URI for display in comment rows.
1095 pub user_avatar_uri: Option<SharedUri>,
1096 /// Subscription to keep the action handler alive.
1097 _subscription: Subscription,
1098}
1099
1100/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1101///
1102/// See the [module level documentation](self) for more information.
1103pub struct Editor {
1104 focus_handle: FocusHandle,
1105 last_focused_descendant: Option<WeakFocusHandle>,
1106 /// The text buffer being edited
1107 buffer: Entity<MultiBuffer>,
1108 /// Map of how text in the buffer should be displayed.
1109 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1110 pub display_map: Entity<DisplayMap>,
1111 placeholder_display_map: Option<Entity<DisplayMap>>,
1112 pub selections: SelectionsCollection,
1113 pub scroll_manager: ScrollManager,
1114 /// When inline assist editors are linked, they all render cursors because
1115 /// typing enters text into each of them, even the ones that aren't focused.
1116 pub(crate) show_cursor_when_unfocused: bool,
1117 columnar_selection_state: Option<ColumnarSelectionState>,
1118 add_selections_state: Option<AddSelectionsState>,
1119 select_next_state: Option<SelectNextState>,
1120 select_prev_state: Option<SelectNextState>,
1121 selection_history: SelectionHistory,
1122 defer_selection_effects: bool,
1123 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1124 autoclose_regions: Vec<AutocloseRegion>,
1125 snippet_stack: InvalidationStack<SnippetState>,
1126 select_syntax_node_history: SelectSyntaxNodeHistory,
1127 ime_transaction: Option<TransactionId>,
1128 pub diagnostics_max_severity: DiagnosticSeverity,
1129 active_diagnostics: ActiveDiagnostic,
1130 show_inline_diagnostics: bool,
1131 inline_diagnostics_update: Task<()>,
1132 inline_diagnostics_enabled: bool,
1133 diagnostics_enabled: bool,
1134 word_completions_enabled: bool,
1135 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1136 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1137 hard_wrap: Option<usize>,
1138 project: Option<Entity<Project>>,
1139 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1140 completion_provider: Option<Rc<dyn CompletionProvider>>,
1141 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1142 blink_manager: Entity<BlinkManager>,
1143 show_cursor_names: bool,
1144 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1145 pub show_local_selections: bool,
1146 mode: EditorMode,
1147 show_breadcrumbs: bool,
1148 show_gutter: bool,
1149 show_scrollbars: ScrollbarAxes,
1150 minimap_visibility: MinimapVisibility,
1151 offset_content: bool,
1152 disable_expand_excerpt_buttons: bool,
1153 delegate_expand_excerpts: bool,
1154 delegate_stage_and_restore: bool,
1155 delegate_open_excerpts: bool,
1156 enable_lsp_data: bool,
1157 enable_runnables: bool,
1158 show_line_numbers: Option<bool>,
1159 use_relative_line_numbers: Option<bool>,
1160 show_git_diff_gutter: Option<bool>,
1161 show_code_actions: Option<bool>,
1162 show_runnables: Option<bool>,
1163 show_breakpoints: Option<bool>,
1164 show_diff_review_button: bool,
1165 show_wrap_guides: Option<bool>,
1166 show_indent_guides: Option<bool>,
1167 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1168 highlight_order: usize,
1169 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1170 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1171 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1172 scrollbar_marker_state: ScrollbarMarkerState,
1173 active_indent_guides_state: ActiveIndentGuidesState,
1174 nav_history: Option<ItemNavHistory>,
1175 context_menu: RefCell<Option<CodeContextMenu>>,
1176 context_menu_options: Option<ContextMenuOptions>,
1177 mouse_context_menu: Option<MouseContextMenu>,
1178 completion_tasks: Vec<(CompletionId, Task<()>)>,
1179 inline_blame_popover: Option<InlineBlamePopover>,
1180 inline_blame_popover_show_task: Option<Task<()>>,
1181 signature_help_state: SignatureHelpState,
1182 auto_signature_help: Option<bool>,
1183 find_all_references_task_sources: Vec<Anchor>,
1184 next_completion_id: CompletionId,
1185 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1186 code_actions_task: Option<Task<Result<()>>>,
1187 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1188 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1189 debounced_selection_highlight_complete: bool,
1190 document_highlights_task: Option<Task<()>>,
1191 linked_editing_range_task: Option<Task<Option<()>>>,
1192 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1193 pending_rename: Option<RenameState>,
1194 searchable: bool,
1195 cursor_shape: CursorShape,
1196 /// Whether the cursor is offset one character to the left when something is
1197 /// selected (needed for vim visual mode)
1198 cursor_offset_on_selection: bool,
1199 current_line_highlight: Option<CurrentLineHighlight>,
1200 /// Whether to collapse search match ranges to just their start position.
1201 /// When true, navigating to a match positions the cursor at the match
1202 /// without selecting the matched text.
1203 collapse_matches: bool,
1204 autoindent_mode: Option<AutoindentMode>,
1205 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1206 input_enabled: bool,
1207 expects_character_input: bool,
1208 use_modal_editing: bool,
1209 read_only: bool,
1210 leader_id: Option<CollaboratorId>,
1211 remote_id: Option<ViewId>,
1212 pub hover_state: HoverState,
1213 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1214 prev_pressure_stage: Option<PressureStage>,
1215 gutter_hovered: bool,
1216 hovered_link_state: Option<HoveredLinkState>,
1217 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1218 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1219 active_edit_prediction: Option<EditPredictionState>,
1220 /// Used to prevent flickering as the user types while the menu is open
1221 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1222 edit_prediction_settings: EditPredictionSettings,
1223 edit_predictions_hidden_for_vim_mode: bool,
1224 show_edit_predictions_override: Option<bool>,
1225 show_completions_on_input_override: Option<bool>,
1226 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1227 edit_prediction_preview: EditPredictionPreview,
1228 edit_prediction_indent_conflict: bool,
1229 edit_prediction_requires_modifier_in_indent_conflict: bool,
1230 next_inlay_id: usize,
1231 next_color_inlay_id: usize,
1232 _subscriptions: Vec<Subscription>,
1233 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1234 gutter_dimensions: GutterDimensions,
1235 style: Option<EditorStyle>,
1236 text_style_refinement: Option<TextStyleRefinement>,
1237 next_editor_action_id: EditorActionId,
1238 editor_actions: Rc<
1239 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1240 >,
1241 use_autoclose: bool,
1242 use_auto_surround: bool,
1243 auto_replace_emoji_shortcode: bool,
1244 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1245 show_git_blame_gutter: bool,
1246 show_git_blame_inline: bool,
1247 show_git_blame_inline_delay_task: Option<Task<()>>,
1248 git_blame_inline_enabled: bool,
1249 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1250 buffer_serialization: Option<BufferSerialization>,
1251 show_selection_menu: Option<bool>,
1252 blame: Option<Entity<GitBlame>>,
1253 blame_subscription: Option<Subscription>,
1254 custom_context_menu: Option<
1255 Box<
1256 dyn 'static
1257 + Fn(
1258 &mut Self,
1259 DisplayPoint,
1260 &mut Window,
1261 &mut Context<Self>,
1262 ) -> Option<Entity<ui::ContextMenu>>,
1263 >,
1264 >,
1265 last_bounds: Option<Bounds<Pixels>>,
1266 last_position_map: Option<Rc<PositionMap>>,
1267 expect_bounds_change: Option<Bounds<Pixels>>,
1268 runnables: RunnableData,
1269 breakpoint_store: Option<Entity<BreakpointStore>>,
1270 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1271 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1272 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1273 /// Active diff review overlays. Multiple overlays can be open simultaneously
1274 /// when hunks have comments stored.
1275 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1276 /// Stored review comments grouped by hunk.
1277 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1278 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1279 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1280 /// Counter for generating unique comment IDs.
1281 next_review_comment_id: usize,
1282 hovered_diff_hunk_row: Option<DisplayRow>,
1283 pull_diagnostics_task: Task<()>,
1284 in_project_search: bool,
1285 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1286 breadcrumb_header: Option<String>,
1287 focused_block: Option<FocusedBlock>,
1288 next_scroll_position: NextScrollCursorCenterTopBottom,
1289 addons: HashMap<TypeId, Box<dyn Addon>>,
1290 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1291 load_diff_task: Option<Shared<Task<()>>>,
1292 /// Whether we are temporarily displaying a diff other than git's
1293 temporary_diff_override: bool,
1294 selection_mark_mode: bool,
1295 toggle_fold_multiple_buffers: Task<()>,
1296 _scroll_cursor_center_top_bottom_task: Task<()>,
1297 serialize_selections: Task<()>,
1298 serialize_folds: Task<()>,
1299 mouse_cursor_hidden: bool,
1300 minimap: Option<Entity<Self>>,
1301 hide_mouse_mode: HideMouseMode,
1302 pub change_list: ChangeList,
1303 inline_value_cache: InlineValueCache,
1304 number_deleted_lines: bool,
1305
1306 selection_drag_state: SelectionDragState,
1307 colors: Option<LspColorData>,
1308 post_scroll_update: Task<()>,
1309 refresh_colors_task: Task<()>,
1310 use_document_folding_ranges: bool,
1311 refresh_folding_ranges_task: Task<()>,
1312 inlay_hints: Option<LspInlayHintData>,
1313 folding_newlines: Task<()>,
1314 select_next_is_case_sensitive: Option<bool>,
1315 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1316 on_local_selections_changed:
1317 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1318 suppress_selection_callback: bool,
1319 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1320 accent_data: Option<AccentData>,
1321 bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1322 semantic_token_state: SemanticTokenState,
1323 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1324 refresh_document_symbols_task: Shared<Task<()>>,
1325 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1326 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1327 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1328 sticky_headers_task: Task<()>,
1329 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1330 pub(crate) colorize_brackets_task: Task<()>,
1331}
1332
1333#[derive(Debug, PartialEq)]
1334struct AccentData {
1335 colors: AccentColors,
1336 overrides: Vec<SharedString>,
1337}
1338
1339fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1340 if debounce_ms > 0 {
1341 Some(Duration::from_millis(debounce_ms))
1342 } else {
1343 None
1344 }
1345}
1346
1347#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1348enum NextScrollCursorCenterTopBottom {
1349 #[default]
1350 Center,
1351 Top,
1352 Bottom,
1353}
1354
1355impl NextScrollCursorCenterTopBottom {
1356 fn next(&self) -> Self {
1357 match self {
1358 Self::Center => Self::Top,
1359 Self::Top => Self::Bottom,
1360 Self::Bottom => Self::Center,
1361 }
1362 }
1363}
1364
1365#[derive(Clone)]
1366pub struct EditorSnapshot {
1367 pub mode: EditorMode,
1368 show_gutter: bool,
1369 offset_content: bool,
1370 show_line_numbers: Option<bool>,
1371 number_deleted_lines: bool,
1372 show_git_diff_gutter: Option<bool>,
1373 show_code_actions: Option<bool>,
1374 show_runnables: Option<bool>,
1375 show_breakpoints: Option<bool>,
1376 git_blame_gutter_max_author_length: Option<usize>,
1377 pub display_snapshot: DisplaySnapshot,
1378 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1379 is_focused: bool,
1380 scroll_anchor: SharedScrollAnchor,
1381 ongoing_scroll: OngoingScroll,
1382 current_line_highlight: CurrentLineHighlight,
1383 gutter_hovered: bool,
1384 semantic_tokens_enabled: bool,
1385}
1386
1387#[derive(Default, Debug, Clone, Copy)]
1388pub struct GutterDimensions {
1389 pub left_padding: Pixels,
1390 pub right_padding: Pixels,
1391 pub width: Pixels,
1392 pub margin: Pixels,
1393 pub git_blame_entries_width: Option<Pixels>,
1394}
1395
1396impl GutterDimensions {
1397 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1398 Self {
1399 margin: Self::default_gutter_margin(font_id, font_size, cx),
1400 ..Default::default()
1401 }
1402 }
1403
1404 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1405 -cx.text_system().descent(font_id, font_size)
1406 }
1407 /// The full width of the space taken up by the gutter.
1408 pub fn full_width(&self) -> Pixels {
1409 self.margin + self.width
1410 }
1411
1412 /// The width of the space reserved for the fold indicators,
1413 /// use alongside 'justify_end' and `gutter_width` to
1414 /// right align content with the line numbers
1415 pub fn fold_area_width(&self) -> Pixels {
1416 self.margin + self.right_padding
1417 }
1418}
1419
1420struct CharacterDimensions {
1421 em_width: Pixels,
1422 em_advance: Pixels,
1423 line_height: Pixels,
1424}
1425
1426#[derive(Debug)]
1427pub struct RemoteSelection {
1428 pub replica_id: ReplicaId,
1429 pub selection: Selection<Anchor>,
1430 pub cursor_shape: CursorShape,
1431 pub collaborator_id: CollaboratorId,
1432 pub line_mode: bool,
1433 pub user_name: Option<SharedString>,
1434 pub color: PlayerColor,
1435}
1436
1437#[derive(Clone, Debug)]
1438struct SelectionHistoryEntry {
1439 selections: Arc<[Selection<Anchor>]>,
1440 select_next_state: Option<SelectNextState>,
1441 select_prev_state: Option<SelectNextState>,
1442 add_selections_state: Option<AddSelectionsState>,
1443}
1444
1445#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1446enum SelectionHistoryMode {
1447 #[default]
1448 Normal,
1449 Undoing,
1450 Redoing,
1451 Skipping,
1452}
1453
1454#[derive(Clone, PartialEq, Eq, Hash)]
1455struct HoveredCursor {
1456 replica_id: ReplicaId,
1457 selection_id: usize,
1458}
1459
1460#[derive(Debug)]
1461/// SelectionEffects controls the side-effects of updating the selection.
1462///
1463/// The default behaviour does "what you mostly want":
1464/// - it pushes to the nav history if the cursor moved by >10 lines
1465/// - it re-triggers completion requests
1466/// - it scrolls to fit
1467///
1468/// You might want to modify these behaviours. For example when doing a "jump"
1469/// like go to definition, we always want to add to nav history; but when scrolling
1470/// in vim mode we never do.
1471///
1472/// Similarly, you might want to disable scrolling if you don't want the viewport to
1473/// move.
1474#[derive(Clone)]
1475pub struct SelectionEffects {
1476 nav_history: Option<bool>,
1477 completions: bool,
1478 scroll: Option<Autoscroll>,
1479}
1480
1481impl Default for SelectionEffects {
1482 fn default() -> Self {
1483 Self {
1484 nav_history: None,
1485 completions: true,
1486 scroll: Some(Autoscroll::fit()),
1487 }
1488 }
1489}
1490impl SelectionEffects {
1491 pub fn scroll(scroll: Autoscroll) -> Self {
1492 Self {
1493 scroll: Some(scroll),
1494 ..Default::default()
1495 }
1496 }
1497
1498 pub fn no_scroll() -> Self {
1499 Self {
1500 scroll: None,
1501 ..Default::default()
1502 }
1503 }
1504
1505 pub fn completions(self, completions: bool) -> Self {
1506 Self {
1507 completions,
1508 ..self
1509 }
1510 }
1511
1512 pub fn nav_history(self, nav_history: bool) -> Self {
1513 Self {
1514 nav_history: Some(nav_history),
1515 ..self
1516 }
1517 }
1518}
1519
1520struct DeferredSelectionEffectsState {
1521 changed: bool,
1522 effects: SelectionEffects,
1523 old_cursor_position: Anchor,
1524 history_entry: SelectionHistoryEntry,
1525}
1526
1527#[derive(Default)]
1528struct SelectionHistory {
1529 #[allow(clippy::type_complexity)]
1530 selections_by_transaction:
1531 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1532 mode: SelectionHistoryMode,
1533 undo_stack: VecDeque<SelectionHistoryEntry>,
1534 redo_stack: VecDeque<SelectionHistoryEntry>,
1535}
1536
1537impl SelectionHistory {
1538 #[track_caller]
1539 fn insert_transaction(
1540 &mut self,
1541 transaction_id: TransactionId,
1542 selections: Arc<[Selection<Anchor>]>,
1543 ) {
1544 if selections.is_empty() {
1545 log::error!(
1546 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1547 std::panic::Location::caller()
1548 );
1549 return;
1550 }
1551 self.selections_by_transaction
1552 .insert(transaction_id, (selections, None));
1553 }
1554
1555 #[allow(clippy::type_complexity)]
1556 fn transaction(
1557 &self,
1558 transaction_id: TransactionId,
1559 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1560 self.selections_by_transaction.get(&transaction_id)
1561 }
1562
1563 #[allow(clippy::type_complexity)]
1564 fn transaction_mut(
1565 &mut self,
1566 transaction_id: TransactionId,
1567 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1568 self.selections_by_transaction.get_mut(&transaction_id)
1569 }
1570
1571 fn push(&mut self, entry: SelectionHistoryEntry) {
1572 if !entry.selections.is_empty() {
1573 match self.mode {
1574 SelectionHistoryMode::Normal => {
1575 self.push_undo(entry);
1576 self.redo_stack.clear();
1577 }
1578 SelectionHistoryMode::Undoing => self.push_redo(entry),
1579 SelectionHistoryMode::Redoing => self.push_undo(entry),
1580 SelectionHistoryMode::Skipping => {}
1581 }
1582 }
1583 }
1584
1585 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1586 if self
1587 .undo_stack
1588 .back()
1589 .is_none_or(|e| e.selections != entry.selections)
1590 {
1591 self.undo_stack.push_back(entry);
1592 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1593 self.undo_stack.pop_front();
1594 }
1595 }
1596 }
1597
1598 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1599 if self
1600 .redo_stack
1601 .back()
1602 .is_none_or(|e| e.selections != entry.selections)
1603 {
1604 self.redo_stack.push_back(entry);
1605 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1606 self.redo_stack.pop_front();
1607 }
1608 }
1609 }
1610}
1611
1612#[derive(Clone, Copy)]
1613pub struct RowHighlightOptions {
1614 pub autoscroll: bool,
1615 pub include_gutter: bool,
1616}
1617
1618impl Default for RowHighlightOptions {
1619 fn default() -> Self {
1620 Self {
1621 autoscroll: Default::default(),
1622 include_gutter: true,
1623 }
1624 }
1625}
1626
1627struct RowHighlight {
1628 index: usize,
1629 range: Range<Anchor>,
1630 color: Hsla,
1631 options: RowHighlightOptions,
1632 type_id: TypeId,
1633}
1634
1635#[derive(Clone, Debug)]
1636struct AddSelectionsState {
1637 groups: Vec<AddSelectionsGroup>,
1638}
1639
1640#[derive(Clone, Debug)]
1641struct AddSelectionsGroup {
1642 above: bool,
1643 stack: Vec<usize>,
1644}
1645
1646#[derive(Clone)]
1647struct SelectNextState {
1648 query: AhoCorasick,
1649 wordwise: bool,
1650 done: bool,
1651}
1652
1653impl std::fmt::Debug for SelectNextState {
1654 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1655 f.debug_struct(std::any::type_name::<Self>())
1656 .field("wordwise", &self.wordwise)
1657 .field("done", &self.done)
1658 .finish()
1659 }
1660}
1661
1662#[derive(Debug)]
1663struct AutocloseRegion {
1664 selection_id: usize,
1665 range: Range<Anchor>,
1666 pair: BracketPair,
1667}
1668
1669#[derive(Debug)]
1670struct SnippetState {
1671 ranges: Vec<Vec<Range<Anchor>>>,
1672 active_index: usize,
1673 choices: Vec<Option<Vec<String>>>,
1674}
1675
1676#[doc(hidden)]
1677pub struct RenameState {
1678 pub range: Range<Anchor>,
1679 pub old_name: Arc<str>,
1680 pub editor: Entity<Editor>,
1681 block_id: CustomBlockId,
1682}
1683
1684struct InvalidationStack<T>(Vec<T>);
1685
1686struct RegisteredEditPredictionDelegate {
1687 provider: Arc<dyn EditPredictionDelegateHandle>,
1688 _subscription: Subscription,
1689}
1690
1691#[derive(Debug, PartialEq, Eq)]
1692pub struct ActiveDiagnosticGroup {
1693 pub active_range: Range<Anchor>,
1694 pub active_message: String,
1695 pub group_id: usize,
1696 pub blocks: HashSet<CustomBlockId>,
1697}
1698
1699#[derive(Debug, PartialEq, Eq)]
1700
1701pub(crate) enum ActiveDiagnostic {
1702 None,
1703 All,
1704 Group(ActiveDiagnosticGroup),
1705}
1706
1707#[derive(Serialize, Deserialize, Clone, Debug)]
1708pub struct ClipboardSelection {
1709 /// The number of bytes in this selection.
1710 pub len: usize,
1711 /// Whether this was a full-line selection.
1712 pub is_entire_line: bool,
1713 /// The indentation of the first line when this content was originally copied.
1714 pub first_line_indent: u32,
1715 #[serde(default)]
1716 pub file_path: Option<PathBuf>,
1717 #[serde(default)]
1718 pub line_range: Option<RangeInclusive<u32>>,
1719}
1720
1721impl ClipboardSelection {
1722 pub fn for_buffer(
1723 len: usize,
1724 is_entire_line: bool,
1725 range: Range<Point>,
1726 buffer: &MultiBufferSnapshot,
1727 project: Option<&Entity<Project>>,
1728 cx: &App,
1729 ) -> Self {
1730 let first_line_indent = buffer
1731 .indent_size_for_line(MultiBufferRow(range.start.row))
1732 .len;
1733
1734 let file_path = util::maybe!({
1735 let project = project?.read(cx);
1736 let file = buffer.file_at(range.start)?;
1737 let project_path = ProjectPath {
1738 worktree_id: file.worktree_id(cx),
1739 path: file.path().clone(),
1740 };
1741 project.absolute_path(&project_path, cx)
1742 });
1743
1744 let line_range = file_path.as_ref().and_then(|_| {
1745 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1746 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1747 if start_excerpt_id == end_excerpt_id {
1748 Some(start_point.row..=end_point.row)
1749 } else {
1750 None
1751 }
1752 });
1753
1754 Self {
1755 len,
1756 is_entire_line,
1757 first_line_indent,
1758 file_path,
1759 line_range,
1760 }
1761 }
1762}
1763
1764// selections, scroll behavior, was newest selection reversed
1765type SelectSyntaxNodeHistoryState = (
1766 Box<[Selection<Anchor>]>,
1767 SelectSyntaxNodeScrollBehavior,
1768 bool,
1769);
1770
1771#[derive(Default)]
1772struct SelectSyntaxNodeHistory {
1773 stack: Vec<SelectSyntaxNodeHistoryState>,
1774 // disable temporarily to allow changing selections without losing the stack
1775 pub disable_clearing: bool,
1776}
1777
1778impl SelectSyntaxNodeHistory {
1779 pub fn try_clear(&mut self) {
1780 if !self.disable_clearing {
1781 self.stack.clear();
1782 }
1783 }
1784
1785 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1786 self.stack.push(selection);
1787 }
1788
1789 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1790 self.stack.pop()
1791 }
1792}
1793
1794enum SelectSyntaxNodeScrollBehavior {
1795 CursorTop,
1796 FitSelection,
1797 CursorBottom,
1798}
1799
1800#[derive(Debug, Clone, Copy)]
1801pub(crate) struct NavigationData {
1802 cursor_anchor: Anchor,
1803 cursor_position: Point,
1804 scroll_anchor: ScrollAnchor,
1805 scroll_top_row: u32,
1806}
1807
1808#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1809pub enum GotoDefinitionKind {
1810 Symbol,
1811 Declaration,
1812 Type,
1813 Implementation,
1814}
1815
1816pub enum FormatTarget {
1817 Buffers(HashSet<Entity<Buffer>>),
1818 Ranges(Vec<Range<MultiBufferPoint>>),
1819}
1820
1821pub(crate) struct FocusedBlock {
1822 id: BlockId,
1823 focus_handle: WeakFocusHandle,
1824}
1825
1826#[derive(Clone, Debug)]
1827pub enum JumpData {
1828 MultiBufferRow {
1829 row: MultiBufferRow,
1830 line_offset_from_top: u32,
1831 },
1832 MultiBufferPoint {
1833 excerpt_id: ExcerptId,
1834 position: Point,
1835 anchor: text::Anchor,
1836 line_offset_from_top: u32,
1837 },
1838}
1839
1840pub enum MultibufferSelectionMode {
1841 First,
1842 All,
1843}
1844
1845#[derive(Clone, Copy, Debug, Default)]
1846pub struct RewrapOptions {
1847 pub override_language_settings: bool,
1848 pub preserve_existing_whitespace: bool,
1849}
1850
1851impl Editor {
1852 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1853 let buffer = cx.new(|cx| Buffer::local("", cx));
1854 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1855 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1856 }
1857
1858 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1859 let buffer = cx.new(|cx| Buffer::local("", cx));
1860 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1861 Self::new(EditorMode::full(), buffer, None, window, cx)
1862 }
1863
1864 pub fn auto_height(
1865 min_lines: usize,
1866 max_lines: usize,
1867 window: &mut Window,
1868 cx: &mut Context<Self>,
1869 ) -> Self {
1870 let buffer = cx.new(|cx| Buffer::local("", cx));
1871 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1872 Self::new(
1873 EditorMode::AutoHeight {
1874 min_lines,
1875 max_lines: Some(max_lines),
1876 },
1877 buffer,
1878 None,
1879 window,
1880 cx,
1881 )
1882 }
1883
1884 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1885 /// The editor grows as tall as needed to fit its content.
1886 pub fn auto_height_unbounded(
1887 min_lines: usize,
1888 window: &mut Window,
1889 cx: &mut Context<Self>,
1890 ) -> Self {
1891 let buffer = cx.new(|cx| Buffer::local("", cx));
1892 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1893 Self::new(
1894 EditorMode::AutoHeight {
1895 min_lines,
1896 max_lines: None,
1897 },
1898 buffer,
1899 None,
1900 window,
1901 cx,
1902 )
1903 }
1904
1905 pub fn for_buffer(
1906 buffer: Entity<Buffer>,
1907 project: Option<Entity<Project>>,
1908 window: &mut Window,
1909 cx: &mut Context<Self>,
1910 ) -> Self {
1911 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1912 Self::new(EditorMode::full(), buffer, project, window, cx)
1913 }
1914
1915 pub fn for_multibuffer(
1916 buffer: Entity<MultiBuffer>,
1917 project: Option<Entity<Project>>,
1918 window: &mut Window,
1919 cx: &mut Context<Self>,
1920 ) -> Self {
1921 Self::new(EditorMode::full(), buffer, project, window, cx)
1922 }
1923
1924 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1925 let mut clone = Self::new(
1926 self.mode.clone(),
1927 self.buffer.clone(),
1928 self.project.clone(),
1929 window,
1930 cx,
1931 );
1932 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1933 let snapshot = display_map.snapshot(cx);
1934 clone.display_map.update(cx, |display_map, cx| {
1935 display_map.set_state(&snapshot, cx);
1936 });
1937 snapshot
1938 });
1939 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1940 clone.folds_did_change(cx);
1941 clone.selections.clone_state(&self.selections);
1942 clone
1943 .scroll_manager
1944 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1945 clone.searchable = self.searchable;
1946 clone.read_only = self.read_only;
1947 clone.buffers_with_disabled_indent_guides =
1948 self.buffers_with_disabled_indent_guides.clone();
1949 clone
1950 }
1951
1952 pub fn new(
1953 mode: EditorMode,
1954 buffer: Entity<MultiBuffer>,
1955 project: Option<Entity<Project>>,
1956 window: &mut Window,
1957 cx: &mut Context<Self>,
1958 ) -> Self {
1959 Editor::new_internal(mode, buffer, project, None, window, cx)
1960 }
1961
1962 pub fn refresh_sticky_headers(
1963 &mut self,
1964 display_snapshot: &DisplaySnapshot,
1965 cx: &mut Context<Editor>,
1966 ) {
1967 if !self.mode.is_full() {
1968 return;
1969 }
1970 let multi_buffer = display_snapshot.buffer_snapshot();
1971 let scroll_anchor = self
1972 .scroll_manager
1973 .native_anchor(display_snapshot, cx)
1974 .anchor;
1975 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
1976 return;
1977 };
1978 let buffer = buffer.clone();
1979
1980 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
1981 let max_row = buffer.max_point().row;
1982 let start_row = buffer_visible_start.row.min(max_row);
1983 let end_row = (buffer_visible_start.row + 10).min(max_row);
1984
1985 let syntax = self.style(cx).syntax.clone();
1986 let background_task = cx.background_spawn(async move {
1987 buffer
1988 .outline_items_containing(
1989 Point::new(start_row, 0)..Point::new(end_row, 0),
1990 true,
1991 Some(syntax.as_ref()),
1992 )
1993 .into_iter()
1994 .map(|outline_item| OutlineItem {
1995 depth: outline_item.depth,
1996 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
1997 source_range_for_text: Anchor::range_in_buffer(
1998 excerpt_id,
1999 outline_item.source_range_for_text,
2000 ),
2001 text: outline_item.text,
2002 highlight_ranges: outline_item.highlight_ranges,
2003 name_ranges: outline_item.name_ranges,
2004 body_range: outline_item
2005 .body_range
2006 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2007 annotation_range: outline_item
2008 .annotation_range
2009 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2010 })
2011 .collect()
2012 });
2013 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2014 let sticky_headers = background_task.await;
2015 this.update(cx, |this, cx| {
2016 this.sticky_headers = Some(sticky_headers);
2017 cx.notify();
2018 })
2019 .ok();
2020 });
2021 }
2022
2023 fn new_internal(
2024 mode: EditorMode,
2025 multi_buffer: Entity<MultiBuffer>,
2026 project: Option<Entity<Project>>,
2027 display_map: Option<Entity<DisplayMap>>,
2028 window: &mut Window,
2029 cx: &mut Context<Self>,
2030 ) -> Self {
2031 debug_assert!(
2032 display_map.is_none() || mode.is_minimap(),
2033 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2034 );
2035
2036 let full_mode = mode.is_full();
2037 let is_minimap = mode.is_minimap();
2038 let diagnostics_max_severity = if full_mode {
2039 EditorSettings::get_global(cx)
2040 .diagnostics_max_severity
2041 .unwrap_or(DiagnosticSeverity::Hint)
2042 } else {
2043 DiagnosticSeverity::Off
2044 };
2045 let style = window.text_style();
2046 let font_size = style.font_size.to_pixels(window.rem_size());
2047 let editor = cx.entity().downgrade();
2048 let fold_placeholder = FoldPlaceholder {
2049 constrain_width: false,
2050 render: Arc::new(move |fold_id, fold_range, cx| {
2051 let editor = editor.clone();
2052 FoldPlaceholder::fold_element(fold_id, cx)
2053 .cursor_pointer()
2054 .child("⋯")
2055 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2056 .on_click(move |_, _window, cx| {
2057 editor
2058 .update(cx, |editor, cx| {
2059 editor.unfold_ranges(
2060 &[fold_range.start..fold_range.end],
2061 true,
2062 false,
2063 cx,
2064 );
2065 cx.stop_propagation();
2066 })
2067 .ok();
2068 })
2069 .into_any()
2070 }),
2071 merge_adjacent: true,
2072 ..FoldPlaceholder::default()
2073 };
2074 let display_map = display_map.unwrap_or_else(|| {
2075 cx.new(|cx| {
2076 DisplayMap::new(
2077 multi_buffer.clone(),
2078 style.font(),
2079 font_size,
2080 None,
2081 FILE_HEADER_HEIGHT,
2082 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2083 fold_placeholder,
2084 diagnostics_max_severity,
2085 cx,
2086 )
2087 })
2088 });
2089
2090 let selections = SelectionsCollection::new();
2091
2092 let blink_manager = cx.new(|cx| {
2093 let mut blink_manager = BlinkManager::new(
2094 CURSOR_BLINK_INTERVAL,
2095 |cx| EditorSettings::get_global(cx).cursor_blink,
2096 cx,
2097 );
2098 if is_minimap {
2099 blink_manager.disable(cx);
2100 }
2101 blink_manager
2102 });
2103
2104 let soft_wrap_mode_override =
2105 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2106
2107 let mut project_subscriptions = Vec::new();
2108 if full_mode && let Some(project) = project.as_ref() {
2109 project_subscriptions.push(cx.subscribe_in(
2110 project,
2111 window,
2112 |editor, _, event, window, cx| match event {
2113 project::Event::RefreshCodeLens => {
2114 // we always query lens with actions, without storing them, always refreshing them
2115 }
2116 project::Event::RefreshInlayHints {
2117 server_id,
2118 request_id,
2119 } => {
2120 editor.refresh_inlay_hints(
2121 InlayHintRefreshReason::RefreshRequested {
2122 server_id: *server_id,
2123 request_id: *request_id,
2124 },
2125 cx,
2126 );
2127 }
2128 project::Event::RefreshSemanticTokens {
2129 server_id,
2130 request_id,
2131 } => {
2132 editor.refresh_semantic_tokens(
2133 None,
2134 Some(RefreshForServer {
2135 server_id: *server_id,
2136 request_id: *request_id,
2137 }),
2138 cx,
2139 );
2140 }
2141 project::Event::LanguageServerRemoved(_) => {
2142 editor.registered_buffers.clear();
2143 editor.register_visible_buffers(cx);
2144 editor.invalidate_semantic_tokens(None);
2145 editor.refresh_runnables(None, window, cx);
2146 editor.update_lsp_data(None, window, cx);
2147 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2148 }
2149 project::Event::SnippetEdit(id, snippet_edits) => {
2150 // todo(lw): Non singletons
2151 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2152 let snapshot = buffer.read(cx).snapshot();
2153 let focus_handle = editor.focus_handle(cx);
2154 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2155 for (range, snippet) in snippet_edits {
2156 let buffer_range =
2157 language::range_from_lsp(*range).to_offset(&snapshot);
2158 editor
2159 .insert_snippet(
2160 &[MultiBufferOffset(buffer_range.start)
2161 ..MultiBufferOffset(buffer_range.end)],
2162 snippet.clone(),
2163 window,
2164 cx,
2165 )
2166 .ok();
2167 }
2168 }
2169 }
2170 }
2171 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2172 let buffer_id = *buffer_id;
2173 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2174 editor.register_buffer(buffer_id, cx);
2175 editor.refresh_runnables(Some(buffer_id), window, cx);
2176 editor.update_lsp_data(Some(buffer_id), window, cx);
2177 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2178 refresh_linked_ranges(editor, window, cx);
2179 editor.refresh_code_actions(window, cx);
2180 editor.refresh_document_highlights(cx);
2181 }
2182 }
2183
2184 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2185 let Some(workspace) = editor.workspace() else {
2186 return;
2187 };
2188 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2189 else {
2190 return;
2191 };
2192
2193 if active_editor.entity_id() == cx.entity_id() {
2194 let entity_id = cx.entity_id();
2195 workspace.update(cx, |this, cx| {
2196 this.panes_mut()
2197 .iter_mut()
2198 .filter(|pane| pane.entity_id() != entity_id)
2199 .for_each(|p| {
2200 p.update(cx, |pane, _| {
2201 pane.nav_history_mut().rename_item(
2202 entity_id,
2203 project_path.clone(),
2204 abs_path.clone().into(),
2205 );
2206 })
2207 });
2208 });
2209
2210 Self::open_transaction_for_hidden_buffers(
2211 workspace,
2212 transaction.clone(),
2213 "Rename".to_string(),
2214 window,
2215 cx,
2216 );
2217 }
2218 }
2219
2220 project::Event::WorkspaceEditApplied(transaction) => {
2221 let Some(workspace) = editor.workspace() else {
2222 return;
2223 };
2224 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2225 else {
2226 return;
2227 };
2228
2229 if active_editor.entity_id() == cx.entity_id() {
2230 Self::open_transaction_for_hidden_buffers(
2231 workspace,
2232 transaction.clone(),
2233 "LSP Edit".to_string(),
2234 window,
2235 cx,
2236 );
2237 }
2238 }
2239
2240 _ => {}
2241 },
2242 ));
2243 if let Some(task_inventory) = project
2244 .read(cx)
2245 .task_store()
2246 .read(cx)
2247 .task_inventory()
2248 .cloned()
2249 {
2250 project_subscriptions.push(cx.observe_in(
2251 &task_inventory,
2252 window,
2253 |editor, _, window, cx| {
2254 editor.refresh_runnables(None, window, cx);
2255 },
2256 ));
2257 };
2258
2259 project_subscriptions.push(cx.subscribe_in(
2260 &project.read(cx).breakpoint_store(),
2261 window,
2262 |editor, _, event, window, cx| match event {
2263 BreakpointStoreEvent::ClearDebugLines => {
2264 editor.clear_row_highlights::<ActiveDebugLine>();
2265 editor.refresh_inline_values(cx);
2266 }
2267 BreakpointStoreEvent::SetDebugLine => {
2268 if editor.go_to_active_debug_line(window, cx) {
2269 cx.stop_propagation();
2270 }
2271
2272 editor.refresh_inline_values(cx);
2273 }
2274 _ => {}
2275 },
2276 ));
2277 let git_store = project.read(cx).git_store().clone();
2278 let project = project.clone();
2279 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2280 if let GitStoreEvent::RepositoryAdded = event {
2281 this.load_diff_task = Some(
2282 update_uncommitted_diff_for_buffer(
2283 cx.entity(),
2284 &project,
2285 this.buffer.read(cx).all_buffers(),
2286 this.buffer.clone(),
2287 cx,
2288 )
2289 .shared(),
2290 );
2291 }
2292 }));
2293 }
2294
2295 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2296
2297 let inlay_hint_settings =
2298 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2299 let focus_handle = cx.focus_handle();
2300 if !is_minimap {
2301 cx.on_focus(&focus_handle, window, Self::handle_focus)
2302 .detach();
2303 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2304 .detach();
2305 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2306 .detach();
2307 cx.on_blur(&focus_handle, window, Self::handle_blur)
2308 .detach();
2309 cx.observe_pending_input(window, Self::observe_pending_input)
2310 .detach();
2311 }
2312
2313 let show_indent_guides =
2314 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2315 Some(false)
2316 } else {
2317 None
2318 };
2319
2320 let breakpoint_store = match (&mode, project.as_ref()) {
2321 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2322 _ => None,
2323 };
2324
2325 let mut code_action_providers = Vec::new();
2326 let mut load_uncommitted_diff = None;
2327 if let Some(project) = project.clone() {
2328 load_uncommitted_diff = Some(
2329 update_uncommitted_diff_for_buffer(
2330 cx.entity(),
2331 &project,
2332 multi_buffer.read(cx).all_buffers(),
2333 multi_buffer.clone(),
2334 cx,
2335 )
2336 .shared(),
2337 );
2338 code_action_providers.push(Rc::new(project) as Rc<_>);
2339 }
2340
2341 let mut editor = Self {
2342 focus_handle,
2343 show_cursor_when_unfocused: false,
2344 last_focused_descendant: None,
2345 buffer: multi_buffer.clone(),
2346 display_map: display_map.clone(),
2347 placeholder_display_map: None,
2348 selections,
2349 scroll_manager: ScrollManager::new(cx),
2350 columnar_selection_state: None,
2351 add_selections_state: None,
2352 select_next_state: None,
2353 select_prev_state: None,
2354 selection_history: SelectionHistory::default(),
2355 defer_selection_effects: false,
2356 deferred_selection_effects_state: None,
2357 autoclose_regions: Vec::new(),
2358 snippet_stack: InvalidationStack::default(),
2359 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2360 ime_transaction: None,
2361 active_diagnostics: ActiveDiagnostic::None,
2362 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2363 inline_diagnostics_update: Task::ready(()),
2364 inline_diagnostics: Vec::new(),
2365 soft_wrap_mode_override,
2366 diagnostics_max_severity,
2367 hard_wrap: None,
2368 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2369 semantics_provider: project
2370 .as_ref()
2371 .map(|project| Rc::new(project.downgrade()) as _),
2372 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2373 project,
2374 blink_manager: blink_manager.clone(),
2375 show_local_selections: true,
2376 show_scrollbars: ScrollbarAxes {
2377 horizontal: full_mode,
2378 vertical: full_mode,
2379 },
2380 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2381 offset_content: !matches!(mode, EditorMode::SingleLine),
2382 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2383 show_gutter: full_mode,
2384 show_line_numbers: (!full_mode).then_some(false),
2385 use_relative_line_numbers: None,
2386 disable_expand_excerpt_buttons: !full_mode,
2387 delegate_expand_excerpts: false,
2388 delegate_stage_and_restore: false,
2389 delegate_open_excerpts: false,
2390 enable_lsp_data: true,
2391 enable_runnables: true,
2392 show_git_diff_gutter: None,
2393 show_code_actions: None,
2394 show_runnables: None,
2395 show_breakpoints: None,
2396 show_diff_review_button: false,
2397 show_wrap_guides: None,
2398 show_indent_guides,
2399 buffers_with_disabled_indent_guides: HashSet::default(),
2400 highlight_order: 0,
2401 highlighted_rows: HashMap::default(),
2402 background_highlights: HashMap::default(),
2403 gutter_highlights: HashMap::default(),
2404 scrollbar_marker_state: ScrollbarMarkerState::default(),
2405 active_indent_guides_state: ActiveIndentGuidesState::default(),
2406 nav_history: None,
2407 context_menu: RefCell::new(None),
2408 context_menu_options: None,
2409 mouse_context_menu: None,
2410 completion_tasks: Vec::new(),
2411 inline_blame_popover: None,
2412 inline_blame_popover_show_task: None,
2413 signature_help_state: SignatureHelpState::default(),
2414 auto_signature_help: None,
2415 find_all_references_task_sources: Vec::new(),
2416 next_completion_id: 0,
2417 next_inlay_id: 0,
2418 code_action_providers,
2419 available_code_actions: None,
2420 code_actions_task: None,
2421 quick_selection_highlight_task: None,
2422 debounced_selection_highlight_task: None,
2423 debounced_selection_highlight_complete: false,
2424 document_highlights_task: None,
2425 linked_editing_range_task: None,
2426 pending_rename: None,
2427 searchable: !is_minimap,
2428 cursor_shape: EditorSettings::get_global(cx)
2429 .cursor_shape
2430 .unwrap_or_default(),
2431 cursor_offset_on_selection: false,
2432 current_line_highlight: None,
2433 autoindent_mode: Some(AutoindentMode::EachLine),
2434 collapse_matches: false,
2435 workspace: None,
2436 input_enabled: !is_minimap,
2437 expects_character_input: !is_minimap,
2438 use_modal_editing: full_mode,
2439 read_only: is_minimap,
2440 use_autoclose: true,
2441 use_auto_surround: true,
2442 auto_replace_emoji_shortcode: false,
2443 jsx_tag_auto_close_enabled_in_any_buffer: false,
2444 leader_id: None,
2445 remote_id: None,
2446 hover_state: HoverState::default(),
2447 pending_mouse_down: None,
2448 prev_pressure_stage: None,
2449 hovered_link_state: None,
2450 edit_prediction_provider: None,
2451 active_edit_prediction: None,
2452 stale_edit_prediction_in_menu: None,
2453 edit_prediction_preview: EditPredictionPreview::Inactive {
2454 released_too_fast: false,
2455 },
2456 inline_diagnostics_enabled: full_mode,
2457 diagnostics_enabled: full_mode,
2458 word_completions_enabled: full_mode,
2459 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2460 gutter_hovered: false,
2461 pixel_position_of_newest_cursor: None,
2462 last_bounds: None,
2463 last_position_map: None,
2464 expect_bounds_change: None,
2465 gutter_dimensions: GutterDimensions::default(),
2466 style: None,
2467 show_cursor_names: false,
2468 hovered_cursors: HashMap::default(),
2469 next_editor_action_id: EditorActionId::default(),
2470 editor_actions: Rc::default(),
2471 edit_predictions_hidden_for_vim_mode: false,
2472 show_edit_predictions_override: None,
2473 show_completions_on_input_override: None,
2474 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2475 edit_prediction_settings: EditPredictionSettings::Disabled,
2476 edit_prediction_indent_conflict: false,
2477 edit_prediction_requires_modifier_in_indent_conflict: true,
2478 custom_context_menu: None,
2479 show_git_blame_gutter: false,
2480 show_git_blame_inline: false,
2481 show_selection_menu: None,
2482 show_git_blame_inline_delay_task: None,
2483 git_blame_inline_enabled: full_mode
2484 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2485 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2486 buffer_serialization: is_minimap.not().then(|| {
2487 BufferSerialization::new(
2488 ProjectSettings::get_global(cx)
2489 .session
2490 .restore_unsaved_buffers,
2491 )
2492 }),
2493 blame: None,
2494 blame_subscription: None,
2495
2496 breakpoint_store,
2497 gutter_breakpoint_indicator: (None, None),
2498 gutter_diff_review_indicator: (None, None),
2499 diff_review_drag_state: None,
2500 diff_review_overlays: Vec::new(),
2501 stored_review_comments: Vec::new(),
2502 next_review_comment_id: 0,
2503 hovered_diff_hunk_row: None,
2504 _subscriptions: (!is_minimap)
2505 .then(|| {
2506 vec![
2507 cx.observe(&multi_buffer, Self::on_buffer_changed),
2508 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2509 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2510 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2511 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2512 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2513 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2514 cx.observe_window_activation(window, |editor, window, cx| {
2515 let active = window.is_window_active();
2516 editor.blink_manager.update(cx, |blink_manager, cx| {
2517 if active {
2518 blink_manager.enable(cx);
2519 } else {
2520 blink_manager.disable(cx);
2521 }
2522 });
2523 if active {
2524 editor.show_mouse_cursor(cx);
2525 }
2526 }),
2527 ]
2528 })
2529 .unwrap_or_default(),
2530 runnables: RunnableData::new(),
2531 pull_diagnostics_task: Task::ready(()),
2532 colors: None,
2533 refresh_colors_task: Task::ready(()),
2534 use_document_folding_ranges: false,
2535 refresh_folding_ranges_task: Task::ready(()),
2536 inlay_hints: None,
2537 next_color_inlay_id: 0,
2538 post_scroll_update: Task::ready(()),
2539 linked_edit_ranges: Default::default(),
2540 in_project_search: false,
2541 previous_search_ranges: None,
2542 breadcrumb_header: None,
2543 focused_block: None,
2544 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2545 addons: HashMap::default(),
2546 registered_buffers: HashMap::default(),
2547 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2548 selection_mark_mode: false,
2549 toggle_fold_multiple_buffers: Task::ready(()),
2550 serialize_selections: Task::ready(()),
2551 serialize_folds: Task::ready(()),
2552 text_style_refinement: None,
2553 load_diff_task: load_uncommitted_diff,
2554 temporary_diff_override: false,
2555 mouse_cursor_hidden: false,
2556 minimap: None,
2557 hide_mouse_mode: EditorSettings::get_global(cx)
2558 .hide_mouse
2559 .unwrap_or_default(),
2560 change_list: ChangeList::new(),
2561 mode,
2562 selection_drag_state: SelectionDragState::None,
2563 folding_newlines: Task::ready(()),
2564 lookup_key: None,
2565 select_next_is_case_sensitive: None,
2566 on_local_selections_changed: None,
2567 suppress_selection_callback: false,
2568 applicable_language_settings: HashMap::default(),
2569 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2570 accent_data: None,
2571 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2572 number_deleted_lines: false,
2573 refresh_matching_bracket_highlights_task: Task::ready(()),
2574 refresh_document_symbols_task: Task::ready(()).shared(),
2575 lsp_document_symbols: HashMap::default(),
2576 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2577 outline_symbols_at_cursor: None,
2578 sticky_headers_task: Task::ready(()),
2579 sticky_headers: None,
2580 colorize_brackets_task: Task::ready(()),
2581 };
2582
2583 if is_minimap {
2584 return editor;
2585 }
2586
2587 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2588 editor.accent_data = editor.fetch_accent_data(cx);
2589
2590 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2591 editor
2592 ._subscriptions
2593 .push(cx.observe(breakpoints, |_, _, cx| {
2594 cx.notify();
2595 }));
2596 }
2597 editor._subscriptions.extend(project_subscriptions);
2598
2599 editor._subscriptions.push(cx.subscribe_in(
2600 &cx.entity(),
2601 window,
2602 |editor, _, e: &EditorEvent, window, cx| match e {
2603 EditorEvent::ScrollPositionChanged { local, .. } => {
2604 if *local {
2605 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2606 editor.inline_blame_popover.take();
2607 let snapshot = editor.snapshot(window, cx);
2608 let new_anchor = editor
2609 .scroll_manager
2610 .native_anchor(&snapshot.display_snapshot, cx);
2611 editor.update_restoration_data(cx, move |data| {
2612 data.scroll_position = (
2613 new_anchor.top_row(snapshot.buffer_snapshot()),
2614 new_anchor.offset,
2615 );
2616 });
2617
2618 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2619 cx.background_executor()
2620 .timer(Duration::from_millis(50))
2621 .await;
2622 editor
2623 .update_in(cx, |editor, window, cx| {
2624 editor.update_data_on_scroll(window, cx)
2625 })
2626 .ok();
2627 });
2628 }
2629 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2630 }
2631 EditorEvent::Edited { .. } => {
2632 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2633 .map(|vim_mode| vim_mode.0)
2634 .unwrap_or(false);
2635 if !vim_mode {
2636 let display_map = editor.display_snapshot(cx);
2637 let selections = editor.selections.all_adjusted_display(&display_map);
2638 let pop_state = editor
2639 .change_list
2640 .last()
2641 .map(|previous| {
2642 previous.len() == selections.len()
2643 && previous.iter().enumerate().all(|(ix, p)| {
2644 p.to_display_point(&display_map).row()
2645 == selections[ix].head().row()
2646 })
2647 })
2648 .unwrap_or(false);
2649 let new_positions = selections
2650 .into_iter()
2651 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2652 .collect();
2653 editor
2654 .change_list
2655 .push_to_change_list(pop_state, new_positions);
2656 }
2657 }
2658 _ => (),
2659 },
2660 ));
2661
2662 if let Some(dap_store) = editor
2663 .project
2664 .as_ref()
2665 .map(|project| project.read(cx).dap_store())
2666 {
2667 let weak_editor = cx.weak_entity();
2668
2669 editor
2670 ._subscriptions
2671 .push(
2672 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2673 let session_entity = cx.entity();
2674 weak_editor
2675 .update(cx, |editor, cx| {
2676 editor._subscriptions.push(
2677 cx.subscribe(&session_entity, Self::on_debug_session_event),
2678 );
2679 })
2680 .ok();
2681 }),
2682 );
2683
2684 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2685 editor
2686 ._subscriptions
2687 .push(cx.subscribe(&session, Self::on_debug_session_event));
2688 }
2689 }
2690
2691 // skip adding the initial selection to selection history
2692 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2693 editor.end_selection(window, cx);
2694 editor.selection_history.mode = SelectionHistoryMode::Normal;
2695
2696 editor.scroll_manager.show_scrollbars(window, cx);
2697 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2698
2699 if full_mode {
2700 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2701 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2702
2703 if editor.git_blame_inline_enabled {
2704 editor.start_git_blame_inline(false, window, cx);
2705 }
2706
2707 editor.go_to_active_debug_line(window, cx);
2708
2709 editor.minimap =
2710 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2711 editor.colors = Some(LspColorData::new(cx));
2712 editor.use_document_folding_ranges = true;
2713 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2714
2715 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2716 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2717 }
2718 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2719 }
2720
2721 editor
2722 }
2723
2724 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2725 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2726 }
2727
2728 pub fn deploy_mouse_context_menu(
2729 &mut self,
2730 position: gpui::Point<Pixels>,
2731 context_menu: Entity<ContextMenu>,
2732 window: &mut Window,
2733 cx: &mut Context<Self>,
2734 ) {
2735 self.mouse_context_menu = Some(MouseContextMenu::new(
2736 self,
2737 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2738 context_menu,
2739 window,
2740 cx,
2741 ));
2742 }
2743
2744 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2745 self.mouse_context_menu
2746 .as_ref()
2747 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2748 }
2749
2750 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2751 if self
2752 .selections
2753 .pending_anchor()
2754 .is_some_and(|pending_selection| {
2755 let snapshot = self.buffer().read(cx).snapshot(cx);
2756 pending_selection.range().includes(range, &snapshot)
2757 })
2758 {
2759 return true;
2760 }
2761
2762 self.selections
2763 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2764 .into_iter()
2765 .any(|selection| {
2766 // This is needed to cover a corner case, if we just check for an existing
2767 // selection in the fold range, having a cursor at the start of the fold
2768 // marks it as selected. Non-empty selections don't cause this.
2769 let length = selection.end - selection.start;
2770 length > 0
2771 })
2772 }
2773
2774 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2775 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2776 }
2777
2778 fn key_context_internal(
2779 &self,
2780 has_active_edit_prediction: bool,
2781 window: &mut Window,
2782 cx: &mut App,
2783 ) -> KeyContext {
2784 let mut key_context = KeyContext::new_with_defaults();
2785 key_context.add("Editor");
2786 let mode = match self.mode {
2787 EditorMode::SingleLine => "single_line",
2788 EditorMode::AutoHeight { .. } => "auto_height",
2789 EditorMode::Minimap { .. } => "minimap",
2790 EditorMode::Full { .. } => "full",
2791 };
2792
2793 if EditorSettings::jupyter_enabled(cx) {
2794 key_context.add("jupyter");
2795 }
2796
2797 key_context.set("mode", mode);
2798 if self.pending_rename.is_some() {
2799 key_context.add("renaming");
2800 }
2801
2802 if let Some(snippet_stack) = self.snippet_stack.last() {
2803 key_context.add("in_snippet");
2804
2805 if snippet_stack.active_index > 0 {
2806 key_context.add("has_previous_tabstop");
2807 }
2808
2809 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2810 key_context.add("has_next_tabstop");
2811 }
2812 }
2813
2814 match self.context_menu.borrow().as_ref() {
2815 Some(CodeContextMenu::Completions(menu)) => {
2816 if menu.visible() {
2817 key_context.add("menu");
2818 key_context.add("showing_completions");
2819 }
2820 }
2821 Some(CodeContextMenu::CodeActions(menu)) => {
2822 if menu.visible() {
2823 key_context.add("menu");
2824 key_context.add("showing_code_actions")
2825 }
2826 }
2827 None => {}
2828 }
2829
2830 if self.signature_help_state.has_multiple_signatures() {
2831 key_context.add("showing_signature_help");
2832 }
2833
2834 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2835 if !self.focus_handle(cx).contains_focused(window, cx)
2836 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2837 {
2838 for addon in self.addons.values() {
2839 addon.extend_key_context(&mut key_context, cx)
2840 }
2841 }
2842
2843 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2844 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2845 Some(
2846 file.full_path(cx)
2847 .extension()?
2848 .to_string_lossy()
2849 .to_lowercase(),
2850 )
2851 }) {
2852 key_context.set("extension", extension);
2853 }
2854 } else {
2855 key_context.add("multibuffer");
2856 }
2857
2858 if has_active_edit_prediction {
2859 if self.edit_prediction_in_conflict() {
2860 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2861 } else {
2862 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2863 key_context.add("copilot_suggestion");
2864 }
2865 }
2866
2867 if self.selection_mark_mode {
2868 key_context.add("selection_mode");
2869 }
2870
2871 let disjoint = self.selections.disjoint_anchors();
2872 let snapshot = self.snapshot(window, cx);
2873 let snapshot = snapshot.buffer_snapshot();
2874 if self.mode == EditorMode::SingleLine
2875 && let [selection] = disjoint
2876 && selection.start == selection.end
2877 && selection.end.to_offset(snapshot) == snapshot.len()
2878 {
2879 key_context.add("end_of_input");
2880 }
2881
2882 if self.has_any_expanded_diff_hunks(cx) {
2883 key_context.add("diffs_expanded");
2884 }
2885
2886 key_context
2887 }
2888
2889 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2890 self.last_bounds.as_ref()
2891 }
2892
2893 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2894 if self.mouse_cursor_hidden {
2895 self.mouse_cursor_hidden = false;
2896 cx.notify();
2897 }
2898 }
2899
2900 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2901 let hide_mouse_cursor = match origin {
2902 HideMouseCursorOrigin::TypingAction => {
2903 matches!(
2904 self.hide_mouse_mode,
2905 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2906 )
2907 }
2908 HideMouseCursorOrigin::MovementAction => {
2909 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2910 }
2911 };
2912 if self.mouse_cursor_hidden != hide_mouse_cursor {
2913 self.mouse_cursor_hidden = hide_mouse_cursor;
2914 cx.notify();
2915 }
2916 }
2917
2918 pub fn edit_prediction_in_conflict(&self) -> bool {
2919 if !self.show_edit_predictions_in_menu() {
2920 return false;
2921 }
2922
2923 let showing_completions = self
2924 .context_menu
2925 .borrow()
2926 .as_ref()
2927 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2928
2929 showing_completions
2930 || self.edit_prediction_requires_modifier()
2931 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2932 // bindings to insert tab characters.
2933 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2934 }
2935
2936 pub fn accept_edit_prediction_keybind(
2937 &self,
2938 granularity: EditPredictionGranularity,
2939 window: &mut Window,
2940 cx: &mut App,
2941 ) -> AcceptEditPredictionBinding {
2942 let key_context = self.key_context_internal(true, window, cx);
2943 let in_conflict = self.edit_prediction_in_conflict();
2944
2945 let bindings =
2946 match granularity {
2947 EditPredictionGranularity::Word => window
2948 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2949 EditPredictionGranularity::Line => window
2950 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2951 EditPredictionGranularity::Full => {
2952 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2953 }
2954 };
2955
2956 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2957 !in_conflict
2958 || binding
2959 .keystrokes()
2960 .first()
2961 .is_some_and(|keystroke| keystroke.modifiers().modified())
2962 }))
2963 }
2964
2965 pub fn new_file(
2966 workspace: &mut Workspace,
2967 _: &workspace::NewFile,
2968 window: &mut Window,
2969 cx: &mut Context<Workspace>,
2970 ) {
2971 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2972 "Failed to create buffer",
2973 window,
2974 cx,
2975 |e, _, _| match e.error_code() {
2976 ErrorCode::RemoteUpgradeRequired => Some(format!(
2977 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2978 e.error_tag("required").unwrap_or("the latest version")
2979 )),
2980 _ => None,
2981 },
2982 );
2983 }
2984
2985 pub fn new_in_workspace(
2986 workspace: &mut Workspace,
2987 window: &mut Window,
2988 cx: &mut Context<Workspace>,
2989 ) -> Task<Result<Entity<Editor>>> {
2990 let project = workspace.project().clone();
2991 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
2992
2993 cx.spawn_in(window, async move |workspace, cx| {
2994 let buffer = create.await?;
2995 workspace.update_in(cx, |workspace, window, cx| {
2996 let editor =
2997 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2998 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2999 editor
3000 })
3001 })
3002 }
3003
3004 fn new_file_vertical(
3005 workspace: &mut Workspace,
3006 _: &workspace::NewFileSplitVertical,
3007 window: &mut Window,
3008 cx: &mut Context<Workspace>,
3009 ) {
3010 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3011 }
3012
3013 fn new_file_horizontal(
3014 workspace: &mut Workspace,
3015 _: &workspace::NewFileSplitHorizontal,
3016 window: &mut Window,
3017 cx: &mut Context<Workspace>,
3018 ) {
3019 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3020 }
3021
3022 fn new_file_split(
3023 workspace: &mut Workspace,
3024 action: &workspace::NewFileSplit,
3025 window: &mut Window,
3026 cx: &mut Context<Workspace>,
3027 ) {
3028 Self::new_file_in_direction(workspace, action.0, window, cx)
3029 }
3030
3031 fn new_file_in_direction(
3032 workspace: &mut Workspace,
3033 direction: SplitDirection,
3034 window: &mut Window,
3035 cx: &mut Context<Workspace>,
3036 ) {
3037 let project = workspace.project().clone();
3038 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3039
3040 cx.spawn_in(window, async move |workspace, cx| {
3041 let buffer = create.await?;
3042 workspace.update_in(cx, move |workspace, window, cx| {
3043 workspace.split_item(
3044 direction,
3045 Box::new(
3046 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3047 ),
3048 window,
3049 cx,
3050 )
3051 })?;
3052 anyhow::Ok(())
3053 })
3054 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3055 match e.error_code() {
3056 ErrorCode::RemoteUpgradeRequired => Some(format!(
3057 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3058 e.error_tag("required").unwrap_or("the latest version")
3059 )),
3060 _ => None,
3061 }
3062 });
3063 }
3064
3065 pub fn leader_id(&self) -> Option<CollaboratorId> {
3066 self.leader_id
3067 }
3068
3069 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3070 &self.buffer
3071 }
3072
3073 pub fn project(&self) -> Option<&Entity<Project>> {
3074 self.project.as_ref()
3075 }
3076
3077 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3078 self.workspace.as_ref()?.0.upgrade()
3079 }
3080
3081 /// Detaches a task and shows an error notification in the workspace if available,
3082 /// otherwise just logs the error.
3083 pub fn detach_and_notify_err<R, E>(
3084 &self,
3085 task: Task<Result<R, E>>,
3086 window: &mut Window,
3087 cx: &mut App,
3088 ) where
3089 E: std::fmt::Debug + std::fmt::Display + 'static,
3090 R: 'static,
3091 {
3092 if let Some(workspace) = self.workspace() {
3093 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3094 } else {
3095 task.detach_and_log_err(cx);
3096 }
3097 }
3098
3099 /// Returns the workspace serialization ID if this editor should be serialized.
3100 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3101 self.workspace
3102 .as_ref()
3103 .filter(|_| self.should_serialize_buffer())
3104 .and_then(|workspace| workspace.1)
3105 }
3106
3107 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3108 self.buffer().read(cx).title(cx)
3109 }
3110
3111 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3112 let git_blame_gutter_max_author_length = self
3113 .render_git_blame_gutter(cx)
3114 .then(|| {
3115 if let Some(blame) = self.blame.as_ref() {
3116 let max_author_length =
3117 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3118 Some(max_author_length)
3119 } else {
3120 None
3121 }
3122 })
3123 .flatten();
3124
3125 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3126
3127 EditorSnapshot {
3128 mode: self.mode.clone(),
3129 show_gutter: self.show_gutter,
3130 offset_content: self.offset_content,
3131 show_line_numbers: self.show_line_numbers,
3132 number_deleted_lines: self.number_deleted_lines,
3133 show_git_diff_gutter: self.show_git_diff_gutter,
3134 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3135 show_code_actions: self.show_code_actions,
3136 show_runnables: self.show_runnables,
3137 show_breakpoints: self.show_breakpoints,
3138 git_blame_gutter_max_author_length,
3139 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3140 display_snapshot,
3141 placeholder_display_snapshot: self
3142 .placeholder_display_map
3143 .as_ref()
3144 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3145 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3146 is_focused: self.focus_handle.is_focused(window),
3147 current_line_highlight: self
3148 .current_line_highlight
3149 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3150 gutter_hovered: self.gutter_hovered,
3151 }
3152 }
3153
3154 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3155 self.buffer.read(cx).language_at(point, cx)
3156 }
3157
3158 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3159 self.buffer.read(cx).read(cx).file_at(point).cloned()
3160 }
3161
3162 pub fn active_excerpt(
3163 &self,
3164 cx: &App,
3165 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3166 self.buffer
3167 .read(cx)
3168 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3169 }
3170
3171 pub fn mode(&self) -> &EditorMode {
3172 &self.mode
3173 }
3174
3175 pub fn set_mode(&mut self, mode: EditorMode) {
3176 self.mode = mode;
3177 }
3178
3179 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3180 self.collaboration_hub.as_deref()
3181 }
3182
3183 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3184 self.collaboration_hub = Some(hub);
3185 }
3186
3187 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3188 self.in_project_search = in_project_search;
3189 }
3190
3191 pub fn set_custom_context_menu(
3192 &mut self,
3193 f: impl 'static
3194 + Fn(
3195 &mut Self,
3196 DisplayPoint,
3197 &mut Window,
3198 &mut Context<Self>,
3199 ) -> Option<Entity<ui::ContextMenu>>,
3200 ) {
3201 self.custom_context_menu = Some(Box::new(f))
3202 }
3203
3204 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3205 self.completion_provider = provider;
3206 }
3207
3208 #[cfg(any(test, feature = "test-support"))]
3209 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3210 self.completion_provider.clone()
3211 }
3212
3213 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3214 self.semantics_provider.clone()
3215 }
3216
3217 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3218 self.semantics_provider = provider;
3219 }
3220
3221 pub fn set_edit_prediction_provider<T>(
3222 &mut self,
3223 provider: Option<Entity<T>>,
3224 window: &mut Window,
3225 cx: &mut Context<Self>,
3226 ) where
3227 T: EditPredictionDelegate,
3228 {
3229 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3230 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3231 if this.focus_handle.is_focused(window) {
3232 this.update_visible_edit_prediction(window, cx);
3233 }
3234 }),
3235 provider: Arc::new(provider),
3236 });
3237 self.update_edit_prediction_settings(cx);
3238 self.refresh_edit_prediction(false, false, window, cx);
3239 }
3240
3241 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3242 self.placeholder_display_map
3243 .as_ref()
3244 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3245 }
3246
3247 pub fn set_placeholder_text(
3248 &mut self,
3249 placeholder_text: &str,
3250 window: &mut Window,
3251 cx: &mut Context<Self>,
3252 ) {
3253 let multibuffer = cx
3254 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3255
3256 let style = window.text_style();
3257
3258 self.placeholder_display_map = Some(cx.new(|cx| {
3259 DisplayMap::new(
3260 multibuffer,
3261 style.font(),
3262 style.font_size.to_pixels(window.rem_size()),
3263 None,
3264 FILE_HEADER_HEIGHT,
3265 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3266 Default::default(),
3267 DiagnosticSeverity::Off,
3268 cx,
3269 )
3270 }));
3271 cx.notify();
3272 }
3273
3274 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3275 self.cursor_shape = cursor_shape;
3276
3277 // Disrupt blink for immediate user feedback that the cursor shape has changed
3278 self.blink_manager.update(cx, BlinkManager::show_cursor);
3279
3280 cx.notify();
3281 }
3282
3283 pub fn cursor_shape(&self) -> CursorShape {
3284 self.cursor_shape
3285 }
3286
3287 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3288 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3289 }
3290
3291 pub fn set_current_line_highlight(
3292 &mut self,
3293 current_line_highlight: Option<CurrentLineHighlight>,
3294 ) {
3295 self.current_line_highlight = current_line_highlight;
3296 }
3297
3298 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3299 self.collapse_matches = collapse_matches;
3300 }
3301
3302 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3303 if self.collapse_matches {
3304 return range.start..range.start;
3305 }
3306 range.clone()
3307 }
3308
3309 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3310 self.display_map.read(cx).clip_at_line_ends
3311 }
3312
3313 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3314 if self.display_map.read(cx).clip_at_line_ends != clip {
3315 self.display_map
3316 .update(cx, |map, _| map.clip_at_line_ends = clip);
3317 }
3318 }
3319
3320 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3321 self.input_enabled = input_enabled;
3322 }
3323
3324 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3325 self.expects_character_input = expects_character_input;
3326 }
3327
3328 pub fn set_edit_predictions_hidden_for_vim_mode(
3329 &mut self,
3330 hidden: bool,
3331 window: &mut Window,
3332 cx: &mut Context<Self>,
3333 ) {
3334 if hidden != self.edit_predictions_hidden_for_vim_mode {
3335 self.edit_predictions_hidden_for_vim_mode = hidden;
3336 if hidden {
3337 self.update_visible_edit_prediction(window, cx);
3338 } else {
3339 self.refresh_edit_prediction(true, false, window, cx);
3340 }
3341 }
3342 }
3343
3344 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3345 self.menu_edit_predictions_policy = value;
3346 }
3347
3348 pub fn set_autoindent(&mut self, autoindent: bool) {
3349 if autoindent {
3350 self.autoindent_mode = Some(AutoindentMode::EachLine);
3351 } else {
3352 self.autoindent_mode = None;
3353 }
3354 }
3355
3356 pub fn capability(&self, cx: &App) -> Capability {
3357 if self.read_only {
3358 Capability::ReadOnly
3359 } else {
3360 self.buffer.read(cx).capability()
3361 }
3362 }
3363
3364 pub fn read_only(&self, cx: &App) -> bool {
3365 self.read_only || self.buffer.read(cx).read_only()
3366 }
3367
3368 pub fn set_read_only(&mut self, read_only: bool) {
3369 self.read_only = read_only;
3370 }
3371
3372 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3373 self.use_autoclose = autoclose;
3374 }
3375
3376 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3377 self.use_auto_surround = auto_surround;
3378 }
3379
3380 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3381 self.auto_replace_emoji_shortcode = auto_replace;
3382 }
3383
3384 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3385 self.buffer_serialization = should_serialize.then(|| {
3386 BufferSerialization::new(
3387 ProjectSettings::get_global(cx)
3388 .session
3389 .restore_unsaved_buffers,
3390 )
3391 })
3392 }
3393
3394 fn should_serialize_buffer(&self) -> bool {
3395 self.buffer_serialization.is_some()
3396 }
3397
3398 pub fn toggle_edit_predictions(
3399 &mut self,
3400 _: &ToggleEditPrediction,
3401 window: &mut Window,
3402 cx: &mut Context<Self>,
3403 ) {
3404 if self.show_edit_predictions_override.is_some() {
3405 self.set_show_edit_predictions(None, window, cx);
3406 } else {
3407 let show_edit_predictions = !self.edit_predictions_enabled();
3408 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3409 }
3410 }
3411
3412 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3413 self.show_completions_on_input_override = show_completions_on_input;
3414 }
3415
3416 pub fn set_show_edit_predictions(
3417 &mut self,
3418 show_edit_predictions: Option<bool>,
3419 window: &mut Window,
3420 cx: &mut Context<Self>,
3421 ) {
3422 self.show_edit_predictions_override = show_edit_predictions;
3423 self.update_edit_prediction_settings(cx);
3424
3425 if let Some(false) = show_edit_predictions {
3426 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3427 } else {
3428 self.refresh_edit_prediction(false, true, window, cx);
3429 }
3430 }
3431
3432 fn edit_predictions_disabled_in_scope(
3433 &self,
3434 buffer: &Entity<Buffer>,
3435 buffer_position: language::Anchor,
3436 cx: &App,
3437 ) -> bool {
3438 let snapshot = buffer.read(cx).snapshot();
3439 let settings = snapshot.settings_at(buffer_position, cx);
3440
3441 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3442 return false;
3443 };
3444
3445 scope.override_name().is_some_and(|scope_name| {
3446 settings
3447 .edit_predictions_disabled_in
3448 .iter()
3449 .any(|s| s == scope_name)
3450 })
3451 }
3452
3453 pub fn set_use_modal_editing(&mut self, to: bool) {
3454 self.use_modal_editing = to;
3455 }
3456
3457 pub fn use_modal_editing(&self) -> bool {
3458 self.use_modal_editing
3459 }
3460
3461 fn selections_did_change(
3462 &mut self,
3463 local: bool,
3464 old_cursor_position: &Anchor,
3465 effects: SelectionEffects,
3466 window: &mut Window,
3467 cx: &mut Context<Self>,
3468 ) {
3469 window.invalidate_character_coordinates();
3470
3471 // Copy selections to primary selection buffer
3472 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3473 if local {
3474 let selections = self
3475 .selections
3476 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3477 let buffer_handle = self.buffer.read(cx).read(cx);
3478
3479 let mut text = String::new();
3480 for (index, selection) in selections.iter().enumerate() {
3481 let text_for_selection = buffer_handle
3482 .text_for_range(selection.start..selection.end)
3483 .collect::<String>();
3484
3485 text.push_str(&text_for_selection);
3486 if index != selections.len() - 1 {
3487 text.push('\n');
3488 }
3489 }
3490
3491 if !text.is_empty() {
3492 cx.write_to_primary(ClipboardItem::new_string(text));
3493 }
3494 }
3495
3496 let selection_anchors = self.selections.disjoint_anchors_arc();
3497
3498 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3499 self.buffer.update(cx, |buffer, cx| {
3500 buffer.set_active_selections(
3501 &selection_anchors,
3502 self.selections.line_mode(),
3503 self.cursor_shape,
3504 cx,
3505 )
3506 });
3507 }
3508 let display_map = self
3509 .display_map
3510 .update(cx, |display_map, cx| display_map.snapshot(cx));
3511 let buffer = display_map.buffer_snapshot();
3512 if self.selections.count() == 1 {
3513 self.add_selections_state = None;
3514 }
3515 self.select_next_state = None;
3516 self.select_prev_state = None;
3517 self.select_syntax_node_history.try_clear();
3518 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3519 self.snippet_stack.invalidate(&selection_anchors, buffer);
3520 self.take_rename(false, window, cx);
3521
3522 let newest_selection = self.selections.newest_anchor();
3523 let new_cursor_position = newest_selection.head();
3524 let selection_start = newest_selection.start;
3525
3526 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3527 self.push_to_nav_history(
3528 *old_cursor_position,
3529 Some(new_cursor_position.to_point(buffer)),
3530 false,
3531 effects.nav_history == Some(true),
3532 cx,
3533 );
3534 }
3535
3536 if local {
3537 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3538 self.register_buffer(buffer_id, cx);
3539 }
3540
3541 let mut context_menu = self.context_menu.borrow_mut();
3542 let completion_menu = match context_menu.as_ref() {
3543 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3544 Some(CodeContextMenu::CodeActions(_)) => {
3545 *context_menu = None;
3546 None
3547 }
3548 None => None,
3549 };
3550 let completion_position = completion_menu.map(|menu| menu.initial_position);
3551 drop(context_menu);
3552
3553 if effects.completions
3554 && let Some(completion_position) = completion_position
3555 {
3556 let start_offset = selection_start.to_offset(buffer);
3557 let position_matches = start_offset == completion_position.to_offset(buffer);
3558 let continue_showing = if let Some((snap, ..)) =
3559 buffer.point_to_buffer_offset(completion_position)
3560 && !snap.capability.editable()
3561 {
3562 false
3563 } else if position_matches {
3564 if self.snippet_stack.is_empty() {
3565 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3566 == Some(CharKind::Word)
3567 } else {
3568 // Snippet choices can be shown even when the cursor is in whitespace.
3569 // Dismissing the menu with actions like backspace is handled by
3570 // invalidation regions.
3571 true
3572 }
3573 } else {
3574 false
3575 };
3576
3577 if continue_showing {
3578 self.open_or_update_completions_menu(None, None, false, window, cx);
3579 } else {
3580 self.hide_context_menu(window, cx);
3581 }
3582 }
3583
3584 hide_hover(self, cx);
3585
3586 if old_cursor_position.to_display_point(&display_map).row()
3587 != new_cursor_position.to_display_point(&display_map).row()
3588 {
3589 self.available_code_actions.take();
3590 }
3591 self.refresh_code_actions(window, cx);
3592 self.refresh_document_highlights(cx);
3593 refresh_linked_ranges(self, window, cx);
3594
3595 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3596 self.refresh_matching_bracket_highlights(&display_map, cx);
3597 self.refresh_outline_symbols_at_cursor(cx);
3598 self.update_visible_edit_prediction(window, cx);
3599 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3600 self.inline_blame_popover.take();
3601 if self.git_blame_inline_enabled {
3602 self.start_inline_blame_timer(window, cx);
3603 }
3604 }
3605
3606 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3607
3608 if local && !self.suppress_selection_callback {
3609 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3610 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3611 callback(cursor_position, window, cx);
3612 }
3613 }
3614
3615 cx.emit(EditorEvent::SelectionsChanged { local });
3616
3617 let selections = &self.selections.disjoint_anchors_arc();
3618 if selections.len() == 1 {
3619 cx.emit(SearchEvent::ActiveMatchChanged)
3620 }
3621 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3622 let inmemory_selections = selections
3623 .iter()
3624 .map(|s| {
3625 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3626 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3627 })
3628 .collect();
3629 self.update_restoration_data(cx, |data| {
3630 data.selections = inmemory_selections;
3631 });
3632
3633 if WorkspaceSettings::get(None, cx).restore_on_startup
3634 != RestoreOnStartupBehavior::EmptyTab
3635 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3636 {
3637 let snapshot = self.buffer().read(cx).snapshot(cx);
3638 let selections = selections.clone();
3639 let background_executor = cx.background_executor().clone();
3640 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3641 self.serialize_selections = cx.background_spawn(async move {
3642 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3643 let db_selections = selections
3644 .iter()
3645 .map(|selection| {
3646 (
3647 selection.start.to_offset(&snapshot).0,
3648 selection.end.to_offset(&snapshot).0,
3649 )
3650 })
3651 .collect();
3652
3653 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3654 .await
3655 .with_context(|| {
3656 format!(
3657 "persisting editor selections for editor {editor_id}, \
3658 workspace {workspace_id:?}"
3659 )
3660 })
3661 .log_err();
3662 });
3663 }
3664 }
3665
3666 cx.notify();
3667 }
3668
3669 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3670 use text::ToOffset as _;
3671 use text::ToPoint as _;
3672
3673 if self.mode.is_minimap()
3674 || WorkspaceSettings::get(None, cx).restore_on_startup
3675 == RestoreOnStartupBehavior::EmptyTab
3676 {
3677 return;
3678 }
3679
3680 if !self.buffer().read(cx).is_singleton() {
3681 return;
3682 }
3683
3684 let display_snapshot = self
3685 .display_map
3686 .update(cx, |display_map, cx| display_map.snapshot(cx));
3687 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3688 return;
3689 };
3690 let inmemory_folds = display_snapshot
3691 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3692 .map(|fold| {
3693 fold.range.start.text_anchor.to_point(&snapshot)
3694 ..fold.range.end.text_anchor.to_point(&snapshot)
3695 })
3696 .collect();
3697 self.update_restoration_data(cx, |data| {
3698 data.folds = inmemory_folds;
3699 });
3700
3701 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3702 return;
3703 };
3704
3705 // Get file path for path-based fold storage (survives tab close)
3706 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3707 project::File::from_dyn(buffer.read(cx).file())
3708 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3709 }) else {
3710 return;
3711 };
3712
3713 let background_executor = cx.background_executor().clone();
3714 const FINGERPRINT_LEN: usize = 32;
3715 let db_folds = display_snapshot
3716 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3717 .map(|fold| {
3718 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3719 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3720
3721 // Extract fingerprints - content at fold boundaries for validation on restore
3722 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3723 // content that might change independently.
3724 // start_fp: first min(32, fold_len) bytes of fold content
3725 // end_fp: last min(32, fold_len) bytes of fold content
3726 // Clip to character boundaries to handle multibyte UTF-8 characters.
3727 let fold_len = end - start;
3728 let start_fp_end = snapshot
3729 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3730 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3731 let end_fp_start = snapshot
3732 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3733 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3734
3735 (start, end, start_fp, end_fp)
3736 })
3737 .collect::<Vec<_>>();
3738 self.serialize_folds = cx.background_spawn(async move {
3739 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3740 if db_folds.is_empty() {
3741 // No folds - delete any persisted folds for this file
3742 DB.delete_file_folds(workspace_id, file_path)
3743 .await
3744 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3745 .log_err();
3746 } else {
3747 DB.save_file_folds(workspace_id, file_path, db_folds)
3748 .await
3749 .with_context(|| {
3750 format!("persisting file folds for workspace {workspace_id:?}")
3751 })
3752 .log_err();
3753 }
3754 });
3755 }
3756
3757 pub fn sync_selections(
3758 &mut self,
3759 other: Entity<Editor>,
3760 cx: &mut Context<Self>,
3761 ) -> gpui::Subscription {
3762 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3763 if !other_selections.is_empty() {
3764 self.selections
3765 .change_with(&self.display_snapshot(cx), |selections| {
3766 selections.select_anchors(other_selections);
3767 });
3768 }
3769
3770 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3771 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3772 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3773 if other_selections.is_empty() {
3774 return;
3775 }
3776 let snapshot = this.display_snapshot(cx);
3777 this.selections.change_with(&snapshot, |selections| {
3778 selections.select_anchors(other_selections);
3779 });
3780 }
3781 });
3782
3783 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3784 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3785 let these_selections = this.selections.disjoint_anchors().to_vec();
3786 if these_selections.is_empty() {
3787 return;
3788 }
3789 other.update(cx, |other_editor, cx| {
3790 let snapshot = other_editor.display_snapshot(cx);
3791 other_editor
3792 .selections
3793 .change_with(&snapshot, |selections| {
3794 selections.select_anchors(these_selections);
3795 })
3796 });
3797 }
3798 });
3799
3800 Subscription::join(other_subscription, this_subscription)
3801 }
3802
3803 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3804 if self.buffer().read(cx).is_singleton() {
3805 return;
3806 }
3807 let snapshot = self.buffer.read(cx).snapshot(cx);
3808 let buffer_ids: HashSet<BufferId> = self
3809 .selections
3810 .disjoint_anchor_ranges()
3811 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3812 .collect();
3813 for buffer_id in buffer_ids {
3814 self.unfold_buffer(buffer_id, cx);
3815 }
3816 }
3817
3818 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3819 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3820 /// effects of selection change occur at the end of the transaction.
3821 pub fn change_selections<R>(
3822 &mut self,
3823 effects: SelectionEffects,
3824 window: &mut Window,
3825 cx: &mut Context<Self>,
3826 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3827 ) -> R {
3828 let snapshot = self.display_snapshot(cx);
3829 if let Some(state) = &mut self.deferred_selection_effects_state {
3830 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3831 state.effects.completions = effects.completions;
3832 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3833 let (changed, result) = self.selections.change_with(&snapshot, change);
3834 state.changed |= changed;
3835 return result;
3836 }
3837 let mut state = DeferredSelectionEffectsState {
3838 changed: false,
3839 effects,
3840 old_cursor_position: self.selections.newest_anchor().head(),
3841 history_entry: SelectionHistoryEntry {
3842 selections: self.selections.disjoint_anchors_arc(),
3843 select_next_state: self.select_next_state.clone(),
3844 select_prev_state: self.select_prev_state.clone(),
3845 add_selections_state: self.add_selections_state.clone(),
3846 },
3847 };
3848 let (changed, result) = self.selections.change_with(&snapshot, change);
3849 state.changed = state.changed || changed;
3850 if self.defer_selection_effects {
3851 self.deferred_selection_effects_state = Some(state);
3852 } else {
3853 self.apply_selection_effects(state, window, cx);
3854 }
3855 result
3856 }
3857
3858 /// Defers the effects of selection change, so that the effects of multiple calls to
3859 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3860 /// to selection history and the state of popovers based on selection position aren't
3861 /// erroneously updated.
3862 pub fn with_selection_effects_deferred<R>(
3863 &mut self,
3864 window: &mut Window,
3865 cx: &mut Context<Self>,
3866 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3867 ) -> R {
3868 let already_deferred = self.defer_selection_effects;
3869 self.defer_selection_effects = true;
3870 let result = update(self, window, cx);
3871 if !already_deferred {
3872 self.defer_selection_effects = false;
3873 if let Some(state) = self.deferred_selection_effects_state.take() {
3874 self.apply_selection_effects(state, window, cx);
3875 }
3876 }
3877 result
3878 }
3879
3880 fn apply_selection_effects(
3881 &mut self,
3882 state: DeferredSelectionEffectsState,
3883 window: &mut Window,
3884 cx: &mut Context<Self>,
3885 ) {
3886 if state.changed {
3887 self.selection_history.push(state.history_entry);
3888
3889 if let Some(autoscroll) = state.effects.scroll {
3890 self.request_autoscroll(autoscroll, cx);
3891 }
3892
3893 let old_cursor_position = &state.old_cursor_position;
3894
3895 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3896
3897 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3898 self.show_signature_help_auto(window, cx);
3899 }
3900 }
3901 }
3902
3903 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3904 where
3905 I: IntoIterator<Item = (Range<S>, T)>,
3906 S: ToOffset,
3907 T: Into<Arc<str>>,
3908 {
3909 if self.read_only(cx) {
3910 return;
3911 }
3912
3913 self.buffer
3914 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3915 }
3916
3917 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3918 where
3919 I: IntoIterator<Item = (Range<S>, T)>,
3920 S: ToOffset,
3921 T: Into<Arc<str>>,
3922 {
3923 if self.read_only(cx) {
3924 return;
3925 }
3926
3927 self.buffer.update(cx, |buffer, cx| {
3928 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3929 });
3930 }
3931
3932 pub fn edit_with_block_indent<I, S, T>(
3933 &mut self,
3934 edits: I,
3935 original_indent_columns: Vec<Option<u32>>,
3936 cx: &mut Context<Self>,
3937 ) where
3938 I: IntoIterator<Item = (Range<S>, T)>,
3939 S: ToOffset,
3940 T: Into<Arc<str>>,
3941 {
3942 if self.read_only(cx) {
3943 return;
3944 }
3945
3946 self.buffer.update(cx, |buffer, cx| {
3947 buffer.edit(
3948 edits,
3949 Some(AutoindentMode::Block {
3950 original_indent_columns,
3951 }),
3952 cx,
3953 )
3954 });
3955 }
3956
3957 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3958 self.hide_context_menu(window, cx);
3959
3960 match phase {
3961 SelectPhase::Begin {
3962 position,
3963 add,
3964 click_count,
3965 } => self.begin_selection(position, add, click_count, window, cx),
3966 SelectPhase::BeginColumnar {
3967 position,
3968 goal_column,
3969 reset,
3970 mode,
3971 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3972 SelectPhase::Extend {
3973 position,
3974 click_count,
3975 } => self.extend_selection(position, click_count, window, cx),
3976 SelectPhase::Update {
3977 position,
3978 goal_column,
3979 scroll_delta,
3980 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3981 SelectPhase::End => self.end_selection(window, cx),
3982 }
3983 }
3984
3985 fn extend_selection(
3986 &mut self,
3987 position: DisplayPoint,
3988 click_count: usize,
3989 window: &mut Window,
3990 cx: &mut Context<Self>,
3991 ) {
3992 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3993 let tail = self
3994 .selections
3995 .newest::<MultiBufferOffset>(&display_map)
3996 .tail();
3997 let click_count = click_count.max(match self.selections.select_mode() {
3998 SelectMode::Character => 1,
3999 SelectMode::Word(_) => 2,
4000 SelectMode::Line(_) => 3,
4001 SelectMode::All => 4,
4002 });
4003 self.begin_selection(position, false, click_count, window, cx);
4004
4005 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4006
4007 let current_selection = match self.selections.select_mode() {
4008 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4009 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4010 };
4011
4012 let mut pending_selection = self
4013 .selections
4014 .pending_anchor()
4015 .cloned()
4016 .expect("extend_selection not called with pending selection");
4017
4018 if pending_selection
4019 .start
4020 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4021 == Ordering::Greater
4022 {
4023 pending_selection.start = current_selection.start;
4024 }
4025 if pending_selection
4026 .end
4027 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4028 == Ordering::Less
4029 {
4030 pending_selection.end = current_selection.end;
4031 pending_selection.reversed = true;
4032 }
4033
4034 let mut pending_mode = self.selections.pending_mode().unwrap();
4035 match &mut pending_mode {
4036 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4037 _ => {}
4038 }
4039
4040 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4041 SelectionEffects::scroll(Autoscroll::fit())
4042 } else {
4043 SelectionEffects::no_scroll()
4044 };
4045
4046 self.change_selections(effects, window, cx, |s| {
4047 s.set_pending(pending_selection.clone(), pending_mode);
4048 s.set_is_extending(true);
4049 });
4050 }
4051
4052 fn begin_selection(
4053 &mut self,
4054 position: DisplayPoint,
4055 add: bool,
4056 click_count: usize,
4057 window: &mut Window,
4058 cx: &mut Context<Self>,
4059 ) {
4060 if !self.focus_handle.is_focused(window) {
4061 self.last_focused_descendant = None;
4062 window.focus(&self.focus_handle, cx);
4063 }
4064
4065 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4066 let buffer = display_map.buffer_snapshot();
4067 let position = display_map.clip_point(position, Bias::Left);
4068
4069 let start;
4070 let end;
4071 let mode;
4072 let mut auto_scroll;
4073 match click_count {
4074 1 => {
4075 start = buffer.anchor_before(position.to_point(&display_map));
4076 end = start;
4077 mode = SelectMode::Character;
4078 auto_scroll = true;
4079 }
4080 2 => {
4081 let position = display_map
4082 .clip_point(position, Bias::Left)
4083 .to_offset(&display_map, Bias::Left);
4084 let (range, _) = buffer.surrounding_word(position, None);
4085 start = buffer.anchor_before(range.start);
4086 end = buffer.anchor_before(range.end);
4087 mode = SelectMode::Word(start..end);
4088 auto_scroll = true;
4089 }
4090 3 => {
4091 let position = display_map
4092 .clip_point(position, Bias::Left)
4093 .to_point(&display_map);
4094 let line_start = display_map.prev_line_boundary(position).0;
4095 let next_line_start = buffer.clip_point(
4096 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4097 Bias::Left,
4098 );
4099 start = buffer.anchor_before(line_start);
4100 end = buffer.anchor_before(next_line_start);
4101 mode = SelectMode::Line(start..end);
4102 auto_scroll = true;
4103 }
4104 _ => {
4105 start = buffer.anchor_before(MultiBufferOffset(0));
4106 end = buffer.anchor_before(buffer.len());
4107 mode = SelectMode::All;
4108 auto_scroll = false;
4109 }
4110 }
4111 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4112
4113 let point_to_delete: Option<usize> = {
4114 let selected_points: Vec<Selection<Point>> =
4115 self.selections.disjoint_in_range(start..end, &display_map);
4116
4117 if !add || click_count > 1 {
4118 None
4119 } else if !selected_points.is_empty() {
4120 Some(selected_points[0].id)
4121 } else {
4122 let clicked_point_already_selected =
4123 self.selections.disjoint_anchors().iter().find(|selection| {
4124 selection.start.to_point(buffer) == start.to_point(buffer)
4125 || selection.end.to_point(buffer) == end.to_point(buffer)
4126 });
4127
4128 clicked_point_already_selected.map(|selection| selection.id)
4129 }
4130 };
4131
4132 let selections_count = self.selections.count();
4133 let effects = if auto_scroll {
4134 SelectionEffects::default()
4135 } else {
4136 SelectionEffects::no_scroll()
4137 };
4138
4139 self.change_selections(effects, window, cx, |s| {
4140 if let Some(point_to_delete) = point_to_delete {
4141 s.delete(point_to_delete);
4142
4143 if selections_count == 1 {
4144 s.set_pending_anchor_range(start..end, mode);
4145 }
4146 } else {
4147 if !add {
4148 s.clear_disjoint();
4149 }
4150
4151 s.set_pending_anchor_range(start..end, mode);
4152 }
4153 });
4154 }
4155
4156 fn begin_columnar_selection(
4157 &mut self,
4158 position: DisplayPoint,
4159 goal_column: u32,
4160 reset: bool,
4161 mode: ColumnarMode,
4162 window: &mut Window,
4163 cx: &mut Context<Self>,
4164 ) {
4165 if !self.focus_handle.is_focused(window) {
4166 self.last_focused_descendant = None;
4167 window.focus(&self.focus_handle, cx);
4168 }
4169
4170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4171
4172 if reset {
4173 let pointer_position = display_map
4174 .buffer_snapshot()
4175 .anchor_before(position.to_point(&display_map));
4176
4177 self.change_selections(
4178 SelectionEffects::scroll(Autoscroll::newest()),
4179 window,
4180 cx,
4181 |s| {
4182 s.clear_disjoint();
4183 s.set_pending_anchor_range(
4184 pointer_position..pointer_position,
4185 SelectMode::Character,
4186 );
4187 },
4188 );
4189 };
4190
4191 let tail = self.selections.newest::<Point>(&display_map).tail();
4192 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4193 self.columnar_selection_state = match mode {
4194 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4195 selection_tail: selection_anchor,
4196 display_point: if reset {
4197 if position.column() != goal_column {
4198 Some(DisplayPoint::new(position.row(), goal_column))
4199 } else {
4200 None
4201 }
4202 } else {
4203 None
4204 },
4205 }),
4206 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4207 selection_tail: selection_anchor,
4208 }),
4209 };
4210
4211 if !reset {
4212 self.select_columns(position, goal_column, &display_map, window, cx);
4213 }
4214 }
4215
4216 fn update_selection(
4217 &mut self,
4218 position: DisplayPoint,
4219 goal_column: u32,
4220 scroll_delta: gpui::Point<f32>,
4221 window: &mut Window,
4222 cx: &mut Context<Self>,
4223 ) {
4224 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4225
4226 if self.columnar_selection_state.is_some() {
4227 self.select_columns(position, goal_column, &display_map, window, cx);
4228 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4229 let buffer = display_map.buffer_snapshot();
4230 let head;
4231 let tail;
4232 let mode = self.selections.pending_mode().unwrap();
4233 match &mode {
4234 SelectMode::Character => {
4235 head = position.to_point(&display_map);
4236 tail = pending.tail().to_point(buffer);
4237 }
4238 SelectMode::Word(original_range) => {
4239 let offset = display_map
4240 .clip_point(position, Bias::Left)
4241 .to_offset(&display_map, Bias::Left);
4242 let original_range = original_range.to_offset(buffer);
4243
4244 let head_offset = if buffer.is_inside_word(offset, None)
4245 || original_range.contains(&offset)
4246 {
4247 let (word_range, _) = buffer.surrounding_word(offset, None);
4248 if word_range.start < original_range.start {
4249 word_range.start
4250 } else {
4251 word_range.end
4252 }
4253 } else {
4254 offset
4255 };
4256
4257 head = head_offset.to_point(buffer);
4258 if head_offset <= original_range.start {
4259 tail = original_range.end.to_point(buffer);
4260 } else {
4261 tail = original_range.start.to_point(buffer);
4262 }
4263 }
4264 SelectMode::Line(original_range) => {
4265 let original_range = original_range.to_point(display_map.buffer_snapshot());
4266
4267 let position = display_map
4268 .clip_point(position, Bias::Left)
4269 .to_point(&display_map);
4270 let line_start = display_map.prev_line_boundary(position).0;
4271 let next_line_start = buffer.clip_point(
4272 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4273 Bias::Left,
4274 );
4275
4276 if line_start < original_range.start {
4277 head = line_start
4278 } else {
4279 head = next_line_start
4280 }
4281
4282 if head <= original_range.start {
4283 tail = original_range.end;
4284 } else {
4285 tail = original_range.start;
4286 }
4287 }
4288 SelectMode::All => {
4289 return;
4290 }
4291 };
4292
4293 if head < tail {
4294 pending.start = buffer.anchor_before(head);
4295 pending.end = buffer.anchor_before(tail);
4296 pending.reversed = true;
4297 } else {
4298 pending.start = buffer.anchor_before(tail);
4299 pending.end = buffer.anchor_before(head);
4300 pending.reversed = false;
4301 }
4302
4303 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4304 s.set_pending(pending.clone(), mode);
4305 });
4306 } else {
4307 log::error!("update_selection dispatched with no pending selection");
4308 return;
4309 }
4310
4311 self.apply_scroll_delta(scroll_delta, window, cx);
4312 cx.notify();
4313 }
4314
4315 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4316 self.columnar_selection_state.take();
4317 if let Some(pending_mode) = self.selections.pending_mode() {
4318 let selections = self
4319 .selections
4320 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4321 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4322 s.select(selections);
4323 s.clear_pending();
4324 if s.is_extending() {
4325 s.set_is_extending(false);
4326 } else {
4327 s.set_select_mode(pending_mode);
4328 }
4329 });
4330 }
4331 }
4332
4333 fn select_columns(
4334 &mut self,
4335 head: DisplayPoint,
4336 goal_column: u32,
4337 display_map: &DisplaySnapshot,
4338 window: &mut Window,
4339 cx: &mut Context<Self>,
4340 ) {
4341 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4342 return;
4343 };
4344
4345 let tail = match columnar_state {
4346 ColumnarSelectionState::FromMouse {
4347 selection_tail,
4348 display_point,
4349 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4350 ColumnarSelectionState::FromSelection { selection_tail } => {
4351 selection_tail.to_display_point(display_map)
4352 }
4353 };
4354
4355 let start_row = cmp::min(tail.row(), head.row());
4356 let end_row = cmp::max(tail.row(), head.row());
4357 let start_column = cmp::min(tail.column(), goal_column);
4358 let end_column = cmp::max(tail.column(), goal_column);
4359 let reversed = start_column < tail.column();
4360
4361 let selection_ranges = (start_row.0..=end_row.0)
4362 .map(DisplayRow)
4363 .filter_map(|row| {
4364 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4365 || start_column <= display_map.line_len(row))
4366 && !display_map.is_block_line(row)
4367 {
4368 let start = display_map
4369 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4370 .to_point(display_map);
4371 let end = display_map
4372 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4373 .to_point(display_map);
4374 if reversed {
4375 Some(end..start)
4376 } else {
4377 Some(start..end)
4378 }
4379 } else {
4380 None
4381 }
4382 })
4383 .collect::<Vec<_>>();
4384 if selection_ranges.is_empty() {
4385 return;
4386 }
4387
4388 let ranges = match columnar_state {
4389 ColumnarSelectionState::FromMouse { .. } => {
4390 let mut non_empty_ranges = selection_ranges
4391 .iter()
4392 .filter(|selection_range| selection_range.start != selection_range.end)
4393 .peekable();
4394 if non_empty_ranges.peek().is_some() {
4395 non_empty_ranges.cloned().collect()
4396 } else {
4397 selection_ranges
4398 }
4399 }
4400 _ => selection_ranges,
4401 };
4402
4403 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4404 s.select_ranges(ranges);
4405 });
4406 cx.notify();
4407 }
4408
4409 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4410 self.selections
4411 .all_adjusted(snapshot)
4412 .iter()
4413 .any(|selection| !selection.is_empty())
4414 }
4415
4416 pub fn has_pending_nonempty_selection(&self) -> bool {
4417 let pending_nonempty_selection = match self.selections.pending_anchor() {
4418 Some(Selection { start, end, .. }) => start != end,
4419 None => false,
4420 };
4421
4422 pending_nonempty_selection
4423 || (self.columnar_selection_state.is_some()
4424 && self.selections.disjoint_anchors().len() > 1)
4425 }
4426
4427 pub fn has_pending_selection(&self) -> bool {
4428 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4429 }
4430
4431 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4432 self.selection_mark_mode = false;
4433 self.selection_drag_state = SelectionDragState::None;
4434
4435 if self.dismiss_menus_and_popups(true, window, cx) {
4436 cx.notify();
4437 return;
4438 }
4439 if self.clear_expanded_diff_hunks(cx) {
4440 cx.notify();
4441 return;
4442 }
4443 if self.show_git_blame_gutter {
4444 self.show_git_blame_gutter = false;
4445 cx.notify();
4446 return;
4447 }
4448
4449 if self.mode.is_full()
4450 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4451 {
4452 cx.notify();
4453 return;
4454 }
4455
4456 cx.propagate();
4457 }
4458
4459 pub fn dismiss_menus_and_popups(
4460 &mut self,
4461 is_user_requested: bool,
4462 window: &mut Window,
4463 cx: &mut Context<Self>,
4464 ) -> bool {
4465 let mut dismissed = false;
4466
4467 dismissed |= self.take_rename(false, window, cx).is_some();
4468 dismissed |= self.hide_blame_popover(true, cx);
4469 dismissed |= hide_hover(self, cx);
4470 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4471 dismissed |= self.hide_context_menu(window, cx).is_some();
4472 dismissed |= self.mouse_context_menu.take().is_some();
4473 dismissed |= is_user_requested
4474 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4475 dismissed |= self.snippet_stack.pop().is_some();
4476 if self.diff_review_drag_state.is_some() {
4477 self.cancel_diff_review_drag(cx);
4478 dismissed = true;
4479 }
4480 if !self.diff_review_overlays.is_empty() {
4481 self.dismiss_all_diff_review_overlays(cx);
4482 dismissed = true;
4483 }
4484
4485 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4486 self.dismiss_diagnostics(cx);
4487 dismissed = true;
4488 }
4489
4490 dismissed
4491 }
4492
4493 fn linked_editing_ranges_for(
4494 &self,
4495 selection: Range<text::Anchor>,
4496 cx: &App,
4497 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4498 if self.linked_edit_ranges.is_empty() {
4499 return None;
4500 }
4501 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4502 selection.end.buffer_id.and_then(|end_buffer_id| {
4503 if selection.start.buffer_id != Some(end_buffer_id) {
4504 return None;
4505 }
4506 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4507 let snapshot = buffer.read(cx).snapshot();
4508 self.linked_edit_ranges
4509 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4510 .map(|ranges| (ranges, snapshot, buffer))
4511 })?;
4512 use text::ToOffset as TO;
4513 // find offset from the start of current range to current cursor position
4514 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4515
4516 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4517 let start_difference = start_offset - start_byte_offset;
4518 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4519 let end_difference = end_offset - start_byte_offset;
4520
4521 // Current range has associated linked ranges.
4522 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4523 for range in linked_ranges.iter() {
4524 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4525 let end_offset = start_offset + end_difference;
4526 let start_offset = start_offset + start_difference;
4527 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4528 continue;
4529 }
4530 if self.selections.disjoint_anchor_ranges().any(|s| {
4531 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4532 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4533 {
4534 return false;
4535 }
4536 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4537 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4538 }) {
4539 continue;
4540 }
4541 let start = buffer_snapshot.anchor_after(start_offset);
4542 let end = buffer_snapshot.anchor_after(end_offset);
4543 linked_edits
4544 .entry(buffer.clone())
4545 .or_default()
4546 .push(start..end);
4547 }
4548 Some(linked_edits)
4549 }
4550
4551 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4552 let text: Arc<str> = text.into();
4553
4554 if self.read_only(cx) {
4555 return;
4556 }
4557
4558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4559
4560 self.unfold_buffers_with_selections(cx);
4561
4562 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4563 let mut bracket_inserted = false;
4564 let mut edits = Vec::new();
4565 let mut linked_edits = LinkedEdits::new();
4566 let mut new_selections = Vec::with_capacity(selections.len());
4567 let mut new_autoclose_regions = Vec::new();
4568 let snapshot = self.buffer.read(cx).read(cx);
4569 let mut clear_linked_edit_ranges = false;
4570 let mut all_selections_read_only = true;
4571 let mut has_adjacent_edits = false;
4572 let mut in_adjacent_group = false;
4573
4574 let mut regions = self
4575 .selections_with_autoclose_regions(selections, &snapshot)
4576 .peekable();
4577
4578 while let Some((selection, autoclose_region)) = regions.next() {
4579 if snapshot
4580 .point_to_buffer_point(selection.head())
4581 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4582 {
4583 continue;
4584 }
4585 if snapshot
4586 .point_to_buffer_point(selection.tail())
4587 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4588 {
4589 // note, ideally we'd clip the tail to the closest writeable region towards the head
4590 continue;
4591 }
4592 all_selections_read_only = false;
4593
4594 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4595 // Determine if the inserted text matches the opening or closing
4596 // bracket of any of this language's bracket pairs.
4597 let mut bracket_pair = None;
4598 let mut is_bracket_pair_start = false;
4599 let mut is_bracket_pair_end = false;
4600 if !text.is_empty() {
4601 let mut bracket_pair_matching_end = None;
4602 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4603 // and they are removing the character that triggered IME popup.
4604 for (pair, enabled) in scope.brackets() {
4605 if !pair.close && !pair.surround {
4606 continue;
4607 }
4608
4609 if enabled && pair.start.ends_with(text.as_ref()) {
4610 let prefix_len = pair.start.len() - text.len();
4611 let preceding_text_matches_prefix = prefix_len == 0
4612 || (selection.start.column >= (prefix_len as u32)
4613 && snapshot.contains_str_at(
4614 Point::new(
4615 selection.start.row,
4616 selection.start.column - (prefix_len as u32),
4617 ),
4618 &pair.start[..prefix_len],
4619 ));
4620 if preceding_text_matches_prefix {
4621 bracket_pair = Some(pair.clone());
4622 is_bracket_pair_start = true;
4623 break;
4624 }
4625 }
4626 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4627 {
4628 // take first bracket pair matching end, but don't break in case a later bracket
4629 // pair matches start
4630 bracket_pair_matching_end = Some(pair.clone());
4631 }
4632 }
4633 if let Some(end) = bracket_pair_matching_end
4634 && bracket_pair.is_none()
4635 {
4636 bracket_pair = Some(end);
4637 is_bracket_pair_end = true;
4638 }
4639 }
4640
4641 if let Some(bracket_pair) = bracket_pair {
4642 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4643 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4644 let auto_surround =
4645 self.use_auto_surround && snapshot_settings.use_auto_surround;
4646 if selection.is_empty() {
4647 if is_bracket_pair_start {
4648 // If the inserted text is a suffix of an opening bracket and the
4649 // selection is preceded by the rest of the opening bracket, then
4650 // insert the closing bracket.
4651 let following_text_allows_autoclose = snapshot
4652 .chars_at(selection.start)
4653 .next()
4654 .is_none_or(|c| scope.should_autoclose_before(c));
4655
4656 let preceding_text_allows_autoclose = selection.start.column == 0
4657 || snapshot
4658 .reversed_chars_at(selection.start)
4659 .next()
4660 .is_none_or(|c| {
4661 bracket_pair.start != bracket_pair.end
4662 || !snapshot
4663 .char_classifier_at(selection.start)
4664 .is_word(c)
4665 });
4666
4667 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4668 && bracket_pair.start.len() == 1
4669 {
4670 let target = bracket_pair.start.chars().next().unwrap();
4671 let mut byte_offset = 0u32;
4672 let current_line_count = snapshot
4673 .reversed_chars_at(selection.start)
4674 .take_while(|&c| c != '\n')
4675 .filter(|c| {
4676 byte_offset += c.len_utf8() as u32;
4677 if *c != target {
4678 return false;
4679 }
4680
4681 let point = Point::new(
4682 selection.start.row,
4683 selection.start.column.saturating_sub(byte_offset),
4684 );
4685
4686 let is_enabled = snapshot
4687 .language_scope_at(point)
4688 .and_then(|scope| {
4689 scope
4690 .brackets()
4691 .find(|(pair, _)| {
4692 pair.start == bracket_pair.start
4693 })
4694 .map(|(_, enabled)| enabled)
4695 })
4696 .unwrap_or(true);
4697
4698 let is_delimiter = snapshot
4699 .language_scope_at(Point::new(
4700 point.row,
4701 point.column + 1,
4702 ))
4703 .and_then(|scope| {
4704 scope
4705 .brackets()
4706 .find(|(pair, _)| {
4707 pair.start == bracket_pair.start
4708 })
4709 .map(|(_, enabled)| !enabled)
4710 })
4711 .unwrap_or(false);
4712
4713 is_enabled && !is_delimiter
4714 })
4715 .count();
4716 current_line_count % 2 == 1
4717 } else {
4718 false
4719 };
4720
4721 if autoclose
4722 && bracket_pair.close
4723 && following_text_allows_autoclose
4724 && preceding_text_allows_autoclose
4725 && !is_closing_quote
4726 {
4727 let anchor = snapshot.anchor_before(selection.end);
4728 new_selections.push((selection.map(|_| anchor), text.len()));
4729 new_autoclose_regions.push((
4730 anchor,
4731 text.len(),
4732 selection.id,
4733 bracket_pair.clone(),
4734 ));
4735 edits.push((
4736 selection.range(),
4737 format!("{}{}", text, bracket_pair.end).into(),
4738 ));
4739 bracket_inserted = true;
4740 continue;
4741 }
4742 }
4743
4744 if let Some(region) = autoclose_region {
4745 // If the selection is followed by an auto-inserted closing bracket,
4746 // then don't insert that closing bracket again; just move the selection
4747 // past the closing bracket.
4748 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4749 && text.as_ref() == region.pair.end.as_str()
4750 && snapshot.contains_str_at(region.range.end, text.as_ref());
4751 if should_skip {
4752 let anchor = snapshot.anchor_after(selection.end);
4753 new_selections
4754 .push((selection.map(|_| anchor), region.pair.end.len()));
4755 continue;
4756 }
4757 }
4758
4759 let always_treat_brackets_as_autoclosed = snapshot
4760 .language_settings_at(selection.start, cx)
4761 .always_treat_brackets_as_autoclosed;
4762 if always_treat_brackets_as_autoclosed
4763 && is_bracket_pair_end
4764 && snapshot.contains_str_at(selection.end, text.as_ref())
4765 {
4766 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4767 // and the inserted text is a closing bracket and the selection is followed
4768 // by the closing bracket then move the selection past the closing bracket.
4769 let anchor = snapshot.anchor_after(selection.end);
4770 new_selections.push((selection.map(|_| anchor), text.len()));
4771 continue;
4772 }
4773 }
4774 // If an opening bracket is 1 character long and is typed while
4775 // text is selected, then surround that text with the bracket pair.
4776 else if auto_surround
4777 && bracket_pair.surround
4778 && is_bracket_pair_start
4779 && bracket_pair.start.chars().count() == 1
4780 {
4781 edits.push((selection.start..selection.start, text.clone()));
4782 edits.push((
4783 selection.end..selection.end,
4784 bracket_pair.end.as_str().into(),
4785 ));
4786 bracket_inserted = true;
4787 new_selections.push((
4788 Selection {
4789 id: selection.id,
4790 start: snapshot.anchor_after(selection.start),
4791 end: snapshot.anchor_before(selection.end),
4792 reversed: selection.reversed,
4793 goal: selection.goal,
4794 },
4795 0,
4796 ));
4797 continue;
4798 }
4799 }
4800 }
4801
4802 if self.auto_replace_emoji_shortcode
4803 && selection.is_empty()
4804 && text.as_ref().ends_with(':')
4805 && let Some(possible_emoji_short_code) =
4806 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4807 && !possible_emoji_short_code.is_empty()
4808 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4809 {
4810 let emoji_shortcode_start = Point::new(
4811 selection.start.row,
4812 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4813 );
4814
4815 // Remove shortcode from buffer
4816 edits.push((
4817 emoji_shortcode_start..selection.start,
4818 "".to_string().into(),
4819 ));
4820 new_selections.push((
4821 Selection {
4822 id: selection.id,
4823 start: snapshot.anchor_after(emoji_shortcode_start),
4824 end: snapshot.anchor_before(selection.start),
4825 reversed: selection.reversed,
4826 goal: selection.goal,
4827 },
4828 0,
4829 ));
4830
4831 // Insert emoji
4832 let selection_start_anchor = snapshot.anchor_after(selection.start);
4833 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4834 edits.push((selection.start..selection.end, emoji.to_string().into()));
4835
4836 continue;
4837 }
4838
4839 let next_is_adjacent = regions
4840 .peek()
4841 .is_some_and(|(next, _)| selection.end == next.start);
4842
4843 // If not handling any auto-close operation, then just replace the selected
4844 // text with the given input and move the selection to the end of the
4845 // newly inserted text.
4846 let anchor = if in_adjacent_group || next_is_adjacent {
4847 // After edits the right bias would shift those anchor to the next visible fragment
4848 // but we want to resolve to the previous one
4849 snapshot.anchor_before(selection.end)
4850 } else {
4851 snapshot.anchor_after(selection.end)
4852 };
4853
4854 if !self.linked_edit_ranges.is_empty() {
4855 let start_anchor = snapshot.anchor_before(selection.start);
4856
4857 let is_word_char = text.chars().next().is_none_or(|char| {
4858 let classifier = snapshot
4859 .char_classifier_at(start_anchor.to_offset(&snapshot))
4860 .scope_context(Some(CharScopeContext::LinkedEdit));
4861 classifier.is_word(char)
4862 });
4863 let is_dot = text.as_ref() == ".";
4864 let should_apply_linked_edit = is_word_char || is_dot;
4865
4866 if should_apply_linked_edit {
4867 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
4868 linked_edits.push(&self, anchor_range, text.clone(), cx);
4869 } else {
4870 clear_linked_edit_ranges = true;
4871 }
4872 }
4873
4874 new_selections.push((selection.map(|_| anchor), 0));
4875 edits.push((selection.start..selection.end, text.clone()));
4876
4877 has_adjacent_edits |= next_is_adjacent;
4878 in_adjacent_group = next_is_adjacent;
4879 }
4880
4881 if all_selections_read_only {
4882 return;
4883 }
4884
4885 drop(regions);
4886 drop(snapshot);
4887
4888 self.transact(window, cx, |this, window, cx| {
4889 if clear_linked_edit_ranges {
4890 this.linked_edit_ranges.clear();
4891 }
4892 let initial_buffer_versions =
4893 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4894
4895 this.buffer.update(cx, |buffer, cx| {
4896 if has_adjacent_edits {
4897 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4898 } else {
4899 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4900 }
4901 });
4902 linked_edits.apply(cx);
4903 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4904 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4905 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4906 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4907 new_anchor_selections,
4908 &map,
4909 )
4910 .zip(new_selection_deltas)
4911 .map(|(selection, delta)| Selection {
4912 id: selection.id,
4913 start: selection.start + delta,
4914 end: selection.end + delta,
4915 reversed: selection.reversed,
4916 goal: SelectionGoal::None,
4917 })
4918 .collect::<Vec<_>>();
4919
4920 let mut i = 0;
4921 for (position, delta, selection_id, pair) in new_autoclose_regions {
4922 let position = position.to_offset(map.buffer_snapshot()) + delta;
4923 let start = map.buffer_snapshot().anchor_before(position);
4924 let end = map.buffer_snapshot().anchor_after(position);
4925 while let Some(existing_state) = this.autoclose_regions.get(i) {
4926 match existing_state
4927 .range
4928 .start
4929 .cmp(&start, map.buffer_snapshot())
4930 {
4931 Ordering::Less => i += 1,
4932 Ordering::Greater => break,
4933 Ordering::Equal => {
4934 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4935 Ordering::Less => i += 1,
4936 Ordering::Equal => break,
4937 Ordering::Greater => break,
4938 }
4939 }
4940 }
4941 }
4942 this.autoclose_regions.insert(
4943 i,
4944 AutocloseRegion {
4945 selection_id,
4946 range: start..end,
4947 pair,
4948 },
4949 );
4950 }
4951
4952 let had_active_edit_prediction = this.has_active_edit_prediction();
4953 this.change_selections(
4954 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4955 window,
4956 cx,
4957 |s| s.select(new_selections),
4958 );
4959
4960 if !bracket_inserted
4961 && let Some(on_type_format_task) =
4962 this.trigger_on_type_formatting(text.to_string(), window, cx)
4963 {
4964 on_type_format_task.detach_and_log_err(cx);
4965 }
4966
4967 let editor_settings = EditorSettings::get_global(cx);
4968 if bracket_inserted
4969 && (editor_settings.auto_signature_help
4970 || editor_settings.show_signature_help_after_edits)
4971 {
4972 this.show_signature_help(&ShowSignatureHelp, window, cx);
4973 }
4974
4975 let trigger_in_words =
4976 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4977 if this.hard_wrap.is_some() {
4978 let latest: Range<Point> = this.selections.newest(&map).range();
4979 if latest.is_empty()
4980 && this
4981 .buffer()
4982 .read(cx)
4983 .snapshot(cx)
4984 .line_len(MultiBufferRow(latest.start.row))
4985 == latest.start.column
4986 {
4987 this.rewrap_impl(
4988 RewrapOptions {
4989 override_language_settings: true,
4990 preserve_existing_whitespace: true,
4991 },
4992 cx,
4993 )
4994 }
4995 }
4996 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4997 refresh_linked_ranges(this, window, cx);
4998 this.refresh_edit_prediction(true, false, window, cx);
4999 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5000 });
5001 }
5002
5003 fn find_possible_emoji_shortcode_at_position(
5004 snapshot: &MultiBufferSnapshot,
5005 position: Point,
5006 ) -> Option<String> {
5007 let mut chars = Vec::new();
5008 let mut found_colon = false;
5009 for char in snapshot.reversed_chars_at(position).take(100) {
5010 // Found a possible emoji shortcode in the middle of the buffer
5011 if found_colon {
5012 if char.is_whitespace() {
5013 chars.reverse();
5014 return Some(chars.iter().collect());
5015 }
5016 // If the previous character is not a whitespace, we are in the middle of a word
5017 // and we only want to complete the shortcode if the word is made up of other emojis
5018 let mut containing_word = String::new();
5019 for ch in snapshot
5020 .reversed_chars_at(position)
5021 .skip(chars.len() + 1)
5022 .take(100)
5023 {
5024 if ch.is_whitespace() {
5025 break;
5026 }
5027 containing_word.push(ch);
5028 }
5029 let containing_word = containing_word.chars().rev().collect::<String>();
5030 if util::word_consists_of_emojis(containing_word.as_str()) {
5031 chars.reverse();
5032 return Some(chars.iter().collect());
5033 }
5034 }
5035
5036 if char.is_whitespace() || !char.is_ascii() {
5037 return None;
5038 }
5039 if char == ':' {
5040 found_colon = true;
5041 } else {
5042 chars.push(char);
5043 }
5044 }
5045 // Found a possible emoji shortcode at the beginning of the buffer
5046 chars.reverse();
5047 Some(chars.iter().collect())
5048 }
5049
5050 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5051 if self.read_only(cx) {
5052 return;
5053 }
5054
5055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5056 self.transact(window, cx, |this, window, cx| {
5057 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5058 let selections = this
5059 .selections
5060 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5061 let multi_buffer = this.buffer.read(cx);
5062 let buffer = multi_buffer.snapshot(cx);
5063 selections
5064 .iter()
5065 .map(|selection| {
5066 let start_point = selection.start.to_point(&buffer);
5067 let mut existing_indent =
5068 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5069 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5070 let start = selection.start;
5071 let end = selection.end;
5072 let selection_is_empty = start == end;
5073 let language_scope = buffer.language_scope_at(start);
5074 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5075 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5076 &buffer,
5077 start..end,
5078 language,
5079 )
5080 || NewlineConfig::insert_extra_newline_tree_sitter(
5081 &buffer,
5082 start..end,
5083 );
5084
5085 let mut newline_config = NewlineConfig::Newline {
5086 additional_indent: IndentSize::spaces(0),
5087 extra_line_additional_indent: if needs_extra_newline {
5088 Some(IndentSize::spaces(0))
5089 } else {
5090 None
5091 },
5092 prevent_auto_indent: false,
5093 };
5094
5095 let comment_delimiter = maybe!({
5096 if !selection_is_empty {
5097 return None;
5098 }
5099
5100 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5101 return None;
5102 }
5103
5104 return comment_delimiter_for_newline(
5105 &start_point,
5106 &buffer,
5107 language,
5108 );
5109 });
5110
5111 let doc_delimiter = maybe!({
5112 if !selection_is_empty {
5113 return None;
5114 }
5115
5116 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5117 return None;
5118 }
5119
5120 return documentation_delimiter_for_newline(
5121 &start_point,
5122 &buffer,
5123 language,
5124 &mut newline_config,
5125 );
5126 });
5127
5128 let list_delimiter = maybe!({
5129 if !selection_is_empty {
5130 return None;
5131 }
5132
5133 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5134 return None;
5135 }
5136
5137 return list_delimiter_for_newline(
5138 &start_point,
5139 &buffer,
5140 language,
5141 &mut newline_config,
5142 );
5143 });
5144
5145 (
5146 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5147 newline_config,
5148 )
5149 } else {
5150 (
5151 None,
5152 NewlineConfig::Newline {
5153 additional_indent: IndentSize::spaces(0),
5154 extra_line_additional_indent: None,
5155 prevent_auto_indent: false,
5156 },
5157 )
5158 };
5159
5160 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5161 NewlineConfig::ClearCurrentLine => {
5162 let row_start =
5163 buffer.point_to_offset(Point::new(start_point.row, 0));
5164 (row_start, String::new(), false)
5165 }
5166 NewlineConfig::UnindentCurrentLine { continuation } => {
5167 let row_start =
5168 buffer.point_to_offset(Point::new(start_point.row, 0));
5169 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5170 let tab_size_indent = IndentSize::spaces(tab_size.get());
5171 let reduced_indent =
5172 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5173 let mut new_text = String::new();
5174 new_text.extend(reduced_indent.chars());
5175 new_text.push_str(continuation);
5176 (row_start, new_text, true)
5177 }
5178 NewlineConfig::Newline {
5179 additional_indent,
5180 extra_line_additional_indent,
5181 prevent_auto_indent,
5182 } => {
5183 let auto_indent_mode =
5184 buffer.language_settings_at(start, cx).auto_indent;
5185 let preserve_indent =
5186 auto_indent_mode != language::AutoIndentMode::None;
5187 let apply_syntax_indent =
5188 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5189 let capacity_for_delimiter =
5190 delimiter.as_deref().map(str::len).unwrap_or_default();
5191 let existing_indent_len = if preserve_indent {
5192 existing_indent.len as usize
5193 } else {
5194 0
5195 };
5196 let extra_line_len = extra_line_additional_indent
5197 .map(|i| 1 + existing_indent_len + i.len as usize)
5198 .unwrap_or(0);
5199 let mut new_text = String::with_capacity(
5200 1 + capacity_for_delimiter
5201 + existing_indent_len
5202 + additional_indent.len as usize
5203 + extra_line_len,
5204 );
5205 new_text.push('\n');
5206 if preserve_indent {
5207 new_text.extend(existing_indent.chars());
5208 }
5209 new_text.extend(additional_indent.chars());
5210 if let Some(delimiter) = &delimiter {
5211 new_text.push_str(delimiter);
5212 }
5213 if let Some(extra_indent) = extra_line_additional_indent {
5214 new_text.push('\n');
5215 if preserve_indent {
5216 new_text.extend(existing_indent.chars());
5217 }
5218 new_text.extend(extra_indent.chars());
5219 }
5220 (
5221 start,
5222 new_text,
5223 *prevent_auto_indent || !apply_syntax_indent,
5224 )
5225 }
5226 };
5227
5228 let anchor = buffer.anchor_after(end);
5229 let new_selection = selection.map(|_| anchor);
5230 (
5231 ((edit_start..end, new_text), prevent_auto_indent),
5232 (newline_config.has_extra_line(), new_selection),
5233 )
5234 })
5235 .unzip()
5236 };
5237
5238 let mut auto_indent_edits = Vec::new();
5239 let mut edits = Vec::new();
5240 for (edit, prevent_auto_indent) in edits_with_flags {
5241 if prevent_auto_indent {
5242 edits.push(edit);
5243 } else {
5244 auto_indent_edits.push(edit);
5245 }
5246 }
5247 if !edits.is_empty() {
5248 this.edit(edits, cx);
5249 }
5250 if !auto_indent_edits.is_empty() {
5251 this.edit_with_autoindent(auto_indent_edits, cx);
5252 }
5253
5254 let buffer = this.buffer.read(cx).snapshot(cx);
5255 let new_selections = selection_info
5256 .into_iter()
5257 .map(|(extra_newline_inserted, new_selection)| {
5258 let mut cursor = new_selection.end.to_point(&buffer);
5259 if extra_newline_inserted {
5260 cursor.row -= 1;
5261 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5262 }
5263 new_selection.map(|_| cursor)
5264 })
5265 .collect();
5266
5267 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5268 this.refresh_edit_prediction(true, false, window, cx);
5269 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5270 task.detach_and_log_err(cx);
5271 }
5272 });
5273 }
5274
5275 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5276 if self.read_only(cx) {
5277 return;
5278 }
5279
5280 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5281
5282 let buffer = self.buffer.read(cx);
5283 let snapshot = buffer.snapshot(cx);
5284
5285 let mut edits = Vec::new();
5286 let mut rows = Vec::new();
5287
5288 for (rows_inserted, selection) in self
5289 .selections
5290 .all_adjusted(&self.display_snapshot(cx))
5291 .into_iter()
5292 .enumerate()
5293 {
5294 let cursor = selection.head();
5295 let row = cursor.row;
5296
5297 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5298
5299 let newline = "\n".to_string();
5300 edits.push((start_of_line..start_of_line, newline));
5301
5302 rows.push(row + rows_inserted as u32);
5303 }
5304
5305 self.transact(window, cx, |editor, window, cx| {
5306 editor.edit(edits, cx);
5307
5308 editor.change_selections(Default::default(), window, cx, |s| {
5309 let mut index = 0;
5310 s.move_cursors_with(&mut |map, _, _| {
5311 let row = rows[index];
5312 index += 1;
5313
5314 let point = Point::new(row, 0);
5315 let boundary = map.next_line_boundary(point).1;
5316 let clipped = map.clip_point(boundary, Bias::Left);
5317
5318 (clipped, SelectionGoal::None)
5319 });
5320 });
5321
5322 let mut indent_edits = Vec::new();
5323 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5324 for row in rows {
5325 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5326 for (row, indent) in indents {
5327 if indent.len == 0 {
5328 continue;
5329 }
5330
5331 let text = match indent.kind {
5332 IndentKind::Space => " ".repeat(indent.len as usize),
5333 IndentKind::Tab => "\t".repeat(indent.len as usize),
5334 };
5335 let point = Point::new(row.0, 0);
5336 indent_edits.push((point..point, text));
5337 }
5338 }
5339 editor.edit(indent_edits, cx);
5340 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5341 format.detach_and_log_err(cx);
5342 }
5343 });
5344 }
5345
5346 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5347 if self.read_only(cx) {
5348 return;
5349 }
5350
5351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5352
5353 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5354 let mut rows = Vec::new();
5355 let mut rows_inserted = 0;
5356
5357 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5358 let cursor = selection.head();
5359 let row = cursor.row;
5360
5361 let point = Point::new(row, 0);
5362 let Some((buffer_handle, buffer_point, _)) =
5363 self.buffer.read(cx).point_to_buffer_point(point, cx)
5364 else {
5365 continue;
5366 };
5367
5368 buffer_edits
5369 .entry(buffer_handle.entity_id())
5370 .or_insert_with(|| (buffer_handle, Vec::new()))
5371 .1
5372 .push(buffer_point);
5373
5374 rows_inserted += 1;
5375 rows.push(row + rows_inserted);
5376 }
5377
5378 self.transact(window, cx, |editor, window, cx| {
5379 for (_, (buffer_handle, points)) in &buffer_edits {
5380 buffer_handle.update(cx, |buffer, cx| {
5381 let edits: Vec<_> = points
5382 .iter()
5383 .map(|point| {
5384 let target = Point::new(point.row + 1, 0);
5385 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5386 (start_of_line..start_of_line, "\n")
5387 })
5388 .collect();
5389 buffer.edit(edits, None, cx);
5390 });
5391 }
5392
5393 editor.change_selections(Default::default(), window, cx, |s| {
5394 let mut index = 0;
5395 s.move_cursors_with(&mut |map, _, _| {
5396 let row = rows[index];
5397 index += 1;
5398
5399 let point = Point::new(row, 0);
5400 let boundary = map.next_line_boundary(point).1;
5401 let clipped = map.clip_point(boundary, Bias::Left);
5402
5403 (clipped, SelectionGoal::None)
5404 });
5405 });
5406
5407 let mut indent_edits = Vec::new();
5408 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5409 for row in rows {
5410 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5411 for (row, indent) in indents {
5412 if indent.len == 0 {
5413 continue;
5414 }
5415
5416 let text = match indent.kind {
5417 IndentKind::Space => " ".repeat(indent.len as usize),
5418 IndentKind::Tab => "\t".repeat(indent.len as usize),
5419 };
5420 let point = Point::new(row.0, 0);
5421 indent_edits.push((point..point, text));
5422 }
5423 }
5424 editor.edit(indent_edits, cx);
5425 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5426 format.detach_and_log_err(cx);
5427 }
5428 });
5429 }
5430
5431 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5432 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5433 original_indent_columns: Vec::new(),
5434 });
5435 self.replace_selections(text, autoindent, window, cx, false);
5436 }
5437
5438 /// Replaces the editor's selections with the provided `text`, applying the
5439 /// given `autoindent_mode` (`None` will skip autoindentation).
5440 ///
5441 /// Early returns if the editor is in read-only mode, without applying any
5442 /// edits.
5443 fn replace_selections(
5444 &mut self,
5445 text: &str,
5446 autoindent_mode: Option<AutoindentMode>,
5447 window: &mut Window,
5448 cx: &mut Context<Self>,
5449 apply_linked_edits: bool,
5450 ) {
5451 if self.read_only(cx) {
5452 return;
5453 }
5454
5455 let text: Arc<str> = text.into();
5456 self.transact(window, cx, |this, window, cx| {
5457 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5458 let linked_edits = if apply_linked_edits {
5459 this.linked_edits_for_selections(text.clone(), cx)
5460 } else {
5461 LinkedEdits::new()
5462 };
5463
5464 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5465 let anchors = {
5466 let snapshot = buffer.read(cx);
5467 old_selections
5468 .iter()
5469 .map(|s| {
5470 let anchor = snapshot.anchor_after(s.head());
5471 s.map(|_| anchor)
5472 })
5473 .collect::<Vec<_>>()
5474 };
5475 buffer.edit(
5476 old_selections
5477 .iter()
5478 .map(|s| (s.start..s.end, text.clone())),
5479 autoindent_mode,
5480 cx,
5481 );
5482 anchors
5483 });
5484
5485 linked_edits.apply(cx);
5486
5487 this.change_selections(Default::default(), window, cx, |s| {
5488 s.select_anchors(selection_anchors);
5489 });
5490
5491 if apply_linked_edits {
5492 refresh_linked_ranges(this, window, cx);
5493 }
5494
5495 cx.notify();
5496 });
5497 }
5498
5499 /// Collects linked edits for the current selections, pairing each linked
5500 /// range with `text`.
5501 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5502 let mut linked_edits = LinkedEdits::new();
5503 if !self.linked_edit_ranges.is_empty() {
5504 for selection in self.selections.disjoint_anchors() {
5505 let start = selection.start.text_anchor;
5506 let end = selection.end.text_anchor;
5507 linked_edits.push(self, start..end, text.clone(), cx);
5508 }
5509 }
5510 linked_edits
5511 }
5512
5513 /// Deletes the content covered by the current selections and applies
5514 /// linked edits.
5515 pub fn delete_selections_with_linked_edits(
5516 &mut self,
5517 window: &mut Window,
5518 cx: &mut Context<Self>,
5519 ) {
5520 self.replace_selections("", None, window, cx, true);
5521 }
5522
5523 #[cfg(any(test, feature = "test-support"))]
5524 pub fn set_linked_edit_ranges_for_testing(
5525 &mut self,
5526 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5527 cx: &mut Context<Self>,
5528 ) -> Option<()> {
5529 let Some((buffer, _)) = self
5530 .buffer
5531 .read(cx)
5532 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5533 else {
5534 return None;
5535 };
5536 let buffer = buffer.read(cx);
5537 let buffer_id = buffer.remote_id();
5538 let mut linked_ranges = Vec::with_capacity(ranges.len());
5539 for (base_range, linked_ranges_points) in ranges {
5540 let base_anchor =
5541 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5542 let linked_anchors = linked_ranges_points
5543 .into_iter()
5544 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5545 .collect();
5546 linked_ranges.push((base_anchor, linked_anchors));
5547 }
5548 let mut map = HashMap::default();
5549 map.insert(buffer_id, linked_ranges);
5550 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5551 Some(())
5552 }
5553
5554 fn trigger_completion_on_input(
5555 &mut self,
5556 text: &str,
5557 trigger_in_words: bool,
5558 window: &mut Window,
5559 cx: &mut Context<Self>,
5560 ) {
5561 let completions_source = self
5562 .context_menu
5563 .borrow()
5564 .as_ref()
5565 .and_then(|menu| match menu {
5566 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5567 CodeContextMenu::CodeActions(_) => None,
5568 });
5569
5570 match completions_source {
5571 Some(CompletionsMenuSource::Words { .. }) => {
5572 self.open_or_update_completions_menu(
5573 Some(CompletionsMenuSource::Words {
5574 ignore_threshold: false,
5575 }),
5576 None,
5577 trigger_in_words,
5578 window,
5579 cx,
5580 );
5581 }
5582 _ => self.open_or_update_completions_menu(
5583 None,
5584 Some(text.to_owned()).filter(|x| !x.is_empty()),
5585 true,
5586 window,
5587 cx,
5588 ),
5589 }
5590 }
5591
5592 /// If any empty selections is touching the start of its innermost containing autoclose
5593 /// region, expand it to select the brackets.
5594 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5595 let selections = self
5596 .selections
5597 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5598 let buffer = self.buffer.read(cx).read(cx);
5599 let new_selections = self
5600 .selections_with_autoclose_regions(selections, &buffer)
5601 .map(|(mut selection, region)| {
5602 if !selection.is_empty() {
5603 return selection;
5604 }
5605
5606 if let Some(region) = region {
5607 let mut range = region.range.to_offset(&buffer);
5608 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5609 range.start -= region.pair.start.len();
5610 if buffer.contains_str_at(range.start, ®ion.pair.start)
5611 && buffer.contains_str_at(range.end, ®ion.pair.end)
5612 {
5613 range.end += region.pair.end.len();
5614 selection.start = range.start;
5615 selection.end = range.end;
5616
5617 return selection;
5618 }
5619 }
5620 }
5621
5622 let always_treat_brackets_as_autoclosed = buffer
5623 .language_settings_at(selection.start, cx)
5624 .always_treat_brackets_as_autoclosed;
5625
5626 if !always_treat_brackets_as_autoclosed {
5627 return selection;
5628 }
5629
5630 if let Some(scope) = buffer.language_scope_at(selection.start) {
5631 for (pair, enabled) in scope.brackets() {
5632 if !enabled || !pair.close {
5633 continue;
5634 }
5635
5636 if buffer.contains_str_at(selection.start, &pair.end) {
5637 let pair_start_len = pair.start.len();
5638 if buffer.contains_str_at(
5639 selection.start.saturating_sub_usize(pair_start_len),
5640 &pair.start,
5641 ) {
5642 selection.start -= pair_start_len;
5643 selection.end += pair.end.len();
5644
5645 return selection;
5646 }
5647 }
5648 }
5649 }
5650
5651 selection
5652 })
5653 .collect();
5654
5655 drop(buffer);
5656 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5657 selections.select(new_selections)
5658 });
5659 }
5660
5661 /// Iterate the given selections, and for each one, find the smallest surrounding
5662 /// autoclose region. This uses the ordering of the selections and the autoclose
5663 /// regions to avoid repeated comparisons.
5664 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5665 &'a self,
5666 selections: impl IntoIterator<Item = Selection<D>>,
5667 buffer: &'a MultiBufferSnapshot,
5668 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5669 let mut i = 0;
5670 let mut regions = self.autoclose_regions.as_slice();
5671 selections.into_iter().map(move |selection| {
5672 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5673
5674 let mut enclosing = None;
5675 while let Some(pair_state) = regions.get(i) {
5676 if pair_state.range.end.to_offset(buffer) < range.start {
5677 regions = ®ions[i + 1..];
5678 i = 0;
5679 } else if pair_state.range.start.to_offset(buffer) > range.end {
5680 break;
5681 } else {
5682 if pair_state.selection_id == selection.id {
5683 enclosing = Some(pair_state);
5684 }
5685 i += 1;
5686 }
5687 }
5688
5689 (selection, enclosing)
5690 })
5691 }
5692
5693 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5694 fn invalidate_autoclose_regions(
5695 &mut self,
5696 mut selections: &[Selection<Anchor>],
5697 buffer: &MultiBufferSnapshot,
5698 ) {
5699 self.autoclose_regions.retain(|state| {
5700 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5701 return false;
5702 }
5703
5704 let mut i = 0;
5705 while let Some(selection) = selections.get(i) {
5706 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5707 selections = &selections[1..];
5708 continue;
5709 }
5710 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5711 break;
5712 }
5713 if selection.id == state.selection_id {
5714 return true;
5715 } else {
5716 i += 1;
5717 }
5718 }
5719 false
5720 });
5721 }
5722
5723 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5724 let offset = position.to_offset(buffer);
5725 let (word_range, kind) =
5726 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5727 if offset > word_range.start && kind == Some(CharKind::Word) {
5728 Some(
5729 buffer
5730 .text_for_range(word_range.start..offset)
5731 .collect::<String>(),
5732 )
5733 } else {
5734 None
5735 }
5736 }
5737
5738 pub fn visible_excerpts(
5739 &self,
5740 lsp_related_only: bool,
5741 cx: &mut Context<Editor>,
5742 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5743 let project = self.project().cloned();
5744 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5745 let multi_buffer = self.buffer().read(cx);
5746 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5747 multi_buffer_snapshot
5748 .range_to_buffer_ranges(
5749 self.multi_buffer_visible_range(&display_snapshot, cx)
5750 .to_inclusive(),
5751 )
5752 .into_iter()
5753 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5754 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5755 if !lsp_related_only {
5756 return Some((
5757 excerpt_id,
5758 (
5759 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5760 buffer.version().clone(),
5761 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5762 ),
5763 ));
5764 }
5765
5766 let project = project.as_ref()?.read(cx);
5767 let buffer_file = project::File::from_dyn(buffer.file())?;
5768 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5769 let worktree_entry = buffer_worktree
5770 .read(cx)
5771 .entry_for_id(buffer_file.project_entry_id()?)?;
5772 if worktree_entry.is_ignored {
5773 None
5774 } else {
5775 Some((
5776 excerpt_id,
5777 (
5778 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5779 buffer.version().clone(),
5780 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5781 ),
5782 ))
5783 }
5784 })
5785 .collect()
5786 }
5787
5788 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5789 TextLayoutDetails {
5790 text_system: window.text_system().clone(),
5791 editor_style: self.style.clone().unwrap(),
5792 rem_size: window.rem_size(),
5793 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5794 visible_rows: self.visible_line_count(),
5795 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5796 }
5797 }
5798
5799 fn trigger_on_type_formatting(
5800 &self,
5801 input: String,
5802 window: &mut Window,
5803 cx: &mut Context<Self>,
5804 ) -> Option<Task<Result<()>>> {
5805 if input.chars().count() != 1 {
5806 return None;
5807 }
5808
5809 let project = self.project()?;
5810 let position = self.selections.newest_anchor().head();
5811 let (buffer, buffer_position) = self
5812 .buffer
5813 .read(cx)
5814 .text_anchor_for_position(position, cx)?;
5815
5816 let settings = language_settings::language_settings(
5817 buffer
5818 .read(cx)
5819 .language_at(buffer_position)
5820 .map(|l| l.name()),
5821 buffer.read(cx).file(),
5822 cx,
5823 );
5824 if !settings.use_on_type_format {
5825 return None;
5826 }
5827
5828 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5829 // hence we do LSP request & edit on host side only — add formats to host's history.
5830 let push_to_lsp_host_history = true;
5831 // If this is not the host, append its history with new edits.
5832 let push_to_client_history = project.read(cx).is_via_collab();
5833
5834 let on_type_formatting = project.update(cx, |project, cx| {
5835 project.on_type_format(
5836 buffer.clone(),
5837 buffer_position,
5838 input,
5839 push_to_lsp_host_history,
5840 cx,
5841 )
5842 });
5843 Some(cx.spawn_in(window, async move |editor, cx| {
5844 if let Some(transaction) = on_type_formatting.await? {
5845 if push_to_client_history {
5846 buffer.update(cx, |buffer, _| {
5847 buffer.push_transaction(transaction, Instant::now());
5848 buffer.finalize_last_transaction();
5849 });
5850 }
5851 editor.update(cx, |editor, cx| {
5852 editor.refresh_document_highlights(cx);
5853 })?;
5854 }
5855 Ok(())
5856 }))
5857 }
5858
5859 pub fn show_word_completions(
5860 &mut self,
5861 _: &ShowWordCompletions,
5862 window: &mut Window,
5863 cx: &mut Context<Self>,
5864 ) {
5865 self.open_or_update_completions_menu(
5866 Some(CompletionsMenuSource::Words {
5867 ignore_threshold: true,
5868 }),
5869 None,
5870 false,
5871 window,
5872 cx,
5873 );
5874 }
5875
5876 pub fn show_completions(
5877 &mut self,
5878 _: &ShowCompletions,
5879 window: &mut Window,
5880 cx: &mut Context<Self>,
5881 ) {
5882 self.open_or_update_completions_menu(None, None, false, window, cx);
5883 }
5884
5885 fn open_or_update_completions_menu(
5886 &mut self,
5887 requested_source: Option<CompletionsMenuSource>,
5888 trigger: Option<String>,
5889 trigger_in_words: bool,
5890 window: &mut Window,
5891 cx: &mut Context<Self>,
5892 ) {
5893 if self.pending_rename.is_some() {
5894 return;
5895 }
5896
5897 let completions_source = self
5898 .context_menu
5899 .borrow()
5900 .as_ref()
5901 .and_then(|menu| match menu {
5902 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5903 CodeContextMenu::CodeActions(_) => None,
5904 });
5905
5906 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5907
5908 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5909 // inserted and selected. To handle that case, the start of the selection is used so that
5910 // the menu starts with all choices.
5911 let position = self
5912 .selections
5913 .newest_anchor()
5914 .start
5915 .bias_right(&multibuffer_snapshot);
5916 if position.diff_base_anchor.is_some() {
5917 return;
5918 }
5919 let buffer_position = multibuffer_snapshot.anchor_before(position);
5920 let Some(buffer) = buffer_position
5921 .text_anchor
5922 .buffer_id
5923 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5924 else {
5925 return;
5926 };
5927 let buffer_snapshot = buffer.read(cx).snapshot();
5928
5929 let menu_is_open = matches!(
5930 self.context_menu.borrow().as_ref(),
5931 Some(CodeContextMenu::Completions(_))
5932 );
5933
5934 let language = buffer_snapshot
5935 .language_at(buffer_position.text_anchor)
5936 .map(|language| language.name());
5937
5938 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5939 let completion_settings = language_settings.completions.clone();
5940
5941 let show_completions_on_input = self
5942 .show_completions_on_input_override
5943 .unwrap_or(language_settings.show_completions_on_input);
5944 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5945 return;
5946 }
5947
5948 let query: Option<Arc<String>> =
5949 Self::completion_query(&multibuffer_snapshot, buffer_position)
5950 .map(|query| query.into());
5951
5952 drop(multibuffer_snapshot);
5953
5954 // Hide the current completions menu when query is empty. Without this, cached
5955 // completions from before the trigger char may be reused (#32774).
5956 if query.is_none() && menu_is_open {
5957 self.hide_context_menu(window, cx);
5958 }
5959
5960 let mut ignore_word_threshold = false;
5961 let provider = match requested_source {
5962 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5963 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5964 ignore_word_threshold = ignore_threshold;
5965 None
5966 }
5967 Some(CompletionsMenuSource::SnippetChoices)
5968 | Some(CompletionsMenuSource::SnippetsOnly) => {
5969 log::error!("bug: SnippetChoices requested_source is not handled");
5970 None
5971 }
5972 };
5973
5974 let sort_completions = provider
5975 .as_ref()
5976 .is_some_and(|provider| provider.sort_completions());
5977
5978 let filter_completions = provider
5979 .as_ref()
5980 .is_none_or(|provider| provider.filter_completions());
5981
5982 let was_snippets_only = matches!(
5983 completions_source,
5984 Some(CompletionsMenuSource::SnippetsOnly)
5985 );
5986
5987 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5988 if filter_completions {
5989 menu.filter(
5990 query.clone().unwrap_or_default(),
5991 buffer_position.text_anchor,
5992 &buffer,
5993 provider.clone(),
5994 window,
5995 cx,
5996 );
5997 }
5998 // When `is_incomplete` is false, no need to re-query completions when the current query
5999 // is a suffix of the initial query.
6000 let was_complete = !menu.is_incomplete;
6001 if was_complete && !was_snippets_only {
6002 // If the new query is a suffix of the old query (typing more characters) and
6003 // the previous result was complete, the existing completions can be filtered.
6004 //
6005 // Note that snippet completions are always complete.
6006 let query_matches = match (&menu.initial_query, &query) {
6007 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6008 (None, _) => true,
6009 _ => false,
6010 };
6011 if query_matches {
6012 let position_matches = if menu.initial_position == position {
6013 true
6014 } else {
6015 let snapshot = self.buffer.read(cx).read(cx);
6016 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6017 };
6018 if position_matches {
6019 return;
6020 }
6021 }
6022 }
6023 };
6024
6025 let Anchor {
6026 excerpt_id: buffer_excerpt_id,
6027 text_anchor: buffer_position,
6028 ..
6029 } = buffer_position;
6030
6031 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6032 buffer_snapshot.surrounding_word(buffer_position, None)
6033 {
6034 let word_to_exclude = buffer_snapshot
6035 .text_for_range(word_range.clone())
6036 .collect::<String>();
6037 (
6038 buffer_snapshot.anchor_before(word_range.start)
6039 ..buffer_snapshot.anchor_after(buffer_position),
6040 Some(word_to_exclude),
6041 )
6042 } else {
6043 (buffer_position..buffer_position, None)
6044 };
6045
6046 let show_completion_documentation = buffer_snapshot
6047 .settings_at(buffer_position, cx)
6048 .show_completion_documentation;
6049
6050 // The document can be large, so stay in reasonable bounds when searching for words,
6051 // otherwise completion pop-up might be slow to appear.
6052 const WORD_LOOKUP_ROWS: u32 = 5_000;
6053 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6054 let min_word_search = buffer_snapshot.clip_point(
6055 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6056 Bias::Left,
6057 );
6058 let max_word_search = buffer_snapshot.clip_point(
6059 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6060 Bias::Right,
6061 );
6062 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6063 ..buffer_snapshot.point_to_offset(max_word_search);
6064
6065 let skip_digits = query
6066 .as_ref()
6067 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6068
6069 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6070 trigger.as_ref().is_none_or(|trigger| {
6071 provider.is_completion_trigger(
6072 &buffer,
6073 position.text_anchor,
6074 trigger,
6075 trigger_in_words,
6076 cx,
6077 )
6078 })
6079 });
6080
6081 let provider_responses = if let Some(provider) = &provider
6082 && load_provider_completions
6083 {
6084 let trigger_character =
6085 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6086 let completion_context = CompletionContext {
6087 trigger_kind: match &trigger_character {
6088 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6089 None => CompletionTriggerKind::INVOKED,
6090 },
6091 trigger_character,
6092 };
6093
6094 provider.completions(
6095 buffer_excerpt_id,
6096 &buffer,
6097 buffer_position,
6098 completion_context,
6099 window,
6100 cx,
6101 )
6102 } else {
6103 Task::ready(Ok(Vec::new()))
6104 };
6105
6106 let load_word_completions = if !self.word_completions_enabled {
6107 false
6108 } else if requested_source
6109 == Some(CompletionsMenuSource::Words {
6110 ignore_threshold: true,
6111 })
6112 {
6113 true
6114 } else {
6115 load_provider_completions
6116 && completion_settings.words != WordsCompletionMode::Disabled
6117 && (ignore_word_threshold || {
6118 let words_min_length = completion_settings.words_min_length;
6119 // check whether word has at least `words_min_length` characters
6120 let query_chars = query.iter().flat_map(|q| q.chars());
6121 query_chars.take(words_min_length).count() == words_min_length
6122 })
6123 };
6124
6125 let mut words = if load_word_completions {
6126 cx.background_spawn({
6127 let buffer_snapshot = buffer_snapshot.clone();
6128 async move {
6129 buffer_snapshot.words_in_range(WordsQuery {
6130 fuzzy_contents: None,
6131 range: word_search_range,
6132 skip_digits,
6133 })
6134 }
6135 })
6136 } else {
6137 Task::ready(BTreeMap::default())
6138 };
6139
6140 let snippets = if let Some(provider) = &provider
6141 && provider.show_snippets()
6142 && let Some(project) = self.project()
6143 {
6144 let char_classifier = buffer_snapshot
6145 .char_classifier_at(buffer_position)
6146 .scope_context(Some(CharScopeContext::Completion));
6147 project.update(cx, |project, cx| {
6148 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6149 })
6150 } else {
6151 Task::ready(Ok(CompletionResponse {
6152 completions: Vec::new(),
6153 display_options: Default::default(),
6154 is_incomplete: false,
6155 }))
6156 };
6157
6158 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6159
6160 let id = post_inc(&mut self.next_completion_id);
6161 let task = cx.spawn_in(window, async move |editor, cx| {
6162 let Ok(()) = editor.update(cx, |this, _| {
6163 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6164 }) else {
6165 return;
6166 };
6167
6168 // TODO: Ideally completions from different sources would be selectively re-queried, so
6169 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6170 let mut completions = Vec::new();
6171 let mut is_incomplete = false;
6172 let mut display_options: Option<CompletionDisplayOptions> = None;
6173 if let Some(provider_responses) = provider_responses.await.log_err()
6174 && !provider_responses.is_empty()
6175 {
6176 for response in provider_responses {
6177 completions.extend(response.completions);
6178 is_incomplete = is_incomplete || response.is_incomplete;
6179 match display_options.as_mut() {
6180 None => {
6181 display_options = Some(response.display_options);
6182 }
6183 Some(options) => options.merge(&response.display_options),
6184 }
6185 }
6186 if completion_settings.words == WordsCompletionMode::Fallback {
6187 words = Task::ready(BTreeMap::default());
6188 }
6189 }
6190 let display_options = display_options.unwrap_or_default();
6191
6192 let mut words = words.await;
6193 if let Some(word_to_exclude) = &word_to_exclude {
6194 words.remove(word_to_exclude);
6195 }
6196 for lsp_completion in &completions {
6197 words.remove(&lsp_completion.new_text);
6198 }
6199 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6200 replace_range: word_replace_range.clone(),
6201 new_text: word.clone(),
6202 label: CodeLabel::plain(word, None),
6203 match_start: None,
6204 snippet_deduplication_key: None,
6205 icon_path: None,
6206 documentation: None,
6207 source: CompletionSource::BufferWord {
6208 word_range,
6209 resolved: false,
6210 },
6211 insert_text_mode: Some(InsertTextMode::AS_IS),
6212 confirm: None,
6213 }));
6214
6215 completions.extend(
6216 snippets
6217 .await
6218 .into_iter()
6219 .flat_map(|response| response.completions),
6220 );
6221
6222 let menu = if completions.is_empty() {
6223 None
6224 } else {
6225 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6226 let languages = editor
6227 .workspace
6228 .as_ref()
6229 .and_then(|(workspace, _)| workspace.upgrade())
6230 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6231 let menu = CompletionsMenu::new(
6232 id,
6233 requested_source.unwrap_or(if load_provider_completions {
6234 CompletionsMenuSource::Normal
6235 } else {
6236 CompletionsMenuSource::SnippetsOnly
6237 }),
6238 sort_completions,
6239 show_completion_documentation,
6240 position,
6241 query.clone(),
6242 is_incomplete,
6243 buffer.clone(),
6244 completions.into(),
6245 editor
6246 .context_menu()
6247 .borrow_mut()
6248 .as_ref()
6249 .map(|menu| menu.primary_scroll_handle()),
6250 display_options,
6251 snippet_sort_order,
6252 languages,
6253 language,
6254 cx,
6255 );
6256
6257 let query = if filter_completions { query } else { None };
6258 let matches_task = menu.do_async_filtering(
6259 query.unwrap_or_default(),
6260 buffer_position,
6261 &buffer,
6262 cx,
6263 );
6264 (menu, matches_task)
6265 }) else {
6266 return;
6267 };
6268
6269 let matches = matches_task.await;
6270
6271 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6272 // Newer menu already set, so exit.
6273 if let Some(CodeContextMenu::Completions(prev_menu)) =
6274 editor.context_menu.borrow().as_ref()
6275 && prev_menu.id > id
6276 {
6277 return;
6278 };
6279
6280 // Only valid to take prev_menu because either the new menu is immediately set
6281 // below, or the menu is hidden.
6282 if let Some(CodeContextMenu::Completions(prev_menu)) =
6283 editor.context_menu.borrow_mut().take()
6284 {
6285 let position_matches =
6286 if prev_menu.initial_position == menu.initial_position {
6287 true
6288 } else {
6289 let snapshot = editor.buffer.read(cx).read(cx);
6290 prev_menu.initial_position.to_offset(&snapshot)
6291 == menu.initial_position.to_offset(&snapshot)
6292 };
6293 if position_matches {
6294 // Preserve markdown cache before `set_filter_results` because it will
6295 // try to populate the documentation cache.
6296 menu.preserve_markdown_cache(prev_menu);
6297 }
6298 };
6299
6300 menu.set_filter_results(matches, provider, window, cx);
6301 }) else {
6302 return;
6303 };
6304
6305 menu.visible().then_some(menu)
6306 };
6307
6308 editor
6309 .update_in(cx, |editor, window, cx| {
6310 if editor.focus_handle.is_focused(window)
6311 && let Some(menu) = menu
6312 {
6313 *editor.context_menu.borrow_mut() =
6314 Some(CodeContextMenu::Completions(menu));
6315
6316 crate::hover_popover::hide_hover(editor, cx);
6317 if editor.show_edit_predictions_in_menu() {
6318 editor.update_visible_edit_prediction(window, cx);
6319 } else {
6320 editor
6321 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6322 }
6323
6324 cx.notify();
6325 return;
6326 }
6327
6328 if editor.completion_tasks.len() <= 1 {
6329 // If there are no more completion tasks and the last menu was empty, we should hide it.
6330 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6331 // If it was already hidden and we don't show edit predictions in the menu,
6332 // we should also show the edit prediction when available.
6333 if was_hidden && editor.show_edit_predictions_in_menu() {
6334 editor.update_visible_edit_prediction(window, cx);
6335 }
6336 }
6337 })
6338 .ok();
6339 });
6340
6341 self.completion_tasks.push((id, task));
6342 }
6343
6344 #[cfg(any(test, feature = "test-support"))]
6345 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6346 let menu = self.context_menu.borrow();
6347 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6348 let completions = menu.completions.borrow();
6349 Some(completions.to_vec())
6350 } else {
6351 None
6352 }
6353 }
6354
6355 pub fn with_completions_menu_matching_id<R>(
6356 &self,
6357 id: CompletionId,
6358 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6359 ) -> R {
6360 let mut context_menu = self.context_menu.borrow_mut();
6361 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6362 return f(None);
6363 };
6364 if completions_menu.id != id {
6365 return f(None);
6366 }
6367 f(Some(completions_menu))
6368 }
6369
6370 pub fn confirm_completion(
6371 &mut self,
6372 action: &ConfirmCompletion,
6373 window: &mut Window,
6374 cx: &mut Context<Self>,
6375 ) -> Option<Task<Result<()>>> {
6376 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6377 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6378 }
6379
6380 pub fn confirm_completion_insert(
6381 &mut self,
6382 _: &ConfirmCompletionInsert,
6383 window: &mut Window,
6384 cx: &mut Context<Self>,
6385 ) -> Option<Task<Result<()>>> {
6386 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6387 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6388 }
6389
6390 pub fn confirm_completion_replace(
6391 &mut self,
6392 _: &ConfirmCompletionReplace,
6393 window: &mut Window,
6394 cx: &mut Context<Self>,
6395 ) -> Option<Task<Result<()>>> {
6396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6397 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6398 }
6399
6400 pub fn compose_completion(
6401 &mut self,
6402 action: &ComposeCompletion,
6403 window: &mut Window,
6404 cx: &mut Context<Self>,
6405 ) -> Option<Task<Result<()>>> {
6406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6407 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6408 }
6409
6410 fn do_completion(
6411 &mut self,
6412 item_ix: Option<usize>,
6413 intent: CompletionIntent,
6414 window: &mut Window,
6415 cx: &mut Context<Editor>,
6416 ) -> Option<Task<Result<()>>> {
6417 use language::ToOffset as _;
6418
6419 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6420 else {
6421 return None;
6422 };
6423
6424 let candidate_id = {
6425 let entries = completions_menu.entries.borrow();
6426 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6427 if self.show_edit_predictions_in_menu() {
6428 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6429 }
6430 mat.candidate_id
6431 };
6432
6433 let completion = completions_menu
6434 .completions
6435 .borrow()
6436 .get(candidate_id)?
6437 .clone();
6438 cx.stop_propagation();
6439
6440 let buffer_handle = completions_menu.buffer.clone();
6441
6442 let CompletionEdit {
6443 new_text,
6444 snippet,
6445 replace_range,
6446 } = process_completion_for_edit(
6447 &completion,
6448 intent,
6449 &buffer_handle,
6450 &completions_menu.initial_position.text_anchor,
6451 cx,
6452 );
6453
6454 let buffer = buffer_handle.read(cx);
6455 let snapshot = self.buffer.read(cx).snapshot(cx);
6456 let newest_anchor = self.selections.newest_anchor();
6457 let replace_range_multibuffer = {
6458 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6459 excerpt.map_range_from_buffer(replace_range.clone())
6460 };
6461 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6462 return None;
6463 }
6464
6465 let old_text = buffer
6466 .text_for_range(replace_range.clone())
6467 .collect::<String>();
6468 let lookbehind = newest_anchor
6469 .start
6470 .text_anchor
6471 .to_offset(buffer)
6472 .saturating_sub(replace_range.start.0);
6473 let lookahead = replace_range
6474 .end
6475 .0
6476 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6477 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6478 let suffix = &old_text[lookbehind.min(old_text.len())..];
6479
6480 let selections = self
6481 .selections
6482 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6483 let mut ranges = Vec::new();
6484 let mut all_commit_ranges = Vec::new();
6485 let mut linked_edits = LinkedEdits::new();
6486
6487 let text: Arc<str> = new_text.clone().into();
6488 for selection in &selections {
6489 let range = if selection.id == newest_anchor.id {
6490 replace_range_multibuffer.clone()
6491 } else {
6492 let mut range = selection.range();
6493
6494 // if prefix is present, don't duplicate it
6495 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6496 range.start = range.start.saturating_sub_usize(lookbehind);
6497
6498 // if suffix is also present, mimic the newest cursor and replace it
6499 if selection.id != newest_anchor.id
6500 && snapshot.contains_str_at(range.end, suffix)
6501 {
6502 range.end += lookahead;
6503 }
6504 }
6505 range
6506 };
6507
6508 ranges.push(range.clone());
6509
6510 let start_anchor = snapshot.anchor_before(range.start);
6511 let end_anchor = snapshot.anchor_after(range.end);
6512 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6513 all_commit_ranges.push(anchor_range.clone());
6514
6515 if !self.linked_edit_ranges.is_empty() {
6516 linked_edits.push(&self, anchor_range, text.clone(), cx);
6517 }
6518 }
6519
6520 let common_prefix_len = old_text
6521 .chars()
6522 .zip(new_text.chars())
6523 .take_while(|(a, b)| a == b)
6524 .map(|(a, _)| a.len_utf8())
6525 .sum::<usize>();
6526
6527 cx.emit(EditorEvent::InputHandled {
6528 utf16_range_to_replace: None,
6529 text: new_text[common_prefix_len..].into(),
6530 });
6531
6532 self.transact(window, cx, |editor, window, cx| {
6533 if let Some(mut snippet) = snippet {
6534 snippet.text = new_text.to_string();
6535 editor
6536 .insert_snippet(&ranges, snippet, window, cx)
6537 .log_err();
6538 } else {
6539 editor.buffer.update(cx, |multi_buffer, cx| {
6540 let auto_indent = match completion.insert_text_mode {
6541 Some(InsertTextMode::AS_IS) => None,
6542 _ => editor.autoindent_mode.clone(),
6543 };
6544 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6545 multi_buffer.edit(edits, auto_indent, cx);
6546 });
6547 }
6548 linked_edits.apply(cx);
6549 editor.refresh_edit_prediction(true, false, window, cx);
6550 });
6551 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6552
6553 let show_new_completions_on_confirm = completion
6554 .confirm
6555 .as_ref()
6556 .is_some_and(|confirm| confirm(intent, window, cx));
6557 if show_new_completions_on_confirm {
6558 self.open_or_update_completions_menu(None, None, false, window, cx);
6559 }
6560
6561 let provider = self.completion_provider.as_ref()?;
6562
6563 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6564 let command = lsp_store.as_ref().and_then(|lsp_store| {
6565 let CompletionSource::Lsp {
6566 lsp_completion,
6567 server_id,
6568 ..
6569 } = &completion.source
6570 else {
6571 return None;
6572 };
6573 let lsp_command = lsp_completion.command.as_ref()?;
6574 let available_commands = lsp_store
6575 .read(cx)
6576 .lsp_server_capabilities
6577 .get(server_id)
6578 .and_then(|server_capabilities| {
6579 server_capabilities
6580 .execute_command_provider
6581 .as_ref()
6582 .map(|options| options.commands.as_slice())
6583 })?;
6584 if available_commands.contains(&lsp_command.command) {
6585 Some(CodeAction {
6586 server_id: *server_id,
6587 range: language::Anchor::MIN..language::Anchor::MIN,
6588 lsp_action: LspAction::Command(lsp_command.clone()),
6589 resolved: false,
6590 })
6591 } else {
6592 None
6593 }
6594 });
6595
6596 drop(completion);
6597 let apply_edits = provider.apply_additional_edits_for_completion(
6598 buffer_handle.clone(),
6599 completions_menu.completions.clone(),
6600 candidate_id,
6601 true,
6602 all_commit_ranges,
6603 cx,
6604 );
6605
6606 let editor_settings = EditorSettings::get_global(cx);
6607 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6608 // After the code completion is finished, users often want to know what signatures are needed.
6609 // so we should automatically call signature_help
6610 self.show_signature_help(&ShowSignatureHelp, window, cx);
6611 }
6612
6613 Some(cx.spawn_in(window, async move |editor, cx| {
6614 apply_edits.await?;
6615
6616 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6617 let title = command.lsp_action.title().to_owned();
6618 let project_transaction = lsp_store
6619 .update(cx, |lsp_store, cx| {
6620 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6621 })
6622 .await
6623 .context("applying post-completion command")?;
6624 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6625 Self::open_project_transaction(
6626 &editor,
6627 workspace.downgrade(),
6628 project_transaction,
6629 title,
6630 cx,
6631 )
6632 .await?;
6633 }
6634 }
6635
6636 Ok(())
6637 }))
6638 }
6639
6640 pub fn toggle_code_actions(
6641 &mut self,
6642 action: &ToggleCodeActions,
6643 window: &mut Window,
6644 cx: &mut Context<Self>,
6645 ) {
6646 let quick_launch = action.quick_launch;
6647 let mut context_menu = self.context_menu.borrow_mut();
6648 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6649 if code_actions.deployed_from == action.deployed_from {
6650 // Toggle if we're selecting the same one
6651 *context_menu = None;
6652 cx.notify();
6653 return;
6654 } else {
6655 // Otherwise, clear it and start a new one
6656 *context_menu = None;
6657 cx.notify();
6658 }
6659 }
6660 drop(context_menu);
6661 let snapshot = self.snapshot(window, cx);
6662 let deployed_from = action.deployed_from.clone();
6663 let action = action.clone();
6664 self.completion_tasks.clear();
6665 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6666
6667 let multibuffer_point = match &action.deployed_from {
6668 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6669 DisplayPoint::new(*row, 0).to_point(&snapshot)
6670 }
6671 _ => self
6672 .selections
6673 .newest::<Point>(&snapshot.display_snapshot)
6674 .head(),
6675 };
6676 let Some((buffer, buffer_row)) = snapshot
6677 .buffer_snapshot()
6678 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6679 .and_then(|(buffer_snapshot, range)| {
6680 self.buffer()
6681 .read(cx)
6682 .buffer(buffer_snapshot.remote_id())
6683 .map(|buffer| (buffer, range.start.row))
6684 })
6685 else {
6686 return;
6687 };
6688 let buffer_id = buffer.read(cx).remote_id();
6689 let tasks = self
6690 .runnables
6691 .runnables((buffer_id, buffer_row))
6692 .map(|t| Arc::new(t.to_owned()));
6693
6694 if !self.focus_handle.is_focused(window) {
6695 return;
6696 }
6697 let project = self.project.clone();
6698
6699 let code_actions_task = match deployed_from {
6700 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6701 _ => self.code_actions(buffer_row, window, cx),
6702 };
6703
6704 let runnable_task = match deployed_from {
6705 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6706 _ => {
6707 let mut task_context_task = Task::ready(None);
6708 if let Some(tasks) = &tasks
6709 && let Some(project) = project
6710 {
6711 task_context_task =
6712 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6713 }
6714
6715 cx.spawn_in(window, {
6716 let buffer = buffer.clone();
6717 async move |editor, cx| {
6718 let task_context = task_context_task.await;
6719
6720 let resolved_tasks =
6721 tasks
6722 .zip(task_context.clone())
6723 .map(|(tasks, task_context)| ResolvedTasks {
6724 templates: tasks.resolve(&task_context).collect(),
6725 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6726 multibuffer_point.row,
6727 tasks.column,
6728 )),
6729 });
6730 let debug_scenarios = editor
6731 .update(cx, |editor, cx| {
6732 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6733 })?
6734 .await;
6735 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6736 }
6737 })
6738 }
6739 };
6740
6741 cx.spawn_in(window, async move |editor, cx| {
6742 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6743 let code_actions = code_actions_task.await;
6744 let spawn_straight_away = quick_launch
6745 && resolved_tasks
6746 .as_ref()
6747 .is_some_and(|tasks| tasks.templates.len() == 1)
6748 && code_actions
6749 .as_ref()
6750 .is_none_or(|actions| actions.is_empty())
6751 && debug_scenarios.is_empty();
6752
6753 editor.update_in(cx, |editor, window, cx| {
6754 crate::hover_popover::hide_hover(editor, cx);
6755 let actions = CodeActionContents::new(
6756 resolved_tasks,
6757 code_actions,
6758 debug_scenarios,
6759 task_context.unwrap_or_default(),
6760 );
6761
6762 // Don't show the menu if there are no actions available
6763 if actions.is_empty() {
6764 cx.notify();
6765 return Task::ready(Ok(()));
6766 }
6767
6768 *editor.context_menu.borrow_mut() =
6769 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6770 buffer,
6771 actions,
6772 selected_item: Default::default(),
6773 scroll_handle: UniformListScrollHandle::default(),
6774 deployed_from,
6775 }));
6776 cx.notify();
6777 if spawn_straight_away
6778 && let Some(task) = editor.confirm_code_action(
6779 &ConfirmCodeAction { item_ix: Some(0) },
6780 window,
6781 cx,
6782 )
6783 {
6784 return task;
6785 }
6786
6787 Task::ready(Ok(()))
6788 })
6789 })
6790 .detach_and_log_err(cx);
6791 }
6792
6793 fn debug_scenarios(
6794 &mut self,
6795 resolved_tasks: &Option<ResolvedTasks>,
6796 buffer: &Entity<Buffer>,
6797 cx: &mut App,
6798 ) -> Task<Vec<task::DebugScenario>> {
6799 maybe!({
6800 let project = self.project()?;
6801 let dap_store = project.read(cx).dap_store();
6802 let mut scenarios = vec![];
6803 let resolved_tasks = resolved_tasks.as_ref()?;
6804 let buffer = buffer.read(cx);
6805 let language = buffer.language()?;
6806 let file = buffer.file();
6807 let debug_adapter = language_settings(language.name().into(), file, cx)
6808 .debuggers
6809 .first()
6810 .map(SharedString::from)
6811 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6812
6813 dap_store.update(cx, |dap_store, cx| {
6814 for (_, task) in &resolved_tasks.templates {
6815 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6816 task.original_task().clone(),
6817 debug_adapter.clone().into(),
6818 task.display_label().to_owned().into(),
6819 cx,
6820 );
6821 scenarios.push(maybe_scenario);
6822 }
6823 });
6824 Some(cx.background_spawn(async move {
6825 futures::future::join_all(scenarios)
6826 .await
6827 .into_iter()
6828 .flatten()
6829 .collect::<Vec<_>>()
6830 }))
6831 })
6832 .unwrap_or_else(|| Task::ready(vec![]))
6833 }
6834
6835 fn code_actions(
6836 &mut self,
6837 buffer_row: u32,
6838 window: &mut Window,
6839 cx: &mut Context<Self>,
6840 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6841 let mut task = self.code_actions_task.take();
6842 cx.spawn_in(window, async move |editor, cx| {
6843 while let Some(prev_task) = task {
6844 prev_task.await.log_err();
6845 task = editor
6846 .update(cx, |this, _| this.code_actions_task.take())
6847 .ok()?;
6848 }
6849
6850 editor
6851 .update(cx, |editor, cx| {
6852 editor
6853 .available_code_actions
6854 .clone()
6855 .and_then(|(location, code_actions)| {
6856 let snapshot = location.buffer.read(cx).snapshot();
6857 let point_range = location.range.to_point(&snapshot);
6858 let point_range = point_range.start.row..=point_range.end.row;
6859 if point_range.contains(&buffer_row) {
6860 Some(code_actions)
6861 } else {
6862 None
6863 }
6864 })
6865 })
6866 .ok()
6867 .flatten()
6868 })
6869 }
6870
6871 pub fn confirm_code_action(
6872 &mut self,
6873 action: &ConfirmCodeAction,
6874 window: &mut Window,
6875 cx: &mut Context<Self>,
6876 ) -> Option<Task<Result<()>>> {
6877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6878
6879 let actions_menu =
6880 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6881 menu
6882 } else {
6883 return None;
6884 };
6885
6886 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6887 let action = actions_menu.actions.get(action_ix)?;
6888 let title = action.label();
6889 let buffer = actions_menu.buffer;
6890 let workspace = self.workspace()?;
6891
6892 match action {
6893 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6894 workspace.update(cx, |workspace, cx| {
6895 workspace.schedule_resolved_task(
6896 task_source_kind,
6897 resolved_task,
6898 false,
6899 window,
6900 cx,
6901 );
6902
6903 Some(Task::ready(Ok(())))
6904 })
6905 }
6906 CodeActionsItem::CodeAction {
6907 excerpt_id,
6908 action,
6909 provider,
6910 } => {
6911 let apply_code_action =
6912 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6913 let workspace = workspace.downgrade();
6914 Some(cx.spawn_in(window, async move |editor, cx| {
6915 let project_transaction = apply_code_action.await?;
6916 Self::open_project_transaction(
6917 &editor,
6918 workspace,
6919 project_transaction,
6920 title,
6921 cx,
6922 )
6923 .await
6924 }))
6925 }
6926 CodeActionsItem::DebugScenario(scenario) => {
6927 let context = actions_menu.actions.context.into();
6928
6929 workspace.update(cx, |workspace, cx| {
6930 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6931 workspace.start_debug_session(
6932 scenario,
6933 context,
6934 Some(buffer),
6935 None,
6936 window,
6937 cx,
6938 );
6939 });
6940 Some(Task::ready(Ok(())))
6941 }
6942 }
6943 }
6944
6945 fn open_transaction_for_hidden_buffers(
6946 workspace: Entity<Workspace>,
6947 transaction: ProjectTransaction,
6948 title: String,
6949 window: &mut Window,
6950 cx: &mut Context<Self>,
6951 ) {
6952 if transaction.0.is_empty() {
6953 return;
6954 }
6955
6956 let edited_buffers_already_open = {
6957 let other_editors: Vec<Entity<Editor>> = workspace
6958 .read(cx)
6959 .panes()
6960 .iter()
6961 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6962 .filter(|editor| editor.entity_id() != cx.entity_id())
6963 .collect();
6964
6965 transaction.0.keys().all(|buffer| {
6966 other_editors.iter().any(|editor| {
6967 let multi_buffer = editor.read(cx).buffer();
6968 multi_buffer.read(cx).is_singleton()
6969 && multi_buffer
6970 .read(cx)
6971 .as_singleton()
6972 .map_or(false, |singleton| {
6973 singleton.entity_id() == buffer.entity_id()
6974 })
6975 })
6976 })
6977 };
6978 if !edited_buffers_already_open {
6979 let workspace = workspace.downgrade();
6980 cx.defer_in(window, move |_, window, cx| {
6981 cx.spawn_in(window, async move |editor, cx| {
6982 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6983 .await
6984 .ok()
6985 })
6986 .detach();
6987 });
6988 }
6989 }
6990
6991 pub async fn open_project_transaction(
6992 editor: &WeakEntity<Editor>,
6993 workspace: WeakEntity<Workspace>,
6994 transaction: ProjectTransaction,
6995 title: String,
6996 cx: &mut AsyncWindowContext,
6997 ) -> Result<()> {
6998 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6999 cx.update(|_, cx| {
7000 entries.sort_unstable_by_key(|(buffer, _)| {
7001 buffer.read(cx).file().map(|f| f.path().clone())
7002 });
7003 })?;
7004 if entries.is_empty() {
7005 return Ok(());
7006 }
7007
7008 // If the project transaction's edits are all contained within this editor, then
7009 // avoid opening a new editor to display them.
7010
7011 if let [(buffer, transaction)] = &*entries {
7012 let excerpt = editor.update(cx, |editor, cx| {
7013 editor
7014 .buffer()
7015 .read(cx)
7016 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7017 })?;
7018 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7019 && excerpted_buffer == *buffer
7020 {
7021 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7022 let excerpt_range = excerpt_range.to_offset(buffer);
7023 buffer
7024 .edited_ranges_for_transaction::<usize>(transaction)
7025 .all(|range| {
7026 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7027 })
7028 });
7029
7030 if all_edits_within_excerpt {
7031 return Ok(());
7032 }
7033 }
7034 }
7035
7036 let mut ranges_to_highlight = Vec::new();
7037 let excerpt_buffer = cx.new(|cx| {
7038 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7039 for (buffer_handle, transaction) in &entries {
7040 let edited_ranges = buffer_handle
7041 .read(cx)
7042 .edited_ranges_for_transaction::<Point>(transaction)
7043 .collect::<Vec<_>>();
7044 let (ranges, _) = multibuffer.set_excerpts_for_path(
7045 PathKey::for_buffer(buffer_handle, cx),
7046 buffer_handle.clone(),
7047 edited_ranges,
7048 multibuffer_context_lines(cx),
7049 cx,
7050 );
7051
7052 ranges_to_highlight.extend(ranges);
7053 }
7054 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7055 multibuffer
7056 });
7057
7058 workspace.update_in(cx, |workspace, window, cx| {
7059 let project = workspace.project().clone();
7060 let editor =
7061 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7062 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7063 editor.update(cx, |editor, cx| {
7064 editor.highlight_background(
7065 HighlightKey::Editor,
7066 &ranges_to_highlight,
7067 |_, theme| theme.colors().editor_highlighted_line_background,
7068 cx,
7069 );
7070 });
7071 })?;
7072
7073 Ok(())
7074 }
7075
7076 pub fn clear_code_action_providers(&mut self) {
7077 self.code_action_providers.clear();
7078 self.available_code_actions.take();
7079 }
7080
7081 pub fn add_code_action_provider(
7082 &mut self,
7083 provider: Rc<dyn CodeActionProvider>,
7084 window: &mut Window,
7085 cx: &mut Context<Self>,
7086 ) {
7087 if self
7088 .code_action_providers
7089 .iter()
7090 .any(|existing_provider| existing_provider.id() == provider.id())
7091 {
7092 return;
7093 }
7094
7095 self.code_action_providers.push(provider);
7096 self.refresh_code_actions(window, cx);
7097 }
7098
7099 pub fn remove_code_action_provider(
7100 &mut self,
7101 id: Arc<str>,
7102 window: &mut Window,
7103 cx: &mut Context<Self>,
7104 ) {
7105 self.code_action_providers
7106 .retain(|provider| provider.id() != id);
7107 self.refresh_code_actions(window, cx);
7108 }
7109
7110 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7111 !self.code_action_providers.is_empty()
7112 && EditorSettings::get_global(cx).toolbar.code_actions
7113 }
7114
7115 pub fn has_available_code_actions(&self) -> bool {
7116 self.available_code_actions
7117 .as_ref()
7118 .is_some_and(|(_, actions)| !actions.is_empty())
7119 }
7120
7121 fn render_inline_code_actions(
7122 &self,
7123 icon_size: ui::IconSize,
7124 display_row: DisplayRow,
7125 is_active: bool,
7126 cx: &mut Context<Self>,
7127 ) -> AnyElement {
7128 let show_tooltip = !self.context_menu_visible();
7129 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7130 .icon_size(icon_size)
7131 .shape(ui::IconButtonShape::Square)
7132 .icon_color(ui::Color::Hidden)
7133 .toggle_state(is_active)
7134 .when(show_tooltip, |this| {
7135 this.tooltip({
7136 let focus_handle = self.focus_handle.clone();
7137 move |_window, cx| {
7138 Tooltip::for_action_in(
7139 "Toggle Code Actions",
7140 &ToggleCodeActions {
7141 deployed_from: None,
7142 quick_launch: false,
7143 },
7144 &focus_handle,
7145 cx,
7146 )
7147 }
7148 })
7149 })
7150 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7151 window.focus(&editor.focus_handle(cx), cx);
7152 editor.toggle_code_actions(
7153 &crate::actions::ToggleCodeActions {
7154 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7155 display_row,
7156 )),
7157 quick_launch: false,
7158 },
7159 window,
7160 cx,
7161 );
7162 }))
7163 .into_any_element()
7164 }
7165
7166 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7167 &self.context_menu
7168 }
7169
7170 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7171 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7172 cx.background_executor()
7173 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7174 .await;
7175
7176 let (start_buffer, start, _, end, newest_selection) = this
7177 .update(cx, |this, cx| {
7178 let newest_selection = this.selections.newest_anchor().clone();
7179 if newest_selection.head().diff_base_anchor.is_some() {
7180 return None;
7181 }
7182 let display_snapshot = this.display_snapshot(cx);
7183 let newest_selection_adjusted =
7184 this.selections.newest_adjusted(&display_snapshot);
7185 let buffer = this.buffer.read(cx);
7186
7187 let (start_buffer, start) =
7188 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7189 let (end_buffer, end) =
7190 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7191
7192 Some((start_buffer, start, end_buffer, end, newest_selection))
7193 })?
7194 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7195 .context(
7196 "Expected selection to lie in a single buffer when refreshing code actions",
7197 )?;
7198 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7199 let providers = this.code_action_providers.clone();
7200 let tasks = this
7201 .code_action_providers
7202 .iter()
7203 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7204 .collect::<Vec<_>>();
7205 (providers, tasks)
7206 })?;
7207
7208 let mut actions = Vec::new();
7209 for (provider, provider_actions) in
7210 providers.into_iter().zip(future::join_all(tasks).await)
7211 {
7212 if let Some(provider_actions) = provider_actions.log_err() {
7213 actions.extend(provider_actions.into_iter().map(|action| {
7214 AvailableCodeAction {
7215 excerpt_id: newest_selection.start.excerpt_id,
7216 action,
7217 provider: provider.clone(),
7218 }
7219 }));
7220 }
7221 }
7222
7223 this.update(cx, |this, cx| {
7224 this.available_code_actions = if actions.is_empty() {
7225 None
7226 } else {
7227 Some((
7228 Location {
7229 buffer: start_buffer,
7230 range: start..end,
7231 },
7232 actions.into(),
7233 ))
7234 };
7235 cx.notify();
7236 })
7237 }));
7238 }
7239
7240 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7241 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7242 self.show_git_blame_inline = false;
7243
7244 self.show_git_blame_inline_delay_task =
7245 Some(cx.spawn_in(window, async move |this, cx| {
7246 cx.background_executor().timer(delay).await;
7247
7248 this.update(cx, |this, cx| {
7249 this.show_git_blame_inline = true;
7250 cx.notify();
7251 })
7252 .log_err();
7253 }));
7254 }
7255 }
7256
7257 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7258 let snapshot = self.snapshot(window, cx);
7259 let cursor = self
7260 .selections
7261 .newest::<Point>(&snapshot.display_snapshot)
7262 .head();
7263 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7264 else {
7265 return;
7266 };
7267
7268 if self.blame.is_none() {
7269 self.start_git_blame(true, window, cx);
7270 }
7271 let Some(blame) = self.blame.as_ref() else {
7272 return;
7273 };
7274
7275 let row_info = RowInfo {
7276 buffer_id: Some(buffer.remote_id()),
7277 buffer_row: Some(point.row),
7278 ..Default::default()
7279 };
7280 let Some((buffer, blame_entry)) = blame
7281 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7282 .flatten()
7283 else {
7284 return;
7285 };
7286
7287 let anchor = self.selections.newest_anchor().head();
7288 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7289 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7290 self.show_blame_popover(
7291 buffer,
7292 &blame_entry,
7293 position + last_bounds.origin,
7294 true,
7295 cx,
7296 );
7297 };
7298 }
7299
7300 fn show_blame_popover(
7301 &mut self,
7302 buffer: BufferId,
7303 blame_entry: &BlameEntry,
7304 position: gpui::Point<Pixels>,
7305 ignore_timeout: bool,
7306 cx: &mut Context<Self>,
7307 ) {
7308 if let Some(state) = &mut self.inline_blame_popover {
7309 state.hide_task.take();
7310 } else {
7311 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7312 let blame_entry = blame_entry.clone();
7313 let show_task = cx.spawn(async move |editor, cx| {
7314 if !ignore_timeout {
7315 cx.background_executor()
7316 .timer(std::time::Duration::from_millis(blame_popover_delay))
7317 .await;
7318 }
7319 editor
7320 .update(cx, |editor, cx| {
7321 editor.inline_blame_popover_show_task.take();
7322 let Some(blame) = editor.blame.as_ref() else {
7323 return;
7324 };
7325 let blame = blame.read(cx);
7326 let details = blame.details_for_entry(buffer, &blame_entry);
7327 let markdown = cx.new(|cx| {
7328 Markdown::new(
7329 details
7330 .as_ref()
7331 .map(|message| message.message.clone())
7332 .unwrap_or_default(),
7333 None,
7334 None,
7335 cx,
7336 )
7337 });
7338 editor.inline_blame_popover = Some(InlineBlamePopover {
7339 position,
7340 hide_task: None,
7341 popover_bounds: None,
7342 popover_state: InlineBlamePopoverState {
7343 scroll_handle: ScrollHandle::new(),
7344 commit_message: details,
7345 markdown,
7346 },
7347 keyboard_grace: ignore_timeout,
7348 });
7349 cx.notify();
7350 })
7351 .ok();
7352 });
7353 self.inline_blame_popover_show_task = Some(show_task);
7354 }
7355 }
7356
7357 pub fn has_mouse_context_menu(&self) -> bool {
7358 self.mouse_context_menu.is_some()
7359 }
7360
7361 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7362 self.inline_blame_popover_show_task.take();
7363 if let Some(state) = &mut self.inline_blame_popover {
7364 let hide_task = cx.spawn(async move |editor, cx| {
7365 if !ignore_timeout {
7366 cx.background_executor()
7367 .timer(std::time::Duration::from_millis(100))
7368 .await;
7369 }
7370 editor
7371 .update(cx, |editor, cx| {
7372 editor.inline_blame_popover.take();
7373 cx.notify();
7374 })
7375 .ok();
7376 });
7377 state.hide_task = Some(hide_task);
7378 true
7379 } else {
7380 false
7381 }
7382 }
7383
7384 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7385 if self.pending_rename.is_some() {
7386 return None;
7387 }
7388
7389 let provider = self.semantics_provider.clone()?;
7390 let buffer = self.buffer.read(cx);
7391 let newest_selection = self.selections.newest_anchor().clone();
7392 let cursor_position = newest_selection.head();
7393 let (cursor_buffer, cursor_buffer_position) =
7394 buffer.text_anchor_for_position(cursor_position, cx)?;
7395 let (tail_buffer, tail_buffer_position) =
7396 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7397 if cursor_buffer != tail_buffer {
7398 return None;
7399 }
7400
7401 let snapshot = cursor_buffer.read(cx).snapshot();
7402 let word_ranges = cx.background_spawn(async move {
7403 // this might look odd to put on the background thread, but
7404 // `surrounding_word` can be quite expensive as it calls into
7405 // tree-sitter language scopes
7406 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7407 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7408 (start_word_range, end_word_range)
7409 });
7410
7411 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7412 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7413 let (start_word_range, end_word_range) = word_ranges.await;
7414 if start_word_range != end_word_range {
7415 this.update(cx, |this, cx| {
7416 this.document_highlights_task.take();
7417 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7418 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7419 })
7420 .ok();
7421 return;
7422 }
7423 cx.background_executor()
7424 .timer(Duration::from_millis(debounce))
7425 .await;
7426
7427 let highlights = if let Some(highlights) = cx.update(|cx| {
7428 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7429 }) {
7430 highlights.await.log_err()
7431 } else {
7432 None
7433 };
7434
7435 if let Some(highlights) = highlights {
7436 this.update(cx, |this, cx| {
7437 if this.pending_rename.is_some() {
7438 return;
7439 }
7440
7441 let buffer = this.buffer.read(cx);
7442 if buffer
7443 .text_anchor_for_position(cursor_position, cx)
7444 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7445 {
7446 return;
7447 }
7448
7449 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7450 let mut write_ranges = Vec::new();
7451 let mut read_ranges = Vec::new();
7452 for highlight in highlights {
7453 let buffer_id = cursor_buffer.read(cx).remote_id();
7454 for (excerpt_id, _, excerpt_range) in
7455 buffer.excerpts_for_buffer(buffer_id, cx)
7456 {
7457 let start = highlight
7458 .range
7459 .start
7460 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7461 let end = highlight
7462 .range
7463 .end
7464 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7465 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7466 continue;
7467 }
7468
7469 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7470 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7471 write_ranges.push(range);
7472 } else {
7473 read_ranges.push(range);
7474 }
7475 }
7476 }
7477
7478 this.highlight_background(
7479 HighlightKey::DocumentHighlightRead,
7480 &read_ranges,
7481 |_, theme| theme.colors().editor_document_highlight_read_background,
7482 cx,
7483 );
7484 this.highlight_background(
7485 HighlightKey::DocumentHighlightWrite,
7486 &write_ranges,
7487 |_, theme| theme.colors().editor_document_highlight_write_background,
7488 cx,
7489 );
7490 cx.notify();
7491 })
7492 .log_err();
7493 }
7494 }));
7495 None
7496 }
7497
7498 fn prepare_highlight_query_from_selection(
7499 &mut self,
7500 snapshot: &DisplaySnapshot,
7501 cx: &mut Context<Editor>,
7502 ) -> Option<(String, Range<Anchor>)> {
7503 if matches!(self.mode, EditorMode::SingleLine) {
7504 return None;
7505 }
7506 if !EditorSettings::get_global(cx).selection_highlight {
7507 return None;
7508 }
7509 if self.selections.count() != 1 || self.selections.line_mode() {
7510 return None;
7511 }
7512 let selection = self.selections.newest::<Point>(&snapshot);
7513 // If the selection spans multiple rows OR it is empty
7514 if selection.start.row != selection.end.row
7515 || selection.start.column == selection.end.column
7516 {
7517 return None;
7518 }
7519 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7520 let query = snapshot
7521 .buffer_snapshot()
7522 .text_for_range(selection_anchor_range.clone())
7523 .collect::<String>();
7524 if query.trim().is_empty() {
7525 return None;
7526 }
7527 Some((query, selection_anchor_range))
7528 }
7529
7530 #[ztracing::instrument(skip_all)]
7531 fn update_selection_occurrence_highlights(
7532 &mut self,
7533 multi_buffer_snapshot: MultiBufferSnapshot,
7534 query_text: String,
7535 query_range: Range<Anchor>,
7536 multi_buffer_range_to_query: Range<Point>,
7537 use_debounce: bool,
7538 window: &mut Window,
7539 cx: &mut Context<Editor>,
7540 ) -> Task<()> {
7541 cx.spawn_in(window, async move |editor, cx| {
7542 if use_debounce {
7543 cx.background_executor()
7544 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7545 .await;
7546 }
7547 let match_task = cx.background_spawn(async move {
7548 let buffer_ranges = multi_buffer_snapshot
7549 .range_to_buffer_ranges(
7550 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7551 )
7552 .into_iter()
7553 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7554 let mut match_ranges = Vec::new();
7555 let Ok(regex) = project::search::SearchQuery::text(
7556 query_text,
7557 false,
7558 false,
7559 false,
7560 Default::default(),
7561 Default::default(),
7562 false,
7563 None,
7564 ) else {
7565 return Vec::default();
7566 };
7567 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7568 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7569 match_ranges.extend(
7570 regex
7571 .search(
7572 buffer_snapshot,
7573 Some(search_range.start.0..search_range.end.0),
7574 )
7575 .await
7576 .into_iter()
7577 .filter_map(|match_range| {
7578 let match_start = buffer_snapshot
7579 .anchor_after(search_range.start + match_range.start);
7580 let match_end = buffer_snapshot
7581 .anchor_before(search_range.start + match_range.end);
7582 let match_anchor_range =
7583 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7584 (match_anchor_range != query_range).then_some(match_anchor_range)
7585 }),
7586 );
7587 }
7588 match_ranges
7589 });
7590 let match_ranges = match_task.await;
7591 editor
7592 .update_in(cx, |editor, _, cx| {
7593 if use_debounce {
7594 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7595 editor.debounced_selection_highlight_complete = true;
7596 } else if editor.debounced_selection_highlight_complete {
7597 return;
7598 }
7599 if !match_ranges.is_empty() {
7600 editor.highlight_background(
7601 HighlightKey::SelectedTextHighlight,
7602 &match_ranges,
7603 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7604 cx,
7605 )
7606 }
7607 })
7608 .log_err();
7609 })
7610 }
7611
7612 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7613 struct NewlineFold;
7614 let type_id = std::any::TypeId::of::<NewlineFold>();
7615 if !self.mode.is_single_line() {
7616 return;
7617 }
7618 let snapshot = self.snapshot(window, cx);
7619 if snapshot.buffer_snapshot().max_point().row == 0 {
7620 return;
7621 }
7622 let task = cx.background_spawn(async move {
7623 let new_newlines = snapshot
7624 .buffer_chars_at(MultiBufferOffset(0))
7625 .filter_map(|(c, i)| {
7626 if c == '\n' {
7627 Some(
7628 snapshot.buffer_snapshot().anchor_after(i)
7629 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7630 )
7631 } else {
7632 None
7633 }
7634 })
7635 .collect::<Vec<_>>();
7636 let existing_newlines = snapshot
7637 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7638 .filter_map(|fold| {
7639 if fold.placeholder.type_tag == Some(type_id) {
7640 Some(fold.range.start..fold.range.end)
7641 } else {
7642 None
7643 }
7644 })
7645 .collect::<Vec<_>>();
7646
7647 (new_newlines, existing_newlines)
7648 });
7649 self.folding_newlines = cx.spawn(async move |this, cx| {
7650 let (new_newlines, existing_newlines) = task.await;
7651 if new_newlines == existing_newlines {
7652 return;
7653 }
7654 let placeholder = FoldPlaceholder {
7655 render: Arc::new(move |_, _, cx| {
7656 div()
7657 .bg(cx.theme().status().hint_background)
7658 .border_b_1()
7659 .size_full()
7660 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7661 .border_color(cx.theme().status().hint)
7662 .child("\\n")
7663 .into_any()
7664 }),
7665 constrain_width: false,
7666 merge_adjacent: false,
7667 type_tag: Some(type_id),
7668 collapsed_text: None,
7669 };
7670 let creases = new_newlines
7671 .into_iter()
7672 .map(|range| Crease::simple(range, placeholder.clone()))
7673 .collect();
7674 this.update(cx, |this, cx| {
7675 this.display_map.update(cx, |display_map, cx| {
7676 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7677 display_map.fold(creases, cx);
7678 });
7679 })
7680 .ok();
7681 });
7682 }
7683
7684 #[ztracing::instrument(skip_all)]
7685 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7686 if !self.lsp_data_enabled() {
7687 return;
7688 }
7689 let cursor = self.selections.newest_anchor().head();
7690 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7691
7692 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7693 self.outline_symbols_at_cursor =
7694 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7695 cx.emit(EditorEvent::OutlineSymbolsChanged);
7696 cx.notify();
7697 } else {
7698 let syntax = cx.theme().syntax().clone();
7699 let background_task = cx.background_spawn(async move {
7700 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7701 });
7702 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7703 cx.spawn(async move |this, cx| {
7704 let symbols = background_task.await;
7705 this.update(cx, |this, cx| {
7706 this.outline_symbols_at_cursor = symbols;
7707 cx.emit(EditorEvent::OutlineSymbolsChanged);
7708 cx.notify();
7709 })
7710 .ok();
7711 });
7712 }
7713 }
7714
7715 #[ztracing::instrument(skip_all)]
7716 fn refresh_selected_text_highlights(
7717 &mut self,
7718 snapshot: &DisplaySnapshot,
7719 on_buffer_edit: bool,
7720 window: &mut Window,
7721 cx: &mut Context<Editor>,
7722 ) {
7723 let Some((query_text, query_range)) =
7724 self.prepare_highlight_query_from_selection(snapshot, cx)
7725 else {
7726 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7727 self.quick_selection_highlight_task.take();
7728 self.debounced_selection_highlight_task.take();
7729 self.debounced_selection_highlight_complete = false;
7730 return;
7731 };
7732 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7733 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7734 let query_changed = self
7735 .quick_selection_highlight_task
7736 .as_ref()
7737 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7738 if query_changed {
7739 self.debounced_selection_highlight_complete = false;
7740 }
7741 if on_buffer_edit || query_changed {
7742 self.quick_selection_highlight_task = Some((
7743 query_range.clone(),
7744 self.update_selection_occurrence_highlights(
7745 snapshot.buffer.clone(),
7746 query_text.clone(),
7747 query_range.clone(),
7748 self.multi_buffer_visible_range(&display_snapshot, cx),
7749 false,
7750 window,
7751 cx,
7752 ),
7753 ));
7754 }
7755 if on_buffer_edit
7756 || self
7757 .debounced_selection_highlight_task
7758 .as_ref()
7759 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7760 {
7761 let multi_buffer_start = multi_buffer_snapshot
7762 .anchor_before(MultiBufferOffset(0))
7763 .to_point(&multi_buffer_snapshot);
7764 let multi_buffer_end = multi_buffer_snapshot
7765 .anchor_after(multi_buffer_snapshot.len())
7766 .to_point(&multi_buffer_snapshot);
7767 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7768 self.debounced_selection_highlight_task = Some((
7769 query_range.clone(),
7770 self.update_selection_occurrence_highlights(
7771 snapshot.buffer.clone(),
7772 query_text,
7773 query_range,
7774 multi_buffer_full_range,
7775 true,
7776 window,
7777 cx,
7778 ),
7779 ));
7780 }
7781 }
7782
7783 pub fn multi_buffer_visible_range(
7784 &self,
7785 display_snapshot: &DisplaySnapshot,
7786 cx: &App,
7787 ) -> Range<Point> {
7788 let visible_start = self
7789 .scroll_manager
7790 .native_anchor(display_snapshot, cx)
7791 .anchor
7792 .to_point(display_snapshot.buffer_snapshot())
7793 .to_display_point(display_snapshot);
7794
7795 let mut target_end = visible_start;
7796 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7797
7798 visible_start.to_point(display_snapshot)
7799 ..display_snapshot
7800 .clip_point(target_end, Bias::Right)
7801 .to_point(display_snapshot)
7802 }
7803
7804 pub fn refresh_edit_prediction(
7805 &mut self,
7806 debounce: bool,
7807 user_requested: bool,
7808 window: &mut Window,
7809 cx: &mut Context<Self>,
7810 ) -> Option<()> {
7811 if self.leader_id.is_some() {
7812 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7813 return None;
7814 }
7815
7816 let cursor = self.selections.newest_anchor().head();
7817 let (buffer, cursor_buffer_position) =
7818 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7819
7820 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7821 return None;
7822 }
7823
7824 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7825 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7826 return None;
7827 }
7828
7829 self.update_visible_edit_prediction(window, cx);
7830
7831 if !user_requested
7832 && (!self.should_show_edit_predictions()
7833 || !self.is_focused(window)
7834 || buffer.read(cx).is_empty())
7835 {
7836 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7837 return None;
7838 }
7839
7840 self.edit_prediction_provider()?
7841 .refresh(buffer, cursor_buffer_position, debounce, cx);
7842 Some(())
7843 }
7844
7845 fn show_edit_predictions_in_menu(&self) -> bool {
7846 match self.edit_prediction_settings {
7847 EditPredictionSettings::Disabled => false,
7848 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7849 }
7850 }
7851
7852 pub fn edit_predictions_enabled(&self) -> bool {
7853 match self.edit_prediction_settings {
7854 EditPredictionSettings::Disabled => false,
7855 EditPredictionSettings::Enabled { .. } => true,
7856 }
7857 }
7858
7859 fn edit_prediction_requires_modifier(&self) -> bool {
7860 match self.edit_prediction_settings {
7861 EditPredictionSettings::Disabled => false,
7862 EditPredictionSettings::Enabled {
7863 preview_requires_modifier,
7864 ..
7865 } => preview_requires_modifier,
7866 }
7867 }
7868
7869 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7870 if self.edit_prediction_provider.is_none() {
7871 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7872 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7873 return;
7874 }
7875
7876 let selection = self.selections.newest_anchor();
7877 let cursor = selection.head();
7878
7879 if let Some((buffer, cursor_buffer_position)) =
7880 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7881 {
7882 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7883 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7884 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7885 return;
7886 }
7887 self.edit_prediction_settings =
7888 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7889 }
7890 }
7891
7892 fn edit_prediction_settings_at_position(
7893 &self,
7894 buffer: &Entity<Buffer>,
7895 buffer_position: language::Anchor,
7896 cx: &App,
7897 ) -> EditPredictionSettings {
7898 if !self.mode.is_full()
7899 || !self.show_edit_predictions_override.unwrap_or(true)
7900 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7901 {
7902 return EditPredictionSettings::Disabled;
7903 }
7904
7905 let buffer = buffer.read(cx);
7906
7907 let file = buffer.file();
7908
7909 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7910 return EditPredictionSettings::Disabled;
7911 };
7912
7913 let by_provider = matches!(
7914 self.menu_edit_predictions_policy,
7915 MenuEditPredictionsPolicy::ByProvider
7916 );
7917
7918 let show_in_menu = by_provider
7919 && self
7920 .edit_prediction_provider
7921 .as_ref()
7922 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7923
7924 let preview_requires_modifier =
7925 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7926
7927 EditPredictionSettings::Enabled {
7928 show_in_menu,
7929 preview_requires_modifier,
7930 }
7931 }
7932
7933 fn should_show_edit_predictions(&self) -> bool {
7934 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7935 }
7936
7937 pub fn edit_prediction_preview_is_active(&self) -> bool {
7938 matches!(
7939 self.edit_prediction_preview,
7940 EditPredictionPreview::Active { .. }
7941 )
7942 }
7943
7944 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7945 let cursor = self.selections.newest_anchor().head();
7946 if let Some((buffer, cursor_position)) =
7947 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7948 {
7949 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7950 } else {
7951 false
7952 }
7953 }
7954
7955 pub fn supports_minimap(&self, cx: &App) -> bool {
7956 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7957 }
7958
7959 fn edit_predictions_enabled_in_buffer(
7960 &self,
7961 buffer: &Entity<Buffer>,
7962 buffer_position: language::Anchor,
7963 cx: &App,
7964 ) -> bool {
7965 maybe!({
7966 if self.read_only(cx) || self.leader_id.is_some() {
7967 return Some(false);
7968 }
7969 let provider = self.edit_prediction_provider()?;
7970 if !provider.is_enabled(buffer, buffer_position, cx) {
7971 return Some(false);
7972 }
7973 let buffer = buffer.read(cx);
7974 let Some(file) = buffer.file() else {
7975 return Some(true);
7976 };
7977 let settings = all_language_settings(Some(file), cx);
7978 Some(settings.edit_predictions_enabled_for_file(file, cx))
7979 })
7980 .unwrap_or(false)
7981 }
7982
7983 pub fn show_edit_prediction(
7984 &mut self,
7985 _: &ShowEditPrediction,
7986 window: &mut Window,
7987 cx: &mut Context<Self>,
7988 ) {
7989 if !self.has_active_edit_prediction() {
7990 self.refresh_edit_prediction(false, true, window, cx);
7991 return;
7992 }
7993
7994 self.update_visible_edit_prediction(window, cx);
7995 }
7996
7997 pub fn display_cursor_names(
7998 &mut self,
7999 _: &DisplayCursorNames,
8000 window: &mut Window,
8001 cx: &mut Context<Self>,
8002 ) {
8003 self.show_cursor_names(window, cx);
8004 }
8005
8006 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8007 self.show_cursor_names = true;
8008 cx.notify();
8009 cx.spawn_in(window, async move |this, cx| {
8010 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8011 this.update(cx, |this, cx| {
8012 this.show_cursor_names = false;
8013 cx.notify()
8014 })
8015 .ok()
8016 })
8017 .detach();
8018 }
8019
8020 pub fn accept_partial_edit_prediction(
8021 &mut self,
8022 granularity: EditPredictionGranularity,
8023 window: &mut Window,
8024 cx: &mut Context<Self>,
8025 ) {
8026 if self.show_edit_predictions_in_menu() {
8027 self.hide_context_menu(window, cx);
8028 }
8029
8030 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8031 return;
8032 };
8033
8034 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8035 return;
8036 }
8037
8038 match &active_edit_prediction.completion {
8039 EditPrediction::MoveWithin { target, .. } => {
8040 let target = *target;
8041
8042 if matches!(granularity, EditPredictionGranularity::Full) {
8043 if let Some(position_map) = &self.last_position_map {
8044 let target_row = target.to_display_point(&position_map.snapshot).row();
8045 let is_visible = position_map.visible_row_range.contains(&target_row);
8046
8047 if is_visible || !self.edit_prediction_requires_modifier() {
8048 self.unfold_ranges(&[target..target], true, false, cx);
8049 self.change_selections(
8050 SelectionEffects::scroll(Autoscroll::newest()),
8051 window,
8052 cx,
8053 |selections| {
8054 selections.select_anchor_ranges([target..target]);
8055 },
8056 );
8057 self.clear_row_highlights::<EditPredictionPreview>();
8058 self.edit_prediction_preview
8059 .set_previous_scroll_position(None);
8060 } else {
8061 // Highlight and request scroll
8062 self.edit_prediction_preview
8063 .set_previous_scroll_position(Some(
8064 position_map.snapshot.scroll_anchor,
8065 ));
8066 self.highlight_rows::<EditPredictionPreview>(
8067 target..target,
8068 cx.theme().colors().editor_highlighted_line_background,
8069 RowHighlightOptions {
8070 autoscroll: true,
8071 ..Default::default()
8072 },
8073 cx,
8074 );
8075 self.request_autoscroll(Autoscroll::fit(), cx);
8076 }
8077 }
8078 } else {
8079 self.change_selections(
8080 SelectionEffects::scroll(Autoscroll::newest()),
8081 window,
8082 cx,
8083 |selections| {
8084 selections.select_anchor_ranges([target..target]);
8085 },
8086 );
8087 }
8088 }
8089 EditPrediction::MoveOutside { snapshot, target } => {
8090 if let Some(workspace) = self.workspace() {
8091 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8092 .detach_and_log_err(cx);
8093 }
8094 }
8095 EditPrediction::Edit {
8096 edits,
8097 cursor_position,
8098 ..
8099 } => {
8100 self.report_edit_prediction_event(
8101 active_edit_prediction.completion_id.clone(),
8102 true,
8103 cx,
8104 );
8105
8106 match granularity {
8107 EditPredictionGranularity::Full => {
8108 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8109
8110 // Compute fallback cursor position BEFORE applying the edit,
8111 // so the anchor tracks through the edit correctly
8112 let fallback_cursor_target = {
8113 let snapshot = self.buffer.read(cx).snapshot(cx);
8114 edits.last().unwrap().0.end.bias_right(&snapshot)
8115 };
8116
8117 self.buffer.update(cx, |buffer, cx| {
8118 buffer.edit(edits.iter().cloned(), None, cx)
8119 });
8120
8121 if let Some(provider) = self.edit_prediction_provider() {
8122 provider.accept(cx);
8123 }
8124
8125 // Resolve cursor position after the edit is applied
8126 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8127 // The anchor tracks through the edit, then we add the offset
8128 let snapshot = self.buffer.read(cx).snapshot(cx);
8129 let base_offset = anchor.to_offset(&snapshot).0;
8130 let target_offset =
8131 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8132 snapshot.anchor_after(target_offset)
8133 } else {
8134 fallback_cursor_target
8135 };
8136
8137 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8138 s.select_anchor_ranges([cursor_target..cursor_target]);
8139 });
8140
8141 let selections = self.selections.disjoint_anchors_arc();
8142 if let Some(transaction_id_now) =
8143 self.buffer.read(cx).last_transaction_id(cx)
8144 {
8145 if transaction_id_prev != Some(transaction_id_now) {
8146 self.selection_history
8147 .insert_transaction(transaction_id_now, selections);
8148 }
8149 }
8150
8151 self.update_visible_edit_prediction(window, cx);
8152 if self.active_edit_prediction.is_none() {
8153 self.refresh_edit_prediction(true, true, window, cx);
8154 }
8155 cx.notify();
8156 }
8157 _ => {
8158 let snapshot = self.buffer.read(cx).snapshot(cx);
8159 let cursor_offset = self
8160 .selections
8161 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8162 .head();
8163
8164 let insertion = edits.iter().find_map(|(range, text)| {
8165 let range = range.to_offset(&snapshot);
8166 if range.is_empty() && range.start == cursor_offset {
8167 Some(text)
8168 } else {
8169 None
8170 }
8171 });
8172
8173 if let Some(text) = insertion {
8174 let text_to_insert = match granularity {
8175 EditPredictionGranularity::Word => {
8176 let mut partial = text
8177 .chars()
8178 .by_ref()
8179 .take_while(|c| c.is_alphabetic())
8180 .collect::<String>();
8181 if partial.is_empty() {
8182 partial = text
8183 .chars()
8184 .by_ref()
8185 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8186 .collect::<String>();
8187 }
8188 partial
8189 }
8190 EditPredictionGranularity::Line => {
8191 if let Some(line) = text.split_inclusive('\n').next() {
8192 line.to_string()
8193 } else {
8194 text.to_string()
8195 }
8196 }
8197 EditPredictionGranularity::Full => unreachable!(),
8198 };
8199
8200 cx.emit(EditorEvent::InputHandled {
8201 utf16_range_to_replace: None,
8202 text: text_to_insert.clone().into(),
8203 });
8204
8205 self.replace_selections(&text_to_insert, None, window, cx, false);
8206 self.refresh_edit_prediction(true, true, window, cx);
8207 cx.notify();
8208 } else {
8209 self.accept_partial_edit_prediction(
8210 EditPredictionGranularity::Full,
8211 window,
8212 cx,
8213 );
8214 }
8215 }
8216 }
8217 }
8218 }
8219
8220 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8221 }
8222
8223 pub fn accept_next_word_edit_prediction(
8224 &mut self,
8225 _: &AcceptNextWordEditPrediction,
8226 window: &mut Window,
8227 cx: &mut Context<Self>,
8228 ) {
8229 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8230 }
8231
8232 pub fn accept_next_line_edit_prediction(
8233 &mut self,
8234 _: &AcceptNextLineEditPrediction,
8235 window: &mut Window,
8236 cx: &mut Context<Self>,
8237 ) {
8238 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8239 }
8240
8241 pub fn accept_edit_prediction(
8242 &mut self,
8243 _: &AcceptEditPrediction,
8244 window: &mut Window,
8245 cx: &mut Context<Self>,
8246 ) {
8247 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8248 }
8249
8250 fn discard_edit_prediction(
8251 &mut self,
8252 reason: EditPredictionDiscardReason,
8253 cx: &mut Context<Self>,
8254 ) -> bool {
8255 if reason == EditPredictionDiscardReason::Rejected {
8256 let completion_id = self
8257 .active_edit_prediction
8258 .as_ref()
8259 .and_then(|active_completion| active_completion.completion_id.clone());
8260
8261 self.report_edit_prediction_event(completion_id, false, cx);
8262 }
8263
8264 if let Some(provider) = self.edit_prediction_provider() {
8265 provider.discard(reason, cx);
8266 }
8267
8268 self.take_active_edit_prediction(cx)
8269 }
8270
8271 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8272 let Some(provider) = self.edit_prediction_provider() else {
8273 return;
8274 };
8275
8276 let Some((_, buffer, _)) = self
8277 .buffer
8278 .read(cx)
8279 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8280 else {
8281 return;
8282 };
8283
8284 let extension = buffer
8285 .read(cx)
8286 .file()
8287 .and_then(|file| Some(file.path().extension()?.to_string()));
8288
8289 let event_type = match accepted {
8290 true => "Edit Prediction Accepted",
8291 false => "Edit Prediction Discarded",
8292 };
8293 telemetry::event!(
8294 event_type,
8295 provider = provider.name(),
8296 prediction_id = id,
8297 suggestion_accepted = accepted,
8298 file_extension = extension,
8299 );
8300 }
8301
8302 fn open_editor_at_anchor(
8303 snapshot: &language::BufferSnapshot,
8304 target: language::Anchor,
8305 workspace: &Entity<Workspace>,
8306 window: &mut Window,
8307 cx: &mut App,
8308 ) -> Task<Result<()>> {
8309 workspace.update(cx, |workspace, cx| {
8310 let path = snapshot.file().map(|file| file.full_path(cx));
8311 let Some(path) =
8312 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8313 else {
8314 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8315 };
8316 let target = text::ToPoint::to_point(&target, snapshot);
8317 let item = workspace.open_path(path, None, true, window, cx);
8318 window.spawn(cx, async move |cx| {
8319 let Some(editor) = item.await?.downcast::<Editor>() else {
8320 return Ok(());
8321 };
8322 editor
8323 .update_in(cx, |editor, window, cx| {
8324 editor.go_to_singleton_buffer_point(target, window, cx);
8325 })
8326 .ok();
8327 anyhow::Ok(())
8328 })
8329 })
8330 }
8331
8332 pub fn has_active_edit_prediction(&self) -> bool {
8333 self.active_edit_prediction.is_some()
8334 }
8335
8336 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8337 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8338 return false;
8339 };
8340
8341 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8342 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8343 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8344 true
8345 }
8346
8347 /// Returns true when we're displaying the edit prediction popover below the cursor
8348 /// like we are not previewing and the LSP autocomplete menu is visible
8349 /// or we are in `when_holding_modifier` mode.
8350 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8351 if self.edit_prediction_preview_is_active()
8352 || !self.show_edit_predictions_in_menu()
8353 || !self.edit_predictions_enabled()
8354 {
8355 return false;
8356 }
8357
8358 if self.has_visible_completions_menu() {
8359 return true;
8360 }
8361
8362 has_completion && self.edit_prediction_requires_modifier()
8363 }
8364
8365 fn handle_modifiers_changed(
8366 &mut self,
8367 modifiers: Modifiers,
8368 position_map: &PositionMap,
8369 window: &mut Window,
8370 cx: &mut Context<Self>,
8371 ) {
8372 // Ensure that the edit prediction preview is updated, even when not
8373 // enabled, if there's an active edit prediction preview.
8374 if self.show_edit_predictions_in_menu()
8375 || matches!(
8376 self.edit_prediction_preview,
8377 EditPredictionPreview::Active { .. }
8378 )
8379 {
8380 self.update_edit_prediction_preview(&modifiers, window, cx);
8381 }
8382
8383 self.update_selection_mode(&modifiers, position_map, window, cx);
8384
8385 let mouse_position = window.mouse_position();
8386 if !position_map.text_hitbox.is_hovered(window) {
8387 return;
8388 }
8389
8390 self.update_hovered_link(
8391 position_map.point_for_position(mouse_position),
8392 Some(mouse_position),
8393 &position_map.snapshot,
8394 modifiers,
8395 window,
8396 cx,
8397 )
8398 }
8399
8400 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8401 match EditorSettings::get_global(cx).multi_cursor_modifier {
8402 MultiCursorModifier::Alt => modifiers.secondary(),
8403 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8404 }
8405 }
8406
8407 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8408 match EditorSettings::get_global(cx).multi_cursor_modifier {
8409 MultiCursorModifier::Alt => modifiers.alt,
8410 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8411 }
8412 }
8413
8414 fn columnar_selection_mode(
8415 modifiers: &Modifiers,
8416 cx: &mut Context<Self>,
8417 ) -> Option<ColumnarMode> {
8418 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8419 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8420 Some(ColumnarMode::FromMouse)
8421 } else if Self::is_alt_pressed(modifiers, cx) {
8422 Some(ColumnarMode::FromSelection)
8423 } else {
8424 None
8425 }
8426 } else {
8427 None
8428 }
8429 }
8430
8431 fn update_selection_mode(
8432 &mut self,
8433 modifiers: &Modifiers,
8434 position_map: &PositionMap,
8435 window: &mut Window,
8436 cx: &mut Context<Self>,
8437 ) {
8438 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8439 return;
8440 };
8441 if self.selections.pending_anchor().is_none() {
8442 return;
8443 }
8444
8445 let mouse_position = window.mouse_position();
8446 let point_for_position = position_map.point_for_position(mouse_position);
8447 let position = point_for_position.previous_valid;
8448
8449 self.select(
8450 SelectPhase::BeginColumnar {
8451 position,
8452 reset: false,
8453 mode,
8454 goal_column: point_for_position.exact_unclipped.column(),
8455 },
8456 window,
8457 cx,
8458 );
8459 }
8460
8461 fn update_edit_prediction_preview(
8462 &mut self,
8463 modifiers: &Modifiers,
8464 window: &mut Window,
8465 cx: &mut Context<Self>,
8466 ) {
8467 let mut modifiers_held = false;
8468
8469 // Check bindings for all granularities.
8470 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8471 let granularities = [
8472 EditPredictionGranularity::Full,
8473 EditPredictionGranularity::Line,
8474 EditPredictionGranularity::Word,
8475 ];
8476
8477 for granularity in granularities {
8478 if let Some(keystroke) = self
8479 .accept_edit_prediction_keybind(granularity, window, cx)
8480 .keystroke()
8481 {
8482 modifiers_held = modifiers_held
8483 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8484 }
8485 }
8486
8487 if modifiers_held {
8488 if matches!(
8489 self.edit_prediction_preview,
8490 EditPredictionPreview::Inactive { .. }
8491 ) {
8492 self.edit_prediction_preview = EditPredictionPreview::Active {
8493 previous_scroll_position: None,
8494 since: Instant::now(),
8495 };
8496
8497 self.update_visible_edit_prediction(window, cx);
8498 cx.notify();
8499 }
8500 } else if let EditPredictionPreview::Active {
8501 previous_scroll_position,
8502 since,
8503 } = self.edit_prediction_preview
8504 {
8505 if let (Some(previous_scroll_position), Some(position_map)) =
8506 (previous_scroll_position, self.last_position_map.as_ref())
8507 {
8508 self.set_scroll_position(
8509 previous_scroll_position
8510 .scroll_position(&position_map.snapshot.display_snapshot),
8511 window,
8512 cx,
8513 );
8514 }
8515
8516 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8517 released_too_fast: since.elapsed() < Duration::from_millis(200),
8518 };
8519 self.clear_row_highlights::<EditPredictionPreview>();
8520 self.update_visible_edit_prediction(window, cx);
8521 cx.notify();
8522 }
8523 }
8524
8525 fn update_visible_edit_prediction(
8526 &mut self,
8527 _window: &mut Window,
8528 cx: &mut Context<Self>,
8529 ) -> Option<()> {
8530 if self.ime_transaction.is_some() {
8531 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8532 return None;
8533 }
8534
8535 let selection = self.selections.newest_anchor();
8536 let cursor = selection.head();
8537 let multibuffer = self.buffer.read(cx).snapshot(cx);
8538
8539 // Check project-level disable_ai setting for the current buffer
8540 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8541 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8542 return None;
8543 }
8544 }
8545 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8546 let excerpt_id = cursor.excerpt_id;
8547
8548 let show_in_menu = self.show_edit_predictions_in_menu();
8549 let completions_menu_has_precedence = !show_in_menu
8550 && (self.context_menu.borrow().is_some()
8551 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8552
8553 if completions_menu_has_precedence
8554 || !offset_selection.is_empty()
8555 || self
8556 .active_edit_prediction
8557 .as_ref()
8558 .is_some_and(|completion| {
8559 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8560 return false;
8561 };
8562 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8563 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8564 !invalidation_range.contains(&offset_selection.head())
8565 })
8566 {
8567 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8568 return None;
8569 }
8570
8571 self.take_active_edit_prediction(cx);
8572 let Some(provider) = self.edit_prediction_provider() else {
8573 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8574 return None;
8575 };
8576
8577 let (buffer, cursor_buffer_position) =
8578 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8579
8580 self.edit_prediction_settings =
8581 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8582
8583 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8584
8585 if self.edit_prediction_indent_conflict {
8586 let cursor_point = cursor.to_point(&multibuffer);
8587 let mut suggested_indent = None;
8588 multibuffer.suggested_indents_callback(
8589 cursor_point.row..cursor_point.row + 1,
8590 &mut |_, indent| {
8591 suggested_indent = Some(indent);
8592 ControlFlow::Break(())
8593 },
8594 cx,
8595 );
8596
8597 if let Some(indent) = suggested_indent
8598 && indent.len == cursor_point.column
8599 {
8600 self.edit_prediction_indent_conflict = false;
8601 }
8602 }
8603
8604 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8605
8606 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8607 {
8608 edit_prediction_types::EditPrediction::Local {
8609 id,
8610 edits,
8611 cursor_position,
8612 edit_preview,
8613 } => (id, edits, cursor_position, edit_preview),
8614 edit_prediction_types::EditPrediction::Jump {
8615 id,
8616 snapshot,
8617 target,
8618 } => {
8619 if let Some(provider) = &self.edit_prediction_provider {
8620 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8621 }
8622 self.stale_edit_prediction_in_menu = None;
8623 self.active_edit_prediction = Some(EditPredictionState {
8624 inlay_ids: vec![],
8625 completion: EditPrediction::MoveOutside { snapshot, target },
8626 completion_id: id,
8627 invalidation_range: None,
8628 });
8629 cx.notify();
8630 return Some(());
8631 }
8632 };
8633
8634 let edits = edits
8635 .into_iter()
8636 .flat_map(|(range, new_text)| {
8637 Some((
8638 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8639 new_text,
8640 ))
8641 })
8642 .collect::<Vec<_>>();
8643 if edits.is_empty() {
8644 return None;
8645 }
8646
8647 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8648 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8649 Some((anchor, predicted.offset))
8650 });
8651
8652 let first_edit_start = edits.first().unwrap().0.start;
8653 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8654 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8655
8656 let last_edit_end = edits.last().unwrap().0.end;
8657 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8658 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8659
8660 let cursor_row = cursor.to_point(&multibuffer).row;
8661
8662 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8663
8664 let mut inlay_ids = Vec::new();
8665 let invalidation_row_range;
8666 let move_invalidation_row_range = if cursor_row < edit_start_row {
8667 Some(cursor_row..edit_end_row)
8668 } else if cursor_row > edit_end_row {
8669 Some(edit_start_row..cursor_row)
8670 } else {
8671 None
8672 };
8673 let supports_jump = self
8674 .edit_prediction_provider
8675 .as_ref()
8676 .map(|provider| provider.provider.supports_jump_to_edit())
8677 .unwrap_or(true);
8678
8679 let is_move = supports_jump
8680 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8681 let completion = if is_move {
8682 if let Some(provider) = &self.edit_prediction_provider {
8683 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8684 }
8685 invalidation_row_range =
8686 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8687 let target = first_edit_start;
8688 EditPrediction::MoveWithin { target, snapshot }
8689 } else {
8690 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8691 && !self.edit_predictions_hidden_for_vim_mode;
8692
8693 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8694 if provider.show_tab_accept_marker() {
8695 EditDisplayMode::TabAccept
8696 } else {
8697 EditDisplayMode::Inline
8698 }
8699 } else {
8700 EditDisplayMode::DiffPopover
8701 };
8702
8703 if show_completions_in_buffer {
8704 if let Some(provider) = &self.edit_prediction_provider {
8705 let suggestion_display_type = match display_mode {
8706 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8707 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8708 SuggestionDisplayType::GhostText
8709 }
8710 };
8711 provider.provider.did_show(suggestion_display_type, cx);
8712 }
8713 if edits
8714 .iter()
8715 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8716 {
8717 let mut inlays = Vec::new();
8718 for (range, new_text) in &edits {
8719 let inlay = Inlay::edit_prediction(
8720 post_inc(&mut self.next_inlay_id),
8721 range.start,
8722 new_text.as_ref(),
8723 );
8724 inlay_ids.push(inlay.id);
8725 inlays.push(inlay);
8726 }
8727
8728 self.splice_inlays(&[], inlays, cx);
8729 } else {
8730 let background_color = cx.theme().status().deleted_background;
8731 self.highlight_text(
8732 HighlightKey::EditPredictionHighlight,
8733 edits.iter().map(|(range, _)| range.clone()).collect(),
8734 HighlightStyle {
8735 background_color: Some(background_color),
8736 ..Default::default()
8737 },
8738 cx,
8739 );
8740 }
8741 }
8742
8743 invalidation_row_range = edit_start_row..edit_end_row;
8744
8745 EditPrediction::Edit {
8746 edits,
8747 cursor_position,
8748 edit_preview,
8749 display_mode,
8750 snapshot,
8751 }
8752 };
8753
8754 let invalidation_range = multibuffer
8755 .anchor_before(Point::new(invalidation_row_range.start, 0))
8756 ..multibuffer.anchor_after(Point::new(
8757 invalidation_row_range.end,
8758 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8759 ));
8760
8761 self.stale_edit_prediction_in_menu = None;
8762 self.active_edit_prediction = Some(EditPredictionState {
8763 inlay_ids,
8764 completion,
8765 completion_id,
8766 invalidation_range: Some(invalidation_range),
8767 });
8768
8769 cx.notify();
8770
8771 Some(())
8772 }
8773
8774 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8775 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8776 }
8777
8778 /// Get all display points of breakpoints that will be rendered within editor
8779 ///
8780 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8781 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8782 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8783 fn active_breakpoints(
8784 &self,
8785 range: Range<DisplayRow>,
8786 window: &mut Window,
8787 cx: &mut Context<Self>,
8788 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8789 let mut breakpoint_display_points = HashMap::default();
8790
8791 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8792 return breakpoint_display_points;
8793 };
8794
8795 let snapshot = self.snapshot(window, cx);
8796
8797 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8798 let Some(project) = self.project() else {
8799 return breakpoint_display_points;
8800 };
8801
8802 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8803 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8804
8805 for (buffer_snapshot, range, excerpt_id) in
8806 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8807 {
8808 let Some(buffer) = project
8809 .read(cx)
8810 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8811 else {
8812 continue;
8813 };
8814 let breakpoints = breakpoint_store.read(cx).breakpoints(
8815 &buffer,
8816 Some(
8817 buffer_snapshot.anchor_before(range.start)
8818 ..buffer_snapshot.anchor_after(range.end),
8819 ),
8820 buffer_snapshot,
8821 cx,
8822 );
8823 for (breakpoint, state) in breakpoints {
8824 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8825 let position = multi_buffer_anchor
8826 .to_point(&multi_buffer_snapshot)
8827 .to_display_point(&snapshot);
8828
8829 breakpoint_display_points.insert(
8830 position.row(),
8831 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8832 );
8833 }
8834 }
8835
8836 breakpoint_display_points
8837 }
8838
8839 fn breakpoint_context_menu(
8840 &self,
8841 anchor: Anchor,
8842 window: &mut Window,
8843 cx: &mut Context<Self>,
8844 ) -> Entity<ui::ContextMenu> {
8845 let weak_editor = cx.weak_entity();
8846 let focus_handle = self.focus_handle(cx);
8847
8848 let row = self
8849 .buffer
8850 .read(cx)
8851 .snapshot(cx)
8852 .summary_for_anchor::<Point>(&anchor)
8853 .row;
8854
8855 let breakpoint = self
8856 .breakpoint_at_row(row, window, cx)
8857 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8858
8859 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8860 "Edit Log Breakpoint"
8861 } else {
8862 "Set Log Breakpoint"
8863 };
8864
8865 let condition_breakpoint_msg = if breakpoint
8866 .as_ref()
8867 .is_some_and(|bp| bp.1.condition.is_some())
8868 {
8869 "Edit Condition Breakpoint"
8870 } else {
8871 "Set Condition Breakpoint"
8872 };
8873
8874 let hit_condition_breakpoint_msg = if breakpoint
8875 .as_ref()
8876 .is_some_and(|bp| bp.1.hit_condition.is_some())
8877 {
8878 "Edit Hit Condition Breakpoint"
8879 } else {
8880 "Set Hit Condition Breakpoint"
8881 };
8882
8883 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8884 "Unset Breakpoint"
8885 } else {
8886 "Set Breakpoint"
8887 };
8888
8889 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8890
8891 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8892 BreakpointState::Enabled => Some("Disable"),
8893 BreakpointState::Disabled => Some("Enable"),
8894 });
8895
8896 let (anchor, breakpoint) =
8897 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8898
8899 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8900 menu.on_blur_subscription(Subscription::new(|| {}))
8901 .context(focus_handle)
8902 .when(run_to_cursor, |this| {
8903 let weak_editor = weak_editor.clone();
8904 this.entry("Run to Cursor", None, move |window, cx| {
8905 weak_editor
8906 .update(cx, |editor, cx| {
8907 editor.change_selections(
8908 SelectionEffects::no_scroll(),
8909 window,
8910 cx,
8911 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8912 );
8913 })
8914 .ok();
8915
8916 window.dispatch_action(Box::new(RunToCursor), cx);
8917 })
8918 .separator()
8919 })
8920 .when_some(toggle_state_msg, |this, msg| {
8921 this.entry(msg, None, {
8922 let weak_editor = weak_editor.clone();
8923 let breakpoint = breakpoint.clone();
8924 move |_window, cx| {
8925 weak_editor
8926 .update(cx, |this, cx| {
8927 this.edit_breakpoint_at_anchor(
8928 anchor,
8929 breakpoint.as_ref().clone(),
8930 BreakpointEditAction::InvertState,
8931 cx,
8932 );
8933 })
8934 .log_err();
8935 }
8936 })
8937 })
8938 .entry(set_breakpoint_msg, None, {
8939 let weak_editor = weak_editor.clone();
8940 let breakpoint = breakpoint.clone();
8941 move |_window, cx| {
8942 weak_editor
8943 .update(cx, |this, cx| {
8944 this.edit_breakpoint_at_anchor(
8945 anchor,
8946 breakpoint.as_ref().clone(),
8947 BreakpointEditAction::Toggle,
8948 cx,
8949 );
8950 })
8951 .log_err();
8952 }
8953 })
8954 .entry(log_breakpoint_msg, None, {
8955 let breakpoint = breakpoint.clone();
8956 let weak_editor = weak_editor.clone();
8957 move |window, cx| {
8958 weak_editor
8959 .update(cx, |this, cx| {
8960 this.add_edit_breakpoint_block(
8961 anchor,
8962 breakpoint.as_ref(),
8963 BreakpointPromptEditAction::Log,
8964 window,
8965 cx,
8966 );
8967 })
8968 .log_err();
8969 }
8970 })
8971 .entry(condition_breakpoint_msg, None, {
8972 let breakpoint = breakpoint.clone();
8973 let weak_editor = weak_editor.clone();
8974 move |window, cx| {
8975 weak_editor
8976 .update(cx, |this, cx| {
8977 this.add_edit_breakpoint_block(
8978 anchor,
8979 breakpoint.as_ref(),
8980 BreakpointPromptEditAction::Condition,
8981 window,
8982 cx,
8983 );
8984 })
8985 .log_err();
8986 }
8987 })
8988 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8989 weak_editor
8990 .update(cx, |this, cx| {
8991 this.add_edit_breakpoint_block(
8992 anchor,
8993 breakpoint.as_ref(),
8994 BreakpointPromptEditAction::HitCondition,
8995 window,
8996 cx,
8997 );
8998 })
8999 .log_err();
9000 })
9001 })
9002 }
9003
9004 fn render_breakpoint(
9005 &self,
9006 position: Anchor,
9007 row: DisplayRow,
9008 breakpoint: &Breakpoint,
9009 state: Option<BreakpointSessionState>,
9010 cx: &mut Context<Self>,
9011 ) -> IconButton {
9012 let is_rejected = state.is_some_and(|s| !s.verified);
9013 // Is it a breakpoint that shows up when hovering over gutter?
9014 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9015 (false, false),
9016 |PhantomBreakpointIndicator {
9017 is_active,
9018 display_row,
9019 collides_with_existing_breakpoint,
9020 }| {
9021 (
9022 is_active && display_row == row,
9023 collides_with_existing_breakpoint,
9024 )
9025 },
9026 );
9027
9028 let (color, icon) = {
9029 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9030 (false, false) => ui::IconName::DebugBreakpoint,
9031 (true, false) => ui::IconName::DebugLogBreakpoint,
9032 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9033 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9034 };
9035
9036 let theme_colors = cx.theme().colors();
9037
9038 let color = if is_phantom {
9039 if collides_with_existing {
9040 Color::Custom(
9041 theme_colors
9042 .debugger_accent
9043 .blend(theme_colors.text.opacity(0.6)),
9044 )
9045 } else {
9046 Color::Hint
9047 }
9048 } else if is_rejected {
9049 Color::Disabled
9050 } else {
9051 Color::Debugger
9052 };
9053
9054 (color, icon)
9055 };
9056
9057 let breakpoint = Arc::from(breakpoint.clone());
9058
9059 let alt_as_text = gpui::Keystroke {
9060 modifiers: Modifiers::secondary_key(),
9061 ..Default::default()
9062 };
9063 let primary_action_text = if breakpoint.is_disabled() {
9064 "Enable breakpoint"
9065 } else if is_phantom && !collides_with_existing {
9066 "Set breakpoint"
9067 } else {
9068 "Unset breakpoint"
9069 };
9070 let focus_handle = self.focus_handle.clone();
9071
9072 let meta = if is_rejected {
9073 SharedString::from("No executable code is associated with this line.")
9074 } else if collides_with_existing && !breakpoint.is_disabled() {
9075 SharedString::from(format!(
9076 "{alt_as_text}-click to disable,\nright-click for more options."
9077 ))
9078 } else {
9079 SharedString::from("Right-click for more options.")
9080 };
9081 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9082 .icon_size(IconSize::XSmall)
9083 .size(ui::ButtonSize::None)
9084 .when(is_rejected, |this| {
9085 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9086 })
9087 .icon_color(color)
9088 .style(ButtonStyle::Transparent)
9089 .on_click(cx.listener({
9090 move |editor, event: &ClickEvent, window, cx| {
9091 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9092 BreakpointEditAction::InvertState
9093 } else {
9094 BreakpointEditAction::Toggle
9095 };
9096
9097 window.focus(&editor.focus_handle(cx), cx);
9098 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9099 editor.edit_breakpoint_at_anchor(
9100 position,
9101 breakpoint.as_ref().clone(),
9102 edit_action,
9103 cx,
9104 );
9105 }
9106 }))
9107 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9108 editor.set_breakpoint_context_menu(
9109 row,
9110 Some(position),
9111 event.position(),
9112 window,
9113 cx,
9114 );
9115 }))
9116 .tooltip(move |_window, cx| {
9117 Tooltip::with_meta_in(
9118 primary_action_text,
9119 Some(&ToggleBreakpoint),
9120 meta.clone(),
9121 &focus_handle,
9122 cx,
9123 )
9124 })
9125 }
9126
9127 fn build_tasks_context(
9128 project: &Entity<Project>,
9129 buffer: &Entity<Buffer>,
9130 buffer_row: u32,
9131 tasks: &Arc<RunnableTasks>,
9132 cx: &mut Context<Self>,
9133 ) -> Task<Option<task::TaskContext>> {
9134 let position = Point::new(buffer_row, tasks.column);
9135 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9136 let location = Location {
9137 buffer: buffer.clone(),
9138 range: range_start..range_start,
9139 };
9140 // Fill in the environmental variables from the tree-sitter captures
9141 let mut captured_task_variables = TaskVariables::default();
9142 for (capture_name, value) in tasks.extra_variables.clone() {
9143 captured_task_variables.insert(
9144 task::VariableName::Custom(capture_name.into()),
9145 value.clone(),
9146 );
9147 }
9148 project.update(cx, |project, cx| {
9149 project.task_store().update(cx, |task_store, cx| {
9150 task_store.task_context_for_location(captured_task_variables, location, cx)
9151 })
9152 })
9153 }
9154
9155 pub fn context_menu_visible(&self) -> bool {
9156 !self.edit_prediction_preview_is_active()
9157 && self
9158 .context_menu
9159 .borrow()
9160 .as_ref()
9161 .is_some_and(|menu| menu.visible())
9162 }
9163
9164 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9165 self.context_menu
9166 .borrow()
9167 .as_ref()
9168 .map(|menu| menu.origin())
9169 }
9170
9171 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9172 self.context_menu_options = Some(options);
9173 }
9174
9175 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9176 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9177
9178 fn render_edit_prediction_popover(
9179 &mut self,
9180 text_bounds: &Bounds<Pixels>,
9181 content_origin: gpui::Point<Pixels>,
9182 right_margin: Pixels,
9183 editor_snapshot: &EditorSnapshot,
9184 visible_row_range: Range<DisplayRow>,
9185 scroll_top: ScrollOffset,
9186 scroll_bottom: ScrollOffset,
9187 line_layouts: &[LineWithInvisibles],
9188 line_height: Pixels,
9189 scroll_position: gpui::Point<ScrollOffset>,
9190 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9191 newest_selection_head: Option<DisplayPoint>,
9192 editor_width: Pixels,
9193 style: &EditorStyle,
9194 window: &mut Window,
9195 cx: &mut App,
9196 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9197 if self.mode().is_minimap() {
9198 return None;
9199 }
9200 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9201
9202 if self.edit_prediction_visible_in_cursor_popover(true) {
9203 return None;
9204 }
9205
9206 match &active_edit_prediction.completion {
9207 EditPrediction::MoveWithin { target, .. } => {
9208 let target_display_point = target.to_display_point(editor_snapshot);
9209
9210 if self.edit_prediction_requires_modifier() {
9211 if !self.edit_prediction_preview_is_active() {
9212 return None;
9213 }
9214
9215 self.render_edit_prediction_modifier_jump_popover(
9216 text_bounds,
9217 content_origin,
9218 visible_row_range,
9219 line_layouts,
9220 line_height,
9221 scroll_pixel_position,
9222 newest_selection_head,
9223 target_display_point,
9224 window,
9225 cx,
9226 )
9227 } else {
9228 self.render_edit_prediction_eager_jump_popover(
9229 text_bounds,
9230 content_origin,
9231 editor_snapshot,
9232 visible_row_range,
9233 scroll_top,
9234 scroll_bottom,
9235 line_height,
9236 scroll_pixel_position,
9237 target_display_point,
9238 editor_width,
9239 window,
9240 cx,
9241 )
9242 }
9243 }
9244 EditPrediction::Edit {
9245 display_mode: EditDisplayMode::Inline,
9246 ..
9247 } => None,
9248 EditPrediction::Edit {
9249 display_mode: EditDisplayMode::TabAccept,
9250 edits,
9251 ..
9252 } => {
9253 let range = &edits.first()?.0;
9254 let target_display_point = range.end.to_display_point(editor_snapshot);
9255
9256 self.render_edit_prediction_end_of_line_popover(
9257 "Accept",
9258 editor_snapshot,
9259 visible_row_range,
9260 target_display_point,
9261 line_height,
9262 scroll_pixel_position,
9263 content_origin,
9264 editor_width,
9265 window,
9266 cx,
9267 )
9268 }
9269 EditPrediction::Edit {
9270 edits,
9271 edit_preview,
9272 display_mode: EditDisplayMode::DiffPopover,
9273 snapshot,
9274 ..
9275 } => self.render_edit_prediction_diff_popover(
9276 text_bounds,
9277 content_origin,
9278 right_margin,
9279 editor_snapshot,
9280 visible_row_range,
9281 line_layouts,
9282 line_height,
9283 scroll_position,
9284 scroll_pixel_position,
9285 newest_selection_head,
9286 editor_width,
9287 style,
9288 edits,
9289 edit_preview,
9290 snapshot,
9291 window,
9292 cx,
9293 ),
9294 EditPrediction::MoveOutside { snapshot, .. } => {
9295 let mut element = self
9296 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9297 .into_any();
9298
9299 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9300 let origin_x = text_bounds.size.width - size.width - px(30.);
9301 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9302 element.prepaint_at(origin, window, cx);
9303
9304 Some((element, origin))
9305 }
9306 }
9307 }
9308
9309 fn render_edit_prediction_modifier_jump_popover(
9310 &mut self,
9311 text_bounds: &Bounds<Pixels>,
9312 content_origin: gpui::Point<Pixels>,
9313 visible_row_range: Range<DisplayRow>,
9314 line_layouts: &[LineWithInvisibles],
9315 line_height: Pixels,
9316 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9317 newest_selection_head: Option<DisplayPoint>,
9318 target_display_point: DisplayPoint,
9319 window: &mut Window,
9320 cx: &mut App,
9321 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9322 let scrolled_content_origin =
9323 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9324
9325 const SCROLL_PADDING_Y: Pixels = px(12.);
9326
9327 if target_display_point.row() < visible_row_range.start {
9328 return self.render_edit_prediction_scroll_popover(
9329 &|_| SCROLL_PADDING_Y,
9330 IconName::ArrowUp,
9331 visible_row_range,
9332 line_layouts,
9333 newest_selection_head,
9334 scrolled_content_origin,
9335 window,
9336 cx,
9337 );
9338 } else if target_display_point.row() >= visible_row_range.end {
9339 return self.render_edit_prediction_scroll_popover(
9340 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9341 IconName::ArrowDown,
9342 visible_row_range,
9343 line_layouts,
9344 newest_selection_head,
9345 scrolled_content_origin,
9346 window,
9347 cx,
9348 );
9349 }
9350
9351 const POLE_WIDTH: Pixels = px(2.);
9352
9353 let line_layout =
9354 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9355 let target_column = target_display_point.column() as usize;
9356
9357 let target_x = line_layout.x_for_index(target_column);
9358 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9359 - scroll_pixel_position.y;
9360
9361 let flag_on_right = target_x < text_bounds.size.width / 2.;
9362
9363 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9364 border_color.l += 0.001;
9365
9366 let mut element = v_flex()
9367 .items_end()
9368 .when(flag_on_right, |el| el.items_start())
9369 .child(if flag_on_right {
9370 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9371 .rounded_bl(px(0.))
9372 .rounded_tl(px(0.))
9373 .border_l_2()
9374 .border_color(border_color)
9375 } else {
9376 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9377 .rounded_br(px(0.))
9378 .rounded_tr(px(0.))
9379 .border_r_2()
9380 .border_color(border_color)
9381 })
9382 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9383 .into_any();
9384
9385 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9386
9387 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9388 - point(
9389 if flag_on_right {
9390 POLE_WIDTH
9391 } else {
9392 size.width - POLE_WIDTH
9393 },
9394 size.height - line_height,
9395 );
9396
9397 origin.x = origin.x.max(content_origin.x);
9398
9399 element.prepaint_at(origin, window, cx);
9400
9401 Some((element, origin))
9402 }
9403
9404 fn render_edit_prediction_scroll_popover(
9405 &mut self,
9406 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9407 scroll_icon: IconName,
9408 visible_row_range: Range<DisplayRow>,
9409 line_layouts: &[LineWithInvisibles],
9410 newest_selection_head: Option<DisplayPoint>,
9411 scrolled_content_origin: gpui::Point<Pixels>,
9412 window: &mut Window,
9413 cx: &mut App,
9414 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9415 let mut element = self
9416 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9417 .into_any();
9418
9419 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9420
9421 let cursor = newest_selection_head?;
9422 let cursor_row_layout =
9423 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9424 let cursor_column = cursor.column() as usize;
9425
9426 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9427
9428 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9429
9430 element.prepaint_at(origin, window, cx);
9431 Some((element, origin))
9432 }
9433
9434 fn render_edit_prediction_eager_jump_popover(
9435 &mut self,
9436 text_bounds: &Bounds<Pixels>,
9437 content_origin: gpui::Point<Pixels>,
9438 editor_snapshot: &EditorSnapshot,
9439 visible_row_range: Range<DisplayRow>,
9440 scroll_top: ScrollOffset,
9441 scroll_bottom: ScrollOffset,
9442 line_height: Pixels,
9443 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9444 target_display_point: DisplayPoint,
9445 editor_width: Pixels,
9446 window: &mut Window,
9447 cx: &mut App,
9448 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9449 if target_display_point.row().as_f64() < scroll_top {
9450 let mut element = self
9451 .render_edit_prediction_line_popover(
9452 "Jump to Edit",
9453 Some(IconName::ArrowUp),
9454 window,
9455 cx,
9456 )
9457 .into_any();
9458
9459 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9460 let offset = point(
9461 (text_bounds.size.width - size.width) / 2.,
9462 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9463 );
9464
9465 let origin = text_bounds.origin + offset;
9466 element.prepaint_at(origin, window, cx);
9467 Some((element, origin))
9468 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9469 let mut element = self
9470 .render_edit_prediction_line_popover(
9471 "Jump to Edit",
9472 Some(IconName::ArrowDown),
9473 window,
9474 cx,
9475 )
9476 .into_any();
9477
9478 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9479 let offset = point(
9480 (text_bounds.size.width - size.width) / 2.,
9481 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9482 );
9483
9484 let origin = text_bounds.origin + offset;
9485 element.prepaint_at(origin, window, cx);
9486 Some((element, origin))
9487 } else {
9488 self.render_edit_prediction_end_of_line_popover(
9489 "Jump to Edit",
9490 editor_snapshot,
9491 visible_row_range,
9492 target_display_point,
9493 line_height,
9494 scroll_pixel_position,
9495 content_origin,
9496 editor_width,
9497 window,
9498 cx,
9499 )
9500 }
9501 }
9502
9503 fn render_edit_prediction_end_of_line_popover(
9504 self: &mut Editor,
9505 label: &'static str,
9506 editor_snapshot: &EditorSnapshot,
9507 visible_row_range: Range<DisplayRow>,
9508 target_display_point: DisplayPoint,
9509 line_height: Pixels,
9510 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9511 content_origin: gpui::Point<Pixels>,
9512 editor_width: Pixels,
9513 window: &mut Window,
9514 cx: &mut App,
9515 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9516 let target_line_end = DisplayPoint::new(
9517 target_display_point.row(),
9518 editor_snapshot.line_len(target_display_point.row()),
9519 );
9520
9521 let mut element = self
9522 .render_edit_prediction_line_popover(label, None, window, cx)
9523 .into_any();
9524
9525 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9526
9527 let line_origin =
9528 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9529
9530 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9531 let mut origin = start_point
9532 + line_origin
9533 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9534 origin.x = origin.x.max(content_origin.x);
9535
9536 let max_x = content_origin.x + editor_width - size.width;
9537
9538 if origin.x > max_x {
9539 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9540
9541 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9542 origin.y += offset;
9543 IconName::ArrowUp
9544 } else {
9545 origin.y -= offset;
9546 IconName::ArrowDown
9547 };
9548
9549 element = self
9550 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9551 .into_any();
9552
9553 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9554
9555 origin.x = content_origin.x + editor_width - size.width - px(2.);
9556 }
9557
9558 element.prepaint_at(origin, window, cx);
9559 Some((element, origin))
9560 }
9561
9562 fn render_edit_prediction_diff_popover(
9563 self: &Editor,
9564 text_bounds: &Bounds<Pixels>,
9565 content_origin: gpui::Point<Pixels>,
9566 right_margin: Pixels,
9567 editor_snapshot: &EditorSnapshot,
9568 visible_row_range: Range<DisplayRow>,
9569 line_layouts: &[LineWithInvisibles],
9570 line_height: Pixels,
9571 scroll_position: gpui::Point<ScrollOffset>,
9572 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9573 newest_selection_head: Option<DisplayPoint>,
9574 editor_width: Pixels,
9575 style: &EditorStyle,
9576 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9577 edit_preview: &Option<language::EditPreview>,
9578 snapshot: &language::BufferSnapshot,
9579 window: &mut Window,
9580 cx: &mut App,
9581 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9582 let edit_start = edits
9583 .first()
9584 .unwrap()
9585 .0
9586 .start
9587 .to_display_point(editor_snapshot);
9588 let edit_end = edits
9589 .last()
9590 .unwrap()
9591 .0
9592 .end
9593 .to_display_point(editor_snapshot);
9594
9595 let is_visible = visible_row_range.contains(&edit_start.row())
9596 || visible_row_range.contains(&edit_end.row());
9597 if !is_visible {
9598 return None;
9599 }
9600
9601 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9602 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9603 } else {
9604 // Fallback for providers without edit_preview
9605 crate::edit_prediction_fallback_text(edits, cx)
9606 };
9607
9608 let styled_text = highlighted_edits.to_styled_text(&style.text);
9609 let line_count = highlighted_edits.text.lines().count();
9610
9611 const BORDER_WIDTH: Pixels = px(1.);
9612
9613 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9614 let has_keybind = keybind.is_some();
9615
9616 let mut element = h_flex()
9617 .items_start()
9618 .child(
9619 h_flex()
9620 .bg(cx.theme().colors().editor_background)
9621 .border(BORDER_WIDTH)
9622 .shadow_xs()
9623 .border_color(cx.theme().colors().border)
9624 .rounded_l_lg()
9625 .when(line_count > 1, |el| el.rounded_br_lg())
9626 .pr_1()
9627 .child(styled_text),
9628 )
9629 .child(
9630 h_flex()
9631 .h(line_height + BORDER_WIDTH * 2.)
9632 .px_1p5()
9633 .gap_1()
9634 // Workaround: For some reason, there's a gap if we don't do this
9635 .ml(-BORDER_WIDTH)
9636 .shadow(vec![gpui::BoxShadow {
9637 color: gpui::black().opacity(0.05),
9638 offset: point(px(1.), px(1.)),
9639 blur_radius: px(2.),
9640 spread_radius: px(0.),
9641 }])
9642 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9643 .border(BORDER_WIDTH)
9644 .border_color(cx.theme().colors().border)
9645 .rounded_r_lg()
9646 .id("edit_prediction_diff_popover_keybind")
9647 .when(!has_keybind, |el| {
9648 let status_colors = cx.theme().status();
9649
9650 el.bg(status_colors.error_background)
9651 .border_color(status_colors.error.opacity(0.6))
9652 .child(Icon::new(IconName::Info).color(Color::Error))
9653 .cursor_default()
9654 .hoverable_tooltip(move |_window, cx| {
9655 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9656 })
9657 })
9658 .children(keybind),
9659 )
9660 .into_any();
9661
9662 let longest_row =
9663 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9664 let longest_line_width = if visible_row_range.contains(&longest_row) {
9665 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9666 } else {
9667 layout_line(
9668 longest_row,
9669 editor_snapshot,
9670 style,
9671 editor_width,
9672 |_| false,
9673 window,
9674 cx,
9675 )
9676 .width
9677 };
9678
9679 let viewport_bounds =
9680 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9681 right: -right_margin,
9682 ..Default::default()
9683 });
9684
9685 let x_after_longest = Pixels::from(
9686 ScrollPixelOffset::from(
9687 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9688 ) - scroll_pixel_position.x,
9689 );
9690
9691 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9692
9693 // Fully visible if it can be displayed within the window (allow overlapping other
9694 // panes). However, this is only allowed if the popover starts within text_bounds.
9695 let can_position_to_the_right = x_after_longest < text_bounds.right()
9696 && x_after_longest + element_bounds.width < viewport_bounds.right();
9697
9698 let mut origin = if can_position_to_the_right {
9699 point(
9700 x_after_longest,
9701 text_bounds.origin.y
9702 + Pixels::from(
9703 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9704 - scroll_pixel_position.y,
9705 ),
9706 )
9707 } else {
9708 let cursor_row = newest_selection_head.map(|head| head.row());
9709 let above_edit = edit_start
9710 .row()
9711 .0
9712 .checked_sub(line_count as u32)
9713 .map(DisplayRow);
9714 let below_edit = Some(edit_end.row() + 1);
9715 let above_cursor =
9716 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9717 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9718
9719 // Place the edit popover adjacent to the edit if there is a location
9720 // available that is onscreen and does not obscure the cursor. Otherwise,
9721 // place it adjacent to the cursor.
9722 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9723 .into_iter()
9724 .flatten()
9725 .find(|&start_row| {
9726 let end_row = start_row + line_count as u32;
9727 visible_row_range.contains(&start_row)
9728 && visible_row_range.contains(&end_row)
9729 && cursor_row
9730 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9731 })?;
9732
9733 content_origin
9734 + point(
9735 Pixels::from(-scroll_pixel_position.x),
9736 Pixels::from(
9737 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9738 ),
9739 )
9740 };
9741
9742 origin.x -= BORDER_WIDTH;
9743
9744 window.with_content_mask(
9745 Some(gpui::ContentMask {
9746 bounds: *text_bounds,
9747 }),
9748 |window| {
9749 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9750 },
9751 );
9752
9753 // Do not return an element, since it will already be drawn due to defer_draw.
9754 None
9755 }
9756
9757 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9758 px(30.)
9759 }
9760
9761 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9762 if self.read_only(cx) {
9763 cx.theme().players().read_only()
9764 } else {
9765 self.style.as_ref().unwrap().local_player
9766 }
9767 }
9768
9769 fn render_edit_prediction_accept_keybind(
9770 &self,
9771 window: &mut Window,
9772 cx: &mut App,
9773 ) -> Option<AnyElement> {
9774 let accept_binding =
9775 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9776 let accept_keystroke = accept_binding.keystroke()?;
9777
9778 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9779
9780 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9781 Color::Accent
9782 } else {
9783 Color::Muted
9784 };
9785
9786 h_flex()
9787 .px_0p5()
9788 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9789 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9790 .text_size(TextSize::XSmall.rems(cx))
9791 .child(h_flex().children(ui::render_modifiers(
9792 accept_keystroke.modifiers(),
9793 PlatformStyle::platform(),
9794 Some(modifiers_color),
9795 Some(IconSize::XSmall.rems().into()),
9796 true,
9797 )))
9798 .when(is_platform_style_mac, |parent| {
9799 parent.child(accept_keystroke.key().to_string())
9800 })
9801 .when(!is_platform_style_mac, |parent| {
9802 parent.child(
9803 Key::new(
9804 util::capitalize(accept_keystroke.key()),
9805 Some(Color::Default),
9806 )
9807 .size(Some(IconSize::XSmall.rems().into())),
9808 )
9809 })
9810 .into_any()
9811 .into()
9812 }
9813
9814 fn render_edit_prediction_line_popover(
9815 &self,
9816 label: impl Into<SharedString>,
9817 icon: Option<IconName>,
9818 window: &mut Window,
9819 cx: &mut App,
9820 ) -> Stateful<Div> {
9821 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9822
9823 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9824 let has_keybind = keybind.is_some();
9825 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9826
9827 h_flex()
9828 .id("ep-line-popover")
9829 .py_0p5()
9830 .pl_1()
9831 .pr(padding_right)
9832 .gap_1()
9833 .rounded_md()
9834 .border_1()
9835 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9836 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9837 .shadow_xs()
9838 .when(!has_keybind, |el| {
9839 let status_colors = cx.theme().status();
9840
9841 el.bg(status_colors.error_background)
9842 .border_color(status_colors.error.opacity(0.6))
9843 .pl_2()
9844 .child(Icon::new(icons.error).color(Color::Error))
9845 .cursor_default()
9846 .hoverable_tooltip(move |_window, cx| {
9847 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9848 })
9849 })
9850 .children(keybind)
9851 .child(
9852 Label::new(label)
9853 .size(LabelSize::Small)
9854 .when(!has_keybind, |el| {
9855 el.color(cx.theme().status().error.into()).strikethrough()
9856 }),
9857 )
9858 .when(!has_keybind, |el| {
9859 el.child(
9860 h_flex().ml_1().child(
9861 Icon::new(IconName::Info)
9862 .size(IconSize::Small)
9863 .color(cx.theme().status().error.into()),
9864 ),
9865 )
9866 })
9867 .when_some(icon, |element, icon| {
9868 element.child(
9869 div()
9870 .mt(px(1.5))
9871 .child(Icon::new(icon).size(IconSize::Small)),
9872 )
9873 })
9874 }
9875
9876 fn render_edit_prediction_jump_outside_popover(
9877 &self,
9878 snapshot: &BufferSnapshot,
9879 window: &mut Window,
9880 cx: &mut App,
9881 ) -> Stateful<Div> {
9882 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9883 let has_keybind = keybind.is_some();
9884 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9885
9886 let file_name = snapshot
9887 .file()
9888 .map(|file| SharedString::new(file.file_name(cx)))
9889 .unwrap_or(SharedString::new_static("untitled"));
9890
9891 h_flex()
9892 .id("ep-jump-outside-popover")
9893 .py_1()
9894 .px_2()
9895 .gap_1()
9896 .rounded_md()
9897 .border_1()
9898 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9899 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9900 .shadow_xs()
9901 .when(!has_keybind, |el| {
9902 let status_colors = cx.theme().status();
9903
9904 el.bg(status_colors.error_background)
9905 .border_color(status_colors.error.opacity(0.6))
9906 .pl_2()
9907 .child(Icon::new(icons.error).color(Color::Error))
9908 .cursor_default()
9909 .hoverable_tooltip(move |_window, cx| {
9910 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9911 })
9912 })
9913 .children(keybind)
9914 .child(
9915 Label::new(file_name)
9916 .size(LabelSize::Small)
9917 .buffer_font(cx)
9918 .when(!has_keybind, |el| {
9919 el.color(cx.theme().status().error.into()).strikethrough()
9920 }),
9921 )
9922 .when(!has_keybind, |el| {
9923 el.child(
9924 h_flex().ml_1().child(
9925 Icon::new(IconName::Info)
9926 .size(IconSize::Small)
9927 .color(cx.theme().status().error.into()),
9928 ),
9929 )
9930 })
9931 .child(
9932 div()
9933 .mt(px(1.5))
9934 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9935 )
9936 }
9937
9938 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9939 let accent_color = cx.theme().colors().text_accent;
9940 let editor_bg_color = cx.theme().colors().editor_background;
9941 editor_bg_color.blend(accent_color.opacity(0.1))
9942 }
9943
9944 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9945 let accent_color = cx.theme().colors().text_accent;
9946 let editor_bg_color = cx.theme().colors().editor_background;
9947 editor_bg_color.blend(accent_color.opacity(0.6))
9948 }
9949 fn get_prediction_provider_icons(
9950 provider: &Option<RegisteredEditPredictionDelegate>,
9951 cx: &App,
9952 ) -> edit_prediction_types::EditPredictionIconSet {
9953 match provider {
9954 Some(provider) => provider.provider.icons(cx),
9955 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
9956 }
9957 }
9958
9959 fn render_edit_prediction_cursor_popover(
9960 &self,
9961 min_width: Pixels,
9962 max_width: Pixels,
9963 cursor_point: Point,
9964 style: &EditorStyle,
9965 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9966 _window: &Window,
9967 cx: &mut Context<Editor>,
9968 ) -> Option<AnyElement> {
9969 let provider = self.edit_prediction_provider.as_ref()?;
9970 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9971
9972 let is_refreshing = provider.provider.is_refreshing(cx);
9973
9974 fn pending_completion_container(icon: IconName) -> Div {
9975 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9976 }
9977
9978 let completion = match &self.active_edit_prediction {
9979 Some(prediction) => {
9980 if !self.has_visible_completions_menu() {
9981 const RADIUS: Pixels = px(6.);
9982 const BORDER_WIDTH: Pixels = px(1.);
9983
9984 return Some(
9985 h_flex()
9986 .elevation_2(cx)
9987 .border(BORDER_WIDTH)
9988 .border_color(cx.theme().colors().border)
9989 .when(accept_keystroke.is_none(), |el| {
9990 el.border_color(cx.theme().status().error)
9991 })
9992 .rounded(RADIUS)
9993 .rounded_tl(px(0.))
9994 .overflow_hidden()
9995 .child(div().px_1p5().child(match &prediction.completion {
9996 EditPrediction::MoveWithin { target, snapshot } => {
9997 use text::ToPoint as _;
9998 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9999 {
10000 Icon::new(icons.down)
10001 } else {
10002 Icon::new(icons.up)
10003 }
10004 }
10005 EditPrediction::MoveOutside { .. } => {
10006 // TODO [zeta2] custom icon for external jump?
10007 Icon::new(icons.base)
10008 }
10009 EditPrediction::Edit { .. } => Icon::new(icons.base),
10010 }))
10011 .child(
10012 h_flex()
10013 .gap_1()
10014 .py_1()
10015 .px_2()
10016 .rounded_r(RADIUS - BORDER_WIDTH)
10017 .border_l_1()
10018 .border_color(cx.theme().colors().border)
10019 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10020 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10021 el.child(
10022 Label::new("Hold")
10023 .size(LabelSize::Small)
10024 .when(accept_keystroke.is_none(), |el| {
10025 el.strikethrough()
10026 })
10027 .line_height_style(LineHeightStyle::UiLabel),
10028 )
10029 })
10030 .id("edit_prediction_cursor_popover_keybind")
10031 .when(accept_keystroke.is_none(), |el| {
10032 let status_colors = cx.theme().status();
10033
10034 el.bg(status_colors.error_background)
10035 .border_color(status_colors.error.opacity(0.6))
10036 .child(Icon::new(IconName::Info).color(Color::Error))
10037 .cursor_default()
10038 .hoverable_tooltip(move |_window, cx| {
10039 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10040 .into()
10041 })
10042 })
10043 .when_some(
10044 accept_keystroke.as_ref(),
10045 |el, accept_keystroke| {
10046 el.child(h_flex().children(ui::render_modifiers(
10047 accept_keystroke.modifiers(),
10048 PlatformStyle::platform(),
10049 Some(Color::Default),
10050 Some(IconSize::XSmall.rems().into()),
10051 false,
10052 )))
10053 },
10054 ),
10055 )
10056 .into_any(),
10057 );
10058 }
10059
10060 self.render_edit_prediction_cursor_popover_preview(
10061 prediction,
10062 cursor_point,
10063 style,
10064 cx,
10065 )?
10066 }
10067
10068 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10069 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10070 stale_completion,
10071 cursor_point,
10072 style,
10073 cx,
10074 )?,
10075
10076 None => pending_completion_container(icons.base)
10077 .child(Label::new("...").size(LabelSize::Small)),
10078 },
10079
10080 None => pending_completion_container(icons.base)
10081 .child(Label::new("...").size(LabelSize::Small)),
10082 };
10083
10084 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10085 completion
10086 .with_animation(
10087 "loading-completion",
10088 Animation::new(Duration::from_secs(2))
10089 .repeat()
10090 .with_easing(pulsating_between(0.4, 0.8)),
10091 |label, delta| label.opacity(delta),
10092 )
10093 .into_any_element()
10094 } else {
10095 completion.into_any_element()
10096 };
10097
10098 let has_completion = self.active_edit_prediction.is_some();
10099
10100 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10101 Some(
10102 h_flex()
10103 .min_w(min_width)
10104 .max_w(max_width)
10105 .flex_1()
10106 .elevation_2(cx)
10107 .border_color(cx.theme().colors().border)
10108 .child(
10109 div()
10110 .flex_1()
10111 .py_1()
10112 .px_2()
10113 .overflow_hidden()
10114 .child(completion),
10115 )
10116 .when_some(accept_keystroke, |el, accept_keystroke| {
10117 if !accept_keystroke.modifiers().modified() {
10118 return el;
10119 }
10120
10121 el.child(
10122 h_flex()
10123 .h_full()
10124 .border_l_1()
10125 .rounded_r_lg()
10126 .border_color(cx.theme().colors().border)
10127 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10128 .gap_1()
10129 .py_1()
10130 .px_2()
10131 .child(
10132 h_flex()
10133 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10134 .when(is_platform_style_mac, |parent| parent.gap_1())
10135 .child(h_flex().children(ui::render_modifiers(
10136 accept_keystroke.modifiers(),
10137 PlatformStyle::platform(),
10138 Some(if !has_completion {
10139 Color::Muted
10140 } else {
10141 Color::Default
10142 }),
10143 None,
10144 false,
10145 ))),
10146 )
10147 .child(Label::new("Preview").into_any_element())
10148 .opacity(if has_completion { 1.0 } else { 0.4 }),
10149 )
10150 })
10151 .into_any(),
10152 )
10153 }
10154
10155 fn render_edit_prediction_cursor_popover_preview(
10156 &self,
10157 completion: &EditPredictionState,
10158 cursor_point: Point,
10159 style: &EditorStyle,
10160 cx: &mut Context<Editor>,
10161 ) -> Option<Div> {
10162 use text::ToPoint as _;
10163
10164 fn render_relative_row_jump(
10165 prefix: impl Into<String>,
10166 current_row: u32,
10167 target_row: u32,
10168 ) -> Div {
10169 let (row_diff, arrow) = if target_row < current_row {
10170 (current_row - target_row, IconName::ArrowUp)
10171 } else {
10172 (target_row - current_row, IconName::ArrowDown)
10173 };
10174
10175 h_flex()
10176 .child(
10177 Label::new(format!("{}{}", prefix.into(), row_diff))
10178 .color(Color::Muted)
10179 .size(LabelSize::Small),
10180 )
10181 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10182 }
10183
10184 let supports_jump = self
10185 .edit_prediction_provider
10186 .as_ref()
10187 .map(|provider| provider.provider.supports_jump_to_edit())
10188 .unwrap_or(true);
10189
10190 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10191
10192 match &completion.completion {
10193 EditPrediction::MoveWithin {
10194 target, snapshot, ..
10195 } => {
10196 if !supports_jump {
10197 return None;
10198 }
10199
10200 Some(
10201 h_flex()
10202 .px_2()
10203 .gap_2()
10204 .flex_1()
10205 .child(
10206 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10207 Icon::new(icons.down)
10208 } else {
10209 Icon::new(icons.up)
10210 },
10211 )
10212 .child(Label::new("Jump to Edit")),
10213 )
10214 }
10215 EditPrediction::MoveOutside { snapshot, .. } => {
10216 let file_name = snapshot
10217 .file()
10218 .map(|file| file.file_name(cx))
10219 .unwrap_or("untitled");
10220 Some(
10221 h_flex()
10222 .px_2()
10223 .gap_2()
10224 .flex_1()
10225 .child(Icon::new(icons.base))
10226 .child(Label::new(format!("Jump to {file_name}"))),
10227 )
10228 }
10229 EditPrediction::Edit {
10230 edits,
10231 edit_preview,
10232 snapshot,
10233 ..
10234 } => {
10235 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10236
10237 let (highlighted_edits, has_more_lines) =
10238 if let Some(edit_preview) = edit_preview.as_ref() {
10239 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10240 .first_line_preview()
10241 } else {
10242 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10243 };
10244
10245 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10246 .with_default_highlights(&style.text, highlighted_edits.highlights);
10247
10248 let preview = h_flex()
10249 .gap_1()
10250 .min_w_16()
10251 .child(styled_text)
10252 .when(has_more_lines, |parent| parent.child("…"));
10253
10254 let left = if supports_jump && first_edit_row != cursor_point.row {
10255 render_relative_row_jump("", cursor_point.row, first_edit_row)
10256 .into_any_element()
10257 } else {
10258 Icon::new(icons.base).into_any_element()
10259 };
10260
10261 Some(
10262 h_flex()
10263 .h_full()
10264 .flex_1()
10265 .gap_2()
10266 .pr_1()
10267 .overflow_x_hidden()
10268 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10269 .child(left)
10270 .child(preview),
10271 )
10272 }
10273 }
10274 }
10275
10276 pub fn render_context_menu(
10277 &mut self,
10278 max_height_in_lines: u32,
10279 window: &mut Window,
10280 cx: &mut Context<Editor>,
10281 ) -> Option<AnyElement> {
10282 let menu = self.context_menu.borrow();
10283 let menu = menu.as_ref()?;
10284 if !menu.visible() {
10285 return None;
10286 };
10287 self.style
10288 .as_ref()
10289 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10290 }
10291
10292 fn render_context_menu_aside(
10293 &mut self,
10294 max_size: Size<Pixels>,
10295 window: &mut Window,
10296 cx: &mut Context<Editor>,
10297 ) -> Option<AnyElement> {
10298 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10299 if menu.visible() {
10300 menu.render_aside(max_size, window, cx)
10301 } else {
10302 None
10303 }
10304 })
10305 }
10306
10307 fn hide_context_menu(
10308 &mut self,
10309 window: &mut Window,
10310 cx: &mut Context<Self>,
10311 ) -> Option<CodeContextMenu> {
10312 cx.notify();
10313 self.completion_tasks.clear();
10314 let context_menu = self.context_menu.borrow_mut().take();
10315 self.stale_edit_prediction_in_menu.take();
10316 self.update_visible_edit_prediction(window, cx);
10317 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10318 && let Some(completion_provider) = &self.completion_provider
10319 {
10320 completion_provider.selection_changed(None, window, cx);
10321 }
10322 context_menu
10323 }
10324
10325 fn show_snippet_choices(
10326 &mut self,
10327 choices: &Vec<String>,
10328 selection: Range<Anchor>,
10329 cx: &mut Context<Self>,
10330 ) {
10331 let Some((_, buffer, _)) = self
10332 .buffer()
10333 .read(cx)
10334 .excerpt_containing(selection.start, cx)
10335 else {
10336 return;
10337 };
10338 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10339 else {
10340 return;
10341 };
10342 if buffer != end_buffer {
10343 log::error!("expected anchor range to have matching buffer IDs");
10344 return;
10345 }
10346
10347 let id = post_inc(&mut self.next_completion_id);
10348 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10349 let mut context_menu = self.context_menu.borrow_mut();
10350 let old_menu = context_menu.take();
10351 *context_menu = Some(CodeContextMenu::Completions(
10352 CompletionsMenu::new_snippet_choices(
10353 id,
10354 true,
10355 choices,
10356 selection,
10357 buffer,
10358 old_menu.map(|menu| menu.primary_scroll_handle()),
10359 snippet_sort_order,
10360 ),
10361 ));
10362 }
10363
10364 pub fn insert_snippet(
10365 &mut self,
10366 insertion_ranges: &[Range<MultiBufferOffset>],
10367 snippet: Snippet,
10368 window: &mut Window,
10369 cx: &mut Context<Self>,
10370 ) -> Result<()> {
10371 struct Tabstop<T> {
10372 is_end_tabstop: bool,
10373 ranges: Vec<Range<T>>,
10374 choices: Option<Vec<String>>,
10375 }
10376
10377 let tabstops = self.buffer.update(cx, |buffer, cx| {
10378 let snippet_text: Arc<str> = snippet.text.clone().into();
10379 let edits = insertion_ranges
10380 .iter()
10381 .cloned()
10382 .map(|range| (range, snippet_text.clone()));
10383 let autoindent_mode = AutoindentMode::Block {
10384 original_indent_columns: Vec::new(),
10385 };
10386 buffer.edit(edits, Some(autoindent_mode), cx);
10387
10388 let snapshot = &*buffer.read(cx);
10389 let snippet = &snippet;
10390 snippet
10391 .tabstops
10392 .iter()
10393 .map(|tabstop| {
10394 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10395 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10396 });
10397 let mut tabstop_ranges = tabstop
10398 .ranges
10399 .iter()
10400 .flat_map(|tabstop_range| {
10401 let mut delta = 0_isize;
10402 insertion_ranges.iter().map(move |insertion_range| {
10403 let insertion_start = insertion_range.start + delta;
10404 delta += snippet.text.len() as isize
10405 - (insertion_range.end - insertion_range.start) as isize;
10406
10407 let start =
10408 (insertion_start + tabstop_range.start).min(snapshot.len());
10409 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10410 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10411 })
10412 })
10413 .collect::<Vec<_>>();
10414 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10415
10416 Tabstop {
10417 is_end_tabstop,
10418 ranges: tabstop_ranges,
10419 choices: tabstop.choices.clone(),
10420 }
10421 })
10422 .collect::<Vec<_>>()
10423 });
10424 if let Some(tabstop) = tabstops.first() {
10425 self.change_selections(Default::default(), window, cx, |s| {
10426 // Reverse order so that the first range is the newest created selection.
10427 // Completions will use it and autoscroll will prioritize it.
10428 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10429 });
10430
10431 if let Some(choices) = &tabstop.choices
10432 && let Some(selection) = tabstop.ranges.first()
10433 {
10434 self.show_snippet_choices(choices, selection.clone(), cx)
10435 }
10436
10437 // If we're already at the last tabstop and it's at the end of the snippet,
10438 // we're done, we don't need to keep the state around.
10439 if !tabstop.is_end_tabstop {
10440 let choices = tabstops
10441 .iter()
10442 .map(|tabstop| tabstop.choices.clone())
10443 .collect();
10444
10445 let ranges = tabstops
10446 .into_iter()
10447 .map(|tabstop| tabstop.ranges)
10448 .collect::<Vec<_>>();
10449
10450 self.snippet_stack.push(SnippetState {
10451 active_index: 0,
10452 ranges,
10453 choices,
10454 });
10455 }
10456
10457 // Check whether the just-entered snippet ends with an auto-closable bracket.
10458 if self.autoclose_regions.is_empty() {
10459 let snapshot = self.buffer.read(cx).snapshot(cx);
10460 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10461 let selection_head = selection.head();
10462 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10463 continue;
10464 };
10465
10466 let mut bracket_pair = None;
10467 let max_lookup_length = scope
10468 .brackets()
10469 .map(|(pair, _)| {
10470 pair.start
10471 .as_str()
10472 .chars()
10473 .count()
10474 .max(pair.end.as_str().chars().count())
10475 })
10476 .max();
10477 if let Some(max_lookup_length) = max_lookup_length {
10478 let next_text = snapshot
10479 .chars_at(selection_head)
10480 .take(max_lookup_length)
10481 .collect::<String>();
10482 let prev_text = snapshot
10483 .reversed_chars_at(selection_head)
10484 .take(max_lookup_length)
10485 .collect::<String>();
10486
10487 for (pair, enabled) in scope.brackets() {
10488 if enabled
10489 && pair.close
10490 && prev_text.starts_with(pair.start.as_str())
10491 && next_text.starts_with(pair.end.as_str())
10492 {
10493 bracket_pair = Some(pair.clone());
10494 break;
10495 }
10496 }
10497 }
10498
10499 if let Some(pair) = bracket_pair {
10500 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10501 let autoclose_enabled =
10502 self.use_autoclose && snapshot_settings.use_autoclose;
10503 if autoclose_enabled {
10504 let start = snapshot.anchor_after(selection_head);
10505 let end = snapshot.anchor_after(selection_head);
10506 self.autoclose_regions.push(AutocloseRegion {
10507 selection_id: selection.id,
10508 range: start..end,
10509 pair,
10510 });
10511 }
10512 }
10513 }
10514 }
10515 }
10516 Ok(())
10517 }
10518
10519 pub fn move_to_next_snippet_tabstop(
10520 &mut self,
10521 window: &mut Window,
10522 cx: &mut Context<Self>,
10523 ) -> bool {
10524 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10525 }
10526
10527 pub fn move_to_prev_snippet_tabstop(
10528 &mut self,
10529 window: &mut Window,
10530 cx: &mut Context<Self>,
10531 ) -> bool {
10532 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10533 }
10534
10535 pub fn move_to_snippet_tabstop(
10536 &mut self,
10537 bias: Bias,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) -> bool {
10541 if let Some(mut snippet) = self.snippet_stack.pop() {
10542 match bias {
10543 Bias::Left => {
10544 if snippet.active_index > 0 {
10545 snippet.active_index -= 1;
10546 } else {
10547 self.snippet_stack.push(snippet);
10548 return false;
10549 }
10550 }
10551 Bias::Right => {
10552 if snippet.active_index + 1 < snippet.ranges.len() {
10553 snippet.active_index += 1;
10554 } else {
10555 self.snippet_stack.push(snippet);
10556 return false;
10557 }
10558 }
10559 }
10560 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10561 self.change_selections(Default::default(), window, cx, |s| {
10562 // Reverse order so that the first range is the newest created selection.
10563 // Completions will use it and autoscroll will prioritize it.
10564 s.select_ranges(current_ranges.iter().rev().cloned())
10565 });
10566
10567 if let Some(choices) = &snippet.choices[snippet.active_index]
10568 && let Some(selection) = current_ranges.first()
10569 {
10570 self.show_snippet_choices(choices, selection.clone(), cx);
10571 }
10572
10573 // If snippet state is not at the last tabstop, push it back on the stack
10574 if snippet.active_index + 1 < snippet.ranges.len() {
10575 self.snippet_stack.push(snippet);
10576 }
10577 return true;
10578 }
10579 }
10580
10581 false
10582 }
10583
10584 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10585 self.transact(window, cx, |this, window, cx| {
10586 this.select_all(&SelectAll, window, cx);
10587 this.insert("", window, cx);
10588 });
10589 }
10590
10591 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10592 if self.read_only(cx) {
10593 return;
10594 }
10595 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10596 self.transact(window, cx, |this, window, cx| {
10597 this.select_autoclose_pair(window, cx);
10598
10599 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10600
10601 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10602 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10603 for selection in &mut selections {
10604 if selection.is_empty() {
10605 let old_head = selection.head();
10606 let mut new_head =
10607 movement::left(&display_map, old_head.to_display_point(&display_map))
10608 .to_point(&display_map);
10609 if let Some((buffer, line_buffer_range)) = display_map
10610 .buffer_snapshot()
10611 .buffer_line_for_row(MultiBufferRow(old_head.row))
10612 {
10613 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10614 let indent_len = match indent_size.kind {
10615 IndentKind::Space => {
10616 buffer.settings_at(line_buffer_range.start, cx).tab_size
10617 }
10618 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10619 };
10620 if old_head.column <= indent_size.len && old_head.column > 0 {
10621 let indent_len = indent_len.get();
10622 new_head = cmp::min(
10623 new_head,
10624 MultiBufferPoint::new(
10625 old_head.row,
10626 ((old_head.column - 1) / indent_len) * indent_len,
10627 ),
10628 );
10629 }
10630 }
10631
10632 selection.set_head(new_head, SelectionGoal::None);
10633 }
10634 }
10635
10636 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10637 this.insert("", window, cx);
10638 linked_edits.apply_with_left_expansion(cx);
10639 this.refresh_edit_prediction(true, false, window, cx);
10640 refresh_linked_ranges(this, window, cx);
10641 });
10642 }
10643
10644 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10645 if self.read_only(cx) {
10646 return;
10647 }
10648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10649 self.transact(window, cx, |this, window, cx| {
10650 this.change_selections(Default::default(), window, cx, |s| {
10651 s.move_with(&mut |map, selection| {
10652 if selection.is_empty() {
10653 let cursor = movement::right(map, selection.head());
10654 selection.end = cursor;
10655 selection.reversed = true;
10656 selection.goal = SelectionGoal::None;
10657 }
10658 })
10659 });
10660 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10661 this.insert("", window, cx);
10662 linked_edits.apply(cx);
10663 this.refresh_edit_prediction(true, false, window, cx);
10664 refresh_linked_ranges(this, window, cx);
10665 });
10666 }
10667
10668 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10669 if self.mode.is_single_line() {
10670 cx.propagate();
10671 return;
10672 }
10673
10674 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10675 if self.move_to_prev_snippet_tabstop(window, cx) {
10676 return;
10677 }
10678 self.outdent(&Outdent, window, cx);
10679 }
10680
10681 pub fn next_snippet_tabstop(
10682 &mut self,
10683 _: &NextSnippetTabstop,
10684 window: &mut Window,
10685 cx: &mut Context<Self>,
10686 ) {
10687 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10688 cx.propagate();
10689 return;
10690 }
10691
10692 if self.move_to_next_snippet_tabstop(window, cx) {
10693 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10694 return;
10695 }
10696 cx.propagate();
10697 }
10698
10699 pub fn previous_snippet_tabstop(
10700 &mut self,
10701 _: &PreviousSnippetTabstop,
10702 window: &mut Window,
10703 cx: &mut Context<Self>,
10704 ) {
10705 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10706 cx.propagate();
10707 return;
10708 }
10709
10710 if self.move_to_prev_snippet_tabstop(window, cx) {
10711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10712 return;
10713 }
10714 cx.propagate();
10715 }
10716
10717 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10718 if self.mode.is_single_line() {
10719 cx.propagate();
10720 return;
10721 }
10722
10723 if self.move_to_next_snippet_tabstop(window, cx) {
10724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10725 return;
10726 }
10727 if self.read_only(cx) {
10728 return;
10729 }
10730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10731 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10732 let buffer = self.buffer.read(cx);
10733 let snapshot = buffer.snapshot(cx);
10734 let rows_iter = selections.iter().map(|s| s.head().row);
10735 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10736
10737 let has_some_cursor_in_whitespace = selections
10738 .iter()
10739 .filter(|selection| selection.is_empty())
10740 .any(|selection| {
10741 let cursor = selection.head();
10742 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10743 cursor.column < current_indent.len
10744 });
10745
10746 let mut edits = Vec::new();
10747 let mut prev_edited_row = 0;
10748 let mut row_delta = 0;
10749 for selection in &mut selections {
10750 if selection.start.row != prev_edited_row {
10751 row_delta = 0;
10752 }
10753 prev_edited_row = selection.end.row;
10754
10755 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10756 if selection.is_empty() {
10757 let cursor = selection.head();
10758 let settings = buffer.language_settings_at(cursor, cx);
10759 if settings.indent_list_on_tab {
10760 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10761 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10762 row_delta = Self::indent_selection(
10763 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10764 );
10765 continue;
10766 }
10767 }
10768 }
10769 }
10770
10771 // If the selection is non-empty, then increase the indentation of the selected lines.
10772 if !selection.is_empty() {
10773 row_delta =
10774 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10775 continue;
10776 }
10777
10778 let cursor = selection.head();
10779 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10780 if let Some(suggested_indent) =
10781 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10782 {
10783 // Don't do anything if already at suggested indent
10784 // and there is any other cursor which is not
10785 if has_some_cursor_in_whitespace
10786 && cursor.column == current_indent.len
10787 && current_indent.len == suggested_indent.len
10788 {
10789 continue;
10790 }
10791
10792 // Adjust line and move cursor to suggested indent
10793 // if cursor is not at suggested indent
10794 if cursor.column < suggested_indent.len
10795 && cursor.column <= current_indent.len
10796 && current_indent.len <= suggested_indent.len
10797 {
10798 selection.start = Point::new(cursor.row, suggested_indent.len);
10799 selection.end = selection.start;
10800 if row_delta == 0 {
10801 edits.extend(Buffer::edit_for_indent_size_adjustment(
10802 cursor.row,
10803 current_indent,
10804 suggested_indent,
10805 ));
10806 row_delta = suggested_indent.len - current_indent.len;
10807 }
10808 continue;
10809 }
10810
10811 // If current indent is more than suggested indent
10812 // only move cursor to current indent and skip indent
10813 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10814 selection.start = Point::new(cursor.row, current_indent.len);
10815 selection.end = selection.start;
10816 continue;
10817 }
10818 }
10819
10820 // Otherwise, insert a hard or soft tab.
10821 let settings = buffer.language_settings_at(cursor, cx);
10822 let tab_size = if settings.hard_tabs {
10823 IndentSize::tab()
10824 } else {
10825 let tab_size = settings.tab_size.get();
10826 let indent_remainder = snapshot
10827 .text_for_range(Point::new(cursor.row, 0)..cursor)
10828 .flat_map(str::chars)
10829 .fold(row_delta % tab_size, |counter: u32, c| {
10830 if c == '\t' {
10831 0
10832 } else {
10833 (counter + 1) % tab_size
10834 }
10835 });
10836
10837 let chars_to_next_tab_stop = tab_size - indent_remainder;
10838 IndentSize::spaces(chars_to_next_tab_stop)
10839 };
10840 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10841 selection.end = selection.start;
10842 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10843 row_delta += tab_size.len;
10844 }
10845
10846 self.transact(window, cx, |this, window, cx| {
10847 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10848 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10849 this.refresh_edit_prediction(true, false, window, cx);
10850 });
10851 }
10852
10853 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10854 if self.read_only(cx) {
10855 return;
10856 }
10857 if self.mode.is_single_line() {
10858 cx.propagate();
10859 return;
10860 }
10861
10862 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10863 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10864 let mut prev_edited_row = 0;
10865 let mut row_delta = 0;
10866 let mut edits = Vec::new();
10867 let buffer = self.buffer.read(cx);
10868 let snapshot = buffer.snapshot(cx);
10869 for selection in &mut selections {
10870 if selection.start.row != prev_edited_row {
10871 row_delta = 0;
10872 }
10873 prev_edited_row = selection.end.row;
10874
10875 row_delta =
10876 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10877 }
10878
10879 self.transact(window, cx, |this, window, cx| {
10880 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10881 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10882 });
10883 }
10884
10885 fn indent_selection(
10886 buffer: &MultiBuffer,
10887 snapshot: &MultiBufferSnapshot,
10888 selection: &mut Selection<Point>,
10889 edits: &mut Vec<(Range<Point>, String)>,
10890 delta_for_start_row: u32,
10891 cx: &App,
10892 ) -> u32 {
10893 let settings = buffer.language_settings_at(selection.start, cx);
10894 let tab_size = settings.tab_size.get();
10895 let indent_kind = if settings.hard_tabs {
10896 IndentKind::Tab
10897 } else {
10898 IndentKind::Space
10899 };
10900 let mut start_row = selection.start.row;
10901 let mut end_row = selection.end.row + 1;
10902
10903 // If a selection ends at the beginning of a line, don't indent
10904 // that last line.
10905 if selection.end.column == 0 && selection.end.row > selection.start.row {
10906 end_row -= 1;
10907 }
10908
10909 // Avoid re-indenting a row that has already been indented by a
10910 // previous selection, but still update this selection's column
10911 // to reflect that indentation.
10912 if delta_for_start_row > 0 {
10913 start_row += 1;
10914 selection.start.column += delta_for_start_row;
10915 if selection.end.row == selection.start.row {
10916 selection.end.column += delta_for_start_row;
10917 }
10918 }
10919
10920 let mut delta_for_end_row = 0;
10921 let has_multiple_rows = start_row + 1 != end_row;
10922 for row in start_row..end_row {
10923 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10924 let indent_delta = match (current_indent.kind, indent_kind) {
10925 (IndentKind::Space, IndentKind::Space) => {
10926 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10927 IndentSize::spaces(columns_to_next_tab_stop)
10928 }
10929 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10930 (_, IndentKind::Tab) => IndentSize::tab(),
10931 };
10932
10933 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10934 0
10935 } else {
10936 selection.start.column
10937 };
10938 let row_start = Point::new(row, start);
10939 edits.push((
10940 row_start..row_start,
10941 indent_delta.chars().collect::<String>(),
10942 ));
10943
10944 // Update this selection's endpoints to reflect the indentation.
10945 if row == selection.start.row {
10946 selection.start.column += indent_delta.len;
10947 }
10948 if row == selection.end.row {
10949 selection.end.column += indent_delta.len;
10950 delta_for_end_row = indent_delta.len;
10951 }
10952 }
10953
10954 if selection.start.row == selection.end.row {
10955 delta_for_start_row + delta_for_end_row
10956 } else {
10957 delta_for_end_row
10958 }
10959 }
10960
10961 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10962 if self.read_only(cx) {
10963 return;
10964 }
10965 if self.mode.is_single_line() {
10966 cx.propagate();
10967 return;
10968 }
10969
10970 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10971 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10972 let selections = self.selections.all::<Point>(&display_map);
10973 let mut deletion_ranges = Vec::new();
10974 let mut last_outdent = None;
10975 {
10976 let buffer = self.buffer.read(cx);
10977 let snapshot = buffer.snapshot(cx);
10978 for selection in &selections {
10979 let settings = buffer.language_settings_at(selection.start, cx);
10980 let tab_size = settings.tab_size.get();
10981 let mut rows = selection.spanned_rows(false, &display_map);
10982
10983 // Avoid re-outdenting a row that has already been outdented by a
10984 // previous selection.
10985 if let Some(last_row) = last_outdent
10986 && last_row == rows.start
10987 {
10988 rows.start = rows.start.next_row();
10989 }
10990 let has_multiple_rows = rows.len() > 1;
10991 for row in rows.iter_rows() {
10992 let indent_size = snapshot.indent_size_for_line(row);
10993 if indent_size.len > 0 {
10994 let deletion_len = match indent_size.kind {
10995 IndentKind::Space => {
10996 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10997 if columns_to_prev_tab_stop == 0 {
10998 tab_size
10999 } else {
11000 columns_to_prev_tab_stop
11001 }
11002 }
11003 IndentKind::Tab => 1,
11004 };
11005 let start = if has_multiple_rows
11006 || deletion_len > selection.start.column
11007 || indent_size.len < selection.start.column
11008 {
11009 0
11010 } else {
11011 selection.start.column - deletion_len
11012 };
11013 deletion_ranges.push(
11014 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11015 );
11016 last_outdent = Some(row);
11017 }
11018 }
11019 }
11020 }
11021
11022 self.transact(window, cx, |this, window, cx| {
11023 this.buffer.update(cx, |buffer, cx| {
11024 let empty_str: Arc<str> = Arc::default();
11025 buffer.edit(
11026 deletion_ranges
11027 .into_iter()
11028 .map(|range| (range, empty_str.clone())),
11029 None,
11030 cx,
11031 );
11032 });
11033 let selections = this
11034 .selections
11035 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11036 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11037 });
11038 }
11039
11040 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11041 if self.read_only(cx) {
11042 return;
11043 }
11044 if self.mode.is_single_line() {
11045 cx.propagate();
11046 return;
11047 }
11048
11049 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11050 let selections = self
11051 .selections
11052 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11053 .into_iter()
11054 .map(|s| s.range());
11055
11056 self.transact(window, cx, |this, window, cx| {
11057 this.buffer.update(cx, |buffer, cx| {
11058 buffer.autoindent_ranges(selections, cx);
11059 });
11060 let selections = this
11061 .selections
11062 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11063 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11064 });
11065 }
11066
11067 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11070 let selections = self.selections.all::<Point>(&display_map);
11071
11072 let mut new_cursors = Vec::new();
11073 let mut edit_ranges = Vec::new();
11074 let mut selections = selections.iter().peekable();
11075 while let Some(selection) = selections.next() {
11076 let mut rows = selection.spanned_rows(false, &display_map);
11077
11078 // Accumulate contiguous regions of rows that we want to delete.
11079 while let Some(next_selection) = selections.peek() {
11080 let next_rows = next_selection.spanned_rows(false, &display_map);
11081 if next_rows.start <= rows.end {
11082 rows.end = next_rows.end;
11083 selections.next().unwrap();
11084 } else {
11085 break;
11086 }
11087 }
11088
11089 let buffer = display_map.buffer_snapshot();
11090 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11091 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11092 // If there's a line after the range, delete the \n from the end of the row range
11093 (
11094 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11095 rows.end,
11096 )
11097 } else {
11098 // If there isn't a line after the range, delete the \n from the line before the
11099 // start of the row range
11100 edit_start = edit_start.saturating_sub_usize(1);
11101 (buffer.len(), rows.start.previous_row())
11102 };
11103
11104 let text_layout_details = self.text_layout_details(window, cx);
11105 let x = display_map.x_for_display_point(
11106 selection.head().to_display_point(&display_map),
11107 &text_layout_details,
11108 );
11109 let row = Point::new(target_row.0, 0)
11110 .to_display_point(&display_map)
11111 .row();
11112 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11113
11114 new_cursors.push((
11115 selection.id,
11116 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11117 SelectionGoal::None,
11118 ));
11119 edit_ranges.push(edit_start..edit_end);
11120 }
11121
11122 self.transact(window, cx, |this, window, cx| {
11123 let buffer = this.buffer.update(cx, |buffer, cx| {
11124 let empty_str: Arc<str> = Arc::default();
11125 buffer.edit(
11126 edit_ranges
11127 .into_iter()
11128 .map(|range| (range, empty_str.clone())),
11129 None,
11130 cx,
11131 );
11132 buffer.snapshot(cx)
11133 });
11134 let new_selections = new_cursors
11135 .into_iter()
11136 .map(|(id, cursor, goal)| {
11137 let cursor = cursor.to_point(&buffer);
11138 Selection {
11139 id,
11140 start: cursor,
11141 end: cursor,
11142 reversed: false,
11143 goal,
11144 }
11145 })
11146 .collect();
11147
11148 this.change_selections(Default::default(), window, cx, |s| {
11149 s.select(new_selections);
11150 });
11151 });
11152 }
11153
11154 pub fn join_lines_impl(
11155 &mut self,
11156 insert_whitespace: bool,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 ) {
11160 if self.read_only(cx) {
11161 return;
11162 }
11163 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11164 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11165 let start = MultiBufferRow(selection.start.row);
11166 // Treat single line selections as if they include the next line. Otherwise this action
11167 // would do nothing for single line selections individual cursors.
11168 let end = if selection.start.row == selection.end.row {
11169 MultiBufferRow(selection.start.row + 1)
11170 } else if selection.end.column == 0 {
11171 // If the selection ends at the start of a line, it's logically at the end of the
11172 // previous line (plus its newline).
11173 // Don't include the end line unless there's only one line selected.
11174 if selection.start.row + 1 == selection.end.row {
11175 MultiBufferRow(selection.end.row)
11176 } else {
11177 MultiBufferRow(selection.end.row - 1)
11178 }
11179 } else {
11180 MultiBufferRow(selection.end.row)
11181 };
11182
11183 if let Some(last_row_range) = row_ranges.last_mut()
11184 && start <= last_row_range.end
11185 {
11186 last_row_range.end = end;
11187 continue;
11188 }
11189 row_ranges.push(start..end);
11190 }
11191
11192 let snapshot = self.buffer.read(cx).snapshot(cx);
11193 let mut cursor_positions = Vec::new();
11194 for row_range in &row_ranges {
11195 let anchor = snapshot.anchor_before(Point::new(
11196 row_range.end.previous_row().0,
11197 snapshot.line_len(row_range.end.previous_row()),
11198 ));
11199 cursor_positions.push(anchor..anchor);
11200 }
11201
11202 self.transact(window, cx, |this, window, cx| {
11203 for row_range in row_ranges.into_iter().rev() {
11204 for row in row_range.iter_rows().rev() {
11205 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11206 let next_line_row = row.next_row();
11207 let indent = snapshot.indent_size_for_line(next_line_row);
11208 let mut join_start_column = indent.len;
11209
11210 if let Some(language_scope) =
11211 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11212 {
11213 let line_end =
11214 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11215 let line_text_after_indent = snapshot
11216 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11217 .collect::<String>();
11218
11219 if !line_text_after_indent.is_empty() {
11220 let block_prefix = language_scope
11221 .block_comment()
11222 .map(|c| c.prefix.as_ref())
11223 .filter(|p| !p.is_empty());
11224 let doc_prefix = language_scope
11225 .documentation_comment()
11226 .map(|c| c.prefix.as_ref())
11227 .filter(|p| !p.is_empty());
11228 let all_prefixes = language_scope
11229 .line_comment_prefixes()
11230 .iter()
11231 .map(|p| p.as_ref())
11232 .chain(block_prefix)
11233 .chain(doc_prefix)
11234 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11235
11236 let mut longest_prefix_len = None;
11237 for prefix in all_prefixes {
11238 let trimmed = prefix.trim_end();
11239 if line_text_after_indent.starts_with(trimmed) {
11240 let candidate_len =
11241 if line_text_after_indent.starts_with(prefix) {
11242 prefix.len()
11243 } else {
11244 trimmed.len()
11245 };
11246 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11247 longest_prefix_len = Some(candidate_len);
11248 }
11249 }
11250 }
11251
11252 if let Some(prefix_len) = longest_prefix_len {
11253 join_start_column =
11254 join_start_column.saturating_add(prefix_len as u32);
11255 }
11256 }
11257 }
11258
11259 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11260
11261 let replace = if snapshot.line_len(next_line_row) > join_start_column
11262 && insert_whitespace
11263 {
11264 " "
11265 } else {
11266 ""
11267 };
11268
11269 this.buffer.update(cx, |buffer, cx| {
11270 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11271 });
11272 }
11273 }
11274
11275 this.change_selections(Default::default(), window, cx, |s| {
11276 s.select_anchor_ranges(cursor_positions)
11277 });
11278 });
11279 }
11280
11281 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11282 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11283 self.join_lines_impl(true, window, cx);
11284 }
11285
11286 pub fn sort_lines_case_sensitive(
11287 &mut self,
11288 _: &SortLinesCaseSensitive,
11289 window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11293 }
11294
11295 pub fn sort_lines_by_length(
11296 &mut self,
11297 _: &SortLinesByLength,
11298 window: &mut Window,
11299 cx: &mut Context<Self>,
11300 ) {
11301 self.manipulate_immutable_lines(window, cx, |lines| {
11302 lines.sort_by_key(|&line| line.chars().count())
11303 })
11304 }
11305
11306 pub fn sort_lines_case_insensitive(
11307 &mut self,
11308 _: &SortLinesCaseInsensitive,
11309 window: &mut Window,
11310 cx: &mut Context<Self>,
11311 ) {
11312 self.manipulate_immutable_lines(window, cx, |lines| {
11313 lines.sort_by_key(|line| line.to_lowercase())
11314 })
11315 }
11316
11317 pub fn unique_lines_case_insensitive(
11318 &mut self,
11319 _: &UniqueLinesCaseInsensitive,
11320 window: &mut Window,
11321 cx: &mut Context<Self>,
11322 ) {
11323 self.manipulate_immutable_lines(window, cx, |lines| {
11324 let mut seen = HashSet::default();
11325 lines.retain(|line| seen.insert(line.to_lowercase()));
11326 })
11327 }
11328
11329 pub fn unique_lines_case_sensitive(
11330 &mut self,
11331 _: &UniqueLinesCaseSensitive,
11332 window: &mut Window,
11333 cx: &mut Context<Self>,
11334 ) {
11335 self.manipulate_immutable_lines(window, cx, |lines| {
11336 let mut seen = HashSet::default();
11337 lines.retain(|line| seen.insert(*line));
11338 })
11339 }
11340
11341 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11342 let snapshot = self.buffer.read(cx).snapshot(cx);
11343 for selection in self.selections.disjoint_anchors_arc().iter() {
11344 if snapshot
11345 .language_at(selection.start)
11346 .and_then(|lang| lang.config().wrap_characters.as_ref())
11347 .is_some()
11348 {
11349 return true;
11350 }
11351 }
11352 false
11353 }
11354
11355 fn wrap_selections_in_tag(
11356 &mut self,
11357 _: &WrapSelectionsInTag,
11358 window: &mut Window,
11359 cx: &mut Context<Self>,
11360 ) {
11361 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11362
11363 let snapshot = self.buffer.read(cx).snapshot(cx);
11364
11365 let mut edits = Vec::new();
11366 let mut boundaries = Vec::new();
11367
11368 for selection in self
11369 .selections
11370 .all_adjusted(&self.display_snapshot(cx))
11371 .iter()
11372 {
11373 let Some(wrap_config) = snapshot
11374 .language_at(selection.start)
11375 .and_then(|lang| lang.config().wrap_characters.clone())
11376 else {
11377 continue;
11378 };
11379
11380 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11381 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11382
11383 let start_before = snapshot.anchor_before(selection.start);
11384 let end_after = snapshot.anchor_after(selection.end);
11385
11386 edits.push((start_before..start_before, open_tag));
11387 edits.push((end_after..end_after, close_tag));
11388
11389 boundaries.push((
11390 start_before,
11391 end_after,
11392 wrap_config.start_prefix.len(),
11393 wrap_config.end_suffix.len(),
11394 ));
11395 }
11396
11397 if edits.is_empty() {
11398 return;
11399 }
11400
11401 self.transact(window, cx, |this, window, cx| {
11402 let buffer = this.buffer.update(cx, |buffer, cx| {
11403 buffer.edit(edits, None, cx);
11404 buffer.snapshot(cx)
11405 });
11406
11407 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11408 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11409 boundaries.into_iter()
11410 {
11411 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11412 let close_offset = end_after
11413 .to_offset(&buffer)
11414 .saturating_sub_usize(end_suffix_len);
11415 new_selections.push(open_offset..open_offset);
11416 new_selections.push(close_offset..close_offset);
11417 }
11418
11419 this.change_selections(Default::default(), window, cx, |s| {
11420 s.select_ranges(new_selections);
11421 });
11422
11423 this.request_autoscroll(Autoscroll::fit(), cx);
11424 });
11425 }
11426
11427 pub fn toggle_read_only(
11428 &mut self,
11429 _: &workspace::ToggleReadOnlyFile,
11430 _: &mut Window,
11431 cx: &mut Context<Self>,
11432 ) {
11433 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11434 buffer.update(cx, |buffer, cx| {
11435 buffer.set_capability(
11436 match buffer.capability() {
11437 Capability::ReadWrite => Capability::Read,
11438 Capability::Read => Capability::ReadWrite,
11439 Capability::ReadOnly => Capability::ReadOnly,
11440 },
11441 cx,
11442 );
11443 })
11444 }
11445 }
11446
11447 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11448 let Some(project) = self.project.clone() else {
11449 return;
11450 };
11451 let task = self.reload(project, window, cx);
11452 self.detach_and_notify_err(task, window, cx);
11453 }
11454
11455 pub fn restore_file(
11456 &mut self,
11457 _: &::git::RestoreFile,
11458 window: &mut Window,
11459 cx: &mut Context<Self>,
11460 ) {
11461 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11462 let mut buffer_ids = HashSet::default();
11463 let snapshot = self.buffer().read(cx).snapshot(cx);
11464 for selection in self
11465 .selections
11466 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11467 {
11468 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11469 }
11470
11471 let buffer = self.buffer().read(cx);
11472 let ranges = buffer_ids
11473 .into_iter()
11474 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11475 .collect::<Vec<_>>();
11476
11477 self.restore_hunks_in_ranges(ranges, window, cx);
11478 }
11479
11480 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11482 let selections = self
11483 .selections
11484 .all(&self.display_snapshot(cx))
11485 .into_iter()
11486 .map(|s| s.range())
11487 .collect();
11488 self.restore_hunks_in_ranges(selections, window, cx);
11489 }
11490
11491 /// Restores the diff hunks in the editor's selections and moves the cursor
11492 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11493 /// not all diff hunks are expanded.
11494 pub fn restore_and_next(
11495 &mut self,
11496 _: &::git::RestoreAndNext,
11497 window: &mut Window,
11498 cx: &mut Context<Self>,
11499 ) {
11500 let selections = self
11501 .selections
11502 .all(&self.display_snapshot(cx))
11503 .into_iter()
11504 .map(|selection| selection.range())
11505 .collect();
11506
11507 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11508 self.restore_hunks_in_ranges(selections, window, cx);
11509
11510 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11511 let wrap_around = !all_diff_hunks_expanded;
11512 let snapshot = self.snapshot(window, cx);
11513 let position = self
11514 .selections
11515 .newest::<Point>(&snapshot.display_snapshot)
11516 .head();
11517
11518 self.go_to_hunk_before_or_after_position(
11519 &snapshot,
11520 position,
11521 Direction::Next,
11522 wrap_around,
11523 window,
11524 cx,
11525 );
11526 }
11527
11528 pub fn restore_hunks_in_ranges(
11529 &mut self,
11530 ranges: Vec<Range<Point>>,
11531 window: &mut Window,
11532 cx: &mut Context<Editor>,
11533 ) {
11534 if self.delegate_stage_and_restore {
11535 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11536 if !hunks.is_empty() {
11537 cx.emit(EditorEvent::RestoreRequested { hunks });
11538 }
11539 return;
11540 }
11541 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11542 self.transact(window, cx, |editor, window, cx| {
11543 editor.restore_diff_hunks(hunks, cx);
11544 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11545 selections.refresh()
11546 });
11547 });
11548 }
11549
11550 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11551 let mut revert_changes = HashMap::default();
11552 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11553 for (buffer_id, hunks) in &chunk_by {
11554 let hunks = hunks.collect::<Vec<_>>();
11555 for hunk in &hunks {
11556 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11557 }
11558 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11559 }
11560 if !revert_changes.is_empty() {
11561 self.buffer().update(cx, |multi_buffer, cx| {
11562 for (buffer_id, changes) in revert_changes {
11563 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11564 buffer.update(cx, |buffer, cx| {
11565 buffer.edit(
11566 changes
11567 .into_iter()
11568 .map(|(range, text)| (range, text.to_string())),
11569 None,
11570 cx,
11571 );
11572 });
11573 }
11574 }
11575 });
11576 }
11577 }
11578
11579 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11580 if let Some(status) = self
11581 .addons
11582 .iter()
11583 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11584 {
11585 return Some(status);
11586 }
11587 self.project
11588 .as_ref()?
11589 .read(cx)
11590 .status_for_buffer_id(buffer_id, cx)
11591 }
11592
11593 pub fn open_active_item_in_terminal(
11594 &mut self,
11595 _: &OpenInTerminal,
11596 window: &mut Window,
11597 cx: &mut Context<Self>,
11598 ) {
11599 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11600 let project_path = buffer.read(cx).project_path(cx)?;
11601 let project = self.project()?.read(cx);
11602 let entry = project.entry_for_path(&project_path, cx)?;
11603 let parent = match &entry.canonical_path {
11604 Some(canonical_path) => canonical_path.to_path_buf(),
11605 None => project.absolute_path(&project_path, cx)?,
11606 }
11607 .parent()?
11608 .to_path_buf();
11609 Some(parent)
11610 }) {
11611 window.dispatch_action(
11612 OpenTerminal {
11613 working_directory,
11614 local: false,
11615 }
11616 .boxed_clone(),
11617 cx,
11618 );
11619 }
11620 }
11621
11622 fn set_breakpoint_context_menu(
11623 &mut self,
11624 display_row: DisplayRow,
11625 position: Option<Anchor>,
11626 clicked_point: gpui::Point<Pixels>,
11627 window: &mut Window,
11628 cx: &mut Context<Self>,
11629 ) {
11630 let source = self
11631 .buffer
11632 .read(cx)
11633 .snapshot(cx)
11634 .anchor_before(Point::new(display_row.0, 0u32));
11635
11636 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11637
11638 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11639 self,
11640 source,
11641 clicked_point,
11642 context_menu,
11643 window,
11644 cx,
11645 );
11646 }
11647
11648 fn add_edit_breakpoint_block(
11649 &mut self,
11650 anchor: Anchor,
11651 breakpoint: &Breakpoint,
11652 edit_action: BreakpointPromptEditAction,
11653 window: &mut Window,
11654 cx: &mut Context<Self>,
11655 ) {
11656 let weak_editor = cx.weak_entity();
11657 let bp_prompt = cx.new(|cx| {
11658 BreakpointPromptEditor::new(
11659 weak_editor,
11660 anchor,
11661 breakpoint.clone(),
11662 edit_action,
11663 window,
11664 cx,
11665 )
11666 });
11667
11668 let height = bp_prompt.update(cx, |this, cx| {
11669 this.prompt
11670 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11671 });
11672 let cloned_prompt = bp_prompt.clone();
11673 let blocks = vec![BlockProperties {
11674 style: BlockStyle::Sticky,
11675 placement: BlockPlacement::Above(anchor),
11676 height: Some(height),
11677 render: Arc::new(move |cx| {
11678 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11679 cloned_prompt.clone().into_any_element()
11680 }),
11681 priority: 0,
11682 }];
11683
11684 let focus_handle = bp_prompt.focus_handle(cx);
11685 window.focus(&focus_handle, cx);
11686
11687 let block_ids = self.insert_blocks(blocks, None, cx);
11688 bp_prompt.update(cx, |prompt, _| {
11689 prompt.add_block_ids(block_ids);
11690 });
11691 }
11692
11693 pub(crate) fn breakpoint_at_row(
11694 &self,
11695 row: u32,
11696 window: &mut Window,
11697 cx: &mut Context<Self>,
11698 ) -> Option<(Anchor, Breakpoint)> {
11699 let snapshot = self.snapshot(window, cx);
11700 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11701
11702 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11703 }
11704
11705 pub(crate) fn breakpoint_at_anchor(
11706 &self,
11707 breakpoint_position: Anchor,
11708 snapshot: &EditorSnapshot,
11709 cx: &mut Context<Self>,
11710 ) -> Option<(Anchor, Breakpoint)> {
11711 let buffer = self
11712 .buffer
11713 .read(cx)
11714 .buffer_for_anchor(breakpoint_position, cx)?;
11715
11716 let enclosing_excerpt = breakpoint_position.excerpt_id;
11717 let buffer_snapshot = buffer.read(cx).snapshot();
11718
11719 let row = buffer_snapshot
11720 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11721 .row;
11722
11723 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11724 let anchor_end = snapshot
11725 .buffer_snapshot()
11726 .anchor_after(Point::new(row, line_len));
11727
11728 self.breakpoint_store
11729 .as_ref()?
11730 .read_with(cx, |breakpoint_store, cx| {
11731 breakpoint_store
11732 .breakpoints(
11733 &buffer,
11734 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11735 &buffer_snapshot,
11736 cx,
11737 )
11738 .next()
11739 .and_then(|(bp, _)| {
11740 let breakpoint_row = buffer_snapshot
11741 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11742 .row;
11743
11744 if breakpoint_row == row {
11745 snapshot
11746 .buffer_snapshot()
11747 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11748 .map(|position| (position, bp.bp.clone()))
11749 } else {
11750 None
11751 }
11752 })
11753 })
11754 }
11755
11756 pub fn edit_log_breakpoint(
11757 &mut self,
11758 _: &EditLogBreakpoint,
11759 window: &mut Window,
11760 cx: &mut Context<Self>,
11761 ) {
11762 if self.breakpoint_store.is_none() {
11763 return;
11764 }
11765
11766 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11767 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11768 message: None,
11769 state: BreakpointState::Enabled,
11770 condition: None,
11771 hit_condition: None,
11772 });
11773
11774 self.add_edit_breakpoint_block(
11775 anchor,
11776 &breakpoint,
11777 BreakpointPromptEditAction::Log,
11778 window,
11779 cx,
11780 );
11781 }
11782 }
11783
11784 fn breakpoints_at_cursors(
11785 &self,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11789 let snapshot = self.snapshot(window, cx);
11790 let cursors = self
11791 .selections
11792 .disjoint_anchors_arc()
11793 .iter()
11794 .map(|selection| {
11795 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11796
11797 let breakpoint_position = self
11798 .breakpoint_at_row(cursor_position.row, window, cx)
11799 .map(|bp| bp.0)
11800 .unwrap_or_else(|| {
11801 snapshot
11802 .display_snapshot
11803 .buffer_snapshot()
11804 .anchor_after(Point::new(cursor_position.row, 0))
11805 });
11806
11807 let breakpoint = self
11808 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11809 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11810
11811 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11812 })
11813 // 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.
11814 .collect::<HashMap<Anchor, _>>();
11815
11816 cursors.into_iter().collect()
11817 }
11818
11819 pub fn enable_breakpoint(
11820 &mut self,
11821 _: &crate::actions::EnableBreakpoint,
11822 window: &mut Window,
11823 cx: &mut Context<Self>,
11824 ) {
11825 if self.breakpoint_store.is_none() {
11826 return;
11827 }
11828
11829 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11830 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11831 continue;
11832 };
11833 self.edit_breakpoint_at_anchor(
11834 anchor,
11835 breakpoint,
11836 BreakpointEditAction::InvertState,
11837 cx,
11838 );
11839 }
11840 }
11841
11842 pub fn disable_breakpoint(
11843 &mut self,
11844 _: &crate::actions::DisableBreakpoint,
11845 window: &mut Window,
11846 cx: &mut Context<Self>,
11847 ) {
11848 if self.breakpoint_store.is_none() {
11849 return;
11850 }
11851
11852 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11853 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11854 continue;
11855 };
11856 self.edit_breakpoint_at_anchor(
11857 anchor,
11858 breakpoint,
11859 BreakpointEditAction::InvertState,
11860 cx,
11861 );
11862 }
11863 }
11864
11865 pub fn toggle_breakpoint(
11866 &mut self,
11867 _: &crate::actions::ToggleBreakpoint,
11868 window: &mut Window,
11869 cx: &mut Context<Self>,
11870 ) {
11871 if self.breakpoint_store.is_none() {
11872 return;
11873 }
11874
11875 let snapshot = self.snapshot(window, cx);
11876 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11877 if self.gutter_breakpoint_indicator.0.is_some() {
11878 let display_row = anchor
11879 .to_point(snapshot.buffer_snapshot())
11880 .to_display_point(&snapshot.display_snapshot)
11881 .row();
11882 self.update_breakpoint_collision_on_toggle(
11883 display_row,
11884 &BreakpointEditAction::Toggle,
11885 );
11886 }
11887
11888 if let Some(breakpoint) = breakpoint {
11889 self.edit_breakpoint_at_anchor(
11890 anchor,
11891 breakpoint,
11892 BreakpointEditAction::Toggle,
11893 cx,
11894 );
11895 } else {
11896 self.edit_breakpoint_at_anchor(
11897 anchor,
11898 Breakpoint::new_standard(),
11899 BreakpointEditAction::Toggle,
11900 cx,
11901 );
11902 }
11903 }
11904 }
11905
11906 fn update_breakpoint_collision_on_toggle(
11907 &mut self,
11908 display_row: DisplayRow,
11909 edit_action: &BreakpointEditAction,
11910 ) {
11911 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
11912 if breakpoint_indicator.display_row == display_row
11913 && matches!(edit_action, BreakpointEditAction::Toggle)
11914 {
11915 breakpoint_indicator.collides_with_existing_breakpoint =
11916 !breakpoint_indicator.collides_with_existing_breakpoint;
11917 }
11918 }
11919 }
11920
11921 pub fn edit_breakpoint_at_anchor(
11922 &mut self,
11923 breakpoint_position: Anchor,
11924 breakpoint: Breakpoint,
11925 edit_action: BreakpointEditAction,
11926 cx: &mut Context<Self>,
11927 ) {
11928 let Some(breakpoint_store) = &self.breakpoint_store else {
11929 return;
11930 };
11931
11932 let Some(buffer) = self
11933 .buffer
11934 .read(cx)
11935 .buffer_for_anchor(breakpoint_position, cx)
11936 else {
11937 return;
11938 };
11939
11940 breakpoint_store.update(cx, |breakpoint_store, cx| {
11941 breakpoint_store.toggle_breakpoint(
11942 buffer,
11943 BreakpointWithPosition {
11944 position: breakpoint_position.text_anchor,
11945 bp: breakpoint,
11946 },
11947 edit_action,
11948 cx,
11949 );
11950 });
11951
11952 cx.notify();
11953 }
11954
11955 #[cfg(any(test, feature = "test-support"))]
11956 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11957 self.breakpoint_store.clone()
11958 }
11959
11960 pub fn prepare_restore_change(
11961 &self,
11962 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11963 hunk: &MultiBufferDiffHunk,
11964 cx: &mut App,
11965 ) -> Option<()> {
11966 if hunk.is_created_file() {
11967 return None;
11968 }
11969 let buffer = self.buffer.read(cx);
11970 let diff = buffer.diff_for(hunk.buffer_id)?;
11971 let buffer = buffer.buffer(hunk.buffer_id)?;
11972 let buffer = buffer.read(cx);
11973 let original_text = diff
11974 .read(cx)
11975 .base_text(cx)
11976 .as_rope()
11977 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11978 let buffer_snapshot = buffer.snapshot();
11979 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11980 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11981 probe
11982 .0
11983 .start
11984 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11985 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11986 }) {
11987 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11988 Some(())
11989 } else {
11990 None
11991 }
11992 }
11993
11994 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11995 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11996 }
11997
11998 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11999 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12000 }
12001
12002 pub fn rotate_selections_forward(
12003 &mut self,
12004 _: &RotateSelectionsForward,
12005 window: &mut Window,
12006 cx: &mut Context<Self>,
12007 ) {
12008 self.rotate_selections(window, cx, false)
12009 }
12010
12011 pub fn rotate_selections_backward(
12012 &mut self,
12013 _: &RotateSelectionsBackward,
12014 window: &mut Window,
12015 cx: &mut Context<Self>,
12016 ) {
12017 self.rotate_selections(window, cx, true)
12018 }
12019
12020 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12021 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12022 let display_snapshot = self.display_snapshot(cx);
12023 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12024
12025 if selections.len() < 2 {
12026 return;
12027 }
12028
12029 let (edits, new_selections) = {
12030 let buffer = self.buffer.read(cx).read(cx);
12031 let has_selections = selections.iter().any(|s| !s.is_empty());
12032 if has_selections {
12033 let mut selected_texts: Vec<String> = selections
12034 .iter()
12035 .map(|selection| {
12036 buffer
12037 .text_for_range(selection.start..selection.end)
12038 .collect()
12039 })
12040 .collect();
12041
12042 if reverse {
12043 selected_texts.rotate_left(1);
12044 } else {
12045 selected_texts.rotate_right(1);
12046 }
12047
12048 let mut offset_delta: i64 = 0;
12049 let mut new_selections = Vec::new();
12050 let edits: Vec<_> = selections
12051 .iter()
12052 .zip(selected_texts.iter())
12053 .map(|(selection, new_text)| {
12054 let old_len = (selection.end.0 - selection.start.0) as i64;
12055 let new_len = new_text.len() as i64;
12056 let adjusted_start =
12057 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12058 let adjusted_end =
12059 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12060
12061 new_selections.push(Selection {
12062 id: selection.id,
12063 start: adjusted_start,
12064 end: adjusted_end,
12065 reversed: selection.reversed,
12066 goal: selection.goal,
12067 });
12068
12069 offset_delta += new_len - old_len;
12070 (selection.start..selection.end, new_text.clone())
12071 })
12072 .collect();
12073 (edits, new_selections)
12074 } else {
12075 let mut all_rows: Vec<u32> = selections
12076 .iter()
12077 .map(|selection| buffer.offset_to_point(selection.start).row)
12078 .collect();
12079 all_rows.sort_unstable();
12080 all_rows.dedup();
12081
12082 if all_rows.len() < 2 {
12083 return;
12084 }
12085
12086 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12087 .iter()
12088 .map(|&row| {
12089 let start = Point::new(row, 0);
12090 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12091 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12092 })
12093 .collect();
12094
12095 let mut line_texts: Vec<String> = line_ranges
12096 .iter()
12097 .map(|range| buffer.text_for_range(range.clone()).collect())
12098 .collect();
12099
12100 if reverse {
12101 line_texts.rotate_left(1);
12102 } else {
12103 line_texts.rotate_right(1);
12104 }
12105
12106 let edits = line_ranges
12107 .iter()
12108 .zip(line_texts.iter())
12109 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12110 .collect();
12111
12112 let num_rows = all_rows.len();
12113 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12114 .iter()
12115 .enumerate()
12116 .map(|(i, &row)| (row, i))
12117 .collect();
12118
12119 // Compute new line start offsets after rotation (handles CRLF)
12120 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12121 let first_line_start = line_ranges[0].start.0;
12122 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12123 for text in line_texts.iter().take(num_rows - 1) {
12124 let prev_start = *new_line_starts.last().unwrap();
12125 new_line_starts.push(prev_start + text.len() + newline_len);
12126 }
12127
12128 let new_selections = selections
12129 .iter()
12130 .map(|selection| {
12131 let point = buffer.offset_to_point(selection.start);
12132 let old_index = row_to_index[&point.row];
12133 let new_index = if reverse {
12134 (old_index + num_rows - 1) % num_rows
12135 } else {
12136 (old_index + 1) % num_rows
12137 };
12138 let new_offset =
12139 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12140 Selection {
12141 id: selection.id,
12142 start: new_offset,
12143 end: new_offset,
12144 reversed: selection.reversed,
12145 goal: selection.goal,
12146 }
12147 })
12148 .collect();
12149
12150 (edits, new_selections)
12151 }
12152 };
12153
12154 self.transact(window, cx, |this, window, cx| {
12155 this.buffer.update(cx, |buffer, cx| {
12156 buffer.edit(edits, None, cx);
12157 });
12158 this.change_selections(Default::default(), window, cx, |s| {
12159 s.select(new_selections);
12160 });
12161 });
12162 }
12163
12164 fn manipulate_lines<M>(
12165 &mut self,
12166 window: &mut Window,
12167 cx: &mut Context<Self>,
12168 mut manipulate: M,
12169 ) where
12170 M: FnMut(&str) -> LineManipulationResult,
12171 {
12172 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12173
12174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12175 let buffer = self.buffer.read(cx).snapshot(cx);
12176
12177 let mut edits = Vec::new();
12178
12179 let selections = self.selections.all::<Point>(&display_map);
12180 let mut selections = selections.iter().peekable();
12181 let mut contiguous_row_selections = Vec::new();
12182 let mut new_selections = Vec::new();
12183 let mut added_lines = 0;
12184 let mut removed_lines = 0;
12185
12186 while let Some(selection) = selections.next() {
12187 let (start_row, end_row) = consume_contiguous_rows(
12188 &mut contiguous_row_selections,
12189 selection,
12190 &display_map,
12191 &mut selections,
12192 );
12193
12194 let start_point = Point::new(start_row.0, 0);
12195 let end_point = Point::new(
12196 end_row.previous_row().0,
12197 buffer.line_len(end_row.previous_row()),
12198 );
12199 let text = buffer
12200 .text_for_range(start_point..end_point)
12201 .collect::<String>();
12202
12203 let LineManipulationResult {
12204 new_text,
12205 line_count_before,
12206 line_count_after,
12207 } = manipulate(&text);
12208
12209 edits.push((start_point..end_point, new_text));
12210
12211 // Selections must change based on added and removed line count
12212 let start_row =
12213 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12214 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12215 new_selections.push(Selection {
12216 id: selection.id,
12217 start: start_row,
12218 end: end_row,
12219 goal: SelectionGoal::None,
12220 reversed: selection.reversed,
12221 });
12222
12223 if line_count_after > line_count_before {
12224 added_lines += line_count_after - line_count_before;
12225 } else if line_count_before > line_count_after {
12226 removed_lines += line_count_before - line_count_after;
12227 }
12228 }
12229
12230 self.transact(window, cx, |this, window, cx| {
12231 let buffer = this.buffer.update(cx, |buffer, cx| {
12232 buffer.edit(edits, None, cx);
12233 buffer.snapshot(cx)
12234 });
12235
12236 // Recalculate offsets on newly edited buffer
12237 let new_selections = new_selections
12238 .iter()
12239 .map(|s| {
12240 let start_point = Point::new(s.start.0, 0);
12241 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12242 Selection {
12243 id: s.id,
12244 start: buffer.point_to_offset(start_point),
12245 end: buffer.point_to_offset(end_point),
12246 goal: s.goal,
12247 reversed: s.reversed,
12248 }
12249 })
12250 .collect();
12251
12252 this.change_selections(Default::default(), window, cx, |s| {
12253 s.select(new_selections);
12254 });
12255
12256 this.request_autoscroll(Autoscroll::fit(), cx);
12257 });
12258 }
12259
12260 fn manipulate_immutable_lines<Fn>(
12261 &mut self,
12262 window: &mut Window,
12263 cx: &mut Context<Self>,
12264 mut callback: Fn,
12265 ) where
12266 Fn: FnMut(&mut Vec<&str>),
12267 {
12268 self.manipulate_lines(window, cx, |text| {
12269 let mut lines: Vec<&str> = text.split('\n').collect();
12270 let line_count_before = lines.len();
12271
12272 callback(&mut lines);
12273
12274 LineManipulationResult {
12275 new_text: lines.join("\n"),
12276 line_count_before,
12277 line_count_after: lines.len(),
12278 }
12279 });
12280 }
12281
12282 fn manipulate_mutable_lines<Fn>(
12283 &mut self,
12284 window: &mut Window,
12285 cx: &mut Context<Self>,
12286 mut callback: Fn,
12287 ) where
12288 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12289 {
12290 self.manipulate_lines(window, cx, |text| {
12291 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12292 let line_count_before = lines.len();
12293
12294 callback(&mut lines);
12295
12296 LineManipulationResult {
12297 new_text: lines.join("\n"),
12298 line_count_before,
12299 line_count_after: lines.len(),
12300 }
12301 });
12302 }
12303
12304 pub fn convert_indentation_to_spaces(
12305 &mut self,
12306 _: &ConvertIndentationToSpaces,
12307 window: &mut Window,
12308 cx: &mut Context<Self>,
12309 ) {
12310 let settings = self.buffer.read(cx).language_settings(cx);
12311 let tab_size = settings.tab_size.get() as usize;
12312
12313 self.manipulate_mutable_lines(window, cx, |lines| {
12314 // Allocates a reasonably sized scratch buffer once for the whole loop
12315 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12316 // Avoids recomputing spaces that could be inserted many times
12317 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12318 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12319 .collect();
12320
12321 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12322 let mut chars = line.as_ref().chars();
12323 let mut col = 0;
12324 let mut changed = false;
12325
12326 for ch in chars.by_ref() {
12327 match ch {
12328 ' ' => {
12329 reindented_line.push(' ');
12330 col += 1;
12331 }
12332 '\t' => {
12333 // \t are converted to spaces depending on the current column
12334 let spaces_len = tab_size - (col % tab_size);
12335 reindented_line.extend(&space_cache[spaces_len - 1]);
12336 col += spaces_len;
12337 changed = true;
12338 }
12339 _ => {
12340 // If we dont append before break, the character is consumed
12341 reindented_line.push(ch);
12342 break;
12343 }
12344 }
12345 }
12346
12347 if !changed {
12348 reindented_line.clear();
12349 continue;
12350 }
12351 // Append the rest of the line and replace old reference with new one
12352 reindented_line.extend(chars);
12353 *line = Cow::Owned(reindented_line.clone());
12354 reindented_line.clear();
12355 }
12356 });
12357 }
12358
12359 pub fn convert_indentation_to_tabs(
12360 &mut self,
12361 _: &ConvertIndentationToTabs,
12362 window: &mut Window,
12363 cx: &mut Context<Self>,
12364 ) {
12365 let settings = self.buffer.read(cx).language_settings(cx);
12366 let tab_size = settings.tab_size.get() as usize;
12367
12368 self.manipulate_mutable_lines(window, cx, |lines| {
12369 // Allocates a reasonably sized buffer once for the whole loop
12370 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12371 // Avoids recomputing spaces that could be inserted many times
12372 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12373 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12374 .collect();
12375
12376 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12377 let mut chars = line.chars();
12378 let mut spaces_count = 0;
12379 let mut first_non_indent_char = None;
12380 let mut changed = false;
12381
12382 for ch in chars.by_ref() {
12383 match ch {
12384 ' ' => {
12385 // Keep track of spaces. Append \t when we reach tab_size
12386 spaces_count += 1;
12387 changed = true;
12388 if spaces_count == tab_size {
12389 reindented_line.push('\t');
12390 spaces_count = 0;
12391 }
12392 }
12393 '\t' => {
12394 reindented_line.push('\t');
12395 spaces_count = 0;
12396 }
12397 _ => {
12398 // Dont append it yet, we might have remaining spaces
12399 first_non_indent_char = Some(ch);
12400 break;
12401 }
12402 }
12403 }
12404
12405 if !changed {
12406 reindented_line.clear();
12407 continue;
12408 }
12409 // Remaining spaces that didn't make a full tab stop
12410 if spaces_count > 0 {
12411 reindented_line.extend(&space_cache[spaces_count - 1]);
12412 }
12413 // If we consume an extra character that was not indentation, add it back
12414 if let Some(extra_char) = first_non_indent_char {
12415 reindented_line.push(extra_char);
12416 }
12417 // Append the rest of the line and replace old reference with new one
12418 reindented_line.extend(chars);
12419 *line = Cow::Owned(reindented_line.clone());
12420 reindented_line.clear();
12421 }
12422 });
12423 }
12424
12425 pub fn convert_to_upper_case(
12426 &mut self,
12427 _: &ConvertToUpperCase,
12428 window: &mut Window,
12429 cx: &mut Context<Self>,
12430 ) {
12431 self.manipulate_text(window, cx, |text| text.to_uppercase())
12432 }
12433
12434 pub fn convert_to_lower_case(
12435 &mut self,
12436 _: &ConvertToLowerCase,
12437 window: &mut Window,
12438 cx: &mut Context<Self>,
12439 ) {
12440 self.manipulate_text(window, cx, |text| text.to_lowercase())
12441 }
12442
12443 pub fn convert_to_title_case(
12444 &mut self,
12445 _: &ConvertToTitleCase,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 self.manipulate_text(window, cx, |text| {
12450 Self::convert_text_case(text, Case::Title)
12451 })
12452 }
12453
12454 pub fn convert_to_snake_case(
12455 &mut self,
12456 _: &ConvertToSnakeCase,
12457 window: &mut Window,
12458 cx: &mut Context<Self>,
12459 ) {
12460 self.manipulate_text(window, cx, |text| {
12461 Self::convert_text_case(text, Case::Snake)
12462 })
12463 }
12464
12465 pub fn convert_to_kebab_case(
12466 &mut self,
12467 _: &ConvertToKebabCase,
12468 window: &mut Window,
12469 cx: &mut Context<Self>,
12470 ) {
12471 self.manipulate_text(window, cx, |text| {
12472 Self::convert_text_case(text, Case::Kebab)
12473 })
12474 }
12475
12476 pub fn convert_to_upper_camel_case(
12477 &mut self,
12478 _: &ConvertToUpperCamelCase,
12479 window: &mut Window,
12480 cx: &mut Context<Self>,
12481 ) {
12482 self.manipulate_text(window, cx, |text| {
12483 Self::convert_text_case(text, Case::UpperCamel)
12484 })
12485 }
12486
12487 pub fn convert_to_lower_camel_case(
12488 &mut self,
12489 _: &ConvertToLowerCamelCase,
12490 window: &mut Window,
12491 cx: &mut Context<Self>,
12492 ) {
12493 self.manipulate_text(window, cx, |text| {
12494 Self::convert_text_case(text, Case::Camel)
12495 })
12496 }
12497
12498 pub fn convert_to_opposite_case(
12499 &mut self,
12500 _: &ConvertToOppositeCase,
12501 window: &mut Window,
12502 cx: &mut Context<Self>,
12503 ) {
12504 self.manipulate_text(window, cx, |text| {
12505 text.chars()
12506 .fold(String::with_capacity(text.len()), |mut t, c| {
12507 if c.is_uppercase() {
12508 t.extend(c.to_lowercase());
12509 } else {
12510 t.extend(c.to_uppercase());
12511 }
12512 t
12513 })
12514 })
12515 }
12516
12517 pub fn convert_to_sentence_case(
12518 &mut self,
12519 _: &ConvertToSentenceCase,
12520 window: &mut Window,
12521 cx: &mut Context<Self>,
12522 ) {
12523 self.manipulate_text(window, cx, |text| {
12524 Self::convert_text_case(text, Case::Sentence)
12525 })
12526 }
12527
12528 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12529 self.manipulate_text(window, cx, |text| {
12530 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12531 if has_upper_case_characters {
12532 text.to_lowercase()
12533 } else {
12534 text.to_uppercase()
12535 }
12536 })
12537 }
12538
12539 pub fn convert_to_rot13(
12540 &mut self,
12541 _: &ConvertToRot13,
12542 window: &mut Window,
12543 cx: &mut Context<Self>,
12544 ) {
12545 self.manipulate_text(window, cx, |text| {
12546 text.chars()
12547 .map(|c| match c {
12548 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12549 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12550 _ => c,
12551 })
12552 .collect()
12553 })
12554 }
12555
12556 fn convert_text_case(text: &str, case: Case) -> String {
12557 text.lines()
12558 .map(|line| {
12559 let trimmed_start = line.trim_start();
12560 let leading = &line[..line.len() - trimmed_start.len()];
12561 let trimmed = trimmed_start.trim_end();
12562 let trailing = &trimmed_start[trimmed.len()..];
12563 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12564 })
12565 .join("\n")
12566 }
12567
12568 pub fn convert_to_rot47(
12569 &mut self,
12570 _: &ConvertToRot47,
12571 window: &mut Window,
12572 cx: &mut Context<Self>,
12573 ) {
12574 self.manipulate_text(window, cx, |text| {
12575 text.chars()
12576 .map(|c| {
12577 let code_point = c as u32;
12578 if code_point >= 33 && code_point <= 126 {
12579 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12580 }
12581 c
12582 })
12583 .collect()
12584 })
12585 }
12586
12587 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12588 where
12589 Fn: FnMut(&str) -> String,
12590 {
12591 let buffer = self.buffer.read(cx).snapshot(cx);
12592
12593 let mut new_selections = Vec::new();
12594 let mut edits = Vec::new();
12595 let mut selection_adjustment = 0isize;
12596
12597 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12598 let selection_is_empty = selection.is_empty();
12599
12600 let (start, end) = if selection_is_empty {
12601 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12602 (word_range.start, word_range.end)
12603 } else {
12604 (
12605 buffer.point_to_offset(selection.start),
12606 buffer.point_to_offset(selection.end),
12607 )
12608 };
12609
12610 let text = buffer.text_for_range(start..end).collect::<String>();
12611 let old_length = text.len() as isize;
12612 let text = callback(&text);
12613
12614 new_selections.push(Selection {
12615 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12616 end: MultiBufferOffset(
12617 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12618 ),
12619 goal: SelectionGoal::None,
12620 id: selection.id,
12621 reversed: selection.reversed,
12622 });
12623
12624 selection_adjustment += old_length - text.len() as isize;
12625
12626 edits.push((start..end, text));
12627 }
12628
12629 self.transact(window, cx, |this, window, cx| {
12630 this.buffer.update(cx, |buffer, cx| {
12631 buffer.edit(edits, None, cx);
12632 });
12633
12634 this.change_selections(Default::default(), window, cx, |s| {
12635 s.select(new_selections);
12636 });
12637
12638 this.request_autoscroll(Autoscroll::fit(), cx);
12639 });
12640 }
12641
12642 pub fn move_selection_on_drop(
12643 &mut self,
12644 selection: &Selection<Anchor>,
12645 target: DisplayPoint,
12646 is_cut: bool,
12647 window: &mut Window,
12648 cx: &mut Context<Self>,
12649 ) {
12650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12651 let buffer = display_map.buffer_snapshot();
12652 let mut edits = Vec::new();
12653 let insert_point = display_map
12654 .clip_point(target, Bias::Left)
12655 .to_point(&display_map);
12656 let text = buffer
12657 .text_for_range(selection.start..selection.end)
12658 .collect::<String>();
12659 if is_cut {
12660 edits.push(((selection.start..selection.end), String::new()));
12661 }
12662 let insert_anchor = buffer.anchor_before(insert_point);
12663 edits.push(((insert_anchor..insert_anchor), text));
12664 let last_edit_start = insert_anchor.bias_left(buffer);
12665 let last_edit_end = insert_anchor.bias_right(buffer);
12666 self.transact(window, cx, |this, window, cx| {
12667 this.buffer.update(cx, |buffer, cx| {
12668 buffer.edit(edits, None, cx);
12669 });
12670 this.change_selections(Default::default(), window, cx, |s| {
12671 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12672 });
12673 });
12674 }
12675
12676 pub fn clear_selection_drag_state(&mut self) {
12677 self.selection_drag_state = SelectionDragState::None;
12678 }
12679
12680 pub fn duplicate(
12681 &mut self,
12682 upwards: bool,
12683 whole_lines: bool,
12684 window: &mut Window,
12685 cx: &mut Context<Self>,
12686 ) {
12687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12688
12689 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12690 let buffer = display_map.buffer_snapshot();
12691 let selections = self.selections.all::<Point>(&display_map);
12692
12693 let mut edits = Vec::new();
12694 let mut selections_iter = selections.iter().peekable();
12695 while let Some(selection) = selections_iter.next() {
12696 let mut rows = selection.spanned_rows(false, &display_map);
12697 // duplicate line-wise
12698 if whole_lines || selection.start == selection.end {
12699 // Avoid duplicating the same lines twice.
12700 while let Some(next_selection) = selections_iter.peek() {
12701 let next_rows = next_selection.spanned_rows(false, &display_map);
12702 if next_rows.start < rows.end {
12703 rows.end = next_rows.end;
12704 selections_iter.next().unwrap();
12705 } else {
12706 break;
12707 }
12708 }
12709
12710 // Copy the text from the selected row region and splice it either at the start
12711 // or end of the region.
12712 let start = Point::new(rows.start.0, 0);
12713 let end = Point::new(
12714 rows.end.previous_row().0,
12715 buffer.line_len(rows.end.previous_row()),
12716 );
12717
12718 let mut text = buffer.text_for_range(start..end).collect::<String>();
12719
12720 let insert_location = if upwards {
12721 // When duplicating upward, we need to insert before the current line.
12722 // If we're on the last line and it doesn't end with a newline,
12723 // we need to add a newline before the duplicated content.
12724 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12725 && buffer.max_point().column > 0
12726 && !text.ends_with('\n');
12727
12728 if needs_leading_newline {
12729 text.insert(0, '\n');
12730 end
12731 } else {
12732 text.push('\n');
12733 Point::new(rows.start.0, 0)
12734 }
12735 } else {
12736 text.push('\n');
12737 start
12738 };
12739 edits.push((insert_location..insert_location, text));
12740 } else {
12741 // duplicate character-wise
12742 let start = selection.start;
12743 let end = selection.end;
12744 let text = buffer.text_for_range(start..end).collect::<String>();
12745 edits.push((selection.end..selection.end, text));
12746 }
12747 }
12748
12749 self.transact(window, cx, |this, window, cx| {
12750 this.buffer.update(cx, |buffer, cx| {
12751 buffer.edit(edits, None, cx);
12752 });
12753
12754 // When duplicating upward with whole lines, move the cursor to the duplicated line
12755 if upwards && whole_lines {
12756 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12757
12758 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12759 let mut new_ranges = Vec::new();
12760 let selections = s.all::<Point>(&display_map);
12761 let mut selections_iter = selections.iter().peekable();
12762
12763 while let Some(first_selection) = selections_iter.next() {
12764 // Group contiguous selections together to find the total row span
12765 let mut group_selections = vec![first_selection];
12766 let mut rows = first_selection.spanned_rows(false, &display_map);
12767
12768 while let Some(next_selection) = selections_iter.peek() {
12769 let next_rows = next_selection.spanned_rows(false, &display_map);
12770 if next_rows.start < rows.end {
12771 rows.end = next_rows.end;
12772 group_selections.push(selections_iter.next().unwrap());
12773 } else {
12774 break;
12775 }
12776 }
12777
12778 let row_count = rows.end.0 - rows.start.0;
12779
12780 // Move all selections in this group up by the total number of duplicated rows
12781 for selection in group_selections {
12782 let new_start = Point::new(
12783 selection.start.row.saturating_sub(row_count),
12784 selection.start.column,
12785 );
12786
12787 let new_end = Point::new(
12788 selection.end.row.saturating_sub(row_count),
12789 selection.end.column,
12790 );
12791
12792 new_ranges.push(new_start..new_end);
12793 }
12794 }
12795
12796 s.select_ranges(new_ranges);
12797 });
12798 }
12799
12800 this.request_autoscroll(Autoscroll::fit(), cx);
12801 });
12802 }
12803
12804 pub fn duplicate_line_up(
12805 &mut self,
12806 _: &DuplicateLineUp,
12807 window: &mut Window,
12808 cx: &mut Context<Self>,
12809 ) {
12810 self.duplicate(true, true, window, cx);
12811 }
12812
12813 pub fn duplicate_line_down(
12814 &mut self,
12815 _: &DuplicateLineDown,
12816 window: &mut Window,
12817 cx: &mut Context<Self>,
12818 ) {
12819 self.duplicate(false, true, window, cx);
12820 }
12821
12822 pub fn duplicate_selection(
12823 &mut self,
12824 _: &DuplicateSelection,
12825 window: &mut Window,
12826 cx: &mut Context<Self>,
12827 ) {
12828 self.duplicate(false, false, window, cx);
12829 }
12830
12831 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12833 if self.mode.is_single_line() {
12834 cx.propagate();
12835 return;
12836 }
12837
12838 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12839 let buffer = self.buffer.read(cx).snapshot(cx);
12840
12841 let mut edits = Vec::new();
12842 let mut unfold_ranges = Vec::new();
12843 let mut refold_creases = Vec::new();
12844
12845 let selections = self.selections.all::<Point>(&display_map);
12846 let mut selections = selections.iter().peekable();
12847 let mut contiguous_row_selections = Vec::new();
12848 let mut new_selections = Vec::new();
12849
12850 while let Some(selection) = selections.next() {
12851 // Find all the selections that span a contiguous row range
12852 let (start_row, end_row) = consume_contiguous_rows(
12853 &mut contiguous_row_selections,
12854 selection,
12855 &display_map,
12856 &mut selections,
12857 );
12858
12859 // Move the text spanned by the row range to be before the line preceding the row range
12860 if start_row.0 > 0 {
12861 let range_to_move = Point::new(
12862 start_row.previous_row().0,
12863 buffer.line_len(start_row.previous_row()),
12864 )
12865 ..Point::new(
12866 end_row.previous_row().0,
12867 buffer.line_len(end_row.previous_row()),
12868 );
12869 let insertion_point = display_map
12870 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12871 .0;
12872
12873 // Don't move lines across excerpts
12874 if buffer
12875 .excerpt_containing(insertion_point..range_to_move.end)
12876 .is_some()
12877 {
12878 let text = buffer
12879 .text_for_range(range_to_move.clone())
12880 .flat_map(|s| s.chars())
12881 .skip(1)
12882 .chain(['\n'])
12883 .collect::<String>();
12884
12885 edits.push((
12886 buffer.anchor_after(range_to_move.start)
12887 ..buffer.anchor_before(range_to_move.end),
12888 String::new(),
12889 ));
12890 let insertion_anchor = buffer.anchor_after(insertion_point);
12891 edits.push((insertion_anchor..insertion_anchor, text));
12892
12893 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12894
12895 // Move selections up
12896 new_selections.extend(contiguous_row_selections.drain(..).map(
12897 |mut selection| {
12898 selection.start.row -= row_delta;
12899 selection.end.row -= row_delta;
12900 selection
12901 },
12902 ));
12903
12904 // Move folds up
12905 unfold_ranges.push(range_to_move.clone());
12906 for fold in display_map.folds_in_range(
12907 buffer.anchor_before(range_to_move.start)
12908 ..buffer.anchor_after(range_to_move.end),
12909 ) {
12910 let mut start = fold.range.start.to_point(&buffer);
12911 let mut end = fold.range.end.to_point(&buffer);
12912 start.row -= row_delta;
12913 end.row -= row_delta;
12914 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12915 }
12916 }
12917 }
12918
12919 // If we didn't move line(s), preserve the existing selections
12920 new_selections.append(&mut contiguous_row_selections);
12921 }
12922
12923 self.transact(window, cx, |this, window, cx| {
12924 this.unfold_ranges(&unfold_ranges, true, true, cx);
12925 this.buffer.update(cx, |buffer, cx| {
12926 for (range, text) in edits {
12927 buffer.edit([(range, text)], None, cx);
12928 }
12929 });
12930 this.fold_creases(refold_creases, true, window, cx);
12931 this.change_selections(Default::default(), window, cx, |s| {
12932 s.select(new_selections);
12933 })
12934 });
12935 }
12936
12937 pub fn move_line_down(
12938 &mut self,
12939 _: &MoveLineDown,
12940 window: &mut Window,
12941 cx: &mut Context<Self>,
12942 ) {
12943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12944 if self.mode.is_single_line() {
12945 cx.propagate();
12946 return;
12947 }
12948
12949 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12950 let buffer = self.buffer.read(cx).snapshot(cx);
12951
12952 let mut edits = Vec::new();
12953 let mut unfold_ranges = Vec::new();
12954 let mut refold_creases = Vec::new();
12955
12956 let selections = self.selections.all::<Point>(&display_map);
12957 let mut selections = selections.iter().peekable();
12958 let mut contiguous_row_selections = Vec::new();
12959 let mut new_selections = Vec::new();
12960
12961 while let Some(selection) = selections.next() {
12962 // Find all the selections that span a contiguous row range
12963 let (start_row, end_row) = consume_contiguous_rows(
12964 &mut contiguous_row_selections,
12965 selection,
12966 &display_map,
12967 &mut selections,
12968 );
12969
12970 // Move the text spanned by the row range to be after the last line of the row range
12971 if end_row.0 <= buffer.max_point().row {
12972 let range_to_move =
12973 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12974 let insertion_point = display_map
12975 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12976 .0;
12977
12978 // Don't move lines across excerpt boundaries
12979 if buffer
12980 .excerpt_containing(range_to_move.start..insertion_point)
12981 .is_some()
12982 {
12983 let mut text = String::from("\n");
12984 text.extend(buffer.text_for_range(range_to_move.clone()));
12985 text.pop(); // Drop trailing newline
12986 edits.push((
12987 buffer.anchor_after(range_to_move.start)
12988 ..buffer.anchor_before(range_to_move.end),
12989 String::new(),
12990 ));
12991 let insertion_anchor = buffer.anchor_after(insertion_point);
12992 edits.push((insertion_anchor..insertion_anchor, text));
12993
12994 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12995
12996 // Move selections down
12997 new_selections.extend(contiguous_row_selections.drain(..).map(
12998 |mut selection| {
12999 selection.start.row += row_delta;
13000 selection.end.row += row_delta;
13001 selection
13002 },
13003 ));
13004
13005 // Move folds down
13006 unfold_ranges.push(range_to_move.clone());
13007 for fold in display_map.folds_in_range(
13008 buffer.anchor_before(range_to_move.start)
13009 ..buffer.anchor_after(range_to_move.end),
13010 ) {
13011 let mut start = fold.range.start.to_point(&buffer);
13012 let mut end = fold.range.end.to_point(&buffer);
13013 start.row += row_delta;
13014 end.row += row_delta;
13015 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13016 }
13017 }
13018 }
13019
13020 // If we didn't move line(s), preserve the existing selections
13021 new_selections.append(&mut contiguous_row_selections);
13022 }
13023
13024 self.transact(window, cx, |this, window, cx| {
13025 this.unfold_ranges(&unfold_ranges, true, true, cx);
13026 this.buffer.update(cx, |buffer, cx| {
13027 for (range, text) in edits {
13028 buffer.edit([(range, text)], None, cx);
13029 }
13030 });
13031 this.fold_creases(refold_creases, true, window, cx);
13032 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13033 });
13034 }
13035
13036 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13037 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13038 let text_layout_details = &self.text_layout_details(window, cx);
13039 self.transact(window, cx, |this, window, cx| {
13040 let edits = this.change_selections(Default::default(), window, cx, |s| {
13041 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13042 s.move_with(&mut |display_map, selection| {
13043 if !selection.is_empty() {
13044 return;
13045 }
13046
13047 let mut head = selection.head();
13048 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13049 if head.column() == display_map.line_len(head.row()) {
13050 transpose_offset = display_map
13051 .buffer_snapshot()
13052 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13053 }
13054
13055 if transpose_offset == MultiBufferOffset(0) {
13056 return;
13057 }
13058
13059 *head.column_mut() += 1;
13060 head = display_map.clip_point(head, Bias::Right);
13061 let goal = SelectionGoal::HorizontalPosition(
13062 display_map
13063 .x_for_display_point(head, text_layout_details)
13064 .into(),
13065 );
13066 selection.collapse_to(head, goal);
13067
13068 let transpose_start = display_map
13069 .buffer_snapshot()
13070 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13071 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13072 let transpose_end = display_map
13073 .buffer_snapshot()
13074 .clip_offset(transpose_offset + 1usize, Bias::Right);
13075 if let Some(ch) = display_map
13076 .buffer_snapshot()
13077 .chars_at(transpose_start)
13078 .next()
13079 {
13080 edits.push((transpose_start..transpose_offset, String::new()));
13081 edits.push((transpose_end..transpose_end, ch.to_string()));
13082 }
13083 }
13084 });
13085 edits
13086 });
13087 this.buffer
13088 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13089 let selections = this
13090 .selections
13091 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13092 this.change_selections(Default::default(), window, cx, |s| {
13093 s.select(selections);
13094 });
13095 });
13096 }
13097
13098 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13100 if self.mode.is_single_line() {
13101 cx.propagate();
13102 return;
13103 }
13104
13105 self.rewrap_impl(RewrapOptions::default(), cx)
13106 }
13107
13108 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13109 let buffer = self.buffer.read(cx).snapshot(cx);
13110 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13111
13112 #[derive(Clone, Debug, PartialEq)]
13113 enum CommentFormat {
13114 /// single line comment, with prefix for line
13115 Line(String),
13116 /// single line within a block comment, with prefix for line
13117 BlockLine(String),
13118 /// a single line of a block comment that includes the initial delimiter
13119 BlockCommentWithStart(BlockCommentConfig),
13120 /// a single line of a block comment that includes the ending delimiter
13121 BlockCommentWithEnd(BlockCommentConfig),
13122 }
13123
13124 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13125 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13126 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13127 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13128 .peekable();
13129
13130 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13131 row
13132 } else {
13133 return Vec::new();
13134 };
13135
13136 let language_settings = buffer.language_settings_at(selection.head(), cx);
13137 let language_scope = buffer.language_scope_at(selection.head());
13138
13139 let indent_and_prefix_for_row =
13140 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13141 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13142 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13143 &language_scope
13144 {
13145 let indent_end = Point::new(row, indent.len);
13146 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13147 let line_text_after_indent = buffer
13148 .text_for_range(indent_end..line_end)
13149 .collect::<String>();
13150
13151 let is_within_comment_override = buffer
13152 .language_scope_at(indent_end)
13153 .is_some_and(|scope| scope.override_name() == Some("comment"));
13154 let comment_delimiters = if is_within_comment_override {
13155 // we are within a comment syntax node, but we don't
13156 // yet know what kind of comment: block, doc or line
13157 match (
13158 language_scope.documentation_comment(),
13159 language_scope.block_comment(),
13160 ) {
13161 (Some(config), _) | (_, Some(config))
13162 if buffer.contains_str_at(indent_end, &config.start) =>
13163 {
13164 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13165 }
13166 (Some(config), _) | (_, Some(config))
13167 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13168 {
13169 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13170 }
13171 (Some(config), _) | (_, Some(config))
13172 if buffer.contains_str_at(indent_end, &config.prefix) =>
13173 {
13174 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13175 }
13176 (_, _) => language_scope
13177 .line_comment_prefixes()
13178 .iter()
13179 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13180 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13181 }
13182 } else {
13183 // we not in an overridden comment node, but we may
13184 // be within a non-overridden line comment node
13185 language_scope
13186 .line_comment_prefixes()
13187 .iter()
13188 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13189 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13190 };
13191
13192 let rewrap_prefix = language_scope
13193 .rewrap_prefixes()
13194 .iter()
13195 .find_map(|prefix_regex| {
13196 prefix_regex.find(&line_text_after_indent).map(|mat| {
13197 if mat.start() == 0 {
13198 Some(mat.as_str().to_string())
13199 } else {
13200 None
13201 }
13202 })
13203 })
13204 .flatten();
13205 (comment_delimiters, rewrap_prefix)
13206 } else {
13207 (None, None)
13208 };
13209 (indent, comment_prefix, rewrap_prefix)
13210 };
13211
13212 let mut ranges = Vec::new();
13213 let from_empty_selection = selection.is_empty();
13214
13215 let mut current_range_start = first_row;
13216 let mut prev_row = first_row;
13217 let (
13218 mut current_range_indent,
13219 mut current_range_comment_delimiters,
13220 mut current_range_rewrap_prefix,
13221 ) = indent_and_prefix_for_row(first_row);
13222
13223 for row in non_blank_rows_iter.skip(1) {
13224 let has_paragraph_break = row > prev_row + 1;
13225
13226 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13227 indent_and_prefix_for_row(row);
13228
13229 let has_indent_change = row_indent != current_range_indent;
13230 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13231
13232 let has_boundary_change = has_comment_change
13233 || row_rewrap_prefix.is_some()
13234 || (has_indent_change && current_range_comment_delimiters.is_some());
13235
13236 if has_paragraph_break || has_boundary_change {
13237 ranges.push((
13238 language_settings.clone(),
13239 Point::new(current_range_start, 0)
13240 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13241 current_range_indent,
13242 current_range_comment_delimiters.clone(),
13243 current_range_rewrap_prefix.clone(),
13244 from_empty_selection,
13245 ));
13246 current_range_start = row;
13247 current_range_indent = row_indent;
13248 current_range_comment_delimiters = row_comment_delimiters;
13249 current_range_rewrap_prefix = row_rewrap_prefix;
13250 }
13251 prev_row = row;
13252 }
13253
13254 ranges.push((
13255 language_settings.clone(),
13256 Point::new(current_range_start, 0)
13257 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13258 current_range_indent,
13259 current_range_comment_delimiters,
13260 current_range_rewrap_prefix,
13261 from_empty_selection,
13262 ));
13263
13264 ranges
13265 });
13266
13267 let mut edits = Vec::new();
13268 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13269
13270 for (
13271 language_settings,
13272 wrap_range,
13273 mut indent_size,
13274 comment_prefix,
13275 rewrap_prefix,
13276 from_empty_selection,
13277 ) in wrap_ranges
13278 {
13279 let mut start_row = wrap_range.start.row;
13280 let mut end_row = wrap_range.end.row;
13281
13282 // Skip selections that overlap with a range that has already been rewrapped.
13283 let selection_range = start_row..end_row;
13284 if rewrapped_row_ranges
13285 .iter()
13286 .any(|range| range.overlaps(&selection_range))
13287 {
13288 continue;
13289 }
13290
13291 let tab_size = language_settings.tab_size;
13292
13293 let (line_prefix, inside_comment) = match &comment_prefix {
13294 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13295 (Some(prefix.as_str()), true)
13296 }
13297 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13298 (Some(prefix.as_ref()), true)
13299 }
13300 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13301 start: _,
13302 end: _,
13303 prefix,
13304 tab_size,
13305 })) => {
13306 indent_size.len += tab_size;
13307 (Some(prefix.as_ref()), true)
13308 }
13309 None => (None, false),
13310 };
13311 let indent_prefix = indent_size.chars().collect::<String>();
13312 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13313
13314 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13315 RewrapBehavior::InComments => inside_comment,
13316 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13317 RewrapBehavior::Anywhere => true,
13318 };
13319
13320 let should_rewrap = options.override_language_settings
13321 || allow_rewrap_based_on_language
13322 || self.hard_wrap.is_some();
13323 if !should_rewrap {
13324 continue;
13325 }
13326
13327 if from_empty_selection {
13328 'expand_upwards: while start_row > 0 {
13329 let prev_row = start_row - 1;
13330 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13331 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13332 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13333 {
13334 start_row = prev_row;
13335 } else {
13336 break 'expand_upwards;
13337 }
13338 }
13339
13340 'expand_downwards: while end_row < buffer.max_point().row {
13341 let next_row = end_row + 1;
13342 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13343 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13344 && !buffer.is_line_blank(MultiBufferRow(next_row))
13345 {
13346 end_row = next_row;
13347 } else {
13348 break 'expand_downwards;
13349 }
13350 }
13351 }
13352
13353 let start = Point::new(start_row, 0);
13354 let start_offset = ToOffset::to_offset(&start, &buffer);
13355 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13356 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13357 let mut first_line_delimiter = None;
13358 let mut last_line_delimiter = None;
13359 let Some(lines_without_prefixes) = selection_text
13360 .lines()
13361 .enumerate()
13362 .map(|(ix, line)| {
13363 let line_trimmed = line.trim_start();
13364 if rewrap_prefix.is_some() && ix > 0 {
13365 Ok(line_trimmed)
13366 } else if let Some(
13367 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13368 start,
13369 prefix,
13370 end,
13371 tab_size,
13372 })
13373 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13374 start,
13375 prefix,
13376 end,
13377 tab_size,
13378 }),
13379 ) = &comment_prefix
13380 {
13381 let line_trimmed = line_trimmed
13382 .strip_prefix(start.as_ref())
13383 .map(|s| {
13384 let mut indent_size = indent_size;
13385 indent_size.len -= tab_size;
13386 let indent_prefix: String = indent_size.chars().collect();
13387 first_line_delimiter = Some((indent_prefix, start));
13388 s.trim_start()
13389 })
13390 .unwrap_or(line_trimmed);
13391 let line_trimmed = line_trimmed
13392 .strip_suffix(end.as_ref())
13393 .map(|s| {
13394 last_line_delimiter = Some(end);
13395 s.trim_end()
13396 })
13397 .unwrap_or(line_trimmed);
13398 let line_trimmed = line_trimmed
13399 .strip_prefix(prefix.as_ref())
13400 .unwrap_or(line_trimmed);
13401 Ok(line_trimmed)
13402 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13403 line_trimmed.strip_prefix(prefix).with_context(|| {
13404 format!("line did not start with prefix {prefix:?}: {line:?}")
13405 })
13406 } else {
13407 line_trimmed
13408 .strip_prefix(&line_prefix.trim_start())
13409 .with_context(|| {
13410 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13411 })
13412 }
13413 })
13414 .collect::<Result<Vec<_>, _>>()
13415 .log_err()
13416 else {
13417 continue;
13418 };
13419
13420 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13421 buffer
13422 .language_settings_at(Point::new(start_row, 0), cx)
13423 .preferred_line_length as usize
13424 });
13425
13426 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13427 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13428 } else {
13429 line_prefix.clone()
13430 };
13431
13432 let wrapped_text = {
13433 let mut wrapped_text = wrap_with_prefix(
13434 line_prefix,
13435 subsequent_lines_prefix,
13436 lines_without_prefixes.join("\n"),
13437 wrap_column,
13438 tab_size,
13439 options.preserve_existing_whitespace,
13440 );
13441
13442 if let Some((indent, delimiter)) = first_line_delimiter {
13443 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13444 }
13445 if let Some(last_line) = last_line_delimiter {
13446 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13447 }
13448
13449 wrapped_text
13450 };
13451
13452 // TODO: should always use char-based diff while still supporting cursor behavior that
13453 // matches vim.
13454 let mut diff_options = DiffOptions::default();
13455 if options.override_language_settings {
13456 diff_options.max_word_diff_len = 0;
13457 diff_options.max_word_diff_line_count = 0;
13458 } else {
13459 diff_options.max_word_diff_len = usize::MAX;
13460 diff_options.max_word_diff_line_count = usize::MAX;
13461 }
13462
13463 for (old_range, new_text) in
13464 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13465 {
13466 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13467 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13468 edits.push((edit_start..edit_end, new_text));
13469 }
13470
13471 rewrapped_row_ranges.push(start_row..=end_row);
13472 }
13473
13474 self.buffer
13475 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13476 }
13477
13478 pub fn cut_common(
13479 &mut self,
13480 cut_no_selection_line: bool,
13481 window: &mut Window,
13482 cx: &mut Context<Self>,
13483 ) -> ClipboardItem {
13484 let mut text = String::new();
13485 let buffer = self.buffer.read(cx).snapshot(cx);
13486 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13487 let mut clipboard_selections = Vec::with_capacity(selections.len());
13488 {
13489 let max_point = buffer.max_point();
13490 let mut is_first = true;
13491 let mut prev_selection_was_entire_line = false;
13492 for selection in &mut selections {
13493 let is_entire_line =
13494 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13495 if is_entire_line {
13496 selection.start = Point::new(selection.start.row, 0);
13497 if !selection.is_empty() && selection.end.column == 0 {
13498 selection.end = cmp::min(max_point, selection.end);
13499 } else {
13500 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13501 }
13502 selection.goal = SelectionGoal::None;
13503 }
13504 if is_first {
13505 is_first = false;
13506 } else if !prev_selection_was_entire_line {
13507 text += "\n";
13508 }
13509 prev_selection_was_entire_line = is_entire_line;
13510 let mut len = 0;
13511 for chunk in buffer.text_for_range(selection.start..selection.end) {
13512 text.push_str(chunk);
13513 len += chunk.len();
13514 }
13515
13516 clipboard_selections.push(ClipboardSelection::for_buffer(
13517 len,
13518 is_entire_line,
13519 selection.range(),
13520 &buffer,
13521 self.project.as_ref(),
13522 cx,
13523 ));
13524 }
13525 }
13526
13527 self.transact(window, cx, |this, window, cx| {
13528 this.change_selections(Default::default(), window, cx, |s| {
13529 s.select(selections);
13530 });
13531 this.insert("", window, cx);
13532 });
13533 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13534 }
13535
13536 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13538 let item = self.cut_common(true, window, cx);
13539 cx.write_to_clipboard(item);
13540 }
13541
13542 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13544 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13545 s.move_with(&mut |snapshot, sel| {
13546 if sel.is_empty() {
13547 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13548 }
13549 if sel.is_empty() {
13550 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13551 }
13552 });
13553 });
13554 let item = self.cut_common(false, window, cx);
13555 cx.set_global(KillRing(item))
13556 }
13557
13558 pub fn kill_ring_yank(
13559 &mut self,
13560 _: &KillRingYank,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13565 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13566 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13567 (kill_ring.text().to_string(), kill_ring.metadata_json())
13568 } else {
13569 return;
13570 }
13571 } else {
13572 return;
13573 };
13574 self.do_paste(&text, metadata, false, window, cx);
13575 }
13576
13577 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13578 self.do_copy(true, cx);
13579 }
13580
13581 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13582 self.do_copy(false, cx);
13583 }
13584
13585 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13586 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13587 let buffer = self.buffer.read(cx).read(cx);
13588 let mut text = String::new();
13589 let mut clipboard_selections = Vec::with_capacity(selections.len());
13590
13591 let max_point = buffer.max_point();
13592 let mut is_first = true;
13593 for selection in &selections {
13594 let mut start = selection.start;
13595 let mut end = selection.end;
13596 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13597 let mut add_trailing_newline = false;
13598 if is_entire_line {
13599 start = Point::new(start.row, 0);
13600 let next_line_start = Point::new(end.row + 1, 0);
13601 if next_line_start <= max_point {
13602 end = next_line_start;
13603 } else {
13604 // We're on the last line without a trailing newline.
13605 // Copy to the end of the line and add a newline afterwards.
13606 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13607 add_trailing_newline = true;
13608 }
13609 }
13610
13611 let mut trimmed_selections = Vec::new();
13612 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13613 let row = MultiBufferRow(start.row);
13614 let first_indent = buffer.indent_size_for_line(row);
13615 if first_indent.len == 0 || start.column > first_indent.len {
13616 trimmed_selections.push(start..end);
13617 } else {
13618 trimmed_selections.push(
13619 Point::new(row.0, first_indent.len)
13620 ..Point::new(row.0, buffer.line_len(row)),
13621 );
13622 for row in start.row + 1..=end.row {
13623 let mut line_len = buffer.line_len(MultiBufferRow(row));
13624 if row == end.row {
13625 line_len = end.column;
13626 }
13627 if line_len == 0 {
13628 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13629 continue;
13630 }
13631 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13632 if row_indent_size.len >= first_indent.len {
13633 trimmed_selections
13634 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13635 } else {
13636 trimmed_selections.clear();
13637 trimmed_selections.push(start..end);
13638 break;
13639 }
13640 }
13641 }
13642 } else {
13643 trimmed_selections.push(start..end);
13644 }
13645
13646 let is_multiline_trim = trimmed_selections.len() > 1;
13647 let mut selection_len: usize = 0;
13648 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13649
13650 for trimmed_range in trimmed_selections {
13651 if is_first {
13652 is_first = false;
13653 } else if is_multiline_trim || !prev_selection_was_entire_line {
13654 text.push('\n');
13655 if is_multiline_trim {
13656 selection_len += 1;
13657 }
13658 }
13659 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13660 text.push_str(chunk);
13661 selection_len += chunk.len();
13662 }
13663 if add_trailing_newline {
13664 text.push('\n');
13665 selection_len += 1;
13666 }
13667 }
13668
13669 clipboard_selections.push(ClipboardSelection::for_buffer(
13670 selection_len,
13671 is_entire_line,
13672 start..end,
13673 &buffer,
13674 self.project.as_ref(),
13675 cx,
13676 ));
13677 }
13678
13679 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13680 text,
13681 clipboard_selections,
13682 ));
13683 }
13684
13685 pub fn do_paste(
13686 &mut self,
13687 text: &String,
13688 clipboard_selections: Option<Vec<ClipboardSelection>>,
13689 handle_entire_lines: bool,
13690 window: &mut Window,
13691 cx: &mut Context<Self>,
13692 ) {
13693 if self.read_only(cx) {
13694 return;
13695 }
13696
13697 let clipboard_text = Cow::Borrowed(text.as_str());
13698
13699 self.transact(window, cx, |this, window, cx| {
13700 let had_active_edit_prediction = this.has_active_edit_prediction();
13701 let display_map = this.display_snapshot(cx);
13702 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13703 let cursor_offset = this
13704 .selections
13705 .last::<MultiBufferOffset>(&display_map)
13706 .head();
13707
13708 if let Some(mut clipboard_selections) = clipboard_selections {
13709 let all_selections_were_entire_line =
13710 clipboard_selections.iter().all(|s| s.is_entire_line);
13711 let first_selection_indent_column =
13712 clipboard_selections.first().map(|s| s.first_line_indent);
13713 if clipboard_selections.len() != old_selections.len() {
13714 clipboard_selections.drain(..);
13715 }
13716 let mut auto_indent_on_paste = true;
13717
13718 this.buffer.update(cx, |buffer, cx| {
13719 let snapshot = buffer.read(cx);
13720 auto_indent_on_paste = snapshot
13721 .language_settings_at(cursor_offset, cx)
13722 .auto_indent_on_paste;
13723
13724 let mut start_offset = 0;
13725 let mut edits = Vec::new();
13726 let mut original_indent_columns = Vec::new();
13727 for (ix, selection) in old_selections.iter().enumerate() {
13728 let to_insert;
13729 let entire_line;
13730 let original_indent_column;
13731 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13732 let end_offset = start_offset + clipboard_selection.len;
13733 to_insert = &clipboard_text[start_offset..end_offset];
13734 entire_line = clipboard_selection.is_entire_line;
13735 start_offset = if entire_line {
13736 end_offset
13737 } else {
13738 end_offset + 1
13739 };
13740 original_indent_column = Some(clipboard_selection.first_line_indent);
13741 } else {
13742 to_insert = &*clipboard_text;
13743 entire_line = all_selections_were_entire_line;
13744 original_indent_column = first_selection_indent_column
13745 }
13746
13747 let (range, to_insert) =
13748 if selection.is_empty() && handle_entire_lines && entire_line {
13749 // If the corresponding selection was empty when this slice of the
13750 // clipboard text was written, then the entire line containing the
13751 // selection was copied. If this selection is also currently empty,
13752 // then paste the line before the current line of the buffer.
13753 let column = selection.start.to_point(&snapshot).column as usize;
13754 let line_start = selection.start - column;
13755 (line_start..line_start, Cow::Borrowed(to_insert))
13756 } else {
13757 let language = snapshot.language_at(selection.head());
13758 let range = selection.range();
13759 if let Some(language) = language
13760 && language.name() == "Markdown"
13761 {
13762 edit_for_markdown_paste(
13763 &snapshot,
13764 range,
13765 to_insert,
13766 url::Url::parse(to_insert).ok(),
13767 )
13768 } else {
13769 (range, Cow::Borrowed(to_insert))
13770 }
13771 };
13772
13773 edits.push((range, to_insert));
13774 original_indent_columns.push(original_indent_column);
13775 }
13776 drop(snapshot);
13777
13778 buffer.edit(
13779 edits,
13780 if auto_indent_on_paste {
13781 Some(AutoindentMode::Block {
13782 original_indent_columns,
13783 })
13784 } else {
13785 None
13786 },
13787 cx,
13788 );
13789 });
13790
13791 let selections = this
13792 .selections
13793 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13794 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13795 } else {
13796 let url = url::Url::parse(&clipboard_text).ok();
13797
13798 let auto_indent_mode = if !clipboard_text.is_empty() {
13799 Some(AutoindentMode::Block {
13800 original_indent_columns: Vec::new(),
13801 })
13802 } else {
13803 None
13804 };
13805
13806 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13807 let snapshot = buffer.snapshot(cx);
13808
13809 let anchors = old_selections
13810 .iter()
13811 .map(|s| {
13812 let anchor = snapshot.anchor_after(s.head());
13813 s.map(|_| anchor)
13814 })
13815 .collect::<Vec<_>>();
13816
13817 let mut edits = Vec::new();
13818
13819 // When pasting text without metadata (e.g. copied from an
13820 // external editor using multiple cursors) and the number of
13821 // lines matches the number of selections, distribute one
13822 // line per cursor instead of pasting the whole text at each.
13823 let lines: Vec<&str> = clipboard_text.split('\n').collect();
13824 let distribute_lines =
13825 old_selections.len() > 1 && lines.len() == old_selections.len();
13826
13827 for (ix, selection) in old_selections.iter().enumerate() {
13828 let language = snapshot.language_at(selection.head());
13829 let range = selection.range();
13830
13831 let text_for_cursor: &str = if distribute_lines {
13832 lines[ix]
13833 } else {
13834 &clipboard_text
13835 };
13836
13837 let (edit_range, edit_text) = if let Some(language) = language
13838 && language.name() == "Markdown"
13839 {
13840 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
13841 } else {
13842 (range, Cow::Borrowed(text_for_cursor))
13843 };
13844
13845 edits.push((edit_range, edit_text));
13846 }
13847
13848 drop(snapshot);
13849 buffer.edit(edits, auto_indent_mode, cx);
13850
13851 anchors
13852 });
13853
13854 this.change_selections(Default::default(), window, cx, |s| {
13855 s.select_anchors(selection_anchors);
13856 });
13857 }
13858
13859 // 🤔 | .. | show_in_menu |
13860 // | .. | true true
13861 // | had_edit_prediction | false true
13862
13863 let trigger_in_words =
13864 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13865
13866 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13867 });
13868 }
13869
13870 pub fn diff_clipboard_with_selection(
13871 &mut self,
13872 _: &DiffClipboardWithSelection,
13873 window: &mut Window,
13874 cx: &mut Context<Self>,
13875 ) {
13876 let selections = self
13877 .selections
13878 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13879
13880 if selections.is_empty() {
13881 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13882 return;
13883 };
13884
13885 let clipboard_text = match cx.read_from_clipboard() {
13886 Some(item) => match item.entries().first() {
13887 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13888 _ => None,
13889 },
13890 None => None,
13891 };
13892
13893 let Some(clipboard_text) = clipboard_text else {
13894 log::warn!("Clipboard doesn't contain text.");
13895 return;
13896 };
13897
13898 window.dispatch_action(
13899 Box::new(DiffClipboardWithSelectionData {
13900 clipboard_text,
13901 editor: cx.entity(),
13902 }),
13903 cx,
13904 );
13905 }
13906
13907 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13909 if let Some(item) = cx.read_from_clipboard() {
13910 let entries = item.entries();
13911
13912 match entries.first() {
13913 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13914 // of all the pasted entries.
13915 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13916 .do_paste(
13917 clipboard_string.text(),
13918 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13919 true,
13920 window,
13921 cx,
13922 ),
13923 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13924 }
13925 }
13926 }
13927
13928 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13929 if self.read_only(cx) {
13930 return;
13931 }
13932
13933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13934
13935 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13936 if let Some((selections, _)) =
13937 self.selection_history.transaction(transaction_id).cloned()
13938 {
13939 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13940 s.select_anchors(selections.to_vec());
13941 });
13942 } else {
13943 log::error!(
13944 "No entry in selection_history found for undo. \
13945 This may correspond to a bug where undo does not update the selection. \
13946 If this is occurring, please add details to \
13947 https://github.com/zed-industries/zed/issues/22692"
13948 );
13949 }
13950 self.request_autoscroll(Autoscroll::fit(), cx);
13951 self.unmark_text(window, cx);
13952 self.refresh_edit_prediction(true, false, window, cx);
13953 cx.emit(EditorEvent::Edited { transaction_id });
13954 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13955 }
13956 }
13957
13958 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13959 if self.read_only(cx) {
13960 return;
13961 }
13962
13963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13964
13965 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13966 if let Some((_, Some(selections))) =
13967 self.selection_history.transaction(transaction_id).cloned()
13968 {
13969 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13970 s.select_anchors(selections.to_vec());
13971 });
13972 } else {
13973 log::error!(
13974 "No entry in selection_history found for redo. \
13975 This may correspond to a bug where undo does not update the selection. \
13976 If this is occurring, please add details to \
13977 https://github.com/zed-industries/zed/issues/22692"
13978 );
13979 }
13980 self.request_autoscroll(Autoscroll::fit(), cx);
13981 self.unmark_text(window, cx);
13982 self.refresh_edit_prediction(true, false, window, cx);
13983 cx.emit(EditorEvent::Edited { transaction_id });
13984 }
13985 }
13986
13987 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13988 self.buffer
13989 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13990 }
13991
13992 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13993 self.buffer
13994 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13995 }
13996
13997 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13998 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13999 self.change_selections(Default::default(), window, cx, |s| {
14000 s.move_with(&mut |map, selection| {
14001 let cursor = if selection.is_empty() {
14002 movement::left(map, selection.start)
14003 } else {
14004 selection.start
14005 };
14006 selection.collapse_to(cursor, SelectionGoal::None);
14007 });
14008 })
14009 }
14010
14011 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14013 self.change_selections(Default::default(), window, cx, |s| {
14014 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14015 })
14016 }
14017
14018 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14020 self.change_selections(Default::default(), window, cx, |s| {
14021 s.move_with(&mut |map, selection| {
14022 let cursor = if selection.is_empty() {
14023 movement::right(map, selection.end)
14024 } else {
14025 selection.end
14026 };
14027 selection.collapse_to(cursor, SelectionGoal::None)
14028 });
14029 })
14030 }
14031
14032 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14033 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14034 self.change_selections(Default::default(), window, cx, |s| {
14035 s.move_heads_with(&mut |map, head, _| {
14036 (movement::right(map, head), SelectionGoal::None)
14037 });
14038 });
14039 }
14040
14041 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14042 if self.take_rename(true, window, cx).is_some() {
14043 return;
14044 }
14045
14046 if self.mode.is_single_line() {
14047 cx.propagate();
14048 return;
14049 }
14050
14051 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14052
14053 let text_layout_details = &self.text_layout_details(window, cx);
14054 let selection_count = self.selections.count();
14055 let first_selection = self.selections.first_anchor();
14056
14057 self.change_selections(Default::default(), window, cx, |s| {
14058 s.move_with(&mut |map, selection| {
14059 if !selection.is_empty() {
14060 selection.goal = SelectionGoal::None;
14061 }
14062 let (cursor, goal) = movement::up(
14063 map,
14064 selection.start,
14065 selection.goal,
14066 false,
14067 text_layout_details,
14068 );
14069 selection.collapse_to(cursor, goal);
14070 });
14071 });
14072
14073 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14074 {
14075 cx.propagate();
14076 }
14077 }
14078
14079 pub fn move_up_by_lines(
14080 &mut self,
14081 action: &MoveUpByLines,
14082 window: &mut Window,
14083 cx: &mut Context<Self>,
14084 ) {
14085 if self.take_rename(true, window, cx).is_some() {
14086 return;
14087 }
14088
14089 if self.mode.is_single_line() {
14090 cx.propagate();
14091 return;
14092 }
14093
14094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14095
14096 let text_layout_details = &self.text_layout_details(window, cx);
14097
14098 self.change_selections(Default::default(), window, cx, |s| {
14099 s.move_with(&mut |map, selection| {
14100 if !selection.is_empty() {
14101 selection.goal = SelectionGoal::None;
14102 }
14103 let (cursor, goal) = movement::up_by_rows(
14104 map,
14105 selection.start,
14106 action.lines,
14107 selection.goal,
14108 false,
14109 text_layout_details,
14110 );
14111 selection.collapse_to(cursor, goal);
14112 });
14113 })
14114 }
14115
14116 pub fn move_down_by_lines(
14117 &mut self,
14118 action: &MoveDownByLines,
14119 window: &mut Window,
14120 cx: &mut Context<Self>,
14121 ) {
14122 if self.take_rename(true, window, cx).is_some() {
14123 return;
14124 }
14125
14126 if self.mode.is_single_line() {
14127 cx.propagate();
14128 return;
14129 }
14130
14131 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14132
14133 let text_layout_details = &self.text_layout_details(window, cx);
14134
14135 self.change_selections(Default::default(), window, cx, |s| {
14136 s.move_with(&mut |map, selection| {
14137 if !selection.is_empty() {
14138 selection.goal = SelectionGoal::None;
14139 }
14140 let (cursor, goal) = movement::down_by_rows(
14141 map,
14142 selection.start,
14143 action.lines,
14144 selection.goal,
14145 false,
14146 text_layout_details,
14147 );
14148 selection.collapse_to(cursor, goal);
14149 });
14150 })
14151 }
14152
14153 pub fn select_down_by_lines(
14154 &mut self,
14155 action: &SelectDownByLines,
14156 window: &mut Window,
14157 cx: &mut Context<Self>,
14158 ) {
14159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14160 let text_layout_details = &self.text_layout_details(window, cx);
14161 self.change_selections(Default::default(), window, cx, |s| {
14162 s.move_heads_with(&mut |map, head, goal| {
14163 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14164 })
14165 })
14166 }
14167
14168 pub fn select_up_by_lines(
14169 &mut self,
14170 action: &SelectUpByLines,
14171 window: &mut Window,
14172 cx: &mut Context<Self>,
14173 ) {
14174 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14175 let text_layout_details = &self.text_layout_details(window, cx);
14176 self.change_selections(Default::default(), window, cx, |s| {
14177 s.move_heads_with(&mut |map, head, goal| {
14178 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14179 })
14180 })
14181 }
14182
14183 pub fn select_page_up(
14184 &mut self,
14185 _: &SelectPageUp,
14186 window: &mut Window,
14187 cx: &mut Context<Self>,
14188 ) {
14189 let Some(row_count) = self.visible_row_count() else {
14190 return;
14191 };
14192
14193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14194
14195 let text_layout_details = &self.text_layout_details(window, cx);
14196
14197 self.change_selections(Default::default(), window, cx, |s| {
14198 s.move_heads_with(&mut |map, head, goal| {
14199 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14200 })
14201 })
14202 }
14203
14204 pub fn move_page_up(
14205 &mut self,
14206 action: &MovePageUp,
14207 window: &mut Window,
14208 cx: &mut Context<Self>,
14209 ) {
14210 if self.take_rename(true, window, cx).is_some() {
14211 return;
14212 }
14213
14214 if self
14215 .context_menu
14216 .borrow_mut()
14217 .as_mut()
14218 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14219 .unwrap_or(false)
14220 {
14221 return;
14222 }
14223
14224 if matches!(self.mode, EditorMode::SingleLine) {
14225 cx.propagate();
14226 return;
14227 }
14228
14229 let Some(row_count) = self.visible_row_count() else {
14230 return;
14231 };
14232
14233 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14234
14235 let effects = if action.center_cursor {
14236 SelectionEffects::scroll(Autoscroll::center())
14237 } else {
14238 SelectionEffects::default()
14239 };
14240
14241 let text_layout_details = &self.text_layout_details(window, cx);
14242
14243 self.change_selections(effects, window, cx, |s| {
14244 s.move_with(&mut |map, selection| {
14245 if !selection.is_empty() {
14246 selection.goal = SelectionGoal::None;
14247 }
14248 let (cursor, goal) = movement::up_by_rows(
14249 map,
14250 selection.end,
14251 row_count,
14252 selection.goal,
14253 false,
14254 text_layout_details,
14255 );
14256 selection.collapse_to(cursor, goal);
14257 });
14258 });
14259 }
14260
14261 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14262 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14263 let text_layout_details = &self.text_layout_details(window, cx);
14264 self.change_selections(Default::default(), window, cx, |s| {
14265 s.move_heads_with(&mut |map, head, goal| {
14266 movement::up(map, head, goal, false, text_layout_details)
14267 })
14268 })
14269 }
14270
14271 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14272 self.take_rename(true, window, cx);
14273
14274 if self.mode.is_single_line() {
14275 cx.propagate();
14276 return;
14277 }
14278
14279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14280
14281 let text_layout_details = &self.text_layout_details(window, cx);
14282 let selection_count = self.selections.count();
14283 let first_selection = self.selections.first_anchor();
14284
14285 self.change_selections(Default::default(), window, cx, |s| {
14286 s.move_with(&mut |map, selection| {
14287 if !selection.is_empty() {
14288 selection.goal = SelectionGoal::None;
14289 }
14290 let (cursor, goal) = movement::down(
14291 map,
14292 selection.end,
14293 selection.goal,
14294 false,
14295 text_layout_details,
14296 );
14297 selection.collapse_to(cursor, goal);
14298 });
14299 });
14300
14301 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14302 {
14303 cx.propagate();
14304 }
14305 }
14306
14307 pub fn select_page_down(
14308 &mut self,
14309 _: &SelectPageDown,
14310 window: &mut Window,
14311 cx: &mut Context<Self>,
14312 ) {
14313 let Some(row_count) = self.visible_row_count() else {
14314 return;
14315 };
14316
14317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14318
14319 let text_layout_details = &self.text_layout_details(window, cx);
14320
14321 self.change_selections(Default::default(), window, cx, |s| {
14322 s.move_heads_with(&mut |map, head, goal| {
14323 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14324 })
14325 })
14326 }
14327
14328 pub fn move_page_down(
14329 &mut self,
14330 action: &MovePageDown,
14331 window: &mut Window,
14332 cx: &mut Context<Self>,
14333 ) {
14334 if self.take_rename(true, window, cx).is_some() {
14335 return;
14336 }
14337
14338 if self
14339 .context_menu
14340 .borrow_mut()
14341 .as_mut()
14342 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14343 .unwrap_or(false)
14344 {
14345 return;
14346 }
14347
14348 if matches!(self.mode, EditorMode::SingleLine) {
14349 cx.propagate();
14350 return;
14351 }
14352
14353 let Some(row_count) = self.visible_row_count() else {
14354 return;
14355 };
14356
14357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14358
14359 let effects = if action.center_cursor {
14360 SelectionEffects::scroll(Autoscroll::center())
14361 } else {
14362 SelectionEffects::default()
14363 };
14364
14365 let text_layout_details = &self.text_layout_details(window, cx);
14366 self.change_selections(effects, window, cx, |s| {
14367 s.move_with(&mut |map, selection| {
14368 if !selection.is_empty() {
14369 selection.goal = SelectionGoal::None;
14370 }
14371 let (cursor, goal) = movement::down_by_rows(
14372 map,
14373 selection.end,
14374 row_count,
14375 selection.goal,
14376 false,
14377 text_layout_details,
14378 );
14379 selection.collapse_to(cursor, goal);
14380 });
14381 });
14382 }
14383
14384 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14385 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14386 let text_layout_details = &self.text_layout_details(window, cx);
14387 self.change_selections(Default::default(), window, cx, |s| {
14388 s.move_heads_with(&mut |map, head, goal| {
14389 movement::down(map, head, goal, false, text_layout_details)
14390 })
14391 });
14392 }
14393
14394 pub fn context_menu_first(
14395 &mut self,
14396 _: &ContextMenuFirst,
14397 window: &mut Window,
14398 cx: &mut Context<Self>,
14399 ) {
14400 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14401 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14402 }
14403 }
14404
14405 pub fn context_menu_prev(
14406 &mut self,
14407 _: &ContextMenuPrevious,
14408 window: &mut Window,
14409 cx: &mut Context<Self>,
14410 ) {
14411 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14412 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14413 }
14414 }
14415
14416 pub fn context_menu_next(
14417 &mut self,
14418 _: &ContextMenuNext,
14419 window: &mut Window,
14420 cx: &mut Context<Self>,
14421 ) {
14422 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14423 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14424 }
14425 }
14426
14427 pub fn context_menu_last(
14428 &mut self,
14429 _: &ContextMenuLast,
14430 window: &mut Window,
14431 cx: &mut Context<Self>,
14432 ) {
14433 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14434 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14435 }
14436 }
14437
14438 pub fn signature_help_prev(
14439 &mut self,
14440 _: &SignatureHelpPrevious,
14441 _: &mut Window,
14442 cx: &mut Context<Self>,
14443 ) {
14444 if let Some(popover) = self.signature_help_state.popover_mut() {
14445 if popover.current_signature == 0 {
14446 popover.current_signature = popover.signatures.len() - 1;
14447 } else {
14448 popover.current_signature -= 1;
14449 }
14450 cx.notify();
14451 }
14452 }
14453
14454 pub fn signature_help_next(
14455 &mut self,
14456 _: &SignatureHelpNext,
14457 _: &mut Window,
14458 cx: &mut Context<Self>,
14459 ) {
14460 if let Some(popover) = self.signature_help_state.popover_mut() {
14461 if popover.current_signature + 1 == popover.signatures.len() {
14462 popover.current_signature = 0;
14463 } else {
14464 popover.current_signature += 1;
14465 }
14466 cx.notify();
14467 }
14468 }
14469
14470 pub fn move_to_previous_word_start(
14471 &mut self,
14472 _: &MoveToPreviousWordStart,
14473 window: &mut Window,
14474 cx: &mut Context<Self>,
14475 ) {
14476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14477 self.change_selections(Default::default(), window, cx, |s| {
14478 s.move_cursors_with(&mut |map, head, _| {
14479 (
14480 movement::previous_word_start(map, head),
14481 SelectionGoal::None,
14482 )
14483 });
14484 })
14485 }
14486
14487 pub fn move_to_previous_subword_start(
14488 &mut self,
14489 _: &MoveToPreviousSubwordStart,
14490 window: &mut Window,
14491 cx: &mut Context<Self>,
14492 ) {
14493 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14494 self.change_selections(Default::default(), window, cx, |s| {
14495 s.move_cursors_with(&mut |map, head, _| {
14496 (
14497 movement::previous_subword_start(map, head),
14498 SelectionGoal::None,
14499 )
14500 });
14501 })
14502 }
14503
14504 pub fn select_to_previous_word_start(
14505 &mut self,
14506 _: &SelectToPreviousWordStart,
14507 window: &mut Window,
14508 cx: &mut Context<Self>,
14509 ) {
14510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14511 self.change_selections(Default::default(), window, cx, |s| {
14512 s.move_heads_with(&mut |map, head, _| {
14513 (
14514 movement::previous_word_start(map, head),
14515 SelectionGoal::None,
14516 )
14517 });
14518 })
14519 }
14520
14521 pub fn select_to_previous_subword_start(
14522 &mut self,
14523 _: &SelectToPreviousSubwordStart,
14524 window: &mut Window,
14525 cx: &mut Context<Self>,
14526 ) {
14527 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14528 self.change_selections(Default::default(), window, cx, |s| {
14529 s.move_heads_with(&mut |map, head, _| {
14530 (
14531 movement::previous_subword_start(map, head),
14532 SelectionGoal::None,
14533 )
14534 });
14535 })
14536 }
14537
14538 pub fn delete_to_previous_word_start(
14539 &mut self,
14540 action: &DeleteToPreviousWordStart,
14541 window: &mut Window,
14542 cx: &mut Context<Self>,
14543 ) {
14544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14545 self.transact(window, cx, |this, window, cx| {
14546 this.select_autoclose_pair(window, cx);
14547 this.change_selections(Default::default(), window, cx, |s| {
14548 s.move_with(&mut |map, selection| {
14549 if selection.is_empty() {
14550 let mut cursor = if action.ignore_newlines {
14551 movement::previous_word_start(map, selection.head())
14552 } else {
14553 movement::previous_word_start_or_newline(map, selection.head())
14554 };
14555 cursor = movement::adjust_greedy_deletion(
14556 map,
14557 selection.head(),
14558 cursor,
14559 action.ignore_brackets,
14560 );
14561 selection.set_head(cursor, SelectionGoal::None);
14562 }
14563 });
14564 });
14565 this.insert("", window, cx);
14566 });
14567 }
14568
14569 pub fn delete_to_previous_subword_start(
14570 &mut self,
14571 action: &DeleteToPreviousSubwordStart,
14572 window: &mut Window,
14573 cx: &mut Context<Self>,
14574 ) {
14575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14576 self.transact(window, cx, |this, window, cx| {
14577 this.select_autoclose_pair(window, cx);
14578 this.change_selections(Default::default(), window, cx, |s| {
14579 s.move_with(&mut |map, selection| {
14580 if selection.is_empty() {
14581 let mut cursor = if action.ignore_newlines {
14582 movement::previous_subword_start(map, selection.head())
14583 } else {
14584 movement::previous_subword_start_or_newline(map, selection.head())
14585 };
14586 cursor = movement::adjust_greedy_deletion(
14587 map,
14588 selection.head(),
14589 cursor,
14590 action.ignore_brackets,
14591 );
14592 selection.set_head(cursor, SelectionGoal::None);
14593 }
14594 });
14595 });
14596 this.insert("", window, cx);
14597 });
14598 }
14599
14600 pub fn move_to_next_word_end(
14601 &mut self,
14602 _: &MoveToNextWordEnd,
14603 window: &mut Window,
14604 cx: &mut Context<Self>,
14605 ) {
14606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14607 self.change_selections(Default::default(), window, cx, |s| {
14608 s.move_cursors_with(&mut |map, head, _| {
14609 (movement::next_word_end(map, head), SelectionGoal::None)
14610 });
14611 })
14612 }
14613
14614 pub fn move_to_next_subword_end(
14615 &mut self,
14616 _: &MoveToNextSubwordEnd,
14617 window: &mut Window,
14618 cx: &mut Context<Self>,
14619 ) {
14620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14621 self.change_selections(Default::default(), window, cx, |s| {
14622 s.move_cursors_with(&mut |map, head, _| {
14623 (movement::next_subword_end(map, head), SelectionGoal::None)
14624 });
14625 })
14626 }
14627
14628 pub fn select_to_next_word_end(
14629 &mut self,
14630 _: &SelectToNextWordEnd,
14631 window: &mut Window,
14632 cx: &mut Context<Self>,
14633 ) {
14634 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14635 self.change_selections(Default::default(), window, cx, |s| {
14636 s.move_heads_with(&mut |map, head, _| {
14637 (movement::next_word_end(map, head), SelectionGoal::None)
14638 });
14639 })
14640 }
14641
14642 pub fn select_to_next_subword_end(
14643 &mut self,
14644 _: &SelectToNextSubwordEnd,
14645 window: &mut Window,
14646 cx: &mut Context<Self>,
14647 ) {
14648 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14649 self.change_selections(Default::default(), window, cx, |s| {
14650 s.move_heads_with(&mut |map, head, _| {
14651 (movement::next_subword_end(map, head), SelectionGoal::None)
14652 });
14653 })
14654 }
14655
14656 pub fn delete_to_next_word_end(
14657 &mut self,
14658 action: &DeleteToNextWordEnd,
14659 window: &mut Window,
14660 cx: &mut Context<Self>,
14661 ) {
14662 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14663 self.transact(window, cx, |this, window, cx| {
14664 this.change_selections(Default::default(), window, cx, |s| {
14665 s.move_with(&mut |map, selection| {
14666 if selection.is_empty() {
14667 let mut cursor = if action.ignore_newlines {
14668 movement::next_word_end(map, selection.head())
14669 } else {
14670 movement::next_word_end_or_newline(map, selection.head())
14671 };
14672 cursor = movement::adjust_greedy_deletion(
14673 map,
14674 selection.head(),
14675 cursor,
14676 action.ignore_brackets,
14677 );
14678 selection.set_head(cursor, SelectionGoal::None);
14679 }
14680 });
14681 });
14682 this.insert("", window, cx);
14683 });
14684 }
14685
14686 pub fn delete_to_next_subword_end(
14687 &mut self,
14688 action: &DeleteToNextSubwordEnd,
14689 window: &mut Window,
14690 cx: &mut Context<Self>,
14691 ) {
14692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14693 self.transact(window, cx, |this, window, cx| {
14694 this.change_selections(Default::default(), window, cx, |s| {
14695 s.move_with(&mut |map, selection| {
14696 if selection.is_empty() {
14697 let mut cursor = if action.ignore_newlines {
14698 movement::next_subword_end(map, selection.head())
14699 } else {
14700 movement::next_subword_end_or_newline(map, selection.head())
14701 };
14702 cursor = movement::adjust_greedy_deletion(
14703 map,
14704 selection.head(),
14705 cursor,
14706 action.ignore_brackets,
14707 );
14708 selection.set_head(cursor, SelectionGoal::None);
14709 }
14710 });
14711 });
14712 this.insert("", window, cx);
14713 });
14714 }
14715
14716 pub fn move_to_beginning_of_line(
14717 &mut self,
14718 action: &MoveToBeginningOfLine,
14719 window: &mut Window,
14720 cx: &mut Context<Self>,
14721 ) {
14722 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
14723 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14724 self.change_selections(Default::default(), window, cx, |s| {
14725 s.move_cursors_with(&mut |map, head, _| {
14726 (
14727 movement::indented_line_beginning(
14728 map,
14729 head,
14730 action.stop_at_soft_wraps,
14731 stop_at_indent,
14732 ),
14733 SelectionGoal::None,
14734 )
14735 });
14736 })
14737 }
14738
14739 pub fn select_to_beginning_of_line(
14740 &mut self,
14741 action: &SelectToBeginningOfLine,
14742 window: &mut Window,
14743 cx: &mut Context<Self>,
14744 ) {
14745 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
14746 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14747 self.change_selections(Default::default(), window, cx, |s| {
14748 s.move_heads_with(&mut |map, head, _| {
14749 (
14750 movement::indented_line_beginning(
14751 map,
14752 head,
14753 action.stop_at_soft_wraps,
14754 stop_at_indent,
14755 ),
14756 SelectionGoal::None,
14757 )
14758 });
14759 });
14760 }
14761
14762 pub fn delete_to_beginning_of_line(
14763 &mut self,
14764 action: &DeleteToBeginningOfLine,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) {
14768 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14769 self.transact(window, cx, |this, window, cx| {
14770 this.change_selections(Default::default(), window, cx, |s| {
14771 s.move_with(&mut |_, selection| {
14772 selection.reversed = true;
14773 });
14774 });
14775
14776 this.select_to_beginning_of_line(
14777 &SelectToBeginningOfLine {
14778 stop_at_soft_wraps: false,
14779 stop_at_indent: action.stop_at_indent,
14780 },
14781 window,
14782 cx,
14783 );
14784 this.backspace(&Backspace, window, cx);
14785 });
14786 }
14787
14788 pub fn move_to_end_of_line(
14789 &mut self,
14790 action: &MoveToEndOfLine,
14791 window: &mut Window,
14792 cx: &mut Context<Self>,
14793 ) {
14794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14795 self.change_selections(Default::default(), window, cx, |s| {
14796 s.move_cursors_with(&mut |map, head, _| {
14797 (
14798 movement::line_end(map, head, action.stop_at_soft_wraps),
14799 SelectionGoal::None,
14800 )
14801 });
14802 })
14803 }
14804
14805 pub fn select_to_end_of_line(
14806 &mut self,
14807 action: &SelectToEndOfLine,
14808 window: &mut Window,
14809 cx: &mut Context<Self>,
14810 ) {
14811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14812 self.change_selections(Default::default(), window, cx, |s| {
14813 s.move_heads_with(&mut |map, head, _| {
14814 (
14815 movement::line_end(map, head, action.stop_at_soft_wraps),
14816 SelectionGoal::None,
14817 )
14818 });
14819 })
14820 }
14821
14822 pub fn delete_to_end_of_line(
14823 &mut self,
14824 _: &DeleteToEndOfLine,
14825 window: &mut Window,
14826 cx: &mut Context<Self>,
14827 ) {
14828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14829 self.transact(window, cx, |this, window, cx| {
14830 this.select_to_end_of_line(
14831 &SelectToEndOfLine {
14832 stop_at_soft_wraps: false,
14833 },
14834 window,
14835 cx,
14836 );
14837 this.delete(&Delete, window, cx);
14838 });
14839 }
14840
14841 pub fn cut_to_end_of_line(
14842 &mut self,
14843 action: &CutToEndOfLine,
14844 window: &mut Window,
14845 cx: &mut Context<Self>,
14846 ) {
14847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14848 self.transact(window, cx, |this, window, cx| {
14849 this.select_to_end_of_line(
14850 &SelectToEndOfLine {
14851 stop_at_soft_wraps: false,
14852 },
14853 window,
14854 cx,
14855 );
14856 if !action.stop_at_newlines {
14857 this.change_selections(Default::default(), window, cx, |s| {
14858 s.move_with(&mut |_, sel| {
14859 if sel.is_empty() {
14860 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14861 }
14862 });
14863 });
14864 }
14865 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14866 let item = this.cut_common(false, window, cx);
14867 cx.write_to_clipboard(item);
14868 });
14869 }
14870
14871 pub fn move_to_start_of_paragraph(
14872 &mut self,
14873 _: &MoveToStartOfParagraph,
14874 window: &mut Window,
14875 cx: &mut Context<Self>,
14876 ) {
14877 if matches!(self.mode, EditorMode::SingleLine) {
14878 cx.propagate();
14879 return;
14880 }
14881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14882 self.change_selections(Default::default(), window, cx, |s| {
14883 s.move_with(&mut |map, selection| {
14884 selection.collapse_to(
14885 movement::start_of_paragraph(map, selection.head(), 1),
14886 SelectionGoal::None,
14887 )
14888 });
14889 })
14890 }
14891
14892 pub fn move_to_end_of_paragraph(
14893 &mut self,
14894 _: &MoveToEndOfParagraph,
14895 window: &mut Window,
14896 cx: &mut Context<Self>,
14897 ) {
14898 if matches!(self.mode, EditorMode::SingleLine) {
14899 cx.propagate();
14900 return;
14901 }
14902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14903 self.change_selections(Default::default(), window, cx, |s| {
14904 s.move_with(&mut |map, selection| {
14905 selection.collapse_to(
14906 movement::end_of_paragraph(map, selection.head(), 1),
14907 SelectionGoal::None,
14908 )
14909 });
14910 })
14911 }
14912
14913 pub fn select_to_start_of_paragraph(
14914 &mut self,
14915 _: &SelectToStartOfParagraph,
14916 window: &mut Window,
14917 cx: &mut Context<Self>,
14918 ) {
14919 if matches!(self.mode, EditorMode::SingleLine) {
14920 cx.propagate();
14921 return;
14922 }
14923 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14924 self.change_selections(Default::default(), window, cx, |s| {
14925 s.move_heads_with(&mut |map, head, _| {
14926 (
14927 movement::start_of_paragraph(map, head, 1),
14928 SelectionGoal::None,
14929 )
14930 });
14931 })
14932 }
14933
14934 pub fn select_to_end_of_paragraph(
14935 &mut self,
14936 _: &SelectToEndOfParagraph,
14937 window: &mut Window,
14938 cx: &mut Context<Self>,
14939 ) {
14940 if matches!(self.mode, EditorMode::SingleLine) {
14941 cx.propagate();
14942 return;
14943 }
14944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14945 self.change_selections(Default::default(), window, cx, |s| {
14946 s.move_heads_with(&mut |map, head, _| {
14947 (
14948 movement::end_of_paragraph(map, head, 1),
14949 SelectionGoal::None,
14950 )
14951 });
14952 })
14953 }
14954
14955 pub fn move_to_start_of_excerpt(
14956 &mut self,
14957 _: &MoveToStartOfExcerpt,
14958 window: &mut Window,
14959 cx: &mut Context<Self>,
14960 ) {
14961 if matches!(self.mode, EditorMode::SingleLine) {
14962 cx.propagate();
14963 return;
14964 }
14965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14966 self.change_selections(Default::default(), window, cx, |s| {
14967 s.move_with(&mut |map, selection| {
14968 selection.collapse_to(
14969 movement::start_of_excerpt(
14970 map,
14971 selection.head(),
14972 workspace::searchable::Direction::Prev,
14973 ),
14974 SelectionGoal::None,
14975 )
14976 });
14977 })
14978 }
14979
14980 pub fn move_to_start_of_next_excerpt(
14981 &mut self,
14982 _: &MoveToStartOfNextExcerpt,
14983 window: &mut Window,
14984 cx: &mut Context<Self>,
14985 ) {
14986 if matches!(self.mode, EditorMode::SingleLine) {
14987 cx.propagate();
14988 return;
14989 }
14990
14991 self.change_selections(Default::default(), window, cx, |s| {
14992 s.move_with(&mut |map, selection| {
14993 selection.collapse_to(
14994 movement::start_of_excerpt(
14995 map,
14996 selection.head(),
14997 workspace::searchable::Direction::Next,
14998 ),
14999 SelectionGoal::None,
15000 )
15001 });
15002 })
15003 }
15004
15005 pub fn move_to_end_of_excerpt(
15006 &mut self,
15007 _: &MoveToEndOfExcerpt,
15008 window: &mut Window,
15009 cx: &mut Context<Self>,
15010 ) {
15011 if matches!(self.mode, EditorMode::SingleLine) {
15012 cx.propagate();
15013 return;
15014 }
15015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15016 self.change_selections(Default::default(), window, cx, |s| {
15017 s.move_with(&mut |map, selection| {
15018 selection.collapse_to(
15019 movement::end_of_excerpt(
15020 map,
15021 selection.head(),
15022 workspace::searchable::Direction::Next,
15023 ),
15024 SelectionGoal::None,
15025 )
15026 });
15027 })
15028 }
15029
15030 pub fn move_to_end_of_previous_excerpt(
15031 &mut self,
15032 _: &MoveToEndOfPreviousExcerpt,
15033 window: &mut Window,
15034 cx: &mut Context<Self>,
15035 ) {
15036 if matches!(self.mode, EditorMode::SingleLine) {
15037 cx.propagate();
15038 return;
15039 }
15040 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15041 self.change_selections(Default::default(), window, cx, |s| {
15042 s.move_with(&mut |map, selection| {
15043 selection.collapse_to(
15044 movement::end_of_excerpt(
15045 map,
15046 selection.head(),
15047 workspace::searchable::Direction::Prev,
15048 ),
15049 SelectionGoal::None,
15050 )
15051 });
15052 })
15053 }
15054
15055 pub fn select_to_start_of_excerpt(
15056 &mut self,
15057 _: &SelectToStartOfExcerpt,
15058 window: &mut Window,
15059 cx: &mut Context<Self>,
15060 ) {
15061 if matches!(self.mode, EditorMode::SingleLine) {
15062 cx.propagate();
15063 return;
15064 }
15065 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15066 self.change_selections(Default::default(), window, cx, |s| {
15067 s.move_heads_with(&mut |map, head, _| {
15068 (
15069 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15070 SelectionGoal::None,
15071 )
15072 });
15073 })
15074 }
15075
15076 pub fn select_to_start_of_next_excerpt(
15077 &mut self,
15078 _: &SelectToStartOfNextExcerpt,
15079 window: &mut Window,
15080 cx: &mut Context<Self>,
15081 ) {
15082 if matches!(self.mode, EditorMode::SingleLine) {
15083 cx.propagate();
15084 return;
15085 }
15086 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15087 self.change_selections(Default::default(), window, cx, |s| {
15088 s.move_heads_with(&mut |map, head, _| {
15089 (
15090 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15091 SelectionGoal::None,
15092 )
15093 });
15094 })
15095 }
15096
15097 pub fn select_to_end_of_excerpt(
15098 &mut self,
15099 _: &SelectToEndOfExcerpt,
15100 window: &mut Window,
15101 cx: &mut Context<Self>,
15102 ) {
15103 if matches!(self.mode, EditorMode::SingleLine) {
15104 cx.propagate();
15105 return;
15106 }
15107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15108 self.change_selections(Default::default(), window, cx, |s| {
15109 s.move_heads_with(&mut |map, head, _| {
15110 (
15111 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15112 SelectionGoal::None,
15113 )
15114 });
15115 })
15116 }
15117
15118 pub fn select_to_end_of_previous_excerpt(
15119 &mut self,
15120 _: &SelectToEndOfPreviousExcerpt,
15121 window: &mut Window,
15122 cx: &mut Context<Self>,
15123 ) {
15124 if matches!(self.mode, EditorMode::SingleLine) {
15125 cx.propagate();
15126 return;
15127 }
15128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15129 self.change_selections(Default::default(), window, cx, |s| {
15130 s.move_heads_with(&mut |map, head, _| {
15131 (
15132 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15133 SelectionGoal::None,
15134 )
15135 });
15136 })
15137 }
15138
15139 pub fn move_to_beginning(
15140 &mut self,
15141 _: &MoveToBeginning,
15142 window: &mut Window,
15143 cx: &mut Context<Self>,
15144 ) {
15145 if matches!(self.mode, EditorMode::SingleLine) {
15146 cx.propagate();
15147 return;
15148 }
15149 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15150 self.change_selections(Default::default(), window, cx, |s| {
15151 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15152 });
15153 }
15154
15155 pub fn select_to_beginning(
15156 &mut self,
15157 _: &SelectToBeginning,
15158 window: &mut Window,
15159 cx: &mut Context<Self>,
15160 ) {
15161 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15162 selection.set_head(Point::zero(), SelectionGoal::None);
15163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15164 self.change_selections(Default::default(), window, cx, |s| {
15165 s.select(vec![selection]);
15166 });
15167 }
15168
15169 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15170 if matches!(self.mode, EditorMode::SingleLine) {
15171 cx.propagate();
15172 return;
15173 }
15174 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15175 let cursor = self.buffer.read(cx).read(cx).len();
15176 self.change_selections(Default::default(), window, cx, |s| {
15177 s.select_ranges(vec![cursor..cursor])
15178 });
15179 }
15180
15181 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15182 self.nav_history = nav_history;
15183 }
15184
15185 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15186 self.nav_history.as_ref()
15187 }
15188
15189 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15190 self.push_to_nav_history(
15191 self.selections.newest_anchor().head(),
15192 None,
15193 false,
15194 true,
15195 cx,
15196 );
15197 }
15198
15199 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15200 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15201 let buffer = self.buffer.read(cx).read(cx);
15202 let cursor_position = cursor_anchor.to_point(&buffer);
15203 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15204 let scroll_top_row = scroll_anchor.top_row(&buffer);
15205 drop(buffer);
15206
15207 NavigationData {
15208 cursor_anchor,
15209 cursor_position,
15210 scroll_anchor,
15211 scroll_top_row,
15212 }
15213 }
15214
15215 fn navigation_entry(
15216 &self,
15217 cursor_anchor: Anchor,
15218 cx: &mut Context<Self>,
15219 ) -> Option<NavigationEntry> {
15220 let Some(history) = self.nav_history.clone() else {
15221 return None;
15222 };
15223 let data = self.navigation_data(cursor_anchor, cx);
15224 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15225 }
15226
15227 fn push_to_nav_history(
15228 &mut self,
15229 cursor_anchor: Anchor,
15230 new_position: Option<Point>,
15231 is_deactivate: bool,
15232 always: bool,
15233 cx: &mut Context<Self>,
15234 ) {
15235 let data = self.navigation_data(cursor_anchor, cx);
15236 if let Some(nav_history) = self.nav_history.as_mut() {
15237 if let Some(new_position) = new_position {
15238 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15239 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15240 return;
15241 }
15242 }
15243
15244 nav_history.push(Some(data), cx);
15245 cx.emit(EditorEvent::PushedToNavHistory {
15246 anchor: cursor_anchor,
15247 is_deactivate,
15248 })
15249 }
15250 }
15251
15252 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15254 let buffer = self.buffer.read(cx).snapshot(cx);
15255 let mut selection = self
15256 .selections
15257 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15258 selection.set_head(buffer.len(), SelectionGoal::None);
15259 self.change_selections(Default::default(), window, cx, |s| {
15260 s.select(vec![selection]);
15261 });
15262 }
15263
15264 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15266 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15267 s.select_ranges([Anchor::min()..Anchor::max()]);
15268 });
15269 }
15270
15271 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15273 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15274 let mut selections = self.selections.all::<Point>(&display_map);
15275 let max_point = display_map.buffer_snapshot().max_point();
15276 for selection in &mut selections {
15277 let rows = selection.spanned_rows(true, &display_map);
15278 selection.start = Point::new(rows.start.0, 0);
15279 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15280 selection.reversed = false;
15281 }
15282 self.change_selections(Default::default(), window, cx, |s| {
15283 s.select(selections);
15284 });
15285 }
15286
15287 pub fn split_selection_into_lines(
15288 &mut self,
15289 action: &SplitSelectionIntoLines,
15290 window: &mut Window,
15291 cx: &mut Context<Self>,
15292 ) {
15293 let selections = self
15294 .selections
15295 .all::<Point>(&self.display_snapshot(cx))
15296 .into_iter()
15297 .map(|selection| selection.start..selection.end)
15298 .collect::<Vec<_>>();
15299 self.unfold_ranges(&selections, true, false, cx);
15300
15301 let mut new_selection_ranges = Vec::new();
15302 {
15303 let buffer = self.buffer.read(cx).read(cx);
15304 for selection in selections {
15305 for row in selection.start.row..selection.end.row {
15306 let line_start = Point::new(row, 0);
15307 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15308
15309 if action.keep_selections {
15310 // Keep the selection range for each line
15311 let selection_start = if row == selection.start.row {
15312 selection.start
15313 } else {
15314 line_start
15315 };
15316 new_selection_ranges.push(selection_start..line_end);
15317 } else {
15318 // Collapse to cursor at end of line
15319 new_selection_ranges.push(line_end..line_end);
15320 }
15321 }
15322
15323 let is_multiline_selection = selection.start.row != selection.end.row;
15324 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15325 // so this action feels more ergonomic when paired with other selection operations
15326 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15327 if !should_skip_last {
15328 if action.keep_selections {
15329 if is_multiline_selection {
15330 let line_start = Point::new(selection.end.row, 0);
15331 new_selection_ranges.push(line_start..selection.end);
15332 } else {
15333 new_selection_ranges.push(selection.start..selection.end);
15334 }
15335 } else {
15336 new_selection_ranges.push(selection.end..selection.end);
15337 }
15338 }
15339 }
15340 }
15341 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15342 s.select_ranges(new_selection_ranges);
15343 });
15344 }
15345
15346 pub fn add_selection_above(
15347 &mut self,
15348 action: &AddSelectionAbove,
15349 window: &mut Window,
15350 cx: &mut Context<Self>,
15351 ) {
15352 self.add_selection(true, action.skip_soft_wrap, window, cx);
15353 }
15354
15355 pub fn add_selection_below(
15356 &mut self,
15357 action: &AddSelectionBelow,
15358 window: &mut Window,
15359 cx: &mut Context<Self>,
15360 ) {
15361 self.add_selection(false, action.skip_soft_wrap, window, cx);
15362 }
15363
15364 fn add_selection(
15365 &mut self,
15366 above: bool,
15367 skip_soft_wrap: bool,
15368 window: &mut Window,
15369 cx: &mut Context<Self>,
15370 ) {
15371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15372
15373 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15374 let all_selections = self.selections.all::<Point>(&display_map);
15375 let text_layout_details = self.text_layout_details(window, cx);
15376
15377 let (mut columnar_selections, new_selections_to_columnarize) = {
15378 if let Some(state) = self.add_selections_state.as_ref() {
15379 let columnar_selection_ids: HashSet<_> = state
15380 .groups
15381 .iter()
15382 .flat_map(|group| group.stack.iter())
15383 .copied()
15384 .collect();
15385
15386 all_selections
15387 .into_iter()
15388 .partition(|s| columnar_selection_ids.contains(&s.id))
15389 } else {
15390 (Vec::new(), all_selections)
15391 }
15392 };
15393
15394 let mut state = self
15395 .add_selections_state
15396 .take()
15397 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15398
15399 for selection in new_selections_to_columnarize {
15400 let range = selection.display_range(&display_map).sorted();
15401 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15402 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15403 let positions = start_x.min(end_x)..start_x.max(end_x);
15404 let mut stack = Vec::new();
15405 for row in range.start.row().0..=range.end.row().0 {
15406 if let Some(selection) = self.selections.build_columnar_selection(
15407 &display_map,
15408 DisplayRow(row),
15409 &positions,
15410 selection.reversed,
15411 &text_layout_details,
15412 ) {
15413 stack.push(selection.id);
15414 columnar_selections.push(selection);
15415 }
15416 }
15417 if !stack.is_empty() {
15418 if above {
15419 stack.reverse();
15420 }
15421 state.groups.push(AddSelectionsGroup { above, stack });
15422 }
15423 }
15424
15425 let mut final_selections = Vec::new();
15426 let end_row = if above {
15427 DisplayRow(0)
15428 } else {
15429 display_map.max_point().row()
15430 };
15431
15432 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15433 // positions to place new selections, so we need to keep track of the
15434 // column range of the oldest selection in each group, because
15435 // intermediate selections may have been clamped to shorter lines.
15436 // selections may have been clamped to shorter lines.
15437 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15438 let mut map = HashMap::default();
15439 for group in state.groups.iter() {
15440 if let Some(oldest_id) = group.stack.first() {
15441 if let Some(oldest_selection) =
15442 columnar_selections.iter().find(|s| s.id == *oldest_id)
15443 {
15444 let start_col = oldest_selection.start.column;
15445 let end_col = oldest_selection.end.column;
15446 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15447 for id in &group.stack {
15448 map.insert(*id, goal_columns.clone());
15449 }
15450 }
15451 }
15452 }
15453 map
15454 } else {
15455 HashMap::default()
15456 };
15457
15458 let mut last_added_item_per_group = HashMap::default();
15459 for group in state.groups.iter_mut() {
15460 if let Some(last_id) = group.stack.last() {
15461 last_added_item_per_group.insert(*last_id, group);
15462 }
15463 }
15464
15465 for selection in columnar_selections {
15466 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15467 if above == group.above {
15468 let range = selection.display_range(&display_map).sorted();
15469 debug_assert_eq!(range.start.row(), range.end.row());
15470 let row = range.start.row();
15471 let positions =
15472 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15473 Pixels::from(start)..Pixels::from(end)
15474 } else {
15475 let start_x =
15476 display_map.x_for_display_point(range.start, &text_layout_details);
15477 let end_x =
15478 display_map.x_for_display_point(range.end, &text_layout_details);
15479 start_x.min(end_x)..start_x.max(end_x)
15480 };
15481
15482 let maybe_new_selection = if skip_soft_wrap {
15483 let goal_columns = goal_columns_by_selection_id
15484 .remove(&selection.id)
15485 .unwrap_or_else(|| {
15486 let start_col = selection.start.column;
15487 let end_col = selection.end.column;
15488 start_col.min(end_col)..start_col.max(end_col)
15489 });
15490 self.selections.find_next_columnar_selection_by_buffer_row(
15491 &display_map,
15492 row,
15493 end_row,
15494 above,
15495 &goal_columns,
15496 selection.reversed,
15497 &text_layout_details,
15498 )
15499 } else {
15500 self.selections.find_next_columnar_selection_by_display_row(
15501 &display_map,
15502 row,
15503 end_row,
15504 above,
15505 &positions,
15506 selection.reversed,
15507 &text_layout_details,
15508 )
15509 };
15510
15511 if let Some(new_selection) = maybe_new_selection {
15512 group.stack.push(new_selection.id);
15513 if above {
15514 final_selections.push(new_selection);
15515 final_selections.push(selection);
15516 } else {
15517 final_selections.push(selection);
15518 final_selections.push(new_selection);
15519 }
15520 } else {
15521 final_selections.push(selection);
15522 }
15523 } else {
15524 group.stack.pop();
15525 }
15526 } else {
15527 final_selections.push(selection);
15528 }
15529 }
15530
15531 self.change_selections(Default::default(), window, cx, |s| {
15532 s.select(final_selections);
15533 });
15534
15535 let final_selection_ids: HashSet<_> = self
15536 .selections
15537 .all::<Point>(&display_map)
15538 .iter()
15539 .map(|s| s.id)
15540 .collect();
15541 state.groups.retain_mut(|group| {
15542 // selections might get merged above so we remove invalid items from stacks
15543 group.stack.retain(|id| final_selection_ids.contains(id));
15544
15545 // single selection in stack can be treated as initial state
15546 group.stack.len() > 1
15547 });
15548
15549 if !state.groups.is_empty() {
15550 self.add_selections_state = Some(state);
15551 }
15552 }
15553
15554 pub fn insert_snippet_at_selections(
15555 &mut self,
15556 action: &InsertSnippet,
15557 window: &mut Window,
15558 cx: &mut Context<Self>,
15559 ) {
15560 self.try_insert_snippet_at_selections(action, window, cx)
15561 .log_err();
15562 }
15563
15564 fn try_insert_snippet_at_selections(
15565 &mut self,
15566 action: &InsertSnippet,
15567 window: &mut Window,
15568 cx: &mut Context<Self>,
15569 ) -> Result<()> {
15570 let insertion_ranges = self
15571 .selections
15572 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15573 .into_iter()
15574 .map(|selection| selection.range())
15575 .collect_vec();
15576
15577 let snippet = if let Some(snippet_body) = &action.snippet {
15578 if action.language.is_none() && action.name.is_none() {
15579 Snippet::parse(snippet_body)?
15580 } else {
15581 bail!("`snippet` is mutually exclusive with `language` and `name`")
15582 }
15583 } else if let Some(name) = &action.name {
15584 let project = self.project().context("no project")?;
15585 let snippet_store = project.read(cx).snippets().read(cx);
15586 let snippet = snippet_store
15587 .snippets_for(action.language.clone(), cx)
15588 .into_iter()
15589 .find(|snippet| snippet.name == *name)
15590 .context("snippet not found")?;
15591 Snippet::parse(&snippet.body)?
15592 } else {
15593 // todo(andrew): open modal to select snippet
15594 bail!("`name` or `snippet` is required")
15595 };
15596
15597 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15598 }
15599
15600 fn select_match_ranges(
15601 &mut self,
15602 range: Range<MultiBufferOffset>,
15603 reversed: bool,
15604 replace_newest: bool,
15605 auto_scroll: Option<Autoscroll>,
15606 window: &mut Window,
15607 cx: &mut Context<Editor>,
15608 ) {
15609 self.unfold_ranges(
15610 std::slice::from_ref(&range),
15611 false,
15612 auto_scroll.is_some(),
15613 cx,
15614 );
15615 let effects = if let Some(scroll) = auto_scroll {
15616 SelectionEffects::scroll(scroll)
15617 } else {
15618 SelectionEffects::no_scroll()
15619 };
15620 self.change_selections(effects, window, cx, |s| {
15621 if replace_newest {
15622 s.delete(s.newest_anchor().id);
15623 }
15624 if reversed {
15625 s.insert_range(range.end..range.start);
15626 } else {
15627 s.insert_range(range);
15628 }
15629 });
15630 }
15631
15632 pub fn select_next_match_internal(
15633 &mut self,
15634 display_map: &DisplaySnapshot,
15635 replace_newest: bool,
15636 autoscroll: Option<Autoscroll>,
15637 window: &mut Window,
15638 cx: &mut Context<Self>,
15639 ) -> Result<()> {
15640 let buffer = display_map.buffer_snapshot();
15641 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15642 if let Some(mut select_next_state) = self.select_next_state.take() {
15643 let query = &select_next_state.query;
15644 if !select_next_state.done {
15645 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15646 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15647 let mut next_selected_range = None;
15648
15649 let bytes_after_last_selection =
15650 buffer.bytes_in_range(last_selection.end..buffer.len());
15651 let bytes_before_first_selection =
15652 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15653 let query_matches = query
15654 .stream_find_iter(bytes_after_last_selection)
15655 .map(|result| (last_selection.end, result))
15656 .chain(
15657 query
15658 .stream_find_iter(bytes_before_first_selection)
15659 .map(|result| (MultiBufferOffset(0), result)),
15660 );
15661
15662 for (start_offset, query_match) in query_matches {
15663 let query_match = query_match.unwrap(); // can only fail due to I/O
15664 let offset_range =
15665 start_offset + query_match.start()..start_offset + query_match.end();
15666
15667 if !select_next_state.wordwise
15668 || (!buffer.is_inside_word(offset_range.start, None)
15669 && !buffer.is_inside_word(offset_range.end, None))
15670 {
15671 let idx = selections
15672 .partition_point(|selection| selection.end <= offset_range.start);
15673 let overlaps = selections
15674 .get(idx)
15675 .map_or(false, |selection| selection.start < offset_range.end);
15676
15677 if !overlaps {
15678 next_selected_range = Some(offset_range);
15679 break;
15680 }
15681 }
15682 }
15683
15684 if let Some(next_selected_range) = next_selected_range {
15685 self.select_match_ranges(
15686 next_selected_range,
15687 last_selection.reversed,
15688 replace_newest,
15689 autoscroll,
15690 window,
15691 cx,
15692 );
15693 } else {
15694 select_next_state.done = true;
15695 }
15696 }
15697
15698 self.select_next_state = Some(select_next_state);
15699 } else {
15700 let mut only_carets = true;
15701 let mut same_text_selected = true;
15702 let mut selected_text = None;
15703
15704 let mut selections_iter = selections.iter().peekable();
15705 while let Some(selection) = selections_iter.next() {
15706 if selection.start != selection.end {
15707 only_carets = false;
15708 }
15709
15710 if same_text_selected {
15711 if selected_text.is_none() {
15712 selected_text =
15713 Some(buffer.text_for_range(selection.range()).collect::<String>());
15714 }
15715
15716 if let Some(next_selection) = selections_iter.peek() {
15717 if next_selection.len() == selection.len() {
15718 let next_selected_text = buffer
15719 .text_for_range(next_selection.range())
15720 .collect::<String>();
15721 if Some(next_selected_text) != selected_text {
15722 same_text_selected = false;
15723 selected_text = None;
15724 }
15725 } else {
15726 same_text_selected = false;
15727 selected_text = None;
15728 }
15729 }
15730 }
15731 }
15732
15733 if only_carets {
15734 for selection in &mut selections {
15735 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15736 selection.start = word_range.start;
15737 selection.end = word_range.end;
15738 selection.goal = SelectionGoal::None;
15739 selection.reversed = false;
15740 self.select_match_ranges(
15741 selection.start..selection.end,
15742 selection.reversed,
15743 replace_newest,
15744 autoscroll,
15745 window,
15746 cx,
15747 );
15748 }
15749
15750 if selections.len() == 1 {
15751 let selection = selections
15752 .last()
15753 .expect("ensured that there's only one selection");
15754 let query = buffer
15755 .text_for_range(selection.start..selection.end)
15756 .collect::<String>();
15757 let is_empty = query.is_empty();
15758 let select_state = SelectNextState {
15759 query: self.build_query(&[query], cx)?,
15760 wordwise: true,
15761 done: is_empty,
15762 };
15763 self.select_next_state = Some(select_state);
15764 } else {
15765 self.select_next_state = None;
15766 }
15767 } else if let Some(selected_text) = selected_text {
15768 self.select_next_state = Some(SelectNextState {
15769 query: self.build_query(&[selected_text], cx)?,
15770 wordwise: false,
15771 done: false,
15772 });
15773 self.select_next_match_internal(
15774 display_map,
15775 replace_newest,
15776 autoscroll,
15777 window,
15778 cx,
15779 )?;
15780 }
15781 }
15782 Ok(())
15783 }
15784
15785 pub fn select_all_matches(
15786 &mut self,
15787 _action: &SelectAllMatches,
15788 window: &mut Window,
15789 cx: &mut Context<Self>,
15790 ) -> Result<()> {
15791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15792
15793 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15794
15795 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15796 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15797 else {
15798 return Ok(());
15799 };
15800
15801 let mut new_selections = Vec::new();
15802
15803 let reversed = self
15804 .selections
15805 .oldest::<MultiBufferOffset>(&display_map)
15806 .reversed;
15807 let buffer = display_map.buffer_snapshot();
15808 let query_matches = select_next_state
15809 .query
15810 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15811
15812 for query_match in query_matches.into_iter() {
15813 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15814 let offset_range = if reversed {
15815 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15816 } else {
15817 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15818 };
15819
15820 if !select_next_state.wordwise
15821 || (!buffer.is_inside_word(offset_range.start, None)
15822 && !buffer.is_inside_word(offset_range.end, None))
15823 {
15824 new_selections.push(offset_range.start..offset_range.end);
15825 }
15826 }
15827
15828 select_next_state.done = true;
15829
15830 if new_selections.is_empty() {
15831 log::error!("bug: new_selections is empty in select_all_matches");
15832 return Ok(());
15833 }
15834
15835 self.unfold_ranges(&new_selections, false, false, cx);
15836 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15837 selections.select_ranges(new_selections)
15838 });
15839
15840 Ok(())
15841 }
15842
15843 pub fn select_next(
15844 &mut self,
15845 action: &SelectNext,
15846 window: &mut Window,
15847 cx: &mut Context<Self>,
15848 ) -> Result<()> {
15849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15850 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15851 self.select_next_match_internal(
15852 &display_map,
15853 action.replace_newest,
15854 Some(Autoscroll::newest()),
15855 window,
15856 cx,
15857 )
15858 }
15859
15860 pub fn select_previous(
15861 &mut self,
15862 action: &SelectPrevious,
15863 window: &mut Window,
15864 cx: &mut Context<Self>,
15865 ) -> Result<()> {
15866 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15868 let buffer = display_map.buffer_snapshot();
15869 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15870 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15871 let query = &select_prev_state.query;
15872 if !select_prev_state.done {
15873 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15874 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15875 let mut next_selected_range = None;
15876 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15877 let bytes_before_last_selection =
15878 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15879 let bytes_after_first_selection =
15880 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15881 let query_matches = query
15882 .stream_find_iter(bytes_before_last_selection)
15883 .map(|result| (last_selection.start, result))
15884 .chain(
15885 query
15886 .stream_find_iter(bytes_after_first_selection)
15887 .map(|result| (buffer.len(), result)),
15888 );
15889 for (end_offset, query_match) in query_matches {
15890 let query_match = query_match.unwrap(); // can only fail due to I/O
15891 let offset_range =
15892 end_offset - query_match.end()..end_offset - query_match.start();
15893
15894 if !select_prev_state.wordwise
15895 || (!buffer.is_inside_word(offset_range.start, None)
15896 && !buffer.is_inside_word(offset_range.end, None))
15897 {
15898 next_selected_range = Some(offset_range);
15899 break;
15900 }
15901 }
15902
15903 if let Some(next_selected_range) = next_selected_range {
15904 self.select_match_ranges(
15905 next_selected_range,
15906 last_selection.reversed,
15907 action.replace_newest,
15908 Some(Autoscroll::newest()),
15909 window,
15910 cx,
15911 );
15912 } else {
15913 select_prev_state.done = true;
15914 }
15915 }
15916
15917 self.select_prev_state = Some(select_prev_state);
15918 } else {
15919 let mut only_carets = true;
15920 let mut same_text_selected = true;
15921 let mut selected_text = None;
15922
15923 let mut selections_iter = selections.iter().peekable();
15924 while let Some(selection) = selections_iter.next() {
15925 if selection.start != selection.end {
15926 only_carets = false;
15927 }
15928
15929 if same_text_selected {
15930 if selected_text.is_none() {
15931 selected_text =
15932 Some(buffer.text_for_range(selection.range()).collect::<String>());
15933 }
15934
15935 if let Some(next_selection) = selections_iter.peek() {
15936 if next_selection.len() == selection.len() {
15937 let next_selected_text = buffer
15938 .text_for_range(next_selection.range())
15939 .collect::<String>();
15940 if Some(next_selected_text) != selected_text {
15941 same_text_selected = false;
15942 selected_text = None;
15943 }
15944 } else {
15945 same_text_selected = false;
15946 selected_text = None;
15947 }
15948 }
15949 }
15950 }
15951
15952 if only_carets {
15953 for selection in &mut selections {
15954 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15955 selection.start = word_range.start;
15956 selection.end = word_range.end;
15957 selection.goal = SelectionGoal::None;
15958 selection.reversed = false;
15959 self.select_match_ranges(
15960 selection.start..selection.end,
15961 selection.reversed,
15962 action.replace_newest,
15963 Some(Autoscroll::newest()),
15964 window,
15965 cx,
15966 );
15967 }
15968 if selections.len() == 1 {
15969 let selection = selections
15970 .last()
15971 .expect("ensured that there's only one selection");
15972 let query = buffer
15973 .text_for_range(selection.start..selection.end)
15974 .collect::<String>();
15975 let is_empty = query.is_empty();
15976 let select_state = SelectNextState {
15977 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15978 wordwise: true,
15979 done: is_empty,
15980 };
15981 self.select_prev_state = Some(select_state);
15982 } else {
15983 self.select_prev_state = None;
15984 }
15985 } else if let Some(selected_text) = selected_text {
15986 self.select_prev_state = Some(SelectNextState {
15987 query: self
15988 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15989 wordwise: false,
15990 done: false,
15991 });
15992 self.select_previous(action, window, cx)?;
15993 }
15994 }
15995 Ok(())
15996 }
15997
15998 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15999 /// setting the case sensitivity based on the global
16000 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16001 /// editor's settings.
16002 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16003 where
16004 I: IntoIterator<Item = P>,
16005 P: AsRef<[u8]>,
16006 {
16007 let case_sensitive = self
16008 .select_next_is_case_sensitive
16009 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16010
16011 let mut builder = AhoCorasickBuilder::new();
16012 builder.ascii_case_insensitive(!case_sensitive);
16013 builder.build(patterns)
16014 }
16015
16016 pub fn find_next_match(
16017 &mut self,
16018 _: &FindNextMatch,
16019 window: &mut Window,
16020 cx: &mut Context<Self>,
16021 ) -> Result<()> {
16022 let selections = self.selections.disjoint_anchors_arc();
16023 match selections.first() {
16024 Some(first) if selections.len() >= 2 => {
16025 self.change_selections(Default::default(), window, cx, |s| {
16026 s.select_ranges([first.range()]);
16027 });
16028 }
16029 _ => self.select_next(
16030 &SelectNext {
16031 replace_newest: true,
16032 },
16033 window,
16034 cx,
16035 )?,
16036 }
16037 Ok(())
16038 }
16039
16040 pub fn find_previous_match(
16041 &mut self,
16042 _: &FindPreviousMatch,
16043 window: &mut Window,
16044 cx: &mut Context<Self>,
16045 ) -> Result<()> {
16046 let selections = self.selections.disjoint_anchors_arc();
16047 match selections.last() {
16048 Some(last) if selections.len() >= 2 => {
16049 self.change_selections(Default::default(), window, cx, |s| {
16050 s.select_ranges([last.range()]);
16051 });
16052 }
16053 _ => self.select_previous(
16054 &SelectPrevious {
16055 replace_newest: true,
16056 },
16057 window,
16058 cx,
16059 )?,
16060 }
16061 Ok(())
16062 }
16063
16064 pub fn toggle_comments(
16065 &mut self,
16066 action: &ToggleComments,
16067 window: &mut Window,
16068 cx: &mut Context<Self>,
16069 ) {
16070 if self.read_only(cx) {
16071 return;
16072 }
16073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16074 let text_layout_details = &self.text_layout_details(window, cx);
16075 self.transact(window, cx, |this, window, cx| {
16076 let mut selections = this
16077 .selections
16078 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16079 let mut edits = Vec::new();
16080 let mut selection_edit_ranges = Vec::new();
16081 let mut last_toggled_row = None;
16082 let snapshot = this.buffer.read(cx).read(cx);
16083 let empty_str: Arc<str> = Arc::default();
16084 let mut suffixes_inserted = Vec::new();
16085 let ignore_indent = action.ignore_indent;
16086
16087 fn comment_prefix_range(
16088 snapshot: &MultiBufferSnapshot,
16089 row: MultiBufferRow,
16090 comment_prefix: &str,
16091 comment_prefix_whitespace: &str,
16092 ignore_indent: bool,
16093 ) -> Range<Point> {
16094 let indent_size = if ignore_indent {
16095 0
16096 } else {
16097 snapshot.indent_size_for_line(row).len
16098 };
16099
16100 let start = Point::new(row.0, indent_size);
16101
16102 let mut line_bytes = snapshot
16103 .bytes_in_range(start..snapshot.max_point())
16104 .flatten()
16105 .copied();
16106
16107 // If this line currently begins with the line comment prefix, then record
16108 // the range containing the prefix.
16109 if line_bytes
16110 .by_ref()
16111 .take(comment_prefix.len())
16112 .eq(comment_prefix.bytes())
16113 {
16114 // Include any whitespace that matches the comment prefix.
16115 let matching_whitespace_len = line_bytes
16116 .zip(comment_prefix_whitespace.bytes())
16117 .take_while(|(a, b)| a == b)
16118 .count() as u32;
16119 let end = Point::new(
16120 start.row,
16121 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16122 );
16123 start..end
16124 } else {
16125 start..start
16126 }
16127 }
16128
16129 fn comment_suffix_range(
16130 snapshot: &MultiBufferSnapshot,
16131 row: MultiBufferRow,
16132 comment_suffix: &str,
16133 comment_suffix_has_leading_space: bool,
16134 ) -> Range<Point> {
16135 let end = Point::new(row.0, snapshot.line_len(row));
16136 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16137
16138 let mut line_end_bytes = snapshot
16139 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16140 .flatten()
16141 .copied();
16142
16143 let leading_space_len = if suffix_start_column > 0
16144 && line_end_bytes.next() == Some(b' ')
16145 && comment_suffix_has_leading_space
16146 {
16147 1
16148 } else {
16149 0
16150 };
16151
16152 // If this line currently begins with the line comment prefix, then record
16153 // the range containing the prefix.
16154 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16155 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16156 start..end
16157 } else {
16158 end..end
16159 }
16160 }
16161
16162 // TODO: Handle selections that cross excerpts
16163 for selection in &mut selections {
16164 let start_column = snapshot
16165 .indent_size_for_line(MultiBufferRow(selection.start.row))
16166 .len;
16167 let language = if let Some(language) =
16168 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16169 {
16170 language
16171 } else {
16172 continue;
16173 };
16174
16175 selection_edit_ranges.clear();
16176
16177 // If multiple selections contain a given row, avoid processing that
16178 // row more than once.
16179 let mut start_row = MultiBufferRow(selection.start.row);
16180 if last_toggled_row == Some(start_row) {
16181 start_row = start_row.next_row();
16182 }
16183 let end_row =
16184 if selection.end.row > selection.start.row && selection.end.column == 0 {
16185 MultiBufferRow(selection.end.row - 1)
16186 } else {
16187 MultiBufferRow(selection.end.row)
16188 };
16189 last_toggled_row = Some(end_row);
16190
16191 if start_row > end_row {
16192 continue;
16193 }
16194
16195 // If the language has line comments, toggle those.
16196 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16197
16198 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16199 if ignore_indent {
16200 full_comment_prefixes = full_comment_prefixes
16201 .into_iter()
16202 .map(|s| Arc::from(s.trim_end()))
16203 .collect();
16204 }
16205
16206 if !full_comment_prefixes.is_empty() {
16207 let first_prefix = full_comment_prefixes
16208 .first()
16209 .expect("prefixes is non-empty");
16210 let prefix_trimmed_lengths = full_comment_prefixes
16211 .iter()
16212 .map(|p| p.trim_end_matches(' ').len())
16213 .collect::<SmallVec<[usize; 4]>>();
16214
16215 let mut all_selection_lines_are_comments = true;
16216
16217 for row in start_row.0..=end_row.0 {
16218 let row = MultiBufferRow(row);
16219 if start_row < end_row && snapshot.is_line_blank(row) {
16220 continue;
16221 }
16222
16223 let prefix_range = full_comment_prefixes
16224 .iter()
16225 .zip(prefix_trimmed_lengths.iter().copied())
16226 .map(|(prefix, trimmed_prefix_len)| {
16227 comment_prefix_range(
16228 snapshot.deref(),
16229 row,
16230 &prefix[..trimmed_prefix_len],
16231 &prefix[trimmed_prefix_len..],
16232 ignore_indent,
16233 )
16234 })
16235 .max_by_key(|range| range.end.column - range.start.column)
16236 .expect("prefixes is non-empty");
16237
16238 if prefix_range.is_empty() {
16239 all_selection_lines_are_comments = false;
16240 }
16241
16242 selection_edit_ranges.push(prefix_range);
16243 }
16244
16245 if all_selection_lines_are_comments {
16246 edits.extend(
16247 selection_edit_ranges
16248 .iter()
16249 .cloned()
16250 .map(|range| (range, empty_str.clone())),
16251 );
16252 } else {
16253 let min_column = selection_edit_ranges
16254 .iter()
16255 .map(|range| range.start.column)
16256 .min()
16257 .unwrap_or(0);
16258 edits.extend(selection_edit_ranges.iter().map(|range| {
16259 let position = Point::new(range.start.row, min_column);
16260 (position..position, first_prefix.clone())
16261 }));
16262 }
16263 } else if let Some(BlockCommentConfig {
16264 start: full_comment_prefix,
16265 end: comment_suffix,
16266 ..
16267 }) = language.block_comment()
16268 {
16269 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16270 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16271 let prefix_range = comment_prefix_range(
16272 snapshot.deref(),
16273 start_row,
16274 comment_prefix,
16275 comment_prefix_whitespace,
16276 ignore_indent,
16277 );
16278 let suffix_range = comment_suffix_range(
16279 snapshot.deref(),
16280 end_row,
16281 comment_suffix.trim_start_matches(' '),
16282 comment_suffix.starts_with(' '),
16283 );
16284
16285 if prefix_range.is_empty() || suffix_range.is_empty() {
16286 edits.push((
16287 prefix_range.start..prefix_range.start,
16288 full_comment_prefix.clone(),
16289 ));
16290 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16291 suffixes_inserted.push((end_row, comment_suffix.len()));
16292 } else {
16293 edits.push((prefix_range, empty_str.clone()));
16294 edits.push((suffix_range, empty_str.clone()));
16295 }
16296 } else {
16297 continue;
16298 }
16299 }
16300
16301 drop(snapshot);
16302 this.buffer.update(cx, |buffer, cx| {
16303 buffer.edit(edits, None, cx);
16304 });
16305
16306 // Adjust selections so that they end before any comment suffixes that
16307 // were inserted.
16308 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16309 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16310 let snapshot = this.buffer.read(cx).read(cx);
16311 for selection in &mut selections {
16312 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16313 match row.cmp(&MultiBufferRow(selection.end.row)) {
16314 Ordering::Less => {
16315 suffixes_inserted.next();
16316 continue;
16317 }
16318 Ordering::Greater => break,
16319 Ordering::Equal => {
16320 if selection.end.column == snapshot.line_len(row) {
16321 if selection.is_empty() {
16322 selection.start.column -= suffix_len as u32;
16323 }
16324 selection.end.column -= suffix_len as u32;
16325 }
16326 break;
16327 }
16328 }
16329 }
16330 }
16331
16332 drop(snapshot);
16333 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16334
16335 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16336 let selections_on_single_row = selections.windows(2).all(|selections| {
16337 selections[0].start.row == selections[1].start.row
16338 && selections[0].end.row == selections[1].end.row
16339 && selections[0].start.row == selections[0].end.row
16340 });
16341 let selections_selecting = selections
16342 .iter()
16343 .any(|selection| selection.start != selection.end);
16344 let advance_downwards = action.advance_downwards
16345 && selections_on_single_row
16346 && !selections_selecting
16347 && !matches!(this.mode, EditorMode::SingleLine);
16348
16349 if advance_downwards {
16350 let snapshot = this.buffer.read(cx).snapshot(cx);
16351
16352 this.change_selections(Default::default(), window, cx, |s| {
16353 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16354 let mut point = display_point.to_point(display_snapshot);
16355 point.row += 1;
16356 point = snapshot.clip_point(point, Bias::Left);
16357 let display_point = point.to_display_point(display_snapshot);
16358 let goal = SelectionGoal::HorizontalPosition(
16359 display_snapshot
16360 .x_for_display_point(display_point, text_layout_details)
16361 .into(),
16362 );
16363 (display_point, goal)
16364 })
16365 });
16366 }
16367 });
16368 }
16369
16370 pub fn select_enclosing_symbol(
16371 &mut self,
16372 _: &SelectEnclosingSymbol,
16373 window: &mut Window,
16374 cx: &mut Context<Self>,
16375 ) {
16376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16377
16378 let buffer = self.buffer.read(cx).snapshot(cx);
16379 let old_selections = self
16380 .selections
16381 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16382 .into_boxed_slice();
16383
16384 fn update_selection(
16385 selection: &Selection<MultiBufferOffset>,
16386 buffer_snap: &MultiBufferSnapshot,
16387 ) -> Option<Selection<MultiBufferOffset>> {
16388 let cursor = selection.head();
16389 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16390 for symbol in symbols.iter().rev() {
16391 let start = symbol.range.start.to_offset(buffer_snap);
16392 let end = symbol.range.end.to_offset(buffer_snap);
16393 let new_range = start..end;
16394 if start < selection.start || end > selection.end {
16395 return Some(Selection {
16396 id: selection.id,
16397 start: new_range.start,
16398 end: new_range.end,
16399 goal: SelectionGoal::None,
16400 reversed: selection.reversed,
16401 });
16402 }
16403 }
16404 None
16405 }
16406
16407 let mut selected_larger_symbol = false;
16408 let new_selections = old_selections
16409 .iter()
16410 .map(|selection| match update_selection(selection, &buffer) {
16411 Some(new_selection) => {
16412 if new_selection.range() != selection.range() {
16413 selected_larger_symbol = true;
16414 }
16415 new_selection
16416 }
16417 None => selection.clone(),
16418 })
16419 .collect::<Vec<_>>();
16420
16421 if selected_larger_symbol {
16422 self.change_selections(Default::default(), window, cx, |s| {
16423 s.select(new_selections);
16424 });
16425 }
16426 }
16427
16428 pub fn select_larger_syntax_node(
16429 &mut self,
16430 _: &SelectLargerSyntaxNode,
16431 window: &mut Window,
16432 cx: &mut Context<Self>,
16433 ) {
16434 let Some(visible_row_count) = self.visible_row_count() else {
16435 return;
16436 };
16437 let old_selections: Box<[_]> = self
16438 .selections
16439 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16440 .into();
16441 if old_selections.is_empty() {
16442 return;
16443 }
16444
16445 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16446
16447 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16448 let buffer = self.buffer.read(cx).snapshot(cx);
16449
16450 let mut selected_larger_node = false;
16451 let mut new_selections = old_selections
16452 .iter()
16453 .map(|selection| {
16454 let old_range = selection.start..selection.end;
16455
16456 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16457 // manually select word at selection
16458 if ["string_content", "inline"].contains(&node.kind()) {
16459 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16460 // ignore if word is already selected
16461 if !word_range.is_empty() && old_range != word_range {
16462 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16463 // only select word if start and end point belongs to same word
16464 if word_range == last_word_range {
16465 selected_larger_node = true;
16466 return Selection {
16467 id: selection.id,
16468 start: word_range.start,
16469 end: word_range.end,
16470 goal: SelectionGoal::None,
16471 reversed: selection.reversed,
16472 };
16473 }
16474 }
16475 }
16476 }
16477
16478 let mut new_range = old_range.clone();
16479 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16480 new_range = range;
16481 if !node.is_named() {
16482 continue;
16483 }
16484 if !display_map.intersects_fold(new_range.start)
16485 && !display_map.intersects_fold(new_range.end)
16486 {
16487 break;
16488 }
16489 }
16490
16491 selected_larger_node |= new_range != old_range;
16492 Selection {
16493 id: selection.id,
16494 start: new_range.start,
16495 end: new_range.end,
16496 goal: SelectionGoal::None,
16497 reversed: selection.reversed,
16498 }
16499 })
16500 .collect::<Vec<_>>();
16501
16502 if !selected_larger_node {
16503 return; // don't put this call in the history
16504 }
16505
16506 // scroll based on transformation done to the last selection created by the user
16507 let (last_old, last_new) = old_selections
16508 .last()
16509 .zip(new_selections.last().cloned())
16510 .expect("old_selections isn't empty");
16511
16512 let is_selection_reversed = if new_selections.len() == 1 {
16513 let should_be_reversed = last_old.start != last_new.start;
16514 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16515 should_be_reversed
16516 } else {
16517 last_new.reversed
16518 };
16519
16520 if selected_larger_node {
16521 self.select_syntax_node_history.disable_clearing = true;
16522 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16523 s.select(new_selections.clone());
16524 });
16525 self.select_syntax_node_history.disable_clearing = false;
16526 }
16527
16528 let start_row = last_new.start.to_display_point(&display_map).row().0;
16529 let end_row = last_new.end.to_display_point(&display_map).row().0;
16530 let selection_height = end_row - start_row + 1;
16531 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16532
16533 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16534 let scroll_behavior = if fits_on_the_screen {
16535 self.request_autoscroll(Autoscroll::fit(), cx);
16536 SelectSyntaxNodeScrollBehavior::FitSelection
16537 } else if is_selection_reversed {
16538 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16539 SelectSyntaxNodeScrollBehavior::CursorTop
16540 } else {
16541 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16542 SelectSyntaxNodeScrollBehavior::CursorBottom
16543 };
16544
16545 let old_selections: Box<[Selection<Anchor>]> = old_selections
16546 .iter()
16547 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16548 .collect();
16549 self.select_syntax_node_history.push((
16550 old_selections,
16551 scroll_behavior,
16552 is_selection_reversed,
16553 ));
16554 }
16555
16556 pub fn select_smaller_syntax_node(
16557 &mut self,
16558 _: &SelectSmallerSyntaxNode,
16559 window: &mut Window,
16560 cx: &mut Context<Self>,
16561 ) {
16562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16563
16564 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16565 self.select_syntax_node_history.pop()
16566 {
16567 if let Some(selection) = selections.last_mut() {
16568 selection.reversed = is_selection_reversed;
16569 }
16570
16571 let snapshot = self.buffer.read(cx).snapshot(cx);
16572 let selections: Vec<Selection<MultiBufferOffset>> = selections
16573 .iter()
16574 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16575 .collect();
16576
16577 self.select_syntax_node_history.disable_clearing = true;
16578 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16579 s.select(selections);
16580 });
16581 self.select_syntax_node_history.disable_clearing = false;
16582
16583 match scroll_behavior {
16584 SelectSyntaxNodeScrollBehavior::CursorTop => {
16585 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16586 }
16587 SelectSyntaxNodeScrollBehavior::FitSelection => {
16588 self.request_autoscroll(Autoscroll::fit(), cx);
16589 }
16590 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16591 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16592 }
16593 }
16594 }
16595 }
16596
16597 pub fn unwrap_syntax_node(
16598 &mut self,
16599 _: &UnwrapSyntaxNode,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16604
16605 let buffer = self.buffer.read(cx).snapshot(cx);
16606 let selections = self
16607 .selections
16608 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16609 .into_iter()
16610 // subtracting the offset requires sorting
16611 .sorted_by_key(|i| i.start);
16612
16613 let full_edits = selections
16614 .into_iter()
16615 .filter_map(|selection| {
16616 let child = if selection.is_empty()
16617 && let Some((_, ancestor_range)) =
16618 buffer.syntax_ancestor(selection.start..selection.end)
16619 {
16620 ancestor_range
16621 } else {
16622 selection.range()
16623 };
16624
16625 let mut parent = child.clone();
16626 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16627 parent = ancestor_range;
16628 if parent.start < child.start || parent.end > child.end {
16629 break;
16630 }
16631 }
16632
16633 if parent == child {
16634 return None;
16635 }
16636 let text = buffer.text_for_range(child).collect::<String>();
16637 Some((selection.id, parent, text))
16638 })
16639 .collect::<Vec<_>>();
16640 if full_edits.is_empty() {
16641 return;
16642 }
16643
16644 self.transact(window, cx, |this, window, cx| {
16645 this.buffer.update(cx, |buffer, cx| {
16646 buffer.edit(
16647 full_edits
16648 .iter()
16649 .map(|(_, p, t)| (p.clone(), t.clone()))
16650 .collect::<Vec<_>>(),
16651 None,
16652 cx,
16653 );
16654 });
16655 this.change_selections(Default::default(), window, cx, |s| {
16656 let mut offset = 0;
16657 let mut selections = vec![];
16658 for (id, parent, text) in full_edits {
16659 let start = parent.start - offset;
16660 offset += (parent.end - parent.start) - text.len();
16661 selections.push(Selection {
16662 id,
16663 start,
16664 end: start + text.len(),
16665 reversed: false,
16666 goal: Default::default(),
16667 });
16668 }
16669 s.select(selections);
16670 });
16671 });
16672 }
16673
16674 pub fn select_next_syntax_node(
16675 &mut self,
16676 _: &SelectNextSyntaxNode,
16677 window: &mut Window,
16678 cx: &mut Context<Self>,
16679 ) {
16680 let old_selections: Box<[_]> = self
16681 .selections
16682 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16683 .into();
16684 if old_selections.is_empty() {
16685 return;
16686 }
16687
16688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16689
16690 let buffer = self.buffer.read(cx).snapshot(cx);
16691 let mut selected_sibling = false;
16692
16693 let new_selections = old_selections
16694 .iter()
16695 .map(|selection| {
16696 let old_range = selection.start..selection.end;
16697
16698 let old_range =
16699 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16700 let excerpt = buffer.excerpt_containing(old_range.clone());
16701
16702 if let Some(mut excerpt) = excerpt
16703 && let Some(node) = excerpt
16704 .buffer()
16705 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16706 {
16707 let new_range = excerpt.map_range_from_buffer(
16708 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16709 );
16710 selected_sibling = true;
16711 Selection {
16712 id: selection.id,
16713 start: new_range.start,
16714 end: new_range.end,
16715 goal: SelectionGoal::None,
16716 reversed: selection.reversed,
16717 }
16718 } else {
16719 selection.clone()
16720 }
16721 })
16722 .collect::<Vec<_>>();
16723
16724 if selected_sibling {
16725 self.change_selections(
16726 SelectionEffects::scroll(Autoscroll::fit()),
16727 window,
16728 cx,
16729 |s| {
16730 s.select(new_selections);
16731 },
16732 );
16733 }
16734 }
16735
16736 pub fn select_prev_syntax_node(
16737 &mut self,
16738 _: &SelectPreviousSyntaxNode,
16739 window: &mut Window,
16740 cx: &mut Context<Self>,
16741 ) {
16742 let old_selections: Box<[_]> = self
16743 .selections
16744 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16745 .into();
16746 if old_selections.is_empty() {
16747 return;
16748 }
16749
16750 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16751
16752 let buffer = self.buffer.read(cx).snapshot(cx);
16753 let mut selected_sibling = false;
16754
16755 let new_selections = old_selections
16756 .iter()
16757 .map(|selection| {
16758 let old_range = selection.start..selection.end;
16759 let old_range =
16760 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16761 let excerpt = buffer.excerpt_containing(old_range.clone());
16762
16763 if let Some(mut excerpt) = excerpt
16764 && let Some(node) = excerpt
16765 .buffer()
16766 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16767 {
16768 let new_range = excerpt.map_range_from_buffer(
16769 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16770 );
16771 selected_sibling = true;
16772 Selection {
16773 id: selection.id,
16774 start: new_range.start,
16775 end: new_range.end,
16776 goal: SelectionGoal::None,
16777 reversed: selection.reversed,
16778 }
16779 } else {
16780 selection.clone()
16781 }
16782 })
16783 .collect::<Vec<_>>();
16784
16785 if selected_sibling {
16786 self.change_selections(
16787 SelectionEffects::scroll(Autoscroll::fit()),
16788 window,
16789 cx,
16790 |s| {
16791 s.select(new_selections);
16792 },
16793 );
16794 }
16795 }
16796
16797 pub fn move_to_start_of_larger_syntax_node(
16798 &mut self,
16799 _: &MoveToStartOfLargerSyntaxNode,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) {
16803 self.move_cursors_to_syntax_nodes(window, cx, false);
16804 }
16805
16806 pub fn move_to_end_of_larger_syntax_node(
16807 &mut self,
16808 _: &MoveToEndOfLargerSyntaxNode,
16809 window: &mut Window,
16810 cx: &mut Context<Self>,
16811 ) {
16812 self.move_cursors_to_syntax_nodes(window, cx, true);
16813 }
16814
16815 fn find_syntax_node_boundary(
16816 &self,
16817 selection_pos: MultiBufferOffset,
16818 move_to_end: bool,
16819 display_map: &DisplaySnapshot,
16820 buffer: &MultiBufferSnapshot,
16821 ) -> MultiBufferOffset {
16822 let old_range = selection_pos..selection_pos;
16823 let mut new_pos = selection_pos;
16824 let mut search_range = old_range;
16825 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16826 search_range = range.clone();
16827 if !node.is_named()
16828 || display_map.intersects_fold(range.start)
16829 || display_map.intersects_fold(range.end)
16830 // If cursor is already at the end of the syntax node, continue searching
16831 || (move_to_end && range.end == selection_pos)
16832 // If cursor is already at the start of the syntax node, continue searching
16833 || (!move_to_end && range.start == selection_pos)
16834 {
16835 continue;
16836 }
16837
16838 // If we found a string_content node, find the largest parent that is still string_content
16839 // Enables us to skip to the end of strings without taking multiple steps inside the string
16840 let (_, final_range) = if node.kind() == "string_content" {
16841 let mut current_node = node;
16842 let mut current_range = range;
16843 while let Some((parent, parent_range)) =
16844 buffer.syntax_ancestor(current_range.clone())
16845 {
16846 if parent.kind() == "string_content" {
16847 current_node = parent;
16848 current_range = parent_range;
16849 } else {
16850 break;
16851 }
16852 }
16853
16854 (current_node, current_range)
16855 } else {
16856 (node, range)
16857 };
16858
16859 new_pos = if move_to_end {
16860 final_range.end
16861 } else {
16862 final_range.start
16863 };
16864
16865 break;
16866 }
16867
16868 new_pos
16869 }
16870
16871 fn move_cursors_to_syntax_nodes(
16872 &mut self,
16873 window: &mut Window,
16874 cx: &mut Context<Self>,
16875 move_to_end: bool,
16876 ) -> bool {
16877 let old_selections: Box<[_]> = self
16878 .selections
16879 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16880 .into();
16881 if old_selections.is_empty() {
16882 return false;
16883 }
16884
16885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16886
16887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16888 let buffer = self.buffer.read(cx).snapshot(cx);
16889
16890 let mut any_cursor_moved = false;
16891 let new_selections = old_selections
16892 .iter()
16893 .map(|selection| {
16894 if !selection.is_empty() {
16895 return selection.clone();
16896 }
16897
16898 let selection_pos = selection.head();
16899 let new_pos = self.find_syntax_node_boundary(
16900 selection_pos,
16901 move_to_end,
16902 &display_map,
16903 &buffer,
16904 );
16905
16906 any_cursor_moved |= new_pos != selection_pos;
16907
16908 Selection {
16909 id: selection.id,
16910 start: new_pos,
16911 end: new_pos,
16912 goal: SelectionGoal::None,
16913 reversed: false,
16914 }
16915 })
16916 .collect::<Vec<_>>();
16917
16918 self.change_selections(Default::default(), window, cx, |s| {
16919 s.select(new_selections);
16920 });
16921 self.request_autoscroll(Autoscroll::newest(), cx);
16922
16923 any_cursor_moved
16924 }
16925
16926 pub fn select_to_start_of_larger_syntax_node(
16927 &mut self,
16928 _: &SelectToStartOfLargerSyntaxNode,
16929 window: &mut Window,
16930 cx: &mut Context<Self>,
16931 ) {
16932 self.select_to_syntax_nodes(window, cx, false);
16933 }
16934
16935 pub fn select_to_end_of_larger_syntax_node(
16936 &mut self,
16937 _: &SelectToEndOfLargerSyntaxNode,
16938 window: &mut Window,
16939 cx: &mut Context<Self>,
16940 ) {
16941 self.select_to_syntax_nodes(window, cx, true);
16942 }
16943
16944 fn select_to_syntax_nodes(
16945 &mut self,
16946 window: &mut Window,
16947 cx: &mut Context<Self>,
16948 move_to_end: bool,
16949 ) {
16950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16951
16952 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16953 let buffer = self.buffer.read(cx).snapshot(cx);
16954 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
16955
16956 let new_selections = old_selections
16957 .iter()
16958 .map(|selection| {
16959 let new_pos = self.find_syntax_node_boundary(
16960 selection.head(),
16961 move_to_end,
16962 &display_map,
16963 &buffer,
16964 );
16965
16966 let mut new_selection = selection.clone();
16967 new_selection.set_head(new_pos, SelectionGoal::None);
16968 new_selection
16969 })
16970 .collect::<Vec<_>>();
16971
16972 self.change_selections(Default::default(), window, cx, |s| {
16973 s.select(new_selections);
16974 });
16975 }
16976
16977 pub fn move_to_enclosing_bracket(
16978 &mut self,
16979 _: &MoveToEnclosingBracket,
16980 window: &mut Window,
16981 cx: &mut Context<Self>,
16982 ) {
16983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16984 self.change_selections(Default::default(), window, cx, |s| {
16985 s.move_offsets_with(&mut |snapshot, selection| {
16986 let Some(enclosing_bracket_ranges) =
16987 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16988 else {
16989 return;
16990 };
16991
16992 let mut best_length = usize::MAX;
16993 let mut best_inside = false;
16994 let mut best_in_bracket_range = false;
16995 let mut best_destination = None;
16996 for (open, close) in enclosing_bracket_ranges {
16997 let close = close.to_inclusive();
16998 let length = *close.end() - open.start;
16999 let inside = selection.start >= open.end && selection.end <= *close.start();
17000 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17001 || close.contains(&selection.head());
17002
17003 // If best is next to a bracket and current isn't, skip
17004 if !in_bracket_range && best_in_bracket_range {
17005 continue;
17006 }
17007
17008 // Prefer smaller lengths unless best is inside and current isn't
17009 if length > best_length && (best_inside || !inside) {
17010 continue;
17011 }
17012
17013 best_length = length;
17014 best_inside = inside;
17015 best_in_bracket_range = in_bracket_range;
17016 best_destination = Some(
17017 if close.contains(&selection.start) && close.contains(&selection.end) {
17018 if inside { open.end } else { open.start }
17019 } else if inside {
17020 *close.start()
17021 } else {
17022 *close.end()
17023 },
17024 );
17025 }
17026
17027 if let Some(destination) = best_destination {
17028 selection.collapse_to(destination, SelectionGoal::None);
17029 }
17030 })
17031 });
17032 }
17033
17034 pub fn undo_selection(
17035 &mut self,
17036 _: &UndoSelection,
17037 window: &mut Window,
17038 cx: &mut Context<Self>,
17039 ) {
17040 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17041 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17042 self.selection_history.mode = SelectionHistoryMode::Undoing;
17043 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17044 this.end_selection(window, cx);
17045 this.change_selections(
17046 SelectionEffects::scroll(Autoscroll::newest()),
17047 window,
17048 cx,
17049 |s| s.select_anchors(entry.selections.to_vec()),
17050 );
17051 });
17052 self.selection_history.mode = SelectionHistoryMode::Normal;
17053
17054 self.select_next_state = entry.select_next_state;
17055 self.select_prev_state = entry.select_prev_state;
17056 self.add_selections_state = entry.add_selections_state;
17057 }
17058 }
17059
17060 pub fn redo_selection(
17061 &mut self,
17062 _: &RedoSelection,
17063 window: &mut Window,
17064 cx: &mut Context<Self>,
17065 ) {
17066 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17067 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17068 self.selection_history.mode = SelectionHistoryMode::Redoing;
17069 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17070 this.end_selection(window, cx);
17071 this.change_selections(
17072 SelectionEffects::scroll(Autoscroll::newest()),
17073 window,
17074 cx,
17075 |s| s.select_anchors(entry.selections.to_vec()),
17076 );
17077 });
17078 self.selection_history.mode = SelectionHistoryMode::Normal;
17079
17080 self.select_next_state = entry.select_next_state;
17081 self.select_prev_state = entry.select_prev_state;
17082 self.add_selections_state = entry.add_selections_state;
17083 }
17084 }
17085
17086 pub fn expand_excerpts(
17087 &mut self,
17088 action: &ExpandExcerpts,
17089 _: &mut Window,
17090 cx: &mut Context<Self>,
17091 ) {
17092 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17093 }
17094
17095 pub fn expand_excerpts_down(
17096 &mut self,
17097 action: &ExpandExcerptsDown,
17098 _: &mut Window,
17099 cx: &mut Context<Self>,
17100 ) {
17101 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17102 }
17103
17104 pub fn expand_excerpts_up(
17105 &mut self,
17106 action: &ExpandExcerptsUp,
17107 _: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) {
17110 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17111 }
17112
17113 pub fn expand_excerpts_for_direction(
17114 &mut self,
17115 lines: u32,
17116 direction: ExpandExcerptDirection,
17117 cx: &mut Context<Self>,
17118 ) {
17119 let selections = self.selections.disjoint_anchors_arc();
17120
17121 let lines = if lines == 0 {
17122 EditorSettings::get_global(cx).expand_excerpt_lines
17123 } else {
17124 lines
17125 };
17126
17127 let snapshot = self.buffer.read(cx).snapshot(cx);
17128 let excerpt_ids = selections
17129 .iter()
17130 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17131 .unique()
17132 .sorted()
17133 .collect::<Vec<_>>();
17134
17135 if self.delegate_expand_excerpts {
17136 cx.emit(EditorEvent::ExpandExcerptsRequested {
17137 excerpt_ids,
17138 lines,
17139 direction,
17140 });
17141 return;
17142 }
17143
17144 self.buffer.update(cx, |buffer, cx| {
17145 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17146 })
17147 }
17148
17149 pub fn expand_excerpt(
17150 &mut self,
17151 excerpt: ExcerptId,
17152 direction: ExpandExcerptDirection,
17153 window: &mut Window,
17154 cx: &mut Context<Self>,
17155 ) {
17156 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17157
17158 if self.delegate_expand_excerpts {
17159 cx.emit(EditorEvent::ExpandExcerptsRequested {
17160 excerpt_ids: vec![excerpt],
17161 lines: lines_to_expand,
17162 direction,
17163 });
17164 return;
17165 }
17166
17167 let current_scroll_position = self.scroll_position(cx);
17168 let mut scroll = None;
17169
17170 if direction == ExpandExcerptDirection::Down {
17171 let multi_buffer = self.buffer.read(cx);
17172 let snapshot = multi_buffer.snapshot(cx);
17173 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17174 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17175 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17176 {
17177 let buffer_snapshot = buffer.read(cx).snapshot();
17178 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17179 let last_row = buffer_snapshot.max_point().row;
17180 let lines_below = last_row.saturating_sub(excerpt_end_row);
17181 if lines_below >= lines_to_expand {
17182 scroll = Some(
17183 current_scroll_position
17184 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17185 );
17186 }
17187 }
17188 }
17189 if direction == ExpandExcerptDirection::Up
17190 && self
17191 .buffer
17192 .read(cx)
17193 .snapshot(cx)
17194 .excerpt_before(excerpt)
17195 .is_none()
17196 {
17197 scroll = Some(current_scroll_position);
17198 }
17199
17200 self.buffer.update(cx, |buffer, cx| {
17201 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17202 });
17203
17204 if let Some(new_scroll_position) = scroll {
17205 self.set_scroll_position(new_scroll_position, window, cx);
17206 }
17207 }
17208
17209 pub fn go_to_singleton_buffer_point(
17210 &mut self,
17211 point: Point,
17212 window: &mut Window,
17213 cx: &mut Context<Self>,
17214 ) {
17215 self.go_to_singleton_buffer_range(point..point, window, cx);
17216 }
17217
17218 pub fn go_to_singleton_buffer_range(
17219 &mut self,
17220 range: Range<Point>,
17221 window: &mut Window,
17222 cx: &mut Context<Self>,
17223 ) {
17224 let multibuffer = self.buffer().read(cx);
17225 let Some(buffer) = multibuffer.as_singleton() else {
17226 return;
17227 };
17228 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17229 return;
17230 };
17231 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17232 return;
17233 };
17234 self.change_selections(
17235 SelectionEffects::default().nav_history(true),
17236 window,
17237 cx,
17238 |s| s.select_anchor_ranges([start..end]),
17239 );
17240 }
17241
17242 pub fn go_to_diagnostic(
17243 &mut self,
17244 action: &GoToDiagnostic,
17245 window: &mut Window,
17246 cx: &mut Context<Self>,
17247 ) {
17248 if !self.diagnostics_enabled() {
17249 return;
17250 }
17251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17252 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17253 }
17254
17255 pub fn go_to_prev_diagnostic(
17256 &mut self,
17257 action: &GoToPreviousDiagnostic,
17258 window: &mut Window,
17259 cx: &mut Context<Self>,
17260 ) {
17261 if !self.diagnostics_enabled() {
17262 return;
17263 }
17264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17265 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17266 }
17267
17268 pub fn go_to_diagnostic_impl(
17269 &mut self,
17270 direction: Direction,
17271 severity: GoToDiagnosticSeverityFilter,
17272 window: &mut Window,
17273 cx: &mut Context<Self>,
17274 ) {
17275 let buffer = self.buffer.read(cx).snapshot(cx);
17276 let selection = self
17277 .selections
17278 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17279
17280 let mut active_group_id = None;
17281 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17282 && active_group.active_range.start.to_offset(&buffer) == selection.start
17283 {
17284 active_group_id = Some(active_group.group_id);
17285 }
17286
17287 fn filtered<'a>(
17288 severity: GoToDiagnosticSeverityFilter,
17289 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17290 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17291 diagnostics
17292 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17293 .filter(|entry| entry.range.start != entry.range.end)
17294 .filter(|entry| !entry.diagnostic.is_unnecessary)
17295 }
17296
17297 let before = filtered(
17298 severity,
17299 buffer
17300 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17301 .filter(|entry| entry.range.start <= selection.start),
17302 );
17303 let after = filtered(
17304 severity,
17305 buffer
17306 .diagnostics_in_range(selection.start..buffer.len())
17307 .filter(|entry| entry.range.start >= selection.start),
17308 );
17309
17310 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17311 if direction == Direction::Prev {
17312 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17313 {
17314 for diagnostic in prev_diagnostics.into_iter().rev() {
17315 if diagnostic.range.start != selection.start
17316 || active_group_id
17317 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17318 {
17319 found = Some(diagnostic);
17320 break 'outer;
17321 }
17322 }
17323 }
17324 } else {
17325 for diagnostic in after.chain(before) {
17326 if diagnostic.range.start != selection.start
17327 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17328 {
17329 found = Some(diagnostic);
17330 break;
17331 }
17332 }
17333 }
17334 let Some(next_diagnostic) = found else {
17335 return;
17336 };
17337
17338 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17339 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17340 return;
17341 };
17342 let snapshot = self.snapshot(window, cx);
17343 if snapshot.intersects_fold(next_diagnostic.range.start) {
17344 self.unfold_ranges(
17345 std::slice::from_ref(&next_diagnostic.range),
17346 true,
17347 false,
17348 cx,
17349 );
17350 }
17351 self.change_selections(Default::default(), window, cx, |s| {
17352 s.select_ranges(vec![
17353 next_diagnostic.range.start..next_diagnostic.range.start,
17354 ])
17355 });
17356 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17357 self.refresh_edit_prediction(false, true, window, cx);
17358 }
17359
17360 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17362 let snapshot = self.snapshot(window, cx);
17363 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17364 self.go_to_hunk_before_or_after_position(
17365 &snapshot,
17366 selection.head(),
17367 Direction::Next,
17368 true,
17369 window,
17370 cx,
17371 );
17372 }
17373
17374 pub fn go_to_hunk_before_or_after_position(
17375 &mut self,
17376 snapshot: &EditorSnapshot,
17377 position: Point,
17378 direction: Direction,
17379 wrap_around: bool,
17380 window: &mut Window,
17381 cx: &mut Context<Editor>,
17382 ) {
17383 let row = if direction == Direction::Next {
17384 self.hunk_after_position(snapshot, position, wrap_around)
17385 .map(|hunk| hunk.row_range.start)
17386 } else {
17387 self.hunk_before_position(snapshot, position, wrap_around)
17388 };
17389
17390 if let Some(row) = row {
17391 let destination = Point::new(row.0, 0);
17392 let autoscroll = Autoscroll::center();
17393
17394 self.unfold_ranges(&[destination..destination], false, false, cx);
17395 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17396 s.select_ranges([destination..destination]);
17397 });
17398 }
17399 }
17400
17401 fn hunk_after_position(
17402 &mut self,
17403 snapshot: &EditorSnapshot,
17404 position: Point,
17405 wrap_around: bool,
17406 ) -> Option<MultiBufferDiffHunk> {
17407 let result = snapshot
17408 .buffer_snapshot()
17409 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17410 .find(|hunk| hunk.row_range.start.0 > position.row);
17411
17412 if wrap_around {
17413 result.or_else(|| {
17414 snapshot
17415 .buffer_snapshot()
17416 .diff_hunks_in_range(Point::zero()..position)
17417 .find(|hunk| hunk.row_range.end.0 < position.row)
17418 })
17419 } else {
17420 result
17421 }
17422 }
17423
17424 fn go_to_prev_hunk(
17425 &mut self,
17426 _: &GoToPreviousHunk,
17427 window: &mut Window,
17428 cx: &mut Context<Self>,
17429 ) {
17430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17431 let snapshot = self.snapshot(window, cx);
17432 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17433 self.go_to_hunk_before_or_after_position(
17434 &snapshot,
17435 selection.head(),
17436 Direction::Prev,
17437 true,
17438 window,
17439 cx,
17440 );
17441 }
17442
17443 fn hunk_before_position(
17444 &mut self,
17445 snapshot: &EditorSnapshot,
17446 position: Point,
17447 wrap_around: bool,
17448 ) -> Option<MultiBufferRow> {
17449 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17450
17451 if wrap_around {
17452 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17453 } else {
17454 result
17455 }
17456 }
17457
17458 fn go_to_next_change(
17459 &mut self,
17460 _: &GoToNextChange,
17461 window: &mut Window,
17462 cx: &mut Context<Self>,
17463 ) {
17464 if let Some(selections) = self
17465 .change_list
17466 .next_change(1, Direction::Next)
17467 .map(|s| s.to_vec())
17468 {
17469 self.change_selections(Default::default(), window, cx, |s| {
17470 let map = s.display_snapshot();
17471 s.select_display_ranges(selections.iter().map(|a| {
17472 let point = a.to_display_point(&map);
17473 point..point
17474 }))
17475 })
17476 }
17477 }
17478
17479 fn go_to_previous_change(
17480 &mut self,
17481 _: &GoToPreviousChange,
17482 window: &mut Window,
17483 cx: &mut Context<Self>,
17484 ) {
17485 if let Some(selections) = self
17486 .change_list
17487 .next_change(1, Direction::Prev)
17488 .map(|s| s.to_vec())
17489 {
17490 self.change_selections(Default::default(), window, cx, |s| {
17491 let map = s.display_snapshot();
17492 s.select_display_ranges(selections.iter().map(|a| {
17493 let point = a.to_display_point(&map);
17494 point..point
17495 }))
17496 })
17497 }
17498 }
17499
17500 pub fn go_to_next_document_highlight(
17501 &mut self,
17502 _: &GoToNextDocumentHighlight,
17503 window: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) {
17506 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17507 }
17508
17509 pub fn go_to_prev_document_highlight(
17510 &mut self,
17511 _: &GoToPreviousDocumentHighlight,
17512 window: &mut Window,
17513 cx: &mut Context<Self>,
17514 ) {
17515 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17516 }
17517
17518 pub fn go_to_document_highlight_before_or_after_position(
17519 &mut self,
17520 direction: Direction,
17521 window: &mut Window,
17522 cx: &mut Context<Editor>,
17523 ) {
17524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17525 let snapshot = self.snapshot(window, cx);
17526 let buffer = &snapshot.buffer_snapshot();
17527 let position = self
17528 .selections
17529 .newest::<Point>(&snapshot.display_snapshot)
17530 .head();
17531 let anchor_position = buffer.anchor_after(position);
17532
17533 // Get all document highlights (both read and write)
17534 let mut all_highlights = Vec::new();
17535
17536 if let Some((_, read_highlights)) = self
17537 .background_highlights
17538 .get(&HighlightKey::DocumentHighlightRead)
17539 {
17540 all_highlights.extend(read_highlights.iter());
17541 }
17542
17543 if let Some((_, write_highlights)) = self
17544 .background_highlights
17545 .get(&HighlightKey::DocumentHighlightWrite)
17546 {
17547 all_highlights.extend(write_highlights.iter());
17548 }
17549
17550 if all_highlights.is_empty() {
17551 return;
17552 }
17553
17554 // Sort highlights by position
17555 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17556
17557 let target_highlight = match direction {
17558 Direction::Next => {
17559 // Find the first highlight after the current position
17560 all_highlights
17561 .iter()
17562 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17563 }
17564 Direction::Prev => {
17565 // Find the last highlight before the current position
17566 all_highlights
17567 .iter()
17568 .rev()
17569 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17570 }
17571 };
17572
17573 if let Some(highlight) = target_highlight {
17574 let destination = highlight.start.to_point(buffer);
17575 let autoscroll = Autoscroll::center();
17576
17577 self.unfold_ranges(&[destination..destination], false, false, cx);
17578 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17579 s.select_ranges([destination..destination]);
17580 });
17581 }
17582 }
17583
17584 fn go_to_line<T: 'static>(
17585 &mut self,
17586 position: Anchor,
17587 highlight_color: Option<Hsla>,
17588 window: &mut Window,
17589 cx: &mut Context<Self>,
17590 ) {
17591 let snapshot = self.snapshot(window, cx).display_snapshot;
17592 let position = position.to_point(&snapshot.buffer_snapshot());
17593 let start = snapshot
17594 .buffer_snapshot()
17595 .clip_point(Point::new(position.row, 0), Bias::Left);
17596 let end = start + Point::new(1, 0);
17597 let start = snapshot.buffer_snapshot().anchor_before(start);
17598 let end = snapshot.buffer_snapshot().anchor_before(end);
17599
17600 self.highlight_rows::<T>(
17601 start..end,
17602 highlight_color
17603 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17604 Default::default(),
17605 cx,
17606 );
17607
17608 if self.buffer.read(cx).is_singleton() {
17609 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17610 }
17611 }
17612
17613 pub fn go_to_definition(
17614 &mut self,
17615 _: &GoToDefinition,
17616 window: &mut Window,
17617 cx: &mut Context<Self>,
17618 ) -> Task<Result<Navigated>> {
17619 let definition =
17620 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17621 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17622 cx.spawn_in(window, async move |editor, cx| {
17623 if definition.await? == Navigated::Yes {
17624 return Ok(Navigated::Yes);
17625 }
17626 match fallback_strategy {
17627 GoToDefinitionFallback::None => Ok(Navigated::No),
17628 GoToDefinitionFallback::FindAllReferences => {
17629 match editor.update_in(cx, |editor, window, cx| {
17630 editor.find_all_references(&FindAllReferences::default(), window, cx)
17631 })? {
17632 Some(references) => references.await,
17633 None => Ok(Navigated::No),
17634 }
17635 }
17636 }
17637 })
17638 }
17639
17640 pub fn go_to_declaration(
17641 &mut self,
17642 _: &GoToDeclaration,
17643 window: &mut Window,
17644 cx: &mut Context<Self>,
17645 ) -> Task<Result<Navigated>> {
17646 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17647 }
17648
17649 pub fn go_to_declaration_split(
17650 &mut self,
17651 _: &GoToDeclaration,
17652 window: &mut Window,
17653 cx: &mut Context<Self>,
17654 ) -> Task<Result<Navigated>> {
17655 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17656 }
17657
17658 pub fn go_to_implementation(
17659 &mut self,
17660 _: &GoToImplementation,
17661 window: &mut Window,
17662 cx: &mut Context<Self>,
17663 ) -> Task<Result<Navigated>> {
17664 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17665 }
17666
17667 pub fn go_to_implementation_split(
17668 &mut self,
17669 _: &GoToImplementationSplit,
17670 window: &mut Window,
17671 cx: &mut Context<Self>,
17672 ) -> Task<Result<Navigated>> {
17673 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17674 }
17675
17676 pub fn go_to_type_definition(
17677 &mut self,
17678 _: &GoToTypeDefinition,
17679 window: &mut Window,
17680 cx: &mut Context<Self>,
17681 ) -> Task<Result<Navigated>> {
17682 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17683 }
17684
17685 pub fn go_to_definition_split(
17686 &mut self,
17687 _: &GoToDefinitionSplit,
17688 window: &mut Window,
17689 cx: &mut Context<Self>,
17690 ) -> Task<Result<Navigated>> {
17691 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17692 }
17693
17694 pub fn go_to_type_definition_split(
17695 &mut self,
17696 _: &GoToTypeDefinitionSplit,
17697 window: &mut Window,
17698 cx: &mut Context<Self>,
17699 ) -> Task<Result<Navigated>> {
17700 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17701 }
17702
17703 fn go_to_definition_of_kind(
17704 &mut self,
17705 kind: GotoDefinitionKind,
17706 split: bool,
17707 window: &mut Window,
17708 cx: &mut Context<Self>,
17709 ) -> Task<Result<Navigated>> {
17710 let Some(provider) = self.semantics_provider.clone() else {
17711 return Task::ready(Ok(Navigated::No));
17712 };
17713 let head = self
17714 .selections
17715 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17716 .head();
17717 let buffer = self.buffer.read(cx);
17718 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17719 return Task::ready(Ok(Navigated::No));
17720 };
17721 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17722 return Task::ready(Ok(Navigated::No));
17723 };
17724
17725 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17726
17727 cx.spawn_in(window, async move |editor, cx| {
17728 let Some(definitions) = definitions.await? else {
17729 return Ok(Navigated::No);
17730 };
17731 let navigated = editor
17732 .update_in(cx, |editor, window, cx| {
17733 editor.navigate_to_hover_links(
17734 Some(kind),
17735 definitions
17736 .into_iter()
17737 .filter(|location| {
17738 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17739 })
17740 .map(HoverLink::Text)
17741 .collect::<Vec<_>>(),
17742 nav_entry,
17743 split,
17744 window,
17745 cx,
17746 )
17747 })?
17748 .await?;
17749 anyhow::Ok(navigated)
17750 })
17751 }
17752
17753 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17754 let selection = self.selections.newest_anchor();
17755 let head = selection.head();
17756 let tail = selection.tail();
17757
17758 let Some((buffer, start_position)) =
17759 self.buffer.read(cx).text_anchor_for_position(head, cx)
17760 else {
17761 return;
17762 };
17763
17764 let end_position = if head != tail {
17765 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17766 return;
17767 };
17768 Some(pos)
17769 } else {
17770 None
17771 };
17772
17773 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17774 let url = if let Some(end_pos) = end_position {
17775 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17776 } else {
17777 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17778 };
17779
17780 if let Some(url) = url {
17781 cx.update(|window, cx| {
17782 if parse_zed_link(&url, cx).is_some() {
17783 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17784 } else {
17785 cx.open_url(&url);
17786 }
17787 })?;
17788 }
17789
17790 anyhow::Ok(())
17791 });
17792
17793 url_finder.detach();
17794 }
17795
17796 pub fn open_selected_filename(
17797 &mut self,
17798 _: &OpenSelectedFilename,
17799 window: &mut Window,
17800 cx: &mut Context<Self>,
17801 ) {
17802 let Some(workspace) = self.workspace() else {
17803 return;
17804 };
17805
17806 let position = self.selections.newest_anchor().head();
17807
17808 let Some((buffer, buffer_position)) =
17809 self.buffer.read(cx).text_anchor_for_position(position, cx)
17810 else {
17811 return;
17812 };
17813
17814 let project = self.project.clone();
17815
17816 cx.spawn_in(window, async move |_, cx| {
17817 let result = find_file(&buffer, project, buffer_position, cx).await;
17818
17819 if let Some((_, path)) = result {
17820 workspace
17821 .update_in(cx, |workspace, window, cx| {
17822 workspace.open_resolved_path(path, window, cx)
17823 })?
17824 .await?;
17825 }
17826 anyhow::Ok(())
17827 })
17828 .detach();
17829 }
17830
17831 pub(crate) fn navigate_to_hover_links(
17832 &mut self,
17833 kind: Option<GotoDefinitionKind>,
17834 definitions: Vec<HoverLink>,
17835 origin: Option<NavigationEntry>,
17836 split: bool,
17837 window: &mut Window,
17838 cx: &mut Context<Editor>,
17839 ) -> Task<Result<Navigated>> {
17840 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17841 let mut first_url_or_file = None;
17842 let definitions: Vec<_> = definitions
17843 .into_iter()
17844 .filter_map(|def| match def {
17845 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17846 HoverLink::InlayHint(lsp_location, server_id) => {
17847 let computation =
17848 self.compute_target_location(lsp_location, server_id, window, cx);
17849 Some(cx.background_spawn(computation))
17850 }
17851 HoverLink::Url(url) => {
17852 first_url_or_file = Some(Either::Left(url));
17853 None
17854 }
17855 HoverLink::File(path) => {
17856 first_url_or_file = Some(Either::Right(path));
17857 None
17858 }
17859 })
17860 .collect();
17861
17862 let workspace = self.workspace();
17863
17864 cx.spawn_in(window, async move |editor, cx| {
17865 let locations: Vec<Location> = future::join_all(definitions)
17866 .await
17867 .into_iter()
17868 .filter_map(|location| location.transpose())
17869 .collect::<Result<_>>()
17870 .context("location tasks")?;
17871 let mut locations = cx.update(|_, cx| {
17872 locations
17873 .into_iter()
17874 .map(|location| {
17875 let buffer = location.buffer.read(cx);
17876 (location.buffer, location.range.to_point(buffer))
17877 })
17878 .into_group_map()
17879 })?;
17880 let mut num_locations = 0;
17881 for ranges in locations.values_mut() {
17882 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17883 ranges.dedup();
17884 num_locations += ranges.len();
17885 }
17886
17887 if num_locations > 1 {
17888 let tab_kind = match kind {
17889 Some(GotoDefinitionKind::Implementation) => "Implementations",
17890 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17891 Some(GotoDefinitionKind::Declaration) => "Declarations",
17892 Some(GotoDefinitionKind::Type) => "Types",
17893 };
17894 let title = editor
17895 .update_in(cx, |_, _, cx| {
17896 let target = locations
17897 .iter()
17898 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17899 .map(|(buffer, location)| {
17900 buffer
17901 .read(cx)
17902 .text_for_range(location.clone())
17903 .collect::<String>()
17904 })
17905 .filter(|text| !text.contains('\n'))
17906 .unique()
17907 .take(3)
17908 .join(", ");
17909 if target.is_empty() {
17910 tab_kind.to_owned()
17911 } else {
17912 format!("{tab_kind} for {target}")
17913 }
17914 })
17915 .context("buffer title")?;
17916
17917 let Some(workspace) = workspace else {
17918 return Ok(Navigated::No);
17919 };
17920
17921 let opened = workspace
17922 .update_in(cx, |workspace, window, cx| {
17923 let allow_preview = PreviewTabsSettings::get_global(cx)
17924 .enable_preview_multibuffer_from_code_navigation;
17925 if let Some((target_editor, target_pane)) =
17926 Self::open_locations_in_multibuffer(
17927 workspace,
17928 locations,
17929 title,
17930 split,
17931 allow_preview,
17932 MultibufferSelectionMode::First,
17933 window,
17934 cx,
17935 )
17936 {
17937 // We create our own nav history instead of using
17938 // `target_editor.nav_history` because `nav_history`
17939 // seems to be populated asynchronously when an item
17940 // is added to a pane
17941 let mut nav_history = target_pane
17942 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
17943 target_editor.update(cx, |editor, cx| {
17944 let nav_data = editor
17945 .navigation_data(editor.selections.newest_anchor().head(), cx);
17946 let target =
17947 Some(nav_history.navigation_entry(Some(
17948 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
17949 )));
17950 nav_history.push_tag(origin, target);
17951 })
17952 }
17953 })
17954 .is_ok();
17955
17956 anyhow::Ok(Navigated::from_bool(opened))
17957 } else if num_locations == 0 {
17958 // If there is one url or file, open it directly
17959 match first_url_or_file {
17960 Some(Either::Left(url)) => {
17961 cx.update(|window, cx| {
17962 if parse_zed_link(&url, cx).is_some() {
17963 window
17964 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17965 } else {
17966 cx.open_url(&url);
17967 }
17968 })?;
17969 Ok(Navigated::Yes)
17970 }
17971 Some(Either::Right(path)) => {
17972 // TODO(andrew): respect preview tab settings
17973 // `enable_keep_preview_on_code_navigation` and
17974 // `enable_preview_file_from_code_navigation`
17975 let Some(workspace) = workspace else {
17976 return Ok(Navigated::No);
17977 };
17978 workspace
17979 .update_in(cx, |workspace, window, cx| {
17980 workspace.open_resolved_path(path, window, cx)
17981 })?
17982 .await?;
17983 Ok(Navigated::Yes)
17984 }
17985 None => Ok(Navigated::No),
17986 }
17987 } else {
17988 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17989 let target_range = target_ranges.first().unwrap().clone();
17990
17991 editor.update_in(cx, |editor, window, cx| {
17992 let range = editor.range_for_match(&target_range);
17993 let range = collapse_multiline_range(range);
17994
17995 if !split
17996 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17997 {
17998 editor.go_to_singleton_buffer_range(range, window, cx);
17999
18000 let target =
18001 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18002 if let Some(mut nav_history) = editor.nav_history.clone() {
18003 nav_history.push_tag(origin, target);
18004 }
18005 } else {
18006 let Some(workspace) = workspace else {
18007 return Navigated::No;
18008 };
18009 let pane = workspace.read(cx).active_pane().clone();
18010 window.defer(cx, move |window, cx| {
18011 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18012 workspace.update(cx, |workspace, cx| {
18013 let pane = if split {
18014 workspace.adjacent_pane(window, cx)
18015 } else {
18016 workspace.active_pane().clone()
18017 };
18018
18019 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18020 let keep_old_preview = preview_tabs_settings
18021 .enable_keep_preview_on_code_navigation;
18022 let allow_new_preview = preview_tabs_settings
18023 .enable_preview_file_from_code_navigation;
18024
18025 let editor = workspace.open_project_item(
18026 pane.clone(),
18027 target_buffer.clone(),
18028 true,
18029 true,
18030 keep_old_preview,
18031 allow_new_preview,
18032 window,
18033 cx,
18034 );
18035 (editor, pane)
18036 });
18037 // We create our own nav history instead of using
18038 // `target_editor.nav_history` because `nav_history`
18039 // seems to be populated asynchronously when an item
18040 // is added to a pane
18041 let mut nav_history = target_pane
18042 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18043 target_editor.update(cx, |target_editor, cx| {
18044 // When selecting a definition in a different buffer, disable the nav history
18045 // to avoid creating a history entry at the previous cursor location.
18046 pane.update(cx, |pane, _| pane.disable_history());
18047 target_editor.go_to_singleton_buffer_range(range, window, cx);
18048
18049 let nav_data = target_editor.navigation_data(
18050 target_editor.selections.newest_anchor().head(),
18051 cx,
18052 );
18053 let target =
18054 Some(nav_history.navigation_entry(Some(
18055 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18056 )));
18057 nav_history.push_tag(origin, target);
18058 pane.update(cx, |pane, _| pane.enable_history());
18059 });
18060 });
18061 }
18062 Navigated::Yes
18063 })
18064 }
18065 })
18066 }
18067
18068 fn compute_target_location(
18069 &self,
18070 lsp_location: lsp::Location,
18071 server_id: LanguageServerId,
18072 window: &mut Window,
18073 cx: &mut Context<Self>,
18074 ) -> Task<anyhow::Result<Option<Location>>> {
18075 let Some(project) = self.project.clone() else {
18076 return Task::ready(Ok(None));
18077 };
18078
18079 cx.spawn_in(window, async move |editor, cx| {
18080 let location_task = editor.update(cx, |_, cx| {
18081 project.update(cx, |project, cx| {
18082 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18083 })
18084 })?;
18085 let location = Some({
18086 let target_buffer_handle = location_task.await.context("open local buffer")?;
18087 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18088 let target_start = target_buffer
18089 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18090 let target_end = target_buffer
18091 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18092 target_buffer.anchor_after(target_start)
18093 ..target_buffer.anchor_before(target_end)
18094 });
18095 Location {
18096 buffer: target_buffer_handle,
18097 range,
18098 }
18099 });
18100 Ok(location)
18101 })
18102 }
18103
18104 fn go_to_next_reference(
18105 &mut self,
18106 _: &GoToNextReference,
18107 window: &mut Window,
18108 cx: &mut Context<Self>,
18109 ) {
18110 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18111 if let Some(task) = task {
18112 task.detach();
18113 };
18114 }
18115
18116 fn go_to_prev_reference(
18117 &mut self,
18118 _: &GoToPreviousReference,
18119 window: &mut Window,
18120 cx: &mut Context<Self>,
18121 ) {
18122 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18123 if let Some(task) = task {
18124 task.detach();
18125 };
18126 }
18127
18128 fn go_to_symbol_by_offset(
18129 &mut self,
18130 window: &mut Window,
18131 cx: &mut Context<Self>,
18132 offset: i8,
18133 ) -> Task<Result<()>> {
18134 let editor_snapshot = self.snapshot(window, cx);
18135
18136 // We don't care about multi-buffer symbols
18137 let Some((excerpt_id, _, _)) = editor_snapshot.as_singleton() else {
18138 return Task::ready(Ok(()));
18139 };
18140
18141 let cursor_offset = self
18142 .selections
18143 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18144 .head();
18145
18146 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18147 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18148 let buffer = ed.buffer.read(cx).as_singleton()?;
18149 Some(buffer.read(cx).remote_id())
18150 }) else {
18151 return Ok(());
18152 };
18153
18154 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18155 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18156
18157 let multi_snapshot = editor_snapshot.buffer();
18158 let buffer_range = |range: &Range<_>| {
18159 Anchor::range_in_buffer(excerpt_id, range.clone()).to_offset(multi_snapshot)
18160 };
18161
18162 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18163 let current_idx = outline_items
18164 .iter()
18165 .enumerate()
18166 .filter_map(|(idx, item)| {
18167 // Find the closest outline item by distance between outline text and cursor location
18168 let source_range = buffer_range(&item.source_range_for_text);
18169 let distance_to_closest_endpoint = cmp::min(
18170 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18171 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18172 );
18173
18174 let item_towards_offset =
18175 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18176 == (offset as isize).signum();
18177
18178 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18179
18180 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18181 // we should not already be within the outline's source range. We then pick the closest outline
18182 // item.
18183 (item_towards_offset && !source_range_contains_cursor)
18184 .then_some((distance_to_closest_endpoint, idx))
18185 })
18186 .min()
18187 .map(|(_, idx)| idx);
18188
18189 let Some(idx) = current_idx else {
18190 return;
18191 };
18192
18193 let range = buffer_range(&outline_items[idx].source_range_for_text);
18194 let selection = [range.start..range.start];
18195
18196 let _ = editor
18197 .update(acx, |editor, ecx| {
18198 editor.change_selections(
18199 SelectionEffects::scroll(Autoscroll::newest()),
18200 window,
18201 ecx,
18202 |s| s.select_ranges(selection),
18203 );
18204 })
18205 .ok();
18206 })?;
18207
18208 Ok(())
18209 })
18210 }
18211
18212 fn go_to_next_symbol(
18213 &mut self,
18214 _: &GoToNextSymbol,
18215 window: &mut Window,
18216 cx: &mut Context<Self>,
18217 ) {
18218 self.go_to_symbol_by_offset(window, cx, 1).detach();
18219 }
18220
18221 fn go_to_previous_symbol(
18222 &mut self,
18223 _: &GoToPreviousSymbol,
18224 window: &mut Window,
18225 cx: &mut Context<Self>,
18226 ) {
18227 self.go_to_symbol_by_offset(window, cx, -1).detach();
18228 }
18229
18230 pub fn go_to_reference_before_or_after_position(
18231 &mut self,
18232 direction: Direction,
18233 count: usize,
18234 window: &mut Window,
18235 cx: &mut Context<Self>,
18236 ) -> Option<Task<Result<()>>> {
18237 let selection = self.selections.newest_anchor();
18238 let head = selection.head();
18239
18240 let multi_buffer = self.buffer.read(cx);
18241
18242 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18243 let workspace = self.workspace()?;
18244 let project = workspace.read(cx).project().clone();
18245 let references =
18246 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18247 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18248 let Some(locations) = references.await? else {
18249 return Ok(());
18250 };
18251
18252 if locations.is_empty() {
18253 // totally normal - the cursor may be on something which is not
18254 // a symbol (e.g. a keyword)
18255 log::info!("no references found under cursor");
18256 return Ok(());
18257 }
18258
18259 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18260
18261 let (locations, current_location_index) =
18262 multi_buffer.update(cx, |multi_buffer, cx| {
18263 let mut locations = locations
18264 .into_iter()
18265 .filter_map(|loc| {
18266 let start = multi_buffer.buffer_anchor_to_anchor(
18267 &loc.buffer,
18268 loc.range.start,
18269 cx,
18270 )?;
18271 let end = multi_buffer.buffer_anchor_to_anchor(
18272 &loc.buffer,
18273 loc.range.end,
18274 cx,
18275 )?;
18276 Some(start..end)
18277 })
18278 .collect::<Vec<_>>();
18279
18280 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18281 // There is an O(n) implementation, but given this list will be
18282 // small (usually <100 items), the extra O(log(n)) factor isn't
18283 // worth the (surprisingly large amount of) extra complexity.
18284 locations
18285 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18286
18287 let head_offset = head.to_offset(&multi_buffer_snapshot);
18288
18289 let current_location_index = locations.iter().position(|loc| {
18290 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18291 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18292 });
18293
18294 (locations, current_location_index)
18295 });
18296
18297 let Some(current_location_index) = current_location_index else {
18298 // This indicates something has gone wrong, because we already
18299 // handle the "no references" case above
18300 log::error!(
18301 "failed to find current reference under cursor. Total references: {}",
18302 locations.len()
18303 );
18304 return Ok(());
18305 };
18306
18307 let destination_location_index = match direction {
18308 Direction::Next => (current_location_index + count) % locations.len(),
18309 Direction::Prev => {
18310 (current_location_index + locations.len() - count % locations.len())
18311 % locations.len()
18312 }
18313 };
18314
18315 // TODO(cameron): is this needed?
18316 // the thinking is to avoid "jumping to the current location" (avoid
18317 // polluting "jumplist" in vim terms)
18318 if current_location_index == destination_location_index {
18319 return Ok(());
18320 }
18321
18322 let Range { start, end } = locations[destination_location_index];
18323
18324 editor.update_in(cx, |editor, window, cx| {
18325 let effects = SelectionEffects::default();
18326
18327 editor.unfold_ranges(&[start..end], false, false, cx);
18328 editor.change_selections(effects, window, cx, |s| {
18329 s.select_ranges([start..start]);
18330 });
18331 })?;
18332
18333 Ok(())
18334 }))
18335 }
18336
18337 pub fn find_all_references(
18338 &mut self,
18339 action: &FindAllReferences,
18340 window: &mut Window,
18341 cx: &mut Context<Self>,
18342 ) -> Option<Task<Result<Navigated>>> {
18343 let always_open_multibuffer = action.always_open_multibuffer;
18344 let selection = self.selections.newest_anchor();
18345 let multi_buffer = self.buffer.read(cx);
18346 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18347 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18348 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18349 let head = selection_offset.head();
18350
18351 let head_anchor = multi_buffer_snapshot.anchor_at(
18352 head,
18353 if head < selection_offset.tail() {
18354 Bias::Right
18355 } else {
18356 Bias::Left
18357 },
18358 );
18359
18360 match self
18361 .find_all_references_task_sources
18362 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18363 {
18364 Ok(_) => {
18365 log::info!(
18366 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18367 );
18368 return None;
18369 }
18370 Err(i) => {
18371 self.find_all_references_task_sources.insert(i, head_anchor);
18372 }
18373 }
18374
18375 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18376 let workspace = self.workspace()?;
18377 let project = workspace.read(cx).project().clone();
18378 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18379 Some(cx.spawn_in(window, async move |editor, cx| {
18380 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18381 if let Ok(i) = editor
18382 .find_all_references_task_sources
18383 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18384 {
18385 editor.find_all_references_task_sources.remove(i);
18386 }
18387 });
18388
18389 let Some(locations) = references.await? else {
18390 return anyhow::Ok(Navigated::No);
18391 };
18392 let mut locations = cx.update(|_, cx| {
18393 locations
18394 .into_iter()
18395 .map(|location| {
18396 let buffer = location.buffer.read(cx);
18397 (location.buffer, location.range.to_point(buffer))
18398 })
18399 // if special-casing the single-match case, remove ranges
18400 // that intersect current selection
18401 .filter(|(location_buffer, location)| {
18402 if always_open_multibuffer || &buffer != location_buffer {
18403 return true;
18404 }
18405
18406 !location.contains_inclusive(&selection_point.range())
18407 })
18408 .into_group_map()
18409 })?;
18410 if locations.is_empty() {
18411 return anyhow::Ok(Navigated::No);
18412 }
18413 for ranges in locations.values_mut() {
18414 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18415 ranges.dedup();
18416 }
18417 let mut num_locations = 0;
18418 for ranges in locations.values_mut() {
18419 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18420 ranges.dedup();
18421 num_locations += ranges.len();
18422 }
18423
18424 if num_locations == 1 && !always_open_multibuffer {
18425 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18426 let target_range = target_ranges.first().unwrap().clone();
18427
18428 return editor.update_in(cx, |editor, window, cx| {
18429 let range = target_range.to_point(target_buffer.read(cx));
18430 let range = editor.range_for_match(&range);
18431 let range = range.start..range.start;
18432
18433 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18434 editor.go_to_singleton_buffer_range(range, window, cx);
18435 } else {
18436 let pane = workspace.read(cx).active_pane().clone();
18437 window.defer(cx, move |window, cx| {
18438 let target_editor: Entity<Self> =
18439 workspace.update(cx, |workspace, cx| {
18440 let pane = workspace.active_pane().clone();
18441
18442 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18443 let keep_old_preview = preview_tabs_settings
18444 .enable_keep_preview_on_code_navigation;
18445 let allow_new_preview = preview_tabs_settings
18446 .enable_preview_file_from_code_navigation;
18447
18448 workspace.open_project_item(
18449 pane,
18450 target_buffer.clone(),
18451 true,
18452 true,
18453 keep_old_preview,
18454 allow_new_preview,
18455 window,
18456 cx,
18457 )
18458 });
18459 target_editor.update(cx, |target_editor, cx| {
18460 // When selecting a definition in a different buffer, disable the nav history
18461 // to avoid creating a history entry at the previous cursor location.
18462 pane.update(cx, |pane, _| pane.disable_history());
18463 target_editor.go_to_singleton_buffer_range(range, window, cx);
18464 pane.update(cx, |pane, _| pane.enable_history());
18465 });
18466 });
18467 }
18468 Navigated::No
18469 });
18470 }
18471
18472 workspace.update_in(cx, |workspace, window, cx| {
18473 let target = locations
18474 .iter()
18475 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18476 .map(|(buffer, location)| {
18477 buffer
18478 .read(cx)
18479 .text_for_range(location.clone())
18480 .collect::<String>()
18481 })
18482 .filter(|text| !text.contains('\n'))
18483 .unique()
18484 .take(3)
18485 .join(", ");
18486 let title = if target.is_empty() {
18487 "References".to_owned()
18488 } else {
18489 format!("References to {target}")
18490 };
18491 let allow_preview = PreviewTabsSettings::get_global(cx)
18492 .enable_preview_multibuffer_from_code_navigation;
18493 Self::open_locations_in_multibuffer(
18494 workspace,
18495 locations,
18496 title,
18497 false,
18498 allow_preview,
18499 MultibufferSelectionMode::First,
18500 window,
18501 cx,
18502 );
18503 Navigated::Yes
18504 })
18505 }))
18506 }
18507
18508 /// Opens a multibuffer with the given project locations in it.
18509 pub fn open_locations_in_multibuffer(
18510 workspace: &mut Workspace,
18511 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18512 title: String,
18513 split: bool,
18514 allow_preview: bool,
18515 multibuffer_selection_mode: MultibufferSelectionMode,
18516 window: &mut Window,
18517 cx: &mut Context<Workspace>,
18518 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18519 if locations.is_empty() {
18520 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18521 return None;
18522 }
18523
18524 let capability = workspace.project().read(cx).capability();
18525 let mut ranges = <Vec<Range<Anchor>>>::new();
18526
18527 // a key to find existing multibuffer editors with the same set of locations
18528 // to prevent us from opening more and more multibuffer tabs for searches and the like
18529 let mut key = (title.clone(), vec![]);
18530 let excerpt_buffer = cx.new(|cx| {
18531 let key = &mut key.1;
18532 let mut multibuffer = MultiBuffer::new(capability);
18533 for (buffer, mut ranges_for_buffer) in locations {
18534 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18535 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18536 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18537 PathKey::for_buffer(&buffer, cx),
18538 buffer.clone(),
18539 ranges_for_buffer,
18540 multibuffer_context_lines(cx),
18541 cx,
18542 );
18543 ranges.extend(new_ranges)
18544 }
18545
18546 multibuffer.with_title(title)
18547 });
18548 let existing = workspace.active_pane().update(cx, |pane, cx| {
18549 pane.items()
18550 .filter_map(|item| item.downcast::<Editor>())
18551 .find(|editor| {
18552 editor
18553 .read(cx)
18554 .lookup_key
18555 .as_ref()
18556 .and_then(|it| {
18557 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18558 })
18559 .is_some_and(|it| *it == key)
18560 })
18561 });
18562 let was_existing = existing.is_some();
18563 let editor = existing.unwrap_or_else(|| {
18564 cx.new(|cx| {
18565 let mut editor = Editor::for_multibuffer(
18566 excerpt_buffer,
18567 Some(workspace.project().clone()),
18568 window,
18569 cx,
18570 );
18571 editor.lookup_key = Some(Box::new(key));
18572 editor
18573 })
18574 });
18575 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18576 MultibufferSelectionMode::First => {
18577 if let Some(first_range) = ranges.first() {
18578 editor.change_selections(
18579 SelectionEffects::no_scroll(),
18580 window,
18581 cx,
18582 |selections| {
18583 selections.clear_disjoint();
18584 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18585 },
18586 );
18587 }
18588 editor.highlight_background(
18589 HighlightKey::Editor,
18590 &ranges,
18591 |_, theme| theme.colors().editor_highlighted_line_background,
18592 cx,
18593 );
18594 }
18595 MultibufferSelectionMode::All => {
18596 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18597 selections.clear_disjoint();
18598 selections.select_anchor_ranges(ranges);
18599 });
18600 }
18601 });
18602
18603 let item = Box::new(editor.clone());
18604
18605 let pane = if split {
18606 workspace.adjacent_pane(window, cx)
18607 } else {
18608 workspace.active_pane().clone()
18609 };
18610 let activate_pane = split;
18611
18612 let mut destination_index = None;
18613 pane.update(cx, |pane, cx| {
18614 if allow_preview && !was_existing {
18615 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18616 }
18617 if was_existing && !allow_preview {
18618 pane.unpreview_item_if_preview(item.item_id());
18619 }
18620 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18621 });
18622
18623 Some((editor, pane))
18624 }
18625
18626 pub fn rename(
18627 &mut self,
18628 _: &Rename,
18629 window: &mut Window,
18630 cx: &mut Context<Self>,
18631 ) -> Option<Task<Result<()>>> {
18632 use language::ToOffset as _;
18633
18634 let provider = self.semantics_provider.clone()?;
18635 let selection = self.selections.newest_anchor().clone();
18636 let (cursor_buffer, cursor_buffer_position) = self
18637 .buffer
18638 .read(cx)
18639 .text_anchor_for_position(selection.head(), cx)?;
18640 let (tail_buffer, cursor_buffer_position_end) = self
18641 .buffer
18642 .read(cx)
18643 .text_anchor_for_position(selection.tail(), cx)?;
18644 if tail_buffer != cursor_buffer {
18645 return None;
18646 }
18647
18648 let snapshot = cursor_buffer.read(cx).snapshot();
18649 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18650 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18651 let prepare_rename = provider
18652 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18653 .unwrap_or_else(|| Task::ready(Ok(None)));
18654 drop(snapshot);
18655
18656 Some(cx.spawn_in(window, async move |this, cx| {
18657 let rename_range = if let Some(range) = prepare_rename.await? {
18658 Some(range)
18659 } else {
18660 this.update(cx, |this, cx| {
18661 let buffer = this.buffer.read(cx).snapshot(cx);
18662 let mut buffer_highlights = this
18663 .document_highlights_for_position(selection.head(), &buffer)
18664 .filter(|highlight| {
18665 highlight.start.excerpt_id == selection.head().excerpt_id
18666 && highlight.end.excerpt_id == selection.head().excerpt_id
18667 });
18668 buffer_highlights
18669 .next()
18670 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18671 })?
18672 };
18673 if let Some(rename_range) = rename_range {
18674 this.update_in(cx, |this, window, cx| {
18675 let snapshot = cursor_buffer.read(cx).snapshot();
18676 let rename_buffer_range = rename_range.to_offset(&snapshot);
18677 let cursor_offset_in_rename_range =
18678 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18679 let cursor_offset_in_rename_range_end =
18680 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18681
18682 this.take_rename(false, window, cx);
18683 let buffer = this.buffer.read(cx).read(cx);
18684 let cursor_offset = selection.head().to_offset(&buffer);
18685 let rename_start =
18686 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18687 let rename_end = rename_start + rename_buffer_range.len();
18688 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18689 let mut old_highlight_id = None;
18690 let old_name: Arc<str> = buffer
18691 .chunks(rename_start..rename_end, true)
18692 .map(|chunk| {
18693 if old_highlight_id.is_none() {
18694 old_highlight_id = chunk.syntax_highlight_id;
18695 }
18696 chunk.text
18697 })
18698 .collect::<String>()
18699 .into();
18700
18701 drop(buffer);
18702
18703 // Position the selection in the rename editor so that it matches the current selection.
18704 this.show_local_selections = false;
18705 let rename_editor = cx.new(|cx| {
18706 let mut editor = Editor::single_line(window, cx);
18707 editor.buffer.update(cx, |buffer, cx| {
18708 buffer.edit(
18709 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18710 None,
18711 cx,
18712 )
18713 });
18714 let cursor_offset_in_rename_range =
18715 MultiBufferOffset(cursor_offset_in_rename_range);
18716 let cursor_offset_in_rename_range_end =
18717 MultiBufferOffset(cursor_offset_in_rename_range_end);
18718 let rename_selection_range = match cursor_offset_in_rename_range
18719 .cmp(&cursor_offset_in_rename_range_end)
18720 {
18721 Ordering::Equal => {
18722 editor.select_all(&SelectAll, window, cx);
18723 return editor;
18724 }
18725 Ordering::Less => {
18726 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18727 }
18728 Ordering::Greater => {
18729 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18730 }
18731 };
18732 if rename_selection_range.end.0 > old_name.len() {
18733 editor.select_all(&SelectAll, window, cx);
18734 } else {
18735 editor.change_selections(Default::default(), window, cx, |s| {
18736 s.select_ranges([rename_selection_range]);
18737 });
18738 }
18739 editor
18740 });
18741 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18742 if e == &EditorEvent::Focused {
18743 cx.emit(EditorEvent::FocusedIn)
18744 }
18745 })
18746 .detach();
18747
18748 let write_highlights =
18749 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
18750 let read_highlights =
18751 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
18752 let ranges = write_highlights
18753 .iter()
18754 .flat_map(|(_, ranges)| ranges.iter())
18755 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18756 .cloned()
18757 .collect();
18758
18759 this.highlight_text(
18760 HighlightKey::Rename,
18761 ranges,
18762 HighlightStyle {
18763 fade_out: Some(0.6),
18764 ..Default::default()
18765 },
18766 cx,
18767 );
18768 let rename_focus_handle = rename_editor.focus_handle(cx);
18769 window.focus(&rename_focus_handle, cx);
18770 let block_id = this.insert_blocks(
18771 [BlockProperties {
18772 style: BlockStyle::Flex,
18773 placement: BlockPlacement::Below(range.start),
18774 height: Some(1),
18775 render: Arc::new({
18776 let rename_editor = rename_editor.clone();
18777 move |cx: &mut BlockContext| {
18778 let mut text_style = cx.editor_style.text.clone();
18779 if let Some(highlight_style) = old_highlight_id
18780 .and_then(|h| h.style(&cx.editor_style.syntax))
18781 {
18782 text_style = text_style.highlight(highlight_style);
18783 }
18784 div()
18785 .block_mouse_except_scroll()
18786 .pl(cx.anchor_x)
18787 .child(EditorElement::new(
18788 &rename_editor,
18789 EditorStyle {
18790 background: cx.theme().system().transparent,
18791 local_player: cx.editor_style.local_player,
18792 text: text_style,
18793 scrollbar_width: cx.editor_style.scrollbar_width,
18794 syntax: cx.editor_style.syntax.clone(),
18795 status: cx.editor_style.status.clone(),
18796 inlay_hints_style: HighlightStyle {
18797 font_weight: Some(FontWeight::BOLD),
18798 ..make_inlay_hints_style(cx.app)
18799 },
18800 edit_prediction_styles: make_suggestion_styles(
18801 cx.app,
18802 ),
18803 ..EditorStyle::default()
18804 },
18805 ))
18806 .into_any_element()
18807 }
18808 }),
18809 priority: 0,
18810 }],
18811 Some(Autoscroll::fit()),
18812 cx,
18813 )[0];
18814 this.pending_rename = Some(RenameState {
18815 range,
18816 old_name,
18817 editor: rename_editor,
18818 block_id,
18819 });
18820 })?;
18821 }
18822
18823 Ok(())
18824 }))
18825 }
18826
18827 pub fn confirm_rename(
18828 &mut self,
18829 _: &ConfirmRename,
18830 window: &mut Window,
18831 cx: &mut Context<Self>,
18832 ) -> Option<Task<Result<()>>> {
18833 let rename = self.take_rename(false, window, cx)?;
18834 let workspace = self.workspace()?.downgrade();
18835 let (buffer, start) = self
18836 .buffer
18837 .read(cx)
18838 .text_anchor_for_position(rename.range.start, cx)?;
18839 let (end_buffer, _) = self
18840 .buffer
18841 .read(cx)
18842 .text_anchor_for_position(rename.range.end, cx)?;
18843 if buffer != end_buffer {
18844 return None;
18845 }
18846
18847 let old_name = rename.old_name;
18848 let new_name = rename.editor.read(cx).text(cx);
18849
18850 let rename = self.semantics_provider.as_ref()?.perform_rename(
18851 &buffer,
18852 start,
18853 new_name.clone(),
18854 cx,
18855 )?;
18856
18857 Some(cx.spawn_in(window, async move |editor, cx| {
18858 let project_transaction = rename.await?;
18859 Self::open_project_transaction(
18860 &editor,
18861 workspace,
18862 project_transaction,
18863 format!("Rename: {} → {}", old_name, new_name),
18864 cx,
18865 )
18866 .await?;
18867
18868 editor.update(cx, |editor, cx| {
18869 editor.refresh_document_highlights(cx);
18870 })?;
18871 Ok(())
18872 }))
18873 }
18874
18875 fn take_rename(
18876 &mut self,
18877 moving_cursor: bool,
18878 window: &mut Window,
18879 cx: &mut Context<Self>,
18880 ) -> Option<RenameState> {
18881 let rename = self.pending_rename.take()?;
18882 if rename.editor.focus_handle(cx).is_focused(window) {
18883 window.focus(&self.focus_handle, cx);
18884 }
18885
18886 self.remove_blocks(
18887 [rename.block_id].into_iter().collect(),
18888 Some(Autoscroll::fit()),
18889 cx,
18890 );
18891 self.clear_highlights(HighlightKey::Rename, cx);
18892 self.show_local_selections = true;
18893
18894 if moving_cursor {
18895 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18896 editor
18897 .selections
18898 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18899 .head()
18900 });
18901
18902 // Update the selection to match the position of the selection inside
18903 // the rename editor.
18904 let snapshot = self.buffer.read(cx).read(cx);
18905 let rename_range = rename.range.to_offset(&snapshot);
18906 let cursor_in_editor = snapshot
18907 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18908 .min(rename_range.end);
18909 drop(snapshot);
18910
18911 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18912 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18913 });
18914 } else {
18915 self.refresh_document_highlights(cx);
18916 }
18917
18918 Some(rename)
18919 }
18920
18921 pub fn pending_rename(&self) -> Option<&RenameState> {
18922 self.pending_rename.as_ref()
18923 }
18924
18925 fn format(
18926 &mut self,
18927 _: &Format,
18928 window: &mut Window,
18929 cx: &mut Context<Self>,
18930 ) -> Option<Task<Result<()>>> {
18931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18932
18933 let project = match &self.project {
18934 Some(project) => project.clone(),
18935 None => return None,
18936 };
18937
18938 Some(self.perform_format(
18939 project,
18940 FormatTrigger::Manual,
18941 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18942 window,
18943 cx,
18944 ))
18945 }
18946
18947 fn format_selections(
18948 &mut self,
18949 _: &FormatSelections,
18950 window: &mut Window,
18951 cx: &mut Context<Self>,
18952 ) -> Option<Task<Result<()>>> {
18953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18954
18955 let project = match &self.project {
18956 Some(project) => project.clone(),
18957 None => return None,
18958 };
18959
18960 let ranges = self
18961 .selections
18962 .all_adjusted(&self.display_snapshot(cx))
18963 .into_iter()
18964 .map(|selection| selection.range())
18965 .collect_vec();
18966
18967 Some(self.perform_format(
18968 project,
18969 FormatTrigger::Manual,
18970 FormatTarget::Ranges(ranges),
18971 window,
18972 cx,
18973 ))
18974 }
18975
18976 fn perform_format(
18977 &mut self,
18978 project: Entity<Project>,
18979 trigger: FormatTrigger,
18980 target: FormatTarget,
18981 window: &mut Window,
18982 cx: &mut Context<Self>,
18983 ) -> Task<Result<()>> {
18984 let buffer = self.buffer.clone();
18985 let (buffers, target) = match target {
18986 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18987 FormatTarget::Ranges(selection_ranges) => {
18988 let multi_buffer = buffer.read(cx);
18989 let snapshot = multi_buffer.read(cx);
18990 let mut buffers = HashSet::default();
18991 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18992 BTreeMap::new();
18993 for selection_range in selection_ranges {
18994 for (buffer, buffer_range, _) in
18995 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
18996 {
18997 let buffer_id = buffer.remote_id();
18998 let start = buffer.anchor_before(buffer_range.start);
18999 let end = buffer.anchor_after(buffer_range.end);
19000 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19001 buffer_id_to_ranges
19002 .entry(buffer_id)
19003 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19004 .or_insert_with(|| vec![start..end]);
19005 }
19006 }
19007 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19008 }
19009 };
19010
19011 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19012 let selections_prev = transaction_id_prev
19013 .and_then(|transaction_id_prev| {
19014 // default to selections as they were after the last edit, if we have them,
19015 // instead of how they are now.
19016 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19017 // will take you back to where you made the last edit, instead of staying where you scrolled
19018 self.selection_history
19019 .transaction(transaction_id_prev)
19020 .map(|t| t.0.clone())
19021 })
19022 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19023
19024 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19025 let format = project.update(cx, |project, cx| {
19026 project.format(buffers, target, true, trigger, cx)
19027 });
19028
19029 cx.spawn_in(window, async move |editor, cx| {
19030 let transaction = futures::select_biased! {
19031 transaction = format.log_err().fuse() => transaction,
19032 () = timeout => {
19033 log::warn!("timed out waiting for formatting");
19034 None
19035 }
19036 };
19037
19038 buffer.update(cx, |buffer, cx| {
19039 if let Some(transaction) = transaction
19040 && !buffer.is_singleton()
19041 {
19042 buffer.push_transaction(&transaction.0, cx);
19043 }
19044 cx.notify();
19045 });
19046
19047 if let Some(transaction_id_now) =
19048 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19049 {
19050 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19051 if has_new_transaction {
19052 editor
19053 .update(cx, |editor, _| {
19054 editor
19055 .selection_history
19056 .insert_transaction(transaction_id_now, selections_prev);
19057 })
19058 .ok();
19059 }
19060 }
19061
19062 Ok(())
19063 })
19064 }
19065
19066 fn organize_imports(
19067 &mut self,
19068 _: &OrganizeImports,
19069 window: &mut Window,
19070 cx: &mut Context<Self>,
19071 ) -> Option<Task<Result<()>>> {
19072 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19073 let project = match &self.project {
19074 Some(project) => project.clone(),
19075 None => return None,
19076 };
19077 Some(self.perform_code_action_kind(
19078 project,
19079 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19080 window,
19081 cx,
19082 ))
19083 }
19084
19085 fn perform_code_action_kind(
19086 &mut self,
19087 project: Entity<Project>,
19088 kind: CodeActionKind,
19089 window: &mut Window,
19090 cx: &mut Context<Self>,
19091 ) -> Task<Result<()>> {
19092 let buffer = self.buffer.clone();
19093 let buffers = buffer.read(cx).all_buffers();
19094 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19095 let apply_action = project.update(cx, |project, cx| {
19096 project.apply_code_action_kind(buffers, kind, true, cx)
19097 });
19098 cx.spawn_in(window, async move |_, cx| {
19099 let transaction = futures::select_biased! {
19100 () = timeout => {
19101 log::warn!("timed out waiting for executing code action");
19102 None
19103 }
19104 transaction = apply_action.log_err().fuse() => transaction,
19105 };
19106 buffer.update(cx, |buffer, cx| {
19107 // check if we need this
19108 if let Some(transaction) = transaction
19109 && !buffer.is_singleton()
19110 {
19111 buffer.push_transaction(&transaction.0, cx);
19112 }
19113 cx.notify();
19114 });
19115 Ok(())
19116 })
19117 }
19118
19119 pub fn restart_language_server(
19120 &mut self,
19121 _: &RestartLanguageServer,
19122 _: &mut Window,
19123 cx: &mut Context<Self>,
19124 ) {
19125 if let Some(project) = self.project.clone() {
19126 self.buffer.update(cx, |multi_buffer, cx| {
19127 project.update(cx, |project, cx| {
19128 project.restart_language_servers_for_buffers(
19129 multi_buffer.all_buffers().into_iter().collect(),
19130 HashSet::default(),
19131 cx,
19132 );
19133 });
19134 })
19135 }
19136 }
19137
19138 pub fn stop_language_server(
19139 &mut self,
19140 _: &StopLanguageServer,
19141 _: &mut Window,
19142 cx: &mut Context<Self>,
19143 ) {
19144 if let Some(project) = self.project.clone() {
19145 self.buffer.update(cx, |multi_buffer, cx| {
19146 project.update(cx, |project, cx| {
19147 project.stop_language_servers_for_buffers(
19148 multi_buffer.all_buffers().into_iter().collect(),
19149 HashSet::default(),
19150 cx,
19151 );
19152 });
19153 });
19154 }
19155 }
19156
19157 fn cancel_language_server_work(
19158 workspace: &mut Workspace,
19159 _: &actions::CancelLanguageServerWork,
19160 _: &mut Window,
19161 cx: &mut Context<Workspace>,
19162 ) {
19163 let project = workspace.project();
19164 let buffers = workspace
19165 .active_item(cx)
19166 .and_then(|item| item.act_as::<Editor>(cx))
19167 .map_or(HashSet::default(), |editor| {
19168 editor.read(cx).buffer.read(cx).all_buffers()
19169 });
19170 project.update(cx, |project, cx| {
19171 project.cancel_language_server_work_for_buffers(buffers, cx);
19172 });
19173 }
19174
19175 fn show_character_palette(
19176 &mut self,
19177 _: &ShowCharacterPalette,
19178 window: &mut Window,
19179 _: &mut Context<Self>,
19180 ) {
19181 window.show_character_palette();
19182 }
19183
19184 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19185 if !self.diagnostics_enabled() {
19186 return;
19187 }
19188
19189 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19190 let buffer = self.buffer.read(cx).snapshot(cx);
19191 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19192 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19193 let is_valid = buffer
19194 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19195 .any(|entry| {
19196 entry.diagnostic.is_primary
19197 && !entry.range.is_empty()
19198 && entry.range.start == primary_range_start
19199 && entry.diagnostic.message == active_diagnostics.active_message
19200 });
19201
19202 if !is_valid {
19203 self.dismiss_diagnostics(cx);
19204 }
19205 }
19206 }
19207
19208 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19209 match &self.active_diagnostics {
19210 ActiveDiagnostic::Group(group) => Some(group),
19211 _ => None,
19212 }
19213 }
19214
19215 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19216 if !self.diagnostics_enabled() {
19217 return;
19218 }
19219 self.dismiss_diagnostics(cx);
19220 self.active_diagnostics = ActiveDiagnostic::All;
19221 }
19222
19223 fn activate_diagnostics(
19224 &mut self,
19225 buffer_id: BufferId,
19226 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19227 window: &mut Window,
19228 cx: &mut Context<Self>,
19229 ) {
19230 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19231 return;
19232 }
19233 self.dismiss_diagnostics(cx);
19234 let snapshot = self.snapshot(window, cx);
19235 let buffer = self.buffer.read(cx).snapshot(cx);
19236 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19237 return;
19238 };
19239
19240 let diagnostic_group = buffer
19241 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19242 .collect::<Vec<_>>();
19243
19244 let language_registry = self
19245 .project()
19246 .map(|project| project.read(cx).languages().clone());
19247
19248 let blocks = renderer.render_group(
19249 diagnostic_group,
19250 buffer_id,
19251 snapshot,
19252 cx.weak_entity(),
19253 language_registry,
19254 cx,
19255 );
19256
19257 let blocks = self.display_map.update(cx, |display_map, cx| {
19258 display_map.insert_blocks(blocks, cx).into_iter().collect()
19259 });
19260 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19261 active_range: buffer.anchor_before(diagnostic.range.start)
19262 ..buffer.anchor_after(diagnostic.range.end),
19263 active_message: diagnostic.diagnostic.message.clone(),
19264 group_id: diagnostic.diagnostic.group_id,
19265 blocks,
19266 });
19267 cx.notify();
19268 }
19269
19270 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19271 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19272 return;
19273 };
19274
19275 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19276 if let ActiveDiagnostic::Group(group) = prev {
19277 self.display_map.update(cx, |display_map, cx| {
19278 display_map.remove_blocks(group.blocks, cx);
19279 });
19280 cx.notify();
19281 }
19282 }
19283
19284 /// Disable inline diagnostics rendering for this editor.
19285 pub fn disable_inline_diagnostics(&mut self) {
19286 self.inline_diagnostics_enabled = false;
19287 self.inline_diagnostics_update = Task::ready(());
19288 self.inline_diagnostics.clear();
19289 }
19290
19291 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19292 self.diagnostics_enabled = false;
19293 self.dismiss_diagnostics(cx);
19294 self.inline_diagnostics_update = Task::ready(());
19295 self.inline_diagnostics.clear();
19296 }
19297
19298 pub fn disable_word_completions(&mut self) {
19299 self.word_completions_enabled = false;
19300 }
19301
19302 pub fn diagnostics_enabled(&self) -> bool {
19303 self.diagnostics_enabled && self.lsp_data_enabled()
19304 }
19305
19306 pub fn inline_diagnostics_enabled(&self) -> bool {
19307 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19308 }
19309
19310 pub fn show_inline_diagnostics(&self) -> bool {
19311 self.show_inline_diagnostics
19312 }
19313
19314 pub fn toggle_inline_diagnostics(
19315 &mut self,
19316 _: &ToggleInlineDiagnostics,
19317 window: &mut Window,
19318 cx: &mut Context<Editor>,
19319 ) {
19320 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19321 self.refresh_inline_diagnostics(false, window, cx);
19322 }
19323
19324 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19325 self.diagnostics_max_severity = severity;
19326 self.display_map.update(cx, |display_map, _| {
19327 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19328 });
19329 }
19330
19331 pub fn toggle_diagnostics(
19332 &mut self,
19333 _: &ToggleDiagnostics,
19334 window: &mut Window,
19335 cx: &mut Context<Editor>,
19336 ) {
19337 if !self.diagnostics_enabled() {
19338 return;
19339 }
19340
19341 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19342 EditorSettings::get_global(cx)
19343 .diagnostics_max_severity
19344 .filter(|severity| severity != &DiagnosticSeverity::Off)
19345 .unwrap_or(DiagnosticSeverity::Hint)
19346 } else {
19347 DiagnosticSeverity::Off
19348 };
19349 self.set_max_diagnostics_severity(new_severity, cx);
19350 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19351 self.active_diagnostics = ActiveDiagnostic::None;
19352 self.inline_diagnostics_update = Task::ready(());
19353 self.inline_diagnostics.clear();
19354 } else {
19355 self.refresh_inline_diagnostics(false, window, cx);
19356 }
19357
19358 cx.notify();
19359 }
19360
19361 pub fn toggle_minimap(
19362 &mut self,
19363 _: &ToggleMinimap,
19364 window: &mut Window,
19365 cx: &mut Context<Editor>,
19366 ) {
19367 if self.supports_minimap(cx) {
19368 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19369 }
19370 }
19371
19372 fn refresh_inline_diagnostics(
19373 &mut self,
19374 debounce: bool,
19375 window: &mut Window,
19376 cx: &mut Context<Self>,
19377 ) {
19378 let max_severity = ProjectSettings::get_global(cx)
19379 .diagnostics
19380 .inline
19381 .max_severity
19382 .unwrap_or(self.diagnostics_max_severity);
19383
19384 if !self.inline_diagnostics_enabled()
19385 || !self.diagnostics_enabled()
19386 || !self.show_inline_diagnostics
19387 || max_severity == DiagnosticSeverity::Off
19388 {
19389 self.inline_diagnostics_update = Task::ready(());
19390 self.inline_diagnostics.clear();
19391 return;
19392 }
19393
19394 let debounce_ms = ProjectSettings::get_global(cx)
19395 .diagnostics
19396 .inline
19397 .update_debounce_ms;
19398 let debounce = if debounce && debounce_ms > 0 {
19399 Some(Duration::from_millis(debounce_ms))
19400 } else {
19401 None
19402 };
19403 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19404 if let Some(debounce) = debounce {
19405 cx.background_executor().timer(debounce).await;
19406 }
19407 let Some(snapshot) = editor.upgrade().map(|editor| {
19408 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19409 }) else {
19410 return;
19411 };
19412
19413 let new_inline_diagnostics = cx
19414 .background_spawn(async move {
19415 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19416 for diagnostic_entry in
19417 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19418 {
19419 let message = diagnostic_entry
19420 .diagnostic
19421 .message
19422 .split_once('\n')
19423 .map(|(line, _)| line)
19424 .map(SharedString::new)
19425 .unwrap_or_else(|| {
19426 SharedString::new(&*diagnostic_entry.diagnostic.message)
19427 });
19428 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19429 let (Ok(i) | Err(i)) = inline_diagnostics
19430 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19431 inline_diagnostics.insert(
19432 i,
19433 (
19434 start_anchor,
19435 InlineDiagnostic {
19436 message,
19437 group_id: diagnostic_entry.diagnostic.group_id,
19438 start: diagnostic_entry.range.start.to_point(&snapshot),
19439 is_primary: diagnostic_entry.diagnostic.is_primary,
19440 severity: diagnostic_entry.diagnostic.severity,
19441 },
19442 ),
19443 );
19444 }
19445 inline_diagnostics
19446 })
19447 .await;
19448
19449 editor
19450 .update(cx, |editor, cx| {
19451 editor.inline_diagnostics = new_inline_diagnostics;
19452 cx.notify();
19453 })
19454 .ok();
19455 });
19456 }
19457
19458 fn pull_diagnostics(
19459 &mut self,
19460 buffer_id: BufferId,
19461 _window: &Window,
19462 cx: &mut Context<Self>,
19463 ) -> Option<()> {
19464 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19465 // skip any LSP updates for it.
19466
19467 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19468 return None;
19469 }
19470 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19471 .diagnostics
19472 .lsp_pull_diagnostics;
19473 if !pull_diagnostics_settings.enabled {
19474 return None;
19475 }
19476 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19477 let project = self.project()?.downgrade();
19478 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19479
19480 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19481 cx.background_executor().timer(debounce).await;
19482 if let Ok(task) = project.update(cx, |project, cx| {
19483 project.lsp_store().update(cx, |lsp_store, cx| {
19484 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19485 })
19486 }) {
19487 task.await.log_err();
19488 }
19489 project
19490 .update(cx, |project, cx| {
19491 project.lsp_store().update(cx, |lsp_store, cx| {
19492 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19493 })
19494 })
19495 .log_err();
19496 });
19497
19498 Some(())
19499 }
19500
19501 pub fn set_selections_from_remote(
19502 &mut self,
19503 selections: Vec<Selection<Anchor>>,
19504 pending_selection: Option<Selection<Anchor>>,
19505 window: &mut Window,
19506 cx: &mut Context<Self>,
19507 ) {
19508 let old_cursor_position = self.selections.newest_anchor().head();
19509 self.selections
19510 .change_with(&self.display_snapshot(cx), |s| {
19511 s.select_anchors(selections);
19512 if let Some(pending_selection) = pending_selection {
19513 s.set_pending(pending_selection, SelectMode::Character);
19514 } else {
19515 s.clear_pending();
19516 }
19517 });
19518 self.selections_did_change(
19519 false,
19520 &old_cursor_position,
19521 SelectionEffects::default(),
19522 window,
19523 cx,
19524 );
19525 }
19526
19527 pub fn transact(
19528 &mut self,
19529 window: &mut Window,
19530 cx: &mut Context<Self>,
19531 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19532 ) -> Option<TransactionId> {
19533 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19534 this.start_transaction_at(Instant::now(), window, cx);
19535 update(this, window, cx);
19536 this.end_transaction_at(Instant::now(), cx)
19537 })
19538 }
19539
19540 pub fn start_transaction_at(
19541 &mut self,
19542 now: Instant,
19543 window: &mut Window,
19544 cx: &mut Context<Self>,
19545 ) -> Option<TransactionId> {
19546 self.end_selection(window, cx);
19547 if let Some(tx_id) = self
19548 .buffer
19549 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19550 {
19551 self.selection_history
19552 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19553 cx.emit(EditorEvent::TransactionBegun {
19554 transaction_id: tx_id,
19555 });
19556 Some(tx_id)
19557 } else {
19558 None
19559 }
19560 }
19561
19562 pub fn end_transaction_at(
19563 &mut self,
19564 now: Instant,
19565 cx: &mut Context<Self>,
19566 ) -> Option<TransactionId> {
19567 if let Some(transaction_id) = self
19568 .buffer
19569 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19570 {
19571 if let Some((_, end_selections)) =
19572 self.selection_history.transaction_mut(transaction_id)
19573 {
19574 *end_selections = Some(self.selections.disjoint_anchors_arc());
19575 } else {
19576 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19577 }
19578
19579 cx.emit(EditorEvent::Edited { transaction_id });
19580 Some(transaction_id)
19581 } else {
19582 None
19583 }
19584 }
19585
19586 pub fn modify_transaction_selection_history(
19587 &mut self,
19588 transaction_id: TransactionId,
19589 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19590 ) -> bool {
19591 self.selection_history
19592 .transaction_mut(transaction_id)
19593 .map(modify)
19594 .is_some()
19595 }
19596
19597 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19598 if self.selection_mark_mode {
19599 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19600 s.move_with(&mut |_, sel| {
19601 sel.collapse_to(sel.head(), SelectionGoal::None);
19602 });
19603 })
19604 }
19605 self.selection_mark_mode = true;
19606 cx.notify();
19607 }
19608
19609 pub fn swap_selection_ends(
19610 &mut self,
19611 _: &actions::SwapSelectionEnds,
19612 window: &mut Window,
19613 cx: &mut Context<Self>,
19614 ) {
19615 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19616 s.move_with(&mut |_, sel| {
19617 if sel.start != sel.end {
19618 sel.reversed = !sel.reversed
19619 }
19620 });
19621 });
19622 self.request_autoscroll(Autoscroll::newest(), cx);
19623 cx.notify();
19624 }
19625
19626 pub fn toggle_focus(
19627 workspace: &mut Workspace,
19628 _: &actions::ToggleFocus,
19629 window: &mut Window,
19630 cx: &mut Context<Workspace>,
19631 ) {
19632 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19633 return;
19634 };
19635 workspace.activate_item(&item, true, true, window, cx);
19636 }
19637
19638 pub fn toggle_fold(
19639 &mut self,
19640 _: &actions::ToggleFold,
19641 window: &mut Window,
19642 cx: &mut Context<Self>,
19643 ) {
19644 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19646 let selection = self.selections.newest::<Point>(&display_map);
19647
19648 let range = if selection.is_empty() {
19649 let point = selection.head().to_display_point(&display_map);
19650 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19651 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19652 .to_point(&display_map);
19653 start..end
19654 } else {
19655 selection.range()
19656 };
19657 if display_map.folds_in_range(range).next().is_some() {
19658 self.unfold_lines(&Default::default(), window, cx)
19659 } else {
19660 self.fold(&Default::default(), window, cx)
19661 }
19662 } else {
19663 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19664 let buffer_ids: HashSet<_> = self
19665 .selections
19666 .disjoint_anchor_ranges()
19667 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19668 .collect();
19669
19670 let should_unfold = buffer_ids
19671 .iter()
19672 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19673
19674 for buffer_id in buffer_ids {
19675 if should_unfold {
19676 self.unfold_buffer(buffer_id, cx);
19677 } else {
19678 self.fold_buffer(buffer_id, cx);
19679 }
19680 }
19681 }
19682 }
19683
19684 pub fn toggle_fold_recursive(
19685 &mut self,
19686 _: &actions::ToggleFoldRecursive,
19687 window: &mut Window,
19688 cx: &mut Context<Self>,
19689 ) {
19690 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19691
19692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19693 let range = if selection.is_empty() {
19694 let point = selection.head().to_display_point(&display_map);
19695 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19696 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19697 .to_point(&display_map);
19698 start..end
19699 } else {
19700 selection.range()
19701 };
19702 if display_map.folds_in_range(range).next().is_some() {
19703 self.unfold_recursive(&Default::default(), window, cx)
19704 } else {
19705 self.fold_recursive(&Default::default(), window, cx)
19706 }
19707 }
19708
19709 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19710 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19711 let mut to_fold = Vec::new();
19712 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19713 let selections = self.selections.all_adjusted(&display_map);
19714
19715 for selection in selections {
19716 let range = selection.range().sorted();
19717 let buffer_start_row = range.start.row;
19718
19719 if range.start.row != range.end.row {
19720 let mut found = false;
19721 let mut row = range.start.row;
19722 while row <= range.end.row {
19723 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19724 {
19725 found = true;
19726 row = crease.range().end.row + 1;
19727 to_fold.push(crease);
19728 } else {
19729 row += 1
19730 }
19731 }
19732 if found {
19733 continue;
19734 }
19735 }
19736
19737 for row in (0..=range.start.row).rev() {
19738 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19739 && crease.range().end.row >= buffer_start_row
19740 {
19741 to_fold.push(crease);
19742 if row <= range.start.row {
19743 break;
19744 }
19745 }
19746 }
19747 }
19748
19749 self.fold_creases(to_fold, true, window, cx);
19750 } else {
19751 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19752 let buffer_ids = self
19753 .selections
19754 .disjoint_anchor_ranges()
19755 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19756 .collect::<HashSet<_>>();
19757 for buffer_id in buffer_ids {
19758 self.fold_buffer(buffer_id, cx);
19759 }
19760 }
19761 }
19762
19763 pub fn toggle_fold_all(
19764 &mut self,
19765 _: &actions::ToggleFoldAll,
19766 window: &mut Window,
19767 cx: &mut Context<Self>,
19768 ) {
19769 let has_folds = if self.buffer.read(cx).is_singleton() {
19770 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19771 let has_folds = display_map
19772 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19773 .next()
19774 .is_some();
19775 has_folds
19776 } else {
19777 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19778 let has_folds = buffer_ids
19779 .iter()
19780 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19781 has_folds
19782 };
19783
19784 if has_folds {
19785 self.unfold_all(&actions::UnfoldAll, window, cx);
19786 } else {
19787 self.fold_all(&actions::FoldAll, window, cx);
19788 }
19789 }
19790
19791 fn fold_at_level(
19792 &mut self,
19793 fold_at: &FoldAtLevel,
19794 window: &mut Window,
19795 cx: &mut Context<Self>,
19796 ) {
19797 if !self.buffer.read(cx).is_singleton() {
19798 return;
19799 }
19800
19801 let fold_at_level = fold_at.0;
19802 let snapshot = self.buffer.read(cx).snapshot(cx);
19803 let mut to_fold = Vec::new();
19804 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19805
19806 let row_ranges_to_keep: Vec<Range<u32>> = self
19807 .selections
19808 .all::<Point>(&self.display_snapshot(cx))
19809 .into_iter()
19810 .map(|sel| sel.start.row..sel.end.row)
19811 .collect();
19812
19813 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19814 while start_row < end_row {
19815 match self
19816 .snapshot(window, cx)
19817 .crease_for_buffer_row(MultiBufferRow(start_row))
19818 {
19819 Some(crease) => {
19820 let nested_start_row = crease.range().start.row + 1;
19821 let nested_end_row = crease.range().end.row;
19822
19823 if current_level < fold_at_level {
19824 stack.push((nested_start_row, nested_end_row, current_level + 1));
19825 } else if current_level == fold_at_level {
19826 // Fold iff there is no selection completely contained within the fold region
19827 if !row_ranges_to_keep.iter().any(|selection| {
19828 selection.end >= nested_start_row
19829 && selection.start <= nested_end_row
19830 }) {
19831 to_fold.push(crease);
19832 }
19833 }
19834
19835 start_row = nested_end_row + 1;
19836 }
19837 None => start_row += 1,
19838 }
19839 }
19840 }
19841
19842 self.fold_creases(to_fold, true, window, cx);
19843 }
19844
19845 pub fn fold_at_level_1(
19846 &mut self,
19847 _: &actions::FoldAtLevel1,
19848 window: &mut Window,
19849 cx: &mut Context<Self>,
19850 ) {
19851 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19852 }
19853
19854 pub fn fold_at_level_2(
19855 &mut self,
19856 _: &actions::FoldAtLevel2,
19857 window: &mut Window,
19858 cx: &mut Context<Self>,
19859 ) {
19860 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19861 }
19862
19863 pub fn fold_at_level_3(
19864 &mut self,
19865 _: &actions::FoldAtLevel3,
19866 window: &mut Window,
19867 cx: &mut Context<Self>,
19868 ) {
19869 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19870 }
19871
19872 pub fn fold_at_level_4(
19873 &mut self,
19874 _: &actions::FoldAtLevel4,
19875 window: &mut Window,
19876 cx: &mut Context<Self>,
19877 ) {
19878 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19879 }
19880
19881 pub fn fold_at_level_5(
19882 &mut self,
19883 _: &actions::FoldAtLevel5,
19884 window: &mut Window,
19885 cx: &mut Context<Self>,
19886 ) {
19887 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19888 }
19889
19890 pub fn fold_at_level_6(
19891 &mut self,
19892 _: &actions::FoldAtLevel6,
19893 window: &mut Window,
19894 cx: &mut Context<Self>,
19895 ) {
19896 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19897 }
19898
19899 pub fn fold_at_level_7(
19900 &mut self,
19901 _: &actions::FoldAtLevel7,
19902 window: &mut Window,
19903 cx: &mut Context<Self>,
19904 ) {
19905 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19906 }
19907
19908 pub fn fold_at_level_8(
19909 &mut self,
19910 _: &actions::FoldAtLevel8,
19911 window: &mut Window,
19912 cx: &mut Context<Self>,
19913 ) {
19914 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19915 }
19916
19917 pub fn fold_at_level_9(
19918 &mut self,
19919 _: &actions::FoldAtLevel9,
19920 window: &mut Window,
19921 cx: &mut Context<Self>,
19922 ) {
19923 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19924 }
19925
19926 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19927 if self.buffer.read(cx).is_singleton() {
19928 let mut fold_ranges = Vec::new();
19929 let snapshot = self.buffer.read(cx).snapshot(cx);
19930
19931 for row in 0..snapshot.max_row().0 {
19932 if let Some(foldable_range) = self
19933 .snapshot(window, cx)
19934 .crease_for_buffer_row(MultiBufferRow(row))
19935 {
19936 fold_ranges.push(foldable_range);
19937 }
19938 }
19939
19940 self.fold_creases(fold_ranges, true, window, cx);
19941 } else {
19942 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19943 editor
19944 .update_in(cx, |editor, _, cx| {
19945 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19946 editor.fold_buffer(buffer_id, cx);
19947 }
19948 })
19949 .ok();
19950 });
19951 }
19952 }
19953
19954 pub fn fold_function_bodies(
19955 &mut self,
19956 _: &actions::FoldFunctionBodies,
19957 window: &mut Window,
19958 cx: &mut Context<Self>,
19959 ) {
19960 let snapshot = self.buffer.read(cx).snapshot(cx);
19961
19962 let ranges = snapshot
19963 .text_object_ranges(
19964 MultiBufferOffset(0)..snapshot.len(),
19965 TreeSitterOptions::default(),
19966 )
19967 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19968 .collect::<Vec<_>>();
19969
19970 let creases = ranges
19971 .into_iter()
19972 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19973 .collect();
19974
19975 self.fold_creases(creases, true, window, cx);
19976 }
19977
19978 pub fn fold_recursive(
19979 &mut self,
19980 _: &actions::FoldRecursive,
19981 window: &mut Window,
19982 cx: &mut Context<Self>,
19983 ) {
19984 let mut to_fold = Vec::new();
19985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19986 let selections = self.selections.all_adjusted(&display_map);
19987
19988 for selection in selections {
19989 let range = selection.range().sorted();
19990 let buffer_start_row = range.start.row;
19991
19992 if range.start.row != range.end.row {
19993 let mut found = false;
19994 for row in range.start.row..=range.end.row {
19995 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19996 found = true;
19997 to_fold.push(crease);
19998 }
19999 }
20000 if found {
20001 continue;
20002 }
20003 }
20004
20005 for row in (0..=range.start.row).rev() {
20006 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20007 if crease.range().end.row >= buffer_start_row {
20008 to_fold.push(crease);
20009 } else {
20010 break;
20011 }
20012 }
20013 }
20014 }
20015
20016 self.fold_creases(to_fold, true, window, cx);
20017 }
20018
20019 pub fn fold_at(
20020 &mut self,
20021 buffer_row: MultiBufferRow,
20022 window: &mut Window,
20023 cx: &mut Context<Self>,
20024 ) {
20025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20026
20027 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20028 let autoscroll = self
20029 .selections
20030 .all::<Point>(&display_map)
20031 .iter()
20032 .any(|selection| crease.range().overlaps(&selection.range()));
20033
20034 self.fold_creases(vec![crease], autoscroll, window, cx);
20035 }
20036 }
20037
20038 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20039 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20040 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20041 let buffer = display_map.buffer_snapshot();
20042 let selections = self.selections.all::<Point>(&display_map);
20043 let ranges = selections
20044 .iter()
20045 .map(|s| {
20046 let range = s.display_range(&display_map).sorted();
20047 let mut start = range.start.to_point(&display_map);
20048 let mut end = range.end.to_point(&display_map);
20049 start.column = 0;
20050 end.column = buffer.line_len(MultiBufferRow(end.row));
20051 start..end
20052 })
20053 .collect::<Vec<_>>();
20054
20055 self.unfold_ranges(&ranges, true, true, cx);
20056 } else {
20057 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20058 let buffer_ids = self
20059 .selections
20060 .disjoint_anchor_ranges()
20061 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20062 .collect::<HashSet<_>>();
20063 for buffer_id in buffer_ids {
20064 self.unfold_buffer(buffer_id, cx);
20065 }
20066 }
20067 }
20068
20069 pub fn unfold_recursive(
20070 &mut self,
20071 _: &UnfoldRecursive,
20072 _window: &mut Window,
20073 cx: &mut Context<Self>,
20074 ) {
20075 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20076 let selections = self.selections.all::<Point>(&display_map);
20077 let ranges = selections
20078 .iter()
20079 .map(|s| {
20080 let mut range = s.display_range(&display_map).sorted();
20081 *range.start.column_mut() = 0;
20082 *range.end.column_mut() = display_map.line_len(range.end.row());
20083 let start = range.start.to_point(&display_map);
20084 let end = range.end.to_point(&display_map);
20085 start..end
20086 })
20087 .collect::<Vec<_>>();
20088
20089 self.unfold_ranges(&ranges, true, true, cx);
20090 }
20091
20092 pub fn unfold_at(
20093 &mut self,
20094 buffer_row: MultiBufferRow,
20095 _window: &mut Window,
20096 cx: &mut Context<Self>,
20097 ) {
20098 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20099
20100 let intersection_range = Point::new(buffer_row.0, 0)
20101 ..Point::new(
20102 buffer_row.0,
20103 display_map.buffer_snapshot().line_len(buffer_row),
20104 );
20105
20106 let autoscroll = self
20107 .selections
20108 .all::<Point>(&display_map)
20109 .iter()
20110 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20111
20112 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20113 }
20114
20115 pub fn unfold_all(
20116 &mut self,
20117 _: &actions::UnfoldAll,
20118 _window: &mut Window,
20119 cx: &mut Context<Self>,
20120 ) {
20121 if self.buffer.read(cx).is_singleton() {
20122 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20123 self.unfold_ranges(
20124 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20125 true,
20126 true,
20127 cx,
20128 );
20129 } else {
20130 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20131 editor
20132 .update(cx, |editor, cx| {
20133 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20134 editor.unfold_buffer(buffer_id, cx);
20135 }
20136 })
20137 .ok();
20138 });
20139 }
20140 }
20141
20142 pub fn fold_selected_ranges(
20143 &mut self,
20144 _: &FoldSelectedRanges,
20145 window: &mut Window,
20146 cx: &mut Context<Self>,
20147 ) {
20148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20149 let selections = self.selections.all_adjusted(&display_map);
20150 let ranges = selections
20151 .into_iter()
20152 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20153 .collect::<Vec<_>>();
20154 self.fold_creases(ranges, true, window, cx);
20155 }
20156
20157 pub fn fold_ranges<T: ToOffset + Clone>(
20158 &mut self,
20159 ranges: Vec<Range<T>>,
20160 auto_scroll: bool,
20161 window: &mut Window,
20162 cx: &mut Context<Self>,
20163 ) {
20164 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20165 let ranges = ranges
20166 .into_iter()
20167 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20168 .collect::<Vec<_>>();
20169 self.fold_creases(ranges, auto_scroll, window, cx);
20170 }
20171
20172 pub fn fold_creases<T: ToOffset + Clone>(
20173 &mut self,
20174 creases: Vec<Crease<T>>,
20175 auto_scroll: bool,
20176 window: &mut Window,
20177 cx: &mut Context<Self>,
20178 ) {
20179 if creases.is_empty() {
20180 return;
20181 }
20182
20183 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20184
20185 if auto_scroll {
20186 self.request_autoscroll(Autoscroll::fit(), cx);
20187 }
20188
20189 cx.notify();
20190
20191 self.scrollbar_marker_state.dirty = true;
20192 self.update_data_on_scroll(window, cx);
20193 self.folds_did_change(cx);
20194 }
20195
20196 /// Removes any folds whose ranges intersect any of the given ranges.
20197 pub fn unfold_ranges<T: ToOffset + Clone>(
20198 &mut self,
20199 ranges: &[Range<T>],
20200 inclusive: bool,
20201 auto_scroll: bool,
20202 cx: &mut Context<Self>,
20203 ) {
20204 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20205 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20206 });
20207 self.folds_did_change(cx);
20208 }
20209
20210 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20211 self.fold_buffers([buffer_id], cx);
20212 }
20213
20214 pub fn fold_buffers(
20215 &mut self,
20216 buffer_ids: impl IntoIterator<Item = BufferId>,
20217 cx: &mut Context<Self>,
20218 ) {
20219 if self.buffer().read(cx).is_singleton() {
20220 return;
20221 }
20222
20223 let ids_to_fold: Vec<BufferId> = buffer_ids
20224 .into_iter()
20225 .filter(|id| !self.is_buffer_folded(*id, cx))
20226 .collect();
20227
20228 if ids_to_fold.is_empty() {
20229 return;
20230 }
20231
20232 let mut all_folded_excerpt_ids = Vec::new();
20233 for buffer_id in &ids_to_fold {
20234 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20235 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20236 }
20237
20238 self.display_map.update(cx, |display_map, cx| {
20239 display_map.fold_buffers(ids_to_fold.clone(), cx)
20240 });
20241
20242 let snapshot = self.display_snapshot(cx);
20243 self.selections.change_with(&snapshot, |selections| {
20244 for buffer_id in ids_to_fold {
20245 selections.remove_selections_from_buffer(buffer_id);
20246 }
20247 });
20248
20249 cx.emit(EditorEvent::BufferFoldToggled {
20250 ids: all_folded_excerpt_ids,
20251 folded: true,
20252 });
20253 cx.notify();
20254 }
20255
20256 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20257 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20258 return;
20259 }
20260 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20261 self.display_map.update(cx, |display_map, cx| {
20262 display_map.unfold_buffers([buffer_id], cx);
20263 });
20264 cx.emit(EditorEvent::BufferFoldToggled {
20265 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20266 folded: false,
20267 });
20268 cx.notify();
20269 }
20270
20271 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20272 self.display_map.read(cx).is_buffer_folded(buffer)
20273 }
20274
20275 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20276 if self.buffer().read(cx).is_singleton() {
20277 return false;
20278 }
20279 !self.folded_buffers(cx).is_empty()
20280 }
20281
20282 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20283 self.display_map.read(cx).folded_buffers()
20284 }
20285
20286 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20287 self.display_map.update(cx, |display_map, cx| {
20288 display_map.disable_header_for_buffer(buffer_id, cx);
20289 });
20290 cx.notify();
20291 }
20292
20293 /// Removes any folds with the given ranges.
20294 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20295 &mut self,
20296 ranges: &[Range<T>],
20297 type_id: TypeId,
20298 auto_scroll: bool,
20299 cx: &mut Context<Self>,
20300 ) {
20301 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20302 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20303 });
20304 self.folds_did_change(cx);
20305 }
20306
20307 fn remove_folds_with<T: ToOffset + Clone>(
20308 &mut self,
20309 ranges: &[Range<T>],
20310 auto_scroll: bool,
20311 cx: &mut Context<Self>,
20312 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20313 ) {
20314 if ranges.is_empty() {
20315 return;
20316 }
20317
20318 let mut buffers_affected = HashSet::default();
20319 let multi_buffer = self.buffer().read(cx);
20320 for range in ranges {
20321 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20322 buffers_affected.insert(buffer.read(cx).remote_id());
20323 };
20324 }
20325
20326 self.display_map.update(cx, update);
20327
20328 if auto_scroll {
20329 self.request_autoscroll(Autoscroll::fit(), cx);
20330 }
20331
20332 cx.notify();
20333 self.scrollbar_marker_state.dirty = true;
20334 self.active_indent_guides_state.dirty = true;
20335 }
20336
20337 pub fn update_renderer_widths(
20338 &mut self,
20339 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20340 cx: &mut Context<Self>,
20341 ) -> bool {
20342 self.display_map
20343 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20344 }
20345
20346 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20347 self.display_map.read(cx).fold_placeholder.clone()
20348 }
20349
20350 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20351 self.buffer.update(cx, |buffer, cx| {
20352 buffer.set_all_diff_hunks_expanded(cx);
20353 });
20354 }
20355
20356 pub fn expand_all_diff_hunks(
20357 &mut self,
20358 _: &ExpandAllDiffHunks,
20359 _window: &mut Window,
20360 cx: &mut Context<Self>,
20361 ) {
20362 self.buffer.update(cx, |buffer, cx| {
20363 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20364 });
20365 }
20366
20367 pub fn collapse_all_diff_hunks(
20368 &mut self,
20369 _: &CollapseAllDiffHunks,
20370 _window: &mut Window,
20371 cx: &mut Context<Self>,
20372 ) {
20373 self.buffer.update(cx, |buffer, cx| {
20374 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20375 });
20376 }
20377
20378 pub fn toggle_selected_diff_hunks(
20379 &mut self,
20380 _: &ToggleSelectedDiffHunks,
20381 _window: &mut Window,
20382 cx: &mut Context<Self>,
20383 ) {
20384 let ranges: Vec<_> = self
20385 .selections
20386 .disjoint_anchors()
20387 .iter()
20388 .map(|s| s.range())
20389 .collect();
20390 self.toggle_diff_hunks_in_ranges(ranges, cx);
20391 }
20392
20393 pub fn diff_hunks_in_ranges<'a>(
20394 &'a self,
20395 ranges: &'a [Range<Anchor>],
20396 buffer: &'a MultiBufferSnapshot,
20397 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20398 ranges.iter().flat_map(move |range| {
20399 let end_excerpt_id = range.end.excerpt_id;
20400 let range = range.to_point(buffer);
20401 let mut peek_end = range.end;
20402 if range.end.row < buffer.max_row().0 {
20403 peek_end = Point::new(range.end.row + 1, 0);
20404 }
20405 buffer
20406 .diff_hunks_in_range(range.start..peek_end)
20407 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20408 })
20409 }
20410
20411 pub fn has_stageable_diff_hunks_in_ranges(
20412 &self,
20413 ranges: &[Range<Anchor>],
20414 snapshot: &MultiBufferSnapshot,
20415 ) -> bool {
20416 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20417 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20418 }
20419
20420 pub fn toggle_staged_selected_diff_hunks(
20421 &mut self,
20422 _: &::git::ToggleStaged,
20423 _: &mut Window,
20424 cx: &mut Context<Self>,
20425 ) {
20426 let snapshot = self.buffer.read(cx).snapshot(cx);
20427 let ranges: Vec<_> = self
20428 .selections
20429 .disjoint_anchors()
20430 .iter()
20431 .map(|s| s.range())
20432 .collect();
20433 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20434 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20435 }
20436
20437 pub fn set_render_diff_hunk_controls(
20438 &mut self,
20439 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20440 cx: &mut Context<Self>,
20441 ) {
20442 self.render_diff_hunk_controls = render_diff_hunk_controls;
20443 cx.notify();
20444 }
20445
20446 pub fn stage_and_next(
20447 &mut self,
20448 _: &::git::StageAndNext,
20449 window: &mut Window,
20450 cx: &mut Context<Self>,
20451 ) {
20452 self.do_stage_or_unstage_and_next(true, window, cx);
20453 }
20454
20455 pub fn unstage_and_next(
20456 &mut self,
20457 _: &::git::UnstageAndNext,
20458 window: &mut Window,
20459 cx: &mut Context<Self>,
20460 ) {
20461 self.do_stage_or_unstage_and_next(false, window, cx);
20462 }
20463
20464 pub fn stage_or_unstage_diff_hunks(
20465 &mut self,
20466 stage: bool,
20467 ranges: Vec<Range<Anchor>>,
20468 cx: &mut Context<Self>,
20469 ) {
20470 if self.delegate_stage_and_restore {
20471 let snapshot = self.buffer.read(cx).snapshot(cx);
20472 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20473 if !hunks.is_empty() {
20474 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20475 }
20476 return;
20477 }
20478 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20479 cx.spawn(async move |this, cx| {
20480 task.await?;
20481 this.update(cx, |this, cx| {
20482 let snapshot = this.buffer.read(cx).snapshot(cx);
20483 let chunk_by = this
20484 .diff_hunks_in_ranges(&ranges, &snapshot)
20485 .chunk_by(|hunk| hunk.buffer_id);
20486 for (buffer_id, hunks) in &chunk_by {
20487 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20488 }
20489 })
20490 })
20491 .detach_and_log_err(cx);
20492 }
20493
20494 fn save_buffers_for_ranges_if_needed(
20495 &mut self,
20496 ranges: &[Range<Anchor>],
20497 cx: &mut Context<Editor>,
20498 ) -> Task<Result<()>> {
20499 let multibuffer = self.buffer.read(cx);
20500 let snapshot = multibuffer.read(cx);
20501 let buffer_ids: HashSet<_> = ranges
20502 .iter()
20503 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20504 .collect();
20505 drop(snapshot);
20506
20507 let mut buffers = HashSet::default();
20508 for buffer_id in buffer_ids {
20509 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20510 let buffer = buffer_entity.read(cx);
20511 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20512 {
20513 buffers.insert(buffer_entity);
20514 }
20515 }
20516 }
20517
20518 if let Some(project) = &self.project {
20519 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20520 } else {
20521 Task::ready(Ok(()))
20522 }
20523 }
20524
20525 fn do_stage_or_unstage_and_next(
20526 &mut self,
20527 stage: bool,
20528 window: &mut Window,
20529 cx: &mut Context<Self>,
20530 ) {
20531 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20532
20533 if ranges.iter().any(|range| range.start != range.end) {
20534 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20535 return;
20536 }
20537
20538 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20539
20540 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20541 let wrap_around = !all_diff_hunks_expanded;
20542 let snapshot = self.snapshot(window, cx);
20543 let position = self
20544 .selections
20545 .newest::<Point>(&snapshot.display_snapshot)
20546 .head();
20547
20548 self.go_to_hunk_before_or_after_position(
20549 &snapshot,
20550 position,
20551 Direction::Next,
20552 wrap_around,
20553 window,
20554 cx,
20555 );
20556 }
20557
20558 pub(crate) fn do_stage_or_unstage(
20559 &self,
20560 stage: bool,
20561 buffer_id: BufferId,
20562 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20563 cx: &mut App,
20564 ) -> Option<()> {
20565 let project = self.project()?;
20566 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20567 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20568 let buffer_snapshot = buffer.read(cx).snapshot();
20569 let file_exists = buffer_snapshot
20570 .file()
20571 .is_some_and(|file| file.disk_state().exists());
20572 diff.update(cx, |diff, cx| {
20573 diff.stage_or_unstage_hunks(
20574 stage,
20575 &hunks
20576 .map(|hunk| buffer_diff::DiffHunk {
20577 buffer_range: hunk.buffer_range,
20578 // We don't need to pass in word diffs here because they're only used for rendering and
20579 // this function changes internal state
20580 base_word_diffs: Vec::default(),
20581 buffer_word_diffs: Vec::default(),
20582 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20583 ..hunk.diff_base_byte_range.end.0,
20584 secondary_status: hunk.status.secondary,
20585 range: Point::zero()..Point::zero(), // unused
20586 })
20587 .collect::<Vec<_>>(),
20588 &buffer_snapshot,
20589 file_exists,
20590 cx,
20591 )
20592 });
20593 None
20594 }
20595
20596 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20597 let ranges: Vec<_> = self
20598 .selections
20599 .disjoint_anchors()
20600 .iter()
20601 .map(|s| s.range())
20602 .collect();
20603 self.buffer
20604 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20605 }
20606
20607 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20608 self.buffer.update(cx, |buffer, cx| {
20609 let ranges = vec![Anchor::min()..Anchor::max()];
20610 if !buffer.all_diff_hunks_expanded()
20611 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20612 {
20613 buffer.collapse_diff_hunks(ranges, cx);
20614 true
20615 } else {
20616 false
20617 }
20618 })
20619 }
20620
20621 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20622 if self.buffer.read(cx).all_diff_hunks_expanded() {
20623 return true;
20624 }
20625 let ranges = vec![Anchor::min()..Anchor::max()];
20626 self.buffer
20627 .read(cx)
20628 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20629 }
20630
20631 fn toggle_diff_hunks_in_ranges(
20632 &mut self,
20633 ranges: Vec<Range<Anchor>>,
20634 cx: &mut Context<Editor>,
20635 ) {
20636 self.buffer.update(cx, |buffer, cx| {
20637 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20638 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20639 })
20640 }
20641
20642 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20643 self.buffer.update(cx, |buffer, cx| {
20644 buffer.toggle_single_diff_hunk(range, cx);
20645 })
20646 }
20647
20648 pub(crate) fn apply_all_diff_hunks(
20649 &mut self,
20650 _: &ApplyAllDiffHunks,
20651 window: &mut Window,
20652 cx: &mut Context<Self>,
20653 ) {
20654 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20655
20656 let buffers = self.buffer.read(cx).all_buffers();
20657 for branch_buffer in buffers {
20658 branch_buffer.update(cx, |branch_buffer, cx| {
20659 branch_buffer.merge_into_base(Vec::new(), cx);
20660 });
20661 }
20662
20663 if let Some(project) = self.project.clone() {
20664 self.save(
20665 SaveOptions {
20666 format: true,
20667 autosave: false,
20668 },
20669 project,
20670 window,
20671 cx,
20672 )
20673 .detach_and_log_err(cx);
20674 }
20675 }
20676
20677 pub(crate) fn apply_selected_diff_hunks(
20678 &mut self,
20679 _: &ApplyDiffHunk,
20680 window: &mut Window,
20681 cx: &mut Context<Self>,
20682 ) {
20683 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20684 let snapshot = self.snapshot(window, cx);
20685 let hunks = snapshot.hunks_for_ranges(
20686 self.selections
20687 .all(&snapshot.display_snapshot)
20688 .into_iter()
20689 .map(|selection| selection.range()),
20690 );
20691 let mut ranges_by_buffer = HashMap::default();
20692 self.transact(window, cx, |editor, _window, cx| {
20693 for hunk in hunks {
20694 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20695 ranges_by_buffer
20696 .entry(buffer.clone())
20697 .or_insert_with(Vec::new)
20698 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20699 }
20700 }
20701
20702 for (buffer, ranges) in ranges_by_buffer {
20703 buffer.update(cx, |buffer, cx| {
20704 buffer.merge_into_base(ranges, cx);
20705 });
20706 }
20707 });
20708
20709 if let Some(project) = self.project.clone() {
20710 self.save(
20711 SaveOptions {
20712 format: true,
20713 autosave: false,
20714 },
20715 project,
20716 window,
20717 cx,
20718 )
20719 .detach_and_log_err(cx);
20720 }
20721 }
20722
20723 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20724 if hovered != self.gutter_hovered {
20725 self.gutter_hovered = hovered;
20726 cx.notify();
20727 }
20728 }
20729
20730 pub fn insert_blocks(
20731 &mut self,
20732 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20733 autoscroll: Option<Autoscroll>,
20734 cx: &mut Context<Self>,
20735 ) -> Vec<CustomBlockId> {
20736 let blocks = self
20737 .display_map
20738 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20739 if let Some(autoscroll) = autoscroll {
20740 self.request_autoscroll(autoscroll, cx);
20741 }
20742 cx.notify();
20743 blocks
20744 }
20745
20746 pub fn resize_blocks(
20747 &mut self,
20748 heights: HashMap<CustomBlockId, u32>,
20749 autoscroll: Option<Autoscroll>,
20750 cx: &mut Context<Self>,
20751 ) {
20752 self.display_map
20753 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20754 if let Some(autoscroll) = autoscroll {
20755 self.request_autoscroll(autoscroll, cx);
20756 }
20757 cx.notify();
20758 }
20759
20760 pub fn replace_blocks(
20761 &mut self,
20762 renderers: HashMap<CustomBlockId, RenderBlock>,
20763 autoscroll: Option<Autoscroll>,
20764 cx: &mut Context<Self>,
20765 ) {
20766 self.display_map
20767 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20768 if let Some(autoscroll) = autoscroll {
20769 self.request_autoscroll(autoscroll, cx);
20770 }
20771 cx.notify();
20772 }
20773
20774 pub fn remove_blocks(
20775 &mut self,
20776 block_ids: HashSet<CustomBlockId>,
20777 autoscroll: Option<Autoscroll>,
20778 cx: &mut Context<Self>,
20779 ) {
20780 self.display_map.update(cx, |display_map, cx| {
20781 display_map.remove_blocks(block_ids, cx)
20782 });
20783 if let Some(autoscroll) = autoscroll {
20784 self.request_autoscroll(autoscroll, cx);
20785 }
20786 cx.notify();
20787 }
20788
20789 pub fn row_for_block(
20790 &self,
20791 block_id: CustomBlockId,
20792 cx: &mut Context<Self>,
20793 ) -> Option<DisplayRow> {
20794 self.display_map
20795 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20796 }
20797
20798 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20799 self.focused_block = Some(focused_block);
20800 }
20801
20802 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20803 self.focused_block.take()
20804 }
20805
20806 pub fn insert_creases(
20807 &mut self,
20808 creases: impl IntoIterator<Item = Crease<Anchor>>,
20809 cx: &mut Context<Self>,
20810 ) -> Vec<CreaseId> {
20811 self.display_map
20812 .update(cx, |map, cx| map.insert_creases(creases, cx))
20813 }
20814
20815 pub fn remove_creases(
20816 &mut self,
20817 ids: impl IntoIterator<Item = CreaseId>,
20818 cx: &mut Context<Self>,
20819 ) -> Vec<(CreaseId, Range<Anchor>)> {
20820 self.display_map
20821 .update(cx, |map, cx| map.remove_creases(ids, cx))
20822 }
20823
20824 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20825 self.display_map
20826 .update(cx, |map, cx| map.snapshot(cx))
20827 .longest_row()
20828 }
20829
20830 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20831 self.display_map
20832 .update(cx, |map, cx| map.snapshot(cx))
20833 .max_point()
20834 }
20835
20836 pub fn text(&self, cx: &App) -> String {
20837 self.buffer.read(cx).read(cx).text()
20838 }
20839
20840 pub fn is_empty(&self, cx: &App) -> bool {
20841 self.buffer.read(cx).read(cx).is_empty()
20842 }
20843
20844 pub fn text_option(&self, cx: &App) -> Option<String> {
20845 let text = self.text(cx);
20846 let text = text.trim();
20847
20848 if text.is_empty() {
20849 return None;
20850 }
20851
20852 Some(text.to_string())
20853 }
20854
20855 pub fn set_text(
20856 &mut self,
20857 text: impl Into<Arc<str>>,
20858 window: &mut Window,
20859 cx: &mut Context<Self>,
20860 ) {
20861 self.transact(window, cx, |this, _, cx| {
20862 this.buffer
20863 .read(cx)
20864 .as_singleton()
20865 .expect("you can only call set_text on editors for singleton buffers")
20866 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20867 });
20868 }
20869
20870 pub fn display_text(&self, cx: &mut App) -> String {
20871 self.display_map
20872 .update(cx, |map, cx| map.snapshot(cx))
20873 .text()
20874 }
20875
20876 fn create_minimap(
20877 &self,
20878 minimap_settings: MinimapSettings,
20879 window: &mut Window,
20880 cx: &mut Context<Self>,
20881 ) -> Option<Entity<Self>> {
20882 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20883 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20884 }
20885
20886 fn initialize_new_minimap(
20887 &self,
20888 minimap_settings: MinimapSettings,
20889 window: &mut Window,
20890 cx: &mut Context<Self>,
20891 ) -> Entity<Self> {
20892 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20893 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20894
20895 let mut minimap = Editor::new_internal(
20896 EditorMode::Minimap {
20897 parent: cx.weak_entity(),
20898 },
20899 self.buffer.clone(),
20900 None,
20901 Some(self.display_map.clone()),
20902 window,
20903 cx,
20904 );
20905 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20906 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
20907 minimap.scroll_manager.clone_state(
20908 &self.scroll_manager,
20909 &my_snapshot,
20910 &minimap_snapshot,
20911 cx,
20912 );
20913 minimap.set_text_style_refinement(TextStyleRefinement {
20914 font_size: Some(MINIMAP_FONT_SIZE),
20915 font_weight: Some(MINIMAP_FONT_WEIGHT),
20916 font_family: Some(MINIMAP_FONT_FAMILY),
20917 ..Default::default()
20918 });
20919 minimap.update_minimap_configuration(minimap_settings, cx);
20920 cx.new(|_| minimap)
20921 }
20922
20923 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20924 let current_line_highlight = minimap_settings
20925 .current_line_highlight
20926 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20927 self.set_current_line_highlight(Some(current_line_highlight));
20928 }
20929
20930 pub fn minimap(&self) -> Option<&Entity<Self>> {
20931 self.minimap
20932 .as_ref()
20933 .filter(|_| self.minimap_visibility.visible())
20934 }
20935
20936 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20937 let mut wrap_guides = smallvec![];
20938
20939 if self.show_wrap_guides == Some(false) {
20940 return wrap_guides;
20941 }
20942
20943 let settings = self.buffer.read(cx).language_settings(cx);
20944 if settings.show_wrap_guides {
20945 match self.soft_wrap_mode(cx) {
20946 SoftWrap::Column(soft_wrap) => {
20947 wrap_guides.push((soft_wrap as usize, true));
20948 }
20949 SoftWrap::Bounded(soft_wrap) => {
20950 wrap_guides.push((soft_wrap as usize, true));
20951 }
20952 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20953 }
20954 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20955 }
20956
20957 wrap_guides
20958 }
20959
20960 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20961 let settings = self.buffer.read(cx).language_settings(cx);
20962 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20963 match mode {
20964 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20965 SoftWrap::None
20966 }
20967 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20968 language_settings::SoftWrap::PreferredLineLength => {
20969 SoftWrap::Column(settings.preferred_line_length)
20970 }
20971 language_settings::SoftWrap::Bounded => {
20972 SoftWrap::Bounded(settings.preferred_line_length)
20973 }
20974 }
20975 }
20976
20977 pub fn set_soft_wrap_mode(
20978 &mut self,
20979 mode: language_settings::SoftWrap,
20980 cx: &mut Context<Self>,
20981 ) {
20982 self.soft_wrap_mode_override = Some(mode);
20983 cx.notify();
20984 }
20985
20986 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20987 self.hard_wrap = hard_wrap;
20988 cx.notify();
20989 }
20990
20991 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20992 self.text_style_refinement = Some(style);
20993 }
20994
20995 /// called by the Element so we know what style we were most recently rendered with.
20996 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20997 // We intentionally do not inform the display map about the minimap style
20998 // so that wrapping is not recalculated and stays consistent for the editor
20999 // and its linked minimap.
21000 if !self.mode.is_minimap() {
21001 let font = style.text.font();
21002 let font_size = style.text.font_size.to_pixels(window.rem_size());
21003 let display_map = self
21004 .placeholder_display_map
21005 .as_ref()
21006 .filter(|_| self.is_empty(cx))
21007 .unwrap_or(&self.display_map);
21008
21009 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21010 }
21011 self.style = Some(style);
21012 }
21013
21014 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21015 if self.style.is_none() {
21016 self.style = Some(self.create_style(cx));
21017 }
21018 self.style.as_ref().unwrap()
21019 }
21020
21021 // Called by the element. This method is not designed to be called outside of the editor
21022 // element's layout code because it does not notify when rewrapping is computed synchronously.
21023 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21024 if self.is_empty(cx) {
21025 self.placeholder_display_map
21026 .as_ref()
21027 .map_or(false, |display_map| {
21028 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21029 })
21030 } else {
21031 self.display_map
21032 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21033 }
21034 }
21035
21036 pub fn set_soft_wrap(&mut self) {
21037 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21038 }
21039
21040 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21041 if self.soft_wrap_mode_override.is_some() {
21042 self.soft_wrap_mode_override.take();
21043 } else {
21044 let soft_wrap = match self.soft_wrap_mode(cx) {
21045 SoftWrap::GitDiff => return,
21046 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21047 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21048 language_settings::SoftWrap::None
21049 }
21050 };
21051 self.soft_wrap_mode_override = Some(soft_wrap);
21052 }
21053 cx.notify();
21054 }
21055
21056 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21057 let Some(workspace) = self.workspace() else {
21058 return;
21059 };
21060 let fs = workspace.read(cx).app_state().fs.clone();
21061 let current_show = TabBarSettings::get_global(cx).show;
21062 update_settings_file(fs, cx, move |setting, _| {
21063 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21064 });
21065 }
21066
21067 pub fn toggle_indent_guides(
21068 &mut self,
21069 _: &ToggleIndentGuides,
21070 _: &mut Window,
21071 cx: &mut Context<Self>,
21072 ) {
21073 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21074 self.buffer
21075 .read(cx)
21076 .language_settings(cx)
21077 .indent_guides
21078 .enabled
21079 });
21080 self.show_indent_guides = Some(!currently_enabled);
21081 cx.notify();
21082 }
21083
21084 fn should_show_indent_guides(&self) -> Option<bool> {
21085 self.show_indent_guides
21086 }
21087
21088 pub fn disable_indent_guides_for_buffer(
21089 &mut self,
21090 buffer_id: BufferId,
21091 cx: &mut Context<Self>,
21092 ) {
21093 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21094 cx.notify();
21095 }
21096
21097 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21098 self.buffers_with_disabled_indent_guides
21099 .contains(&buffer_id)
21100 }
21101
21102 pub fn toggle_line_numbers(
21103 &mut self,
21104 _: &ToggleLineNumbers,
21105 _: &mut Window,
21106 cx: &mut Context<Self>,
21107 ) {
21108 let mut editor_settings = EditorSettings::get_global(cx).clone();
21109 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21110 EditorSettings::override_global(editor_settings, cx);
21111 }
21112
21113 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21114 if let Some(show_line_numbers) = self.show_line_numbers {
21115 return show_line_numbers;
21116 }
21117 EditorSettings::get_global(cx).gutter.line_numbers
21118 }
21119
21120 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21121 match (
21122 self.use_relative_line_numbers,
21123 EditorSettings::get_global(cx).relative_line_numbers,
21124 ) {
21125 (None, setting) => setting,
21126 (Some(false), _) => RelativeLineNumbers::Disabled,
21127 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21128 (Some(true), _) => RelativeLineNumbers::Enabled,
21129 }
21130 }
21131
21132 pub fn toggle_relative_line_numbers(
21133 &mut self,
21134 _: &ToggleRelativeLineNumbers,
21135 _: &mut Window,
21136 cx: &mut Context<Self>,
21137 ) {
21138 let is_relative = self.relative_line_numbers(cx);
21139 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21140 }
21141
21142 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21143 self.use_relative_line_numbers = is_relative;
21144 cx.notify();
21145 }
21146
21147 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21148 self.show_gutter = show_gutter;
21149 cx.notify();
21150 }
21151
21152 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21153 self.show_scrollbars = ScrollbarAxes {
21154 horizontal: show,
21155 vertical: show,
21156 };
21157 cx.notify();
21158 }
21159
21160 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21161 self.show_scrollbars.vertical = show;
21162 cx.notify();
21163 }
21164
21165 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21166 self.show_scrollbars.horizontal = show;
21167 cx.notify();
21168 }
21169
21170 pub fn set_minimap_visibility(
21171 &mut self,
21172 minimap_visibility: MinimapVisibility,
21173 window: &mut Window,
21174 cx: &mut Context<Self>,
21175 ) {
21176 if self.minimap_visibility != minimap_visibility {
21177 if minimap_visibility.visible() && self.minimap.is_none() {
21178 let minimap_settings = EditorSettings::get_global(cx).minimap;
21179 self.minimap =
21180 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21181 }
21182 self.minimap_visibility = minimap_visibility;
21183 cx.notify();
21184 }
21185 }
21186
21187 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21188 self.set_show_scrollbars(false, cx);
21189 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21190 }
21191
21192 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21193 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21194 }
21195
21196 /// Normally the text in full mode and auto height editors is padded on the
21197 /// left side by roughly half a character width for improved hit testing.
21198 ///
21199 /// Use this method to disable this for cases where this is not wanted (e.g.
21200 /// if you want to align the editor text with some other text above or below)
21201 /// or if you want to add this padding to single-line editors.
21202 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21203 self.offset_content = offset_content;
21204 cx.notify();
21205 }
21206
21207 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21208 self.show_line_numbers = Some(show_line_numbers);
21209 cx.notify();
21210 }
21211
21212 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21213 self.disable_expand_excerpt_buttons = true;
21214 cx.notify();
21215 }
21216
21217 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21218 self.number_deleted_lines = number;
21219 cx.notify();
21220 }
21221
21222 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21223 self.delegate_expand_excerpts = delegate;
21224 }
21225
21226 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21227 self.delegate_stage_and_restore = delegate;
21228 }
21229
21230 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21231 self.delegate_open_excerpts = delegate;
21232 }
21233
21234 pub fn set_on_local_selections_changed(
21235 &mut self,
21236 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21237 ) {
21238 self.on_local_selections_changed = callback;
21239 }
21240
21241 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21242 self.suppress_selection_callback = suppress;
21243 }
21244
21245 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21246 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21247 cx.notify();
21248 }
21249
21250 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21251 self.show_code_actions = Some(show_code_actions);
21252 cx.notify();
21253 }
21254
21255 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21256 self.show_runnables = Some(show_runnables);
21257 cx.notify();
21258 }
21259
21260 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21261 self.show_breakpoints = Some(show_breakpoints);
21262 cx.notify();
21263 }
21264
21265 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21266 self.show_diff_review_button = show;
21267 cx.notify();
21268 }
21269
21270 pub fn show_diff_review_button(&self) -> bool {
21271 self.show_diff_review_button
21272 }
21273
21274 pub fn render_diff_review_button(
21275 &self,
21276 display_row: DisplayRow,
21277 width: Pixels,
21278 cx: &mut Context<Self>,
21279 ) -> impl IntoElement {
21280 let text_color = cx.theme().colors().text;
21281 let icon_color = cx.theme().colors().icon_accent;
21282
21283 h_flex()
21284 .id("diff_review_button")
21285 .cursor_pointer()
21286 .w(width - px(1.))
21287 .h(relative(0.9))
21288 .justify_center()
21289 .rounded_sm()
21290 .border_1()
21291 .border_color(text_color.opacity(0.1))
21292 .bg(text_color.opacity(0.15))
21293 .hover(|s| {
21294 s.bg(icon_color.opacity(0.4))
21295 .border_color(icon_color.opacity(0.5))
21296 })
21297 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21298 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21299 .on_mouse_down(
21300 gpui::MouseButton::Left,
21301 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21302 editor.start_diff_review_drag(display_row, window, cx);
21303 }),
21304 )
21305 }
21306
21307 pub fn start_diff_review_drag(
21308 &mut self,
21309 display_row: DisplayRow,
21310 window: &mut Window,
21311 cx: &mut Context<Self>,
21312 ) {
21313 let snapshot = self.snapshot(window, cx);
21314 let point = snapshot
21315 .display_snapshot
21316 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21317 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21318 self.diff_review_drag_state = Some(DiffReviewDragState {
21319 start_anchor: anchor,
21320 current_anchor: anchor,
21321 });
21322 cx.notify();
21323 }
21324
21325 pub fn update_diff_review_drag(
21326 &mut self,
21327 display_row: DisplayRow,
21328 window: &mut Window,
21329 cx: &mut Context<Self>,
21330 ) {
21331 if self.diff_review_drag_state.is_none() {
21332 return;
21333 }
21334 let snapshot = self.snapshot(window, cx);
21335 let point = snapshot
21336 .display_snapshot
21337 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21338 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21339 if let Some(drag_state) = &mut self.diff_review_drag_state {
21340 drag_state.current_anchor = anchor;
21341 cx.notify();
21342 }
21343 }
21344
21345 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21346 if let Some(drag_state) = self.diff_review_drag_state.take() {
21347 let snapshot = self.snapshot(window, cx);
21348 let range = drag_state.row_range(&snapshot.display_snapshot);
21349 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21350 }
21351 cx.notify();
21352 }
21353
21354 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21355 self.diff_review_drag_state = None;
21356 cx.notify();
21357 }
21358
21359 /// Calculates the appropriate block height for the diff review overlay.
21360 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21361 /// and 2 lines per comment when expanded.
21362 fn calculate_overlay_height(
21363 &self,
21364 hunk_key: &DiffHunkKey,
21365 comments_expanded: bool,
21366 snapshot: &MultiBufferSnapshot,
21367 ) -> u32 {
21368 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21369 let base_height: u32 = 2; // Input row with avatar and buttons
21370
21371 if comment_count == 0 {
21372 base_height
21373 } else if comments_expanded {
21374 // Header (1 line) + 2 lines per comment
21375 base_height + 1 + (comment_count as u32 * 2)
21376 } else {
21377 // Just header when collapsed
21378 base_height + 1
21379 }
21380 }
21381
21382 pub fn show_diff_review_overlay(
21383 &mut self,
21384 display_range: Range<DisplayRow>,
21385 window: &mut Window,
21386 cx: &mut Context<Self>,
21387 ) {
21388 let Range { start, end } = display_range.sorted();
21389
21390 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21391 let editor_snapshot = self.snapshot(window, cx);
21392
21393 // Convert display rows to multibuffer points
21394 let start_point = editor_snapshot
21395 .display_snapshot
21396 .display_point_to_point(start.as_display_point(), Bias::Left);
21397 let end_point = editor_snapshot
21398 .display_snapshot
21399 .display_point_to_point(end.as_display_point(), Bias::Left);
21400 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21401
21402 // Create anchor range for the selected lines (start of first line to end of last line)
21403 let line_end = Point::new(
21404 end_point.row,
21405 buffer_snapshot.line_len(end_multi_buffer_row),
21406 );
21407 let anchor_range =
21408 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21409
21410 // Compute the hunk key for this display row
21411 let file_path = buffer_snapshot
21412 .file_at(start_point)
21413 .map(|file: &Arc<dyn language::File>| file.path().clone())
21414 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21415 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21416 let new_hunk_key = DiffHunkKey {
21417 file_path,
21418 hunk_start_anchor,
21419 };
21420
21421 // Check if we already have an overlay for this hunk
21422 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21423 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21424 }) {
21425 // Just focus the existing overlay's prompt editor
21426 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21427 window.focus(&focus_handle, cx);
21428 return;
21429 }
21430
21431 // Dismiss overlays that have no comments for their hunks
21432 self.dismiss_overlays_without_comments(cx);
21433
21434 // Get the current user's avatar URI from the project's user_store
21435 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21436 let user_store = project.read(cx).user_store();
21437 user_store
21438 .read(cx)
21439 .current_user()
21440 .map(|user| user.avatar_uri.clone())
21441 });
21442
21443 // Create anchor at the end of the last row so the block appears immediately below it
21444 // Use multibuffer coordinates for anchor creation
21445 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21446 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21447
21448 // Use the hunk key we already computed
21449 let hunk_key = new_hunk_key;
21450
21451 // Create the prompt editor for the review input
21452 let prompt_editor = cx.new(|cx| {
21453 let mut editor = Editor::single_line(window, cx);
21454 editor.set_placeholder_text("Add a review comment...", window, cx);
21455 editor
21456 });
21457
21458 // Register the Newline action on the prompt editor to submit the review
21459 let parent_editor = cx.entity().downgrade();
21460 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21461 prompt_editor.register_action({
21462 let parent_editor = parent_editor.clone();
21463 move |_: &crate::actions::Newline, window, cx| {
21464 if let Some(editor) = parent_editor.upgrade() {
21465 editor.update(cx, |editor, cx| {
21466 editor.submit_diff_review_comment(window, cx);
21467 });
21468 }
21469 }
21470 })
21471 });
21472
21473 // Calculate initial height based on existing comments for this hunk
21474 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21475
21476 // Create the overlay block
21477 let prompt_editor_for_render = prompt_editor.clone();
21478 let hunk_key_for_render = hunk_key.clone();
21479 let editor_handle = cx.entity().downgrade();
21480 let block = BlockProperties {
21481 style: BlockStyle::Sticky,
21482 placement: BlockPlacement::Below(anchor),
21483 height: Some(initial_height),
21484 render: Arc::new(move |cx| {
21485 Self::render_diff_review_overlay(
21486 &prompt_editor_for_render,
21487 &hunk_key_for_render,
21488 &editor_handle,
21489 cx,
21490 )
21491 }),
21492 priority: 0,
21493 };
21494
21495 let block_ids = self.insert_blocks([block], None, cx);
21496 let Some(block_id) = block_ids.into_iter().next() else {
21497 log::error!("Failed to insert diff review overlay block");
21498 return;
21499 };
21500
21501 self.diff_review_overlays.push(DiffReviewOverlay {
21502 anchor_range,
21503 block_id,
21504 prompt_editor: prompt_editor.clone(),
21505 hunk_key,
21506 comments_expanded: true,
21507 inline_edit_editors: HashMap::default(),
21508 inline_edit_subscriptions: HashMap::default(),
21509 user_avatar_uri,
21510 _subscription: subscription,
21511 });
21512
21513 // Focus the prompt editor
21514 let focus_handle = prompt_editor.focus_handle(cx);
21515 window.focus(&focus_handle, cx);
21516
21517 cx.notify();
21518 }
21519
21520 /// Dismisses all diff review overlays.
21521 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21522 if self.diff_review_overlays.is_empty() {
21523 return;
21524 }
21525 let block_ids: HashSet<_> = self
21526 .diff_review_overlays
21527 .drain(..)
21528 .map(|overlay| overlay.block_id)
21529 .collect();
21530 self.remove_blocks(block_ids, None, cx);
21531 cx.notify();
21532 }
21533
21534 /// Dismisses overlays that have no comments stored for their hunks.
21535 /// Keeps overlays that have at least one comment.
21536 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21537 let snapshot = self.buffer.read(cx).snapshot(cx);
21538
21539 // First, compute which overlays have comments (to avoid borrow issues with retain)
21540 let overlays_with_comments: Vec<bool> = self
21541 .diff_review_overlays
21542 .iter()
21543 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21544 .collect();
21545
21546 // Now collect block IDs to remove and retain overlays
21547 let mut block_ids_to_remove = HashSet::default();
21548 let mut index = 0;
21549 self.diff_review_overlays.retain(|overlay| {
21550 let has_comments = overlays_with_comments[index];
21551 index += 1;
21552 if !has_comments {
21553 block_ids_to_remove.insert(overlay.block_id);
21554 }
21555 has_comments
21556 });
21557
21558 if !block_ids_to_remove.is_empty() {
21559 self.remove_blocks(block_ids_to_remove, None, cx);
21560 cx.notify();
21561 }
21562 }
21563
21564 /// Refreshes the diff review overlay block to update its height and render function.
21565 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21566 fn refresh_diff_review_overlay_height(
21567 &mut self,
21568 hunk_key: &DiffHunkKey,
21569 _window: &mut Window,
21570 cx: &mut Context<Self>,
21571 ) {
21572 // Extract all needed data from overlay first to avoid borrow conflicts
21573 let snapshot = self.buffer.read(cx).snapshot(cx);
21574 let (comments_expanded, block_id, prompt_editor) = {
21575 let Some(overlay) = self
21576 .diff_review_overlays
21577 .iter()
21578 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21579 else {
21580 return;
21581 };
21582
21583 (
21584 overlay.comments_expanded,
21585 overlay.block_id,
21586 overlay.prompt_editor.clone(),
21587 )
21588 };
21589
21590 // Calculate new height
21591 let snapshot = self.buffer.read(cx).snapshot(cx);
21592 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21593
21594 // Update the block height using resize_blocks (avoids flicker)
21595 let mut heights = HashMap::default();
21596 heights.insert(block_id, new_height);
21597 self.resize_blocks(heights, None, cx);
21598
21599 // Update the render function using replace_blocks (avoids flicker)
21600 let hunk_key_for_render = hunk_key.clone();
21601 let editor_handle = cx.entity().downgrade();
21602 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21603 Arc::new(move |cx| {
21604 Self::render_diff_review_overlay(
21605 &prompt_editor,
21606 &hunk_key_for_render,
21607 &editor_handle,
21608 cx,
21609 )
21610 });
21611
21612 let mut renderers = HashMap::default();
21613 renderers.insert(block_id, render);
21614 self.replace_blocks(renderers, None, cx);
21615 }
21616
21617 /// Action handler for SubmitDiffReviewComment.
21618 pub fn submit_diff_review_comment_action(
21619 &mut self,
21620 _: &SubmitDiffReviewComment,
21621 window: &mut Window,
21622 cx: &mut Context<Self>,
21623 ) {
21624 self.submit_diff_review_comment(window, cx);
21625 }
21626
21627 /// Stores the diff review comment locally.
21628 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21629 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21630 // Find the overlay that currently has focus
21631 let overlay_index = self
21632 .diff_review_overlays
21633 .iter()
21634 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21635 let Some(overlay_index) = overlay_index else {
21636 return;
21637 };
21638 let overlay = &self.diff_review_overlays[overlay_index];
21639
21640 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21641 if comment_text.is_empty() {
21642 return;
21643 }
21644
21645 let anchor_range = overlay.anchor_range.clone();
21646 let hunk_key = overlay.hunk_key.clone();
21647
21648 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21649
21650 // Clear the prompt editor but keep the overlay open
21651 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21652 overlay.prompt_editor.update(cx, |editor, cx| {
21653 editor.clear(window, cx);
21654 });
21655 }
21656
21657 // Refresh the overlay to update the block height for the new comment
21658 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21659
21660 cx.notify();
21661 }
21662
21663 /// Returns the prompt editor for the diff review overlay, if one is active.
21664 /// This is primarily used for testing.
21665 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21666 self.diff_review_overlays
21667 .first()
21668 .map(|overlay| &overlay.prompt_editor)
21669 }
21670
21671 /// Returns the line range for the first diff review overlay, if one is active.
21672 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21673 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21674 let overlay = self.diff_review_overlays.first()?;
21675 let snapshot = self.buffer.read(cx).snapshot(cx);
21676 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21677 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21678 let start_row = snapshot
21679 .point_to_buffer_point(start_point)
21680 .map(|(_, p, _)| p.row)
21681 .unwrap_or(start_point.row);
21682 let end_row = snapshot
21683 .point_to_buffer_point(end_point)
21684 .map(|(_, p, _)| p.row)
21685 .unwrap_or(end_point.row);
21686 Some((start_row, end_row))
21687 }
21688
21689 /// Sets whether the comments section is expanded in the diff review overlay.
21690 /// This is primarily used for testing.
21691 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21692 for overlay in &mut self.diff_review_overlays {
21693 overlay.comments_expanded = expanded;
21694 }
21695 cx.notify();
21696 }
21697
21698 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21699 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21700 a.file_path == b.file_path
21701 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21702 }
21703
21704 /// Returns comments for a specific hunk, ordered by creation time.
21705 pub fn comments_for_hunk<'a>(
21706 &'a self,
21707 key: &DiffHunkKey,
21708 snapshot: &MultiBufferSnapshot,
21709 ) -> &'a [StoredReviewComment] {
21710 let key_point = key.hunk_start_anchor.to_point(snapshot);
21711 self.stored_review_comments
21712 .iter()
21713 .find(|(k, _)| {
21714 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21715 })
21716 .map(|(_, comments)| comments.as_slice())
21717 .unwrap_or(&[])
21718 }
21719
21720 /// Returns the total count of stored review comments across all hunks.
21721 pub fn total_review_comment_count(&self) -> usize {
21722 self.stored_review_comments
21723 .iter()
21724 .map(|(_, v)| v.len())
21725 .sum()
21726 }
21727
21728 /// Returns the count of comments for a specific hunk.
21729 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21730 let key_point = key.hunk_start_anchor.to_point(snapshot);
21731 self.stored_review_comments
21732 .iter()
21733 .find(|(k, _)| {
21734 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21735 })
21736 .map(|(_, v)| v.len())
21737 .unwrap_or(0)
21738 }
21739
21740 /// Adds a new review comment to a specific hunk.
21741 pub fn add_review_comment(
21742 &mut self,
21743 hunk_key: DiffHunkKey,
21744 comment: String,
21745 anchor_range: Range<Anchor>,
21746 cx: &mut Context<Self>,
21747 ) -> usize {
21748 let id = self.next_review_comment_id;
21749 self.next_review_comment_id += 1;
21750
21751 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21752
21753 let snapshot = self.buffer.read(cx).snapshot(cx);
21754 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21755
21756 // Find existing entry for this hunk or add a new one
21757 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21758 k.file_path == hunk_key.file_path
21759 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21760 }) {
21761 comments.push(stored_comment);
21762 } else {
21763 self.stored_review_comments
21764 .push((hunk_key, vec![stored_comment]));
21765 }
21766
21767 cx.emit(EditorEvent::ReviewCommentsChanged {
21768 total_count: self.total_review_comment_count(),
21769 });
21770 cx.notify();
21771 id
21772 }
21773
21774 /// Removes a review comment by ID from any hunk.
21775 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21776 for (_, comments) in self.stored_review_comments.iter_mut() {
21777 if let Some(index) = comments.iter().position(|c| c.id == id) {
21778 comments.remove(index);
21779 cx.emit(EditorEvent::ReviewCommentsChanged {
21780 total_count: self.total_review_comment_count(),
21781 });
21782 cx.notify();
21783 return true;
21784 }
21785 }
21786 false
21787 }
21788
21789 /// Updates a review comment's text by ID.
21790 pub fn update_review_comment(
21791 &mut self,
21792 id: usize,
21793 new_comment: String,
21794 cx: &mut Context<Self>,
21795 ) -> bool {
21796 for (_, comments) in self.stored_review_comments.iter_mut() {
21797 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21798 comment.comment = new_comment;
21799 comment.is_editing = false;
21800 cx.emit(EditorEvent::ReviewCommentsChanged {
21801 total_count: self.total_review_comment_count(),
21802 });
21803 cx.notify();
21804 return true;
21805 }
21806 }
21807 false
21808 }
21809
21810 /// Sets a comment's editing state.
21811 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21812 for (_, comments) in self.stored_review_comments.iter_mut() {
21813 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21814 comment.is_editing = is_editing;
21815 cx.notify();
21816 return;
21817 }
21818 }
21819 }
21820
21821 /// Takes all stored comments from all hunks, clearing the storage.
21822 /// Returns a Vec of (hunk_key, comments) pairs.
21823 pub fn take_all_review_comments(
21824 &mut self,
21825 cx: &mut Context<Self>,
21826 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21827 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21828 self.dismiss_all_diff_review_overlays(cx);
21829 let comments = std::mem::take(&mut self.stored_review_comments);
21830 // Reset the ID counter since all comments have been taken
21831 self.next_review_comment_id = 0;
21832 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21833 cx.notify();
21834 comments
21835 }
21836
21837 /// Removes review comments whose anchors are no longer valid or whose
21838 /// associated diff hunks no longer exist.
21839 ///
21840 /// This should be called when the buffer changes to prevent orphaned comments
21841 /// from accumulating.
21842 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21843 let snapshot = self.buffer.read(cx).snapshot(cx);
21844 let original_count = self.total_review_comment_count();
21845
21846 // Remove comments with invalid hunk anchors
21847 self.stored_review_comments
21848 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21849
21850 // Also clean up individual comments with invalid anchor ranges
21851 for (_, comments) in &mut self.stored_review_comments {
21852 comments.retain(|comment| {
21853 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
21854 });
21855 }
21856
21857 // Remove empty hunk entries
21858 self.stored_review_comments
21859 .retain(|(_, comments)| !comments.is_empty());
21860
21861 let new_count = self.total_review_comment_count();
21862 if new_count != original_count {
21863 cx.emit(EditorEvent::ReviewCommentsChanged {
21864 total_count: new_count,
21865 });
21866 cx.notify();
21867 }
21868 }
21869
21870 /// Toggles the expanded state of the comments section in the overlay.
21871 pub fn toggle_review_comments_expanded(
21872 &mut self,
21873 _: &ToggleReviewCommentsExpanded,
21874 window: &mut Window,
21875 cx: &mut Context<Self>,
21876 ) {
21877 // Find the overlay that currently has focus, or use the first one
21878 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21879 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21880 overlay.comments_expanded = !overlay.comments_expanded;
21881 Some(overlay.hunk_key.clone())
21882 } else {
21883 None
21884 }
21885 });
21886
21887 // If no focused overlay found, toggle the first one
21888 let hunk_key = overlay_info.or_else(|| {
21889 self.diff_review_overlays.first_mut().map(|overlay| {
21890 overlay.comments_expanded = !overlay.comments_expanded;
21891 overlay.hunk_key.clone()
21892 })
21893 });
21894
21895 if let Some(hunk_key) = hunk_key {
21896 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21897 cx.notify();
21898 }
21899 }
21900
21901 /// Handles the EditReviewComment action - sets a comment into editing mode.
21902 pub fn edit_review_comment(
21903 &mut self,
21904 action: &EditReviewComment,
21905 window: &mut Window,
21906 cx: &mut Context<Self>,
21907 ) {
21908 let comment_id = action.id;
21909
21910 // Set the comment to editing mode
21911 self.set_comment_editing(comment_id, true, cx);
21912
21913 // Find the overlay that contains this comment and create an inline editor if needed
21914 // First, find which hunk this comment belongs to
21915 let hunk_key = self
21916 .stored_review_comments
21917 .iter()
21918 .find_map(|(key, comments)| {
21919 if comments.iter().any(|c| c.id == comment_id) {
21920 Some(key.clone())
21921 } else {
21922 None
21923 }
21924 });
21925
21926 let snapshot = self.buffer.read(cx).snapshot(cx);
21927 if let Some(hunk_key) = hunk_key {
21928 if let Some(overlay) = self
21929 .diff_review_overlays
21930 .iter_mut()
21931 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21932 {
21933 if let std::collections::hash_map::Entry::Vacant(entry) =
21934 overlay.inline_edit_editors.entry(comment_id)
21935 {
21936 // Find the comment text
21937 let comment_text = self
21938 .stored_review_comments
21939 .iter()
21940 .flat_map(|(_, comments)| comments)
21941 .find(|c| c.id == comment_id)
21942 .map(|c| c.comment.clone())
21943 .unwrap_or_default();
21944
21945 // Create inline editor
21946 let parent_editor = cx.entity().downgrade();
21947 let inline_editor = cx.new(|cx| {
21948 let mut editor = Editor::single_line(window, cx);
21949 editor.set_text(&*comment_text, window, cx);
21950 // Select all text for easy replacement
21951 editor.select_all(&crate::actions::SelectAll, window, cx);
21952 editor
21953 });
21954
21955 // Register the Newline action to confirm the edit
21956 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
21957 inline_editor.register_action({
21958 let parent_editor = parent_editor.clone();
21959 move |_: &crate::actions::Newline, window, cx| {
21960 if let Some(editor) = parent_editor.upgrade() {
21961 editor.update(cx, |editor, cx| {
21962 editor.confirm_edit_review_comment(comment_id, window, cx);
21963 });
21964 }
21965 }
21966 })
21967 });
21968
21969 // Store the subscription to keep the action handler alive
21970 overlay
21971 .inline_edit_subscriptions
21972 .insert(comment_id, subscription);
21973
21974 // Focus the inline editor
21975 let focus_handle = inline_editor.focus_handle(cx);
21976 window.focus(&focus_handle, cx);
21977
21978 entry.insert(inline_editor);
21979 }
21980 }
21981 }
21982
21983 cx.notify();
21984 }
21985
21986 /// Confirms an inline edit of a review comment.
21987 pub fn confirm_edit_review_comment(
21988 &mut self,
21989 comment_id: usize,
21990 _window: &mut Window,
21991 cx: &mut Context<Self>,
21992 ) {
21993 // Get the new text from the inline editor
21994 // Find the overlay containing this comment's inline editor
21995 let snapshot = self.buffer.read(cx).snapshot(cx);
21996 let hunk_key = self
21997 .stored_review_comments
21998 .iter()
21999 .find_map(|(key, comments)| {
22000 if comments.iter().any(|c| c.id == comment_id) {
22001 Some(key.clone())
22002 } else {
22003 None
22004 }
22005 });
22006
22007 let new_text = hunk_key
22008 .as_ref()
22009 .and_then(|hunk_key| {
22010 self.diff_review_overlays
22011 .iter()
22012 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22013 })
22014 .as_ref()
22015 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22016 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22017
22018 if let Some(new_text) = new_text {
22019 if !new_text.is_empty() {
22020 self.update_review_comment(comment_id, new_text, cx);
22021 }
22022 }
22023
22024 // Remove the inline editor and its subscription
22025 if let Some(hunk_key) = hunk_key {
22026 if let Some(overlay) = self
22027 .diff_review_overlays
22028 .iter_mut()
22029 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22030 {
22031 overlay.inline_edit_editors.remove(&comment_id);
22032 overlay.inline_edit_subscriptions.remove(&comment_id);
22033 }
22034 }
22035
22036 // Clear editing state
22037 self.set_comment_editing(comment_id, false, cx);
22038 }
22039
22040 /// Cancels an inline edit of a review comment.
22041 pub fn cancel_edit_review_comment(
22042 &mut self,
22043 comment_id: usize,
22044 _window: &mut Window,
22045 cx: &mut Context<Self>,
22046 ) {
22047 // Find which hunk this comment belongs to
22048 let hunk_key = self
22049 .stored_review_comments
22050 .iter()
22051 .find_map(|(key, comments)| {
22052 if comments.iter().any(|c| c.id == comment_id) {
22053 Some(key.clone())
22054 } else {
22055 None
22056 }
22057 });
22058
22059 // Remove the inline editor and its subscription
22060 if let Some(hunk_key) = hunk_key {
22061 let snapshot = self.buffer.read(cx).snapshot(cx);
22062 if let Some(overlay) = self
22063 .diff_review_overlays
22064 .iter_mut()
22065 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22066 {
22067 overlay.inline_edit_editors.remove(&comment_id);
22068 overlay.inline_edit_subscriptions.remove(&comment_id);
22069 }
22070 }
22071
22072 // Clear editing state
22073 self.set_comment_editing(comment_id, false, cx);
22074 }
22075
22076 /// Action handler for ConfirmEditReviewComment.
22077 pub fn confirm_edit_review_comment_action(
22078 &mut self,
22079 action: &ConfirmEditReviewComment,
22080 window: &mut Window,
22081 cx: &mut Context<Self>,
22082 ) {
22083 self.confirm_edit_review_comment(action.id, window, cx);
22084 }
22085
22086 /// Action handler for CancelEditReviewComment.
22087 pub fn cancel_edit_review_comment_action(
22088 &mut self,
22089 action: &CancelEditReviewComment,
22090 window: &mut Window,
22091 cx: &mut Context<Self>,
22092 ) {
22093 self.cancel_edit_review_comment(action.id, window, cx);
22094 }
22095
22096 /// Handles the DeleteReviewComment action - removes a comment.
22097 pub fn delete_review_comment(
22098 &mut self,
22099 action: &DeleteReviewComment,
22100 window: &mut Window,
22101 cx: &mut Context<Self>,
22102 ) {
22103 // Get the hunk key before removing the comment
22104 // Find the hunk key from the comment itself
22105 let comment_id = action.id;
22106 let hunk_key = self
22107 .stored_review_comments
22108 .iter()
22109 .find_map(|(key, comments)| {
22110 if comments.iter().any(|c| c.id == comment_id) {
22111 Some(key.clone())
22112 } else {
22113 None
22114 }
22115 });
22116
22117 // Also get it from the overlay for refresh purposes
22118 let overlay_hunk_key = self
22119 .diff_review_overlays
22120 .first()
22121 .map(|o| o.hunk_key.clone());
22122
22123 self.remove_review_comment(action.id, cx);
22124
22125 // Refresh the overlay height after removing a comment
22126 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22127 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22128 }
22129 }
22130
22131 fn render_diff_review_overlay(
22132 prompt_editor: &Entity<Editor>,
22133 hunk_key: &DiffHunkKey,
22134 editor_handle: &WeakEntity<Editor>,
22135 cx: &mut BlockContext,
22136 ) -> AnyElement {
22137 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22138 if ranges.is_empty() {
22139 return None;
22140 }
22141 let formatted: Vec<String> = ranges
22142 .iter()
22143 .map(|(start, end)| {
22144 let start_line = start + 1;
22145 let end_line = end + 1;
22146 if start_line == end_line {
22147 format!("Line {start_line}")
22148 } else {
22149 format!("Lines {start_line}-{end_line}")
22150 }
22151 })
22152 .collect();
22153 // Don't show label for single line in single excerpt
22154 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22155 return None;
22156 }
22157 Some(formatted.join(" ⋯ "))
22158 }
22159
22160 let theme = cx.theme();
22161 let colors = theme.colors();
22162
22163 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22164 editor_handle
22165 .upgrade()
22166 .map(|editor| {
22167 let editor = editor.read(cx);
22168 let snapshot = editor.buffer().read(cx).snapshot(cx);
22169 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22170 let (expanded, editors, avatar_uri, line_ranges) = editor
22171 .diff_review_overlays
22172 .iter()
22173 .find(|overlay| {
22174 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22175 })
22176 .map(|o| {
22177 let start_point = o.anchor_range.start.to_point(&snapshot);
22178 let end_point = o.anchor_range.end.to_point(&snapshot);
22179 // Get line ranges per excerpt to detect discontinuities
22180 let buffer_ranges =
22181 snapshot.range_to_buffer_ranges(start_point..end_point);
22182 let ranges: Vec<(u32, u32)> = buffer_ranges
22183 .iter()
22184 .map(|(buffer, range, _)| {
22185 let start = buffer.offset_to_point(range.start.0).row;
22186 let end = buffer.offset_to_point(range.end.0).row;
22187 (start, end)
22188 })
22189 .collect();
22190 (
22191 o.comments_expanded,
22192 o.inline_edit_editors.clone(),
22193 o.user_avatar_uri.clone(),
22194 if ranges.is_empty() {
22195 None
22196 } else {
22197 Some(ranges)
22198 },
22199 )
22200 })
22201 .unwrap_or((true, HashMap::default(), None, None));
22202 (comments, expanded, editors, avatar_uri, line_ranges)
22203 })
22204 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22205
22206 let comment_count = comments.len();
22207 let avatar_size = px(20.);
22208 let action_icon_size = IconSize::XSmall;
22209
22210 v_flex()
22211 .w_full()
22212 .bg(colors.editor_background)
22213 .border_b_1()
22214 .border_color(colors.border)
22215 .px_2()
22216 .pb_2()
22217 .gap_2()
22218 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22219 .when_some(line_ranges, |el, ranges| {
22220 let label = format_line_ranges(&ranges);
22221 if let Some(label) = label {
22222 el.child(
22223 h_flex()
22224 .w_full()
22225 .px_2()
22226 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22227 )
22228 } else {
22229 el
22230 }
22231 })
22232 // Top row: editable input with user's avatar
22233 .child(
22234 h_flex()
22235 .w_full()
22236 .items_center()
22237 .gap_2()
22238 .px_2()
22239 .py_1p5()
22240 .rounded_md()
22241 .bg(colors.surface_background)
22242 .child(
22243 div()
22244 .size(avatar_size)
22245 .flex_shrink_0()
22246 .rounded_full()
22247 .overflow_hidden()
22248 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22249 Avatar::new(avatar_uri.clone())
22250 .size(avatar_size)
22251 .into_any_element()
22252 } else {
22253 Icon::new(IconName::Person)
22254 .size(IconSize::Small)
22255 .color(ui::Color::Muted)
22256 .into_any_element()
22257 }),
22258 )
22259 .child(
22260 div()
22261 .flex_1()
22262 .border_1()
22263 .border_color(colors.border)
22264 .rounded_md()
22265 .bg(colors.editor_background)
22266 .px_2()
22267 .py_1()
22268 .child(prompt_editor.clone()),
22269 )
22270 .child(
22271 h_flex()
22272 .flex_shrink_0()
22273 .gap_1()
22274 .child(
22275 IconButton::new("diff-review-close", IconName::Close)
22276 .icon_color(ui::Color::Muted)
22277 .icon_size(action_icon_size)
22278 .tooltip(Tooltip::text("Close"))
22279 .on_click(|_, window, cx| {
22280 window
22281 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22282 }),
22283 )
22284 .child(
22285 IconButton::new("diff-review-add", IconName::Return)
22286 .icon_color(ui::Color::Muted)
22287 .icon_size(action_icon_size)
22288 .tooltip(Tooltip::text("Add comment"))
22289 .on_click(|_, window, cx| {
22290 window.dispatch_action(
22291 Box::new(crate::actions::SubmitDiffReviewComment),
22292 cx,
22293 );
22294 }),
22295 ),
22296 ),
22297 )
22298 // Expandable comments section (only shown when there are comments)
22299 .when(comment_count > 0, |el| {
22300 el.child(Self::render_comments_section(
22301 comments,
22302 comments_expanded,
22303 inline_editors,
22304 user_avatar_uri,
22305 avatar_size,
22306 action_icon_size,
22307 colors,
22308 ))
22309 })
22310 .into_any_element()
22311 }
22312
22313 fn render_comments_section(
22314 comments: Vec<StoredReviewComment>,
22315 expanded: bool,
22316 inline_editors: HashMap<usize, Entity<Editor>>,
22317 user_avatar_uri: Option<SharedUri>,
22318 avatar_size: Pixels,
22319 action_icon_size: IconSize,
22320 colors: &theme::ThemeColors,
22321 ) -> impl IntoElement {
22322 let comment_count = comments.len();
22323
22324 v_flex()
22325 .w_full()
22326 .gap_1()
22327 // Header with expand/collapse toggle
22328 .child(
22329 h_flex()
22330 .id("review-comments-header")
22331 .w_full()
22332 .items_center()
22333 .gap_1()
22334 .px_2()
22335 .py_1()
22336 .cursor_pointer()
22337 .rounded_md()
22338 .hover(|style| style.bg(colors.ghost_element_hover))
22339 .on_click(|_, window: &mut Window, cx| {
22340 window.dispatch_action(
22341 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22342 cx,
22343 );
22344 })
22345 .child(
22346 Icon::new(if expanded {
22347 IconName::ChevronDown
22348 } else {
22349 IconName::ChevronRight
22350 })
22351 .size(IconSize::Small)
22352 .color(ui::Color::Muted),
22353 )
22354 .child(
22355 Label::new(format!(
22356 "{} Comment{}",
22357 comment_count,
22358 if comment_count == 1 { "" } else { "s" }
22359 ))
22360 .size(LabelSize::Small)
22361 .color(Color::Muted),
22362 ),
22363 )
22364 // Comments list (when expanded)
22365 .when(expanded, |el| {
22366 el.children(comments.into_iter().map(|comment| {
22367 let inline_editor = inline_editors.get(&comment.id).cloned();
22368 Self::render_comment_row(
22369 comment,
22370 inline_editor,
22371 user_avatar_uri.clone(),
22372 avatar_size,
22373 action_icon_size,
22374 colors,
22375 )
22376 }))
22377 })
22378 }
22379
22380 fn render_comment_row(
22381 comment: StoredReviewComment,
22382 inline_editor: Option<Entity<Editor>>,
22383 user_avatar_uri: Option<SharedUri>,
22384 avatar_size: Pixels,
22385 action_icon_size: IconSize,
22386 colors: &theme::ThemeColors,
22387 ) -> impl IntoElement {
22388 let comment_id = comment.id;
22389 let is_editing = inline_editor.is_some();
22390
22391 h_flex()
22392 .w_full()
22393 .items_center()
22394 .gap_2()
22395 .px_2()
22396 .py_1p5()
22397 .rounded_md()
22398 .bg(colors.surface_background)
22399 .child(
22400 div()
22401 .size(avatar_size)
22402 .flex_shrink_0()
22403 .rounded_full()
22404 .overflow_hidden()
22405 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22406 Avatar::new(avatar_uri.clone())
22407 .size(avatar_size)
22408 .into_any_element()
22409 } else {
22410 Icon::new(IconName::Person)
22411 .size(IconSize::Small)
22412 .color(ui::Color::Muted)
22413 .into_any_element()
22414 }),
22415 )
22416 .child(if let Some(editor) = inline_editor {
22417 // Inline edit mode: show an editable text field
22418 div()
22419 .flex_1()
22420 .border_1()
22421 .border_color(colors.border)
22422 .rounded_md()
22423 .bg(colors.editor_background)
22424 .px_2()
22425 .py_1()
22426 .child(editor)
22427 .into_any_element()
22428 } else {
22429 // Display mode: show the comment text
22430 div()
22431 .flex_1()
22432 .text_sm()
22433 .text_color(colors.text)
22434 .child(comment.comment)
22435 .into_any_element()
22436 })
22437 .child(if is_editing {
22438 // Editing mode: show close and confirm buttons
22439 h_flex()
22440 .gap_1()
22441 .child(
22442 IconButton::new(
22443 format!("diff-review-cancel-edit-{comment_id}"),
22444 IconName::Close,
22445 )
22446 .icon_color(ui::Color::Muted)
22447 .icon_size(action_icon_size)
22448 .tooltip(Tooltip::text("Cancel"))
22449 .on_click(move |_, window, cx| {
22450 window.dispatch_action(
22451 Box::new(crate::actions::CancelEditReviewComment {
22452 id: comment_id,
22453 }),
22454 cx,
22455 );
22456 }),
22457 )
22458 .child(
22459 IconButton::new(
22460 format!("diff-review-confirm-edit-{comment_id}"),
22461 IconName::Return,
22462 )
22463 .icon_color(ui::Color::Muted)
22464 .icon_size(action_icon_size)
22465 .tooltip(Tooltip::text("Confirm"))
22466 .on_click(move |_, window, cx| {
22467 window.dispatch_action(
22468 Box::new(crate::actions::ConfirmEditReviewComment {
22469 id: comment_id,
22470 }),
22471 cx,
22472 );
22473 }),
22474 )
22475 .into_any_element()
22476 } else {
22477 // Display mode: no action buttons for now (edit/delete not yet implemented)
22478 gpui::Empty.into_any_element()
22479 })
22480 }
22481
22482 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22483 if self.display_map.read(cx).masked != masked {
22484 self.display_map.update(cx, |map, _| map.masked = masked);
22485 }
22486 cx.notify()
22487 }
22488
22489 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22490 self.show_wrap_guides = Some(show_wrap_guides);
22491 cx.notify();
22492 }
22493
22494 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22495 self.show_indent_guides = Some(show_indent_guides);
22496 cx.notify();
22497 }
22498
22499 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22500 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22501 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22502 && let Some(dir) = file.abs_path(cx).parent()
22503 {
22504 return Some(dir.to_owned());
22505 }
22506 }
22507
22508 None
22509 }
22510
22511 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22512 self.active_excerpt(cx)?
22513 .1
22514 .read(cx)
22515 .file()
22516 .and_then(|f| f.as_local())
22517 }
22518
22519 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22520 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22521 let buffer = buffer.read(cx);
22522 if let Some(project_path) = buffer.project_path(cx) {
22523 let project = self.project()?.read(cx);
22524 project.absolute_path(&project_path, cx)
22525 } else {
22526 buffer
22527 .file()
22528 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22529 }
22530 })
22531 }
22532
22533 pub fn reveal_in_finder(
22534 &mut self,
22535 _: &RevealInFileManager,
22536 _window: &mut Window,
22537 cx: &mut Context<Self>,
22538 ) {
22539 if let Some(path) = self.target_file_abs_path(cx) {
22540 if let Some(project) = self.project() {
22541 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22542 } else {
22543 cx.reveal_path(&path);
22544 }
22545 }
22546 }
22547
22548 pub fn copy_path(
22549 &mut self,
22550 _: &zed_actions::workspace::CopyPath,
22551 _window: &mut Window,
22552 cx: &mut Context<Self>,
22553 ) {
22554 if let Some(path) = self.target_file_abs_path(cx)
22555 && let Some(path) = path.to_str()
22556 {
22557 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22558 } else {
22559 cx.propagate();
22560 }
22561 }
22562
22563 pub fn copy_relative_path(
22564 &mut self,
22565 _: &zed_actions::workspace::CopyRelativePath,
22566 _window: &mut Window,
22567 cx: &mut Context<Self>,
22568 ) {
22569 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22570 let project = self.project()?.read(cx);
22571 let path = buffer.read(cx).file()?.path();
22572 let path = path.display(project.path_style(cx));
22573 Some(path)
22574 }) {
22575 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22576 } else {
22577 cx.propagate();
22578 }
22579 }
22580
22581 /// Returns the project path for the editor's buffer, if any buffer is
22582 /// opened in the editor.
22583 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22584 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22585 buffer.read(cx).project_path(cx)
22586 } else {
22587 None
22588 }
22589 }
22590
22591 // Returns true if the editor handled a go-to-line request
22592 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22593 maybe!({
22594 let breakpoint_store = self.breakpoint_store.as_ref()?;
22595
22596 let (active_stack_frame, debug_line_pane_id) = {
22597 let store = breakpoint_store.read(cx);
22598 let active_stack_frame = store.active_position().cloned();
22599 let debug_line_pane_id = store.active_debug_line_pane_id();
22600 (active_stack_frame, debug_line_pane_id)
22601 };
22602
22603 let Some(active_stack_frame) = active_stack_frame else {
22604 self.clear_row_highlights::<ActiveDebugLine>();
22605 return None;
22606 };
22607
22608 if let Some(debug_line_pane_id) = debug_line_pane_id {
22609 if let Some(workspace) = self
22610 .workspace
22611 .as_ref()
22612 .and_then(|(workspace, _)| workspace.upgrade())
22613 {
22614 let editor_pane_id = workspace
22615 .read(cx)
22616 .pane_for_item_id(cx.entity_id())
22617 .map(|pane| pane.entity_id());
22618
22619 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22620 self.clear_row_highlights::<ActiveDebugLine>();
22621 return None;
22622 }
22623 }
22624 }
22625
22626 let position = active_stack_frame.position;
22627 let buffer_id = position.buffer_id?;
22628 let snapshot = self
22629 .project
22630 .as_ref()?
22631 .read(cx)
22632 .buffer_for_id(buffer_id, cx)?
22633 .read(cx)
22634 .snapshot();
22635
22636 let mut handled = false;
22637 for (id, _, ExcerptRange { context, .. }) in
22638 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22639 {
22640 if context.start.cmp(&position, &snapshot).is_ge()
22641 || context.end.cmp(&position, &snapshot).is_lt()
22642 {
22643 continue;
22644 }
22645 let snapshot = self.buffer.read(cx).snapshot(cx);
22646 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22647
22648 handled = true;
22649 self.clear_row_highlights::<ActiveDebugLine>();
22650
22651 self.go_to_line::<ActiveDebugLine>(
22652 multibuffer_anchor,
22653 Some(cx.theme().colors().editor_debugger_active_line_background),
22654 window,
22655 cx,
22656 );
22657
22658 cx.notify();
22659 }
22660
22661 handled.then_some(())
22662 })
22663 .is_some()
22664 }
22665
22666 pub fn copy_file_name_without_extension(
22667 &mut self,
22668 _: &CopyFileNameWithoutExtension,
22669 _: &mut Window,
22670 cx: &mut Context<Self>,
22671 ) {
22672 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22673 let file = buffer.read(cx).file()?;
22674 file.path().file_stem()
22675 }) {
22676 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22677 }
22678 }
22679
22680 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22681 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22682 let file = buffer.read(cx).file()?;
22683 Some(file.file_name(cx))
22684 }) {
22685 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22686 }
22687 }
22688
22689 pub fn toggle_git_blame(
22690 &mut self,
22691 _: &::git::Blame,
22692 window: &mut Window,
22693 cx: &mut Context<Self>,
22694 ) {
22695 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22696
22697 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22698 self.start_git_blame(true, window, cx);
22699 }
22700
22701 cx.notify();
22702 }
22703
22704 pub fn toggle_git_blame_inline(
22705 &mut self,
22706 _: &ToggleGitBlameInline,
22707 window: &mut Window,
22708 cx: &mut Context<Self>,
22709 ) {
22710 self.toggle_git_blame_inline_internal(true, window, cx);
22711 cx.notify();
22712 }
22713
22714 pub fn open_git_blame_commit(
22715 &mut self,
22716 _: &OpenGitBlameCommit,
22717 window: &mut Window,
22718 cx: &mut Context<Self>,
22719 ) {
22720 self.open_git_blame_commit_internal(window, cx);
22721 }
22722
22723 fn open_git_blame_commit_internal(
22724 &mut self,
22725 window: &mut Window,
22726 cx: &mut Context<Self>,
22727 ) -> Option<()> {
22728 let blame = self.blame.as_ref()?;
22729 let snapshot = self.snapshot(window, cx);
22730 let cursor = self
22731 .selections
22732 .newest::<Point>(&snapshot.display_snapshot)
22733 .head();
22734 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22735 let (_, blame_entry) = blame
22736 .update(cx, |blame, cx| {
22737 blame
22738 .blame_for_rows(
22739 &[RowInfo {
22740 buffer_id: Some(buffer.remote_id()),
22741 buffer_row: Some(point.row),
22742 ..Default::default()
22743 }],
22744 cx,
22745 )
22746 .next()
22747 })
22748 .flatten()?;
22749 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22750 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22751 let workspace = self.workspace()?.downgrade();
22752 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22753 None
22754 }
22755
22756 pub fn git_blame_inline_enabled(&self) -> bool {
22757 self.git_blame_inline_enabled
22758 }
22759
22760 pub fn toggle_selection_menu(
22761 &mut self,
22762 _: &ToggleSelectionMenu,
22763 _: &mut Window,
22764 cx: &mut Context<Self>,
22765 ) {
22766 self.show_selection_menu = self
22767 .show_selection_menu
22768 .map(|show_selections_menu| !show_selections_menu)
22769 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22770
22771 cx.notify();
22772 }
22773
22774 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22775 self.show_selection_menu
22776 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22777 }
22778
22779 fn start_git_blame(
22780 &mut self,
22781 user_triggered: bool,
22782 window: &mut Window,
22783 cx: &mut Context<Self>,
22784 ) {
22785 if let Some(project) = self.project() {
22786 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22787 && buffer.read(cx).file().is_none()
22788 {
22789 return;
22790 }
22791
22792 let focused = self.focus_handle(cx).contains_focused(window, cx);
22793
22794 let project = project.clone();
22795 let blame = cx
22796 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22797 self.blame_subscription =
22798 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22799 self.blame = Some(blame);
22800 }
22801 }
22802
22803 fn toggle_git_blame_inline_internal(
22804 &mut self,
22805 user_triggered: bool,
22806 window: &mut Window,
22807 cx: &mut Context<Self>,
22808 ) {
22809 if self.git_blame_inline_enabled {
22810 self.git_blame_inline_enabled = false;
22811 self.show_git_blame_inline = false;
22812 self.show_git_blame_inline_delay_task.take();
22813 } else {
22814 self.git_blame_inline_enabled = true;
22815 self.start_git_blame_inline(user_triggered, window, cx);
22816 }
22817
22818 cx.notify();
22819 }
22820
22821 fn start_git_blame_inline(
22822 &mut self,
22823 user_triggered: bool,
22824 window: &mut Window,
22825 cx: &mut Context<Self>,
22826 ) {
22827 self.start_git_blame(user_triggered, window, cx);
22828
22829 if ProjectSettings::get_global(cx)
22830 .git
22831 .inline_blame_delay()
22832 .is_some()
22833 {
22834 self.start_inline_blame_timer(window, cx);
22835 } else {
22836 self.show_git_blame_inline = true
22837 }
22838 }
22839
22840 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22841 self.blame.as_ref()
22842 }
22843
22844 pub fn show_git_blame_gutter(&self) -> bool {
22845 self.show_git_blame_gutter
22846 }
22847
22848 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22849 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22850 }
22851
22852 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22853 self.show_git_blame_inline
22854 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22855 && !self.newest_selection_head_on_empty_line(cx)
22856 && self.has_blame_entries(cx)
22857 }
22858
22859 fn has_blame_entries(&self, cx: &App) -> bool {
22860 self.blame()
22861 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22862 }
22863
22864 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22865 let cursor_anchor = self.selections.newest_anchor().head();
22866
22867 let snapshot = self.buffer.read(cx).snapshot(cx);
22868 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22869
22870 snapshot.line_len(buffer_row) == 0
22871 }
22872
22873 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22874 let buffer_and_selection = maybe!({
22875 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22876 let selection_range = selection.range();
22877
22878 let multi_buffer = self.buffer().read(cx);
22879 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22880 let buffer_ranges = multi_buffer_snapshot
22881 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
22882
22883 let (buffer, range, _) = if selection.reversed {
22884 buffer_ranges.first()
22885 } else {
22886 buffer_ranges.last()
22887 }?;
22888
22889 let buffer_range = range.to_point(buffer);
22890
22891 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22892 return Some((
22893 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22894 buffer_range.start.row..buffer_range.end.row,
22895 ));
22896 };
22897
22898 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22899 let start =
22900 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
22901 let end =
22902 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
22903
22904 Some((
22905 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22906 start.row..end.row,
22907 ))
22908 });
22909
22910 let Some((buffer, selection)) = buffer_and_selection else {
22911 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22912 };
22913
22914 let Some(project) = self.project() else {
22915 return Task::ready(Err(anyhow!("editor does not have project")));
22916 };
22917
22918 project.update(cx, |project, cx| {
22919 project.get_permalink_to_line(&buffer, selection, cx)
22920 })
22921 }
22922
22923 pub fn copy_permalink_to_line(
22924 &mut self,
22925 _: &CopyPermalinkToLine,
22926 window: &mut Window,
22927 cx: &mut Context<Self>,
22928 ) {
22929 let permalink_task = self.get_permalink_to_line(cx);
22930 let workspace = self.workspace();
22931
22932 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22933 Ok(permalink) => {
22934 cx.update(|_, cx| {
22935 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22936 })
22937 .ok();
22938 }
22939 Err(err) => {
22940 let message = format!("Failed to copy permalink: {err}");
22941
22942 anyhow::Result::<()>::Err(err).log_err();
22943
22944 if let Some(workspace) = workspace {
22945 workspace
22946 .update_in(cx, |workspace, _, cx| {
22947 struct CopyPermalinkToLine;
22948
22949 workspace.show_toast(
22950 Toast::new(
22951 NotificationId::unique::<CopyPermalinkToLine>(),
22952 message,
22953 ),
22954 cx,
22955 )
22956 })
22957 .ok();
22958 }
22959 }
22960 })
22961 .detach();
22962 }
22963
22964 pub fn copy_file_location(
22965 &mut self,
22966 _: &CopyFileLocation,
22967 _: &mut Window,
22968 cx: &mut Context<Self>,
22969 ) {
22970 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22971
22972 let start_line = selection.start.row + 1;
22973 let end_line = selection.end.row + 1;
22974
22975 let end_line = if selection.end.column == 0 && end_line > start_line {
22976 end_line - 1
22977 } else {
22978 end_line
22979 };
22980
22981 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22982 let project = self.project()?.read(cx);
22983 let file = buffer.read(cx).file()?;
22984 let path = file.path().display(project.path_style(cx));
22985
22986 let location = if start_line == end_line {
22987 format!("{path}:{start_line}")
22988 } else {
22989 format!("{path}:{start_line}-{end_line}")
22990 };
22991 Some(location)
22992 }) {
22993 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
22994 }
22995 }
22996
22997 pub fn open_permalink_to_line(
22998 &mut self,
22999 _: &OpenPermalinkToLine,
23000 window: &mut Window,
23001 cx: &mut Context<Self>,
23002 ) {
23003 let permalink_task = self.get_permalink_to_line(cx);
23004 let workspace = self.workspace();
23005
23006 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23007 Ok(permalink) => {
23008 cx.update(|_, cx| {
23009 cx.open_url(permalink.as_ref());
23010 })
23011 .ok();
23012 }
23013 Err(err) => {
23014 let message = format!("Failed to open permalink: {err}");
23015
23016 anyhow::Result::<()>::Err(err).log_err();
23017
23018 if let Some(workspace) = workspace {
23019 workspace.update(cx, |workspace, cx| {
23020 struct OpenPermalinkToLine;
23021
23022 workspace.show_toast(
23023 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23024 cx,
23025 )
23026 });
23027 }
23028 }
23029 })
23030 .detach();
23031 }
23032
23033 pub fn insert_uuid_v4(
23034 &mut self,
23035 _: &InsertUuidV4,
23036 window: &mut Window,
23037 cx: &mut Context<Self>,
23038 ) {
23039 self.insert_uuid(UuidVersion::V4, window, cx);
23040 }
23041
23042 pub fn insert_uuid_v7(
23043 &mut self,
23044 _: &InsertUuidV7,
23045 window: &mut Window,
23046 cx: &mut Context<Self>,
23047 ) {
23048 self.insert_uuid(UuidVersion::V7, window, cx);
23049 }
23050
23051 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23053 self.transact(window, cx, |this, window, cx| {
23054 let edits = this
23055 .selections
23056 .all::<Point>(&this.display_snapshot(cx))
23057 .into_iter()
23058 .map(|selection| {
23059 let uuid = match version {
23060 UuidVersion::V4 => uuid::Uuid::new_v4(),
23061 UuidVersion::V7 => uuid::Uuid::now_v7(),
23062 };
23063
23064 (selection.range(), uuid.to_string())
23065 });
23066 this.edit(edits, cx);
23067 this.refresh_edit_prediction(true, false, window, cx);
23068 });
23069 }
23070
23071 pub fn open_selections_in_multibuffer(
23072 &mut self,
23073 _: &OpenSelectionsInMultibuffer,
23074 window: &mut Window,
23075 cx: &mut Context<Self>,
23076 ) {
23077 let multibuffer = self.buffer.read(cx);
23078
23079 let Some(buffer) = multibuffer.as_singleton() else {
23080 return;
23081 };
23082
23083 let Some(workspace) = self.workspace() else {
23084 return;
23085 };
23086
23087 let title = multibuffer.title(cx).to_string();
23088
23089 let locations = self
23090 .selections
23091 .all_anchors(&self.display_snapshot(cx))
23092 .iter()
23093 .map(|selection| {
23094 (
23095 buffer.clone(),
23096 (selection.start.text_anchor..selection.end.text_anchor)
23097 .to_point(buffer.read(cx)),
23098 )
23099 })
23100 .into_group_map();
23101
23102 cx.spawn_in(window, async move |_, cx| {
23103 workspace.update_in(cx, |workspace, window, cx| {
23104 Self::open_locations_in_multibuffer(
23105 workspace,
23106 locations,
23107 format!("Selections for '{title}'"),
23108 false,
23109 false,
23110 MultibufferSelectionMode::All,
23111 window,
23112 cx,
23113 );
23114 })
23115 })
23116 .detach();
23117 }
23118
23119 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23120 /// last highlight added will be used.
23121 ///
23122 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23123 pub fn highlight_rows<T: 'static>(
23124 &mut self,
23125 range: Range<Anchor>,
23126 color: Hsla,
23127 options: RowHighlightOptions,
23128 cx: &mut Context<Self>,
23129 ) {
23130 let snapshot = self.buffer().read(cx).snapshot(cx);
23131 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23132 let ix = row_highlights.binary_search_by(|highlight| {
23133 Ordering::Equal
23134 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23135 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23136 });
23137
23138 if let Err(mut ix) = ix {
23139 let index = post_inc(&mut self.highlight_order);
23140
23141 // If this range intersects with the preceding highlight, then merge it with
23142 // the preceding highlight. Otherwise insert a new highlight.
23143 let mut merged = false;
23144 if ix > 0 {
23145 let prev_highlight = &mut row_highlights[ix - 1];
23146 if prev_highlight
23147 .range
23148 .end
23149 .cmp(&range.start, &snapshot)
23150 .is_ge()
23151 {
23152 ix -= 1;
23153 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23154 prev_highlight.range.end = range.end;
23155 }
23156 merged = true;
23157 prev_highlight.index = index;
23158 prev_highlight.color = color;
23159 prev_highlight.options = options;
23160 }
23161 }
23162
23163 if !merged {
23164 row_highlights.insert(
23165 ix,
23166 RowHighlight {
23167 range,
23168 index,
23169 color,
23170 options,
23171 type_id: TypeId::of::<T>(),
23172 },
23173 );
23174 }
23175
23176 // If any of the following highlights intersect with this one, merge them.
23177 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23178 let highlight = &row_highlights[ix];
23179 if next_highlight
23180 .range
23181 .start
23182 .cmp(&highlight.range.end, &snapshot)
23183 .is_le()
23184 {
23185 if next_highlight
23186 .range
23187 .end
23188 .cmp(&highlight.range.end, &snapshot)
23189 .is_gt()
23190 {
23191 row_highlights[ix].range.end = next_highlight.range.end;
23192 }
23193 row_highlights.remove(ix + 1);
23194 } else {
23195 break;
23196 }
23197 }
23198 }
23199 }
23200
23201 /// Remove any highlighted row ranges of the given type that intersect the
23202 /// given ranges.
23203 pub fn remove_highlighted_rows<T: 'static>(
23204 &mut self,
23205 ranges_to_remove: Vec<Range<Anchor>>,
23206 cx: &mut Context<Self>,
23207 ) {
23208 let snapshot = self.buffer().read(cx).snapshot(cx);
23209 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23210 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23211 row_highlights.retain(|highlight| {
23212 while let Some(range_to_remove) = ranges_to_remove.peek() {
23213 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23214 Ordering::Less | Ordering::Equal => {
23215 ranges_to_remove.next();
23216 }
23217 Ordering::Greater => {
23218 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23219 Ordering::Less | Ordering::Equal => {
23220 return false;
23221 }
23222 Ordering::Greater => break,
23223 }
23224 }
23225 }
23226 }
23227
23228 true
23229 })
23230 }
23231
23232 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23233 pub fn clear_row_highlights<T: 'static>(&mut self) {
23234 self.highlighted_rows.remove(&TypeId::of::<T>());
23235 }
23236
23237 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23238 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23239 self.highlighted_rows
23240 .get(&TypeId::of::<T>())
23241 .map_or(&[] as &[_], |vec| vec.as_slice())
23242 .iter()
23243 .map(|highlight| (highlight.range.clone(), highlight.color))
23244 }
23245
23246 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23247 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23248 /// Allows to ignore certain kinds of highlights.
23249 pub fn highlighted_display_rows(
23250 &self,
23251 window: &mut Window,
23252 cx: &mut App,
23253 ) -> BTreeMap<DisplayRow, LineHighlight> {
23254 let snapshot = self.snapshot(window, cx);
23255 let mut used_highlight_orders = HashMap::default();
23256 self.highlighted_rows
23257 .iter()
23258 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23259 .fold(
23260 BTreeMap::<DisplayRow, LineHighlight>::new(),
23261 |mut unique_rows, highlight| {
23262 let start = highlight.range.start.to_display_point(&snapshot);
23263 let end = highlight.range.end.to_display_point(&snapshot);
23264 let start_row = start.row().0;
23265 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23266 {
23267 end.row().0.saturating_sub(1)
23268 } else {
23269 end.row().0
23270 };
23271 for row in start_row..=end_row {
23272 let used_index =
23273 used_highlight_orders.entry(row).or_insert(highlight.index);
23274 if highlight.index >= *used_index {
23275 *used_index = highlight.index;
23276 unique_rows.insert(
23277 DisplayRow(row),
23278 LineHighlight {
23279 include_gutter: highlight.options.include_gutter,
23280 border: None,
23281 background: highlight.color.into(),
23282 type_id: Some(highlight.type_id),
23283 },
23284 );
23285 }
23286 }
23287 unique_rows
23288 },
23289 )
23290 }
23291
23292 pub fn highlighted_display_row_for_autoscroll(
23293 &self,
23294 snapshot: &DisplaySnapshot,
23295 ) -> Option<DisplayRow> {
23296 self.highlighted_rows
23297 .values()
23298 .flat_map(|highlighted_rows| highlighted_rows.iter())
23299 .filter_map(|highlight| {
23300 if highlight.options.autoscroll {
23301 Some(highlight.range.start.to_display_point(snapshot).row())
23302 } else {
23303 None
23304 }
23305 })
23306 .min()
23307 }
23308
23309 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23310 self.highlight_background(
23311 HighlightKey::SearchWithinRange,
23312 ranges,
23313 |_, colors| colors.colors().editor_document_highlight_read_background,
23314 cx,
23315 )
23316 }
23317
23318 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23319 self.breadcrumb_header = Some(new_header);
23320 }
23321
23322 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23323 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23324 }
23325
23326 pub fn highlight_background(
23327 &mut self,
23328 key: HighlightKey,
23329 ranges: &[Range<Anchor>],
23330 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23331 cx: &mut Context<Self>,
23332 ) {
23333 self.background_highlights
23334 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23335 self.scrollbar_marker_state.dirty = true;
23336 cx.notify();
23337 }
23338
23339 pub fn clear_background_highlights(
23340 &mut self,
23341 key: HighlightKey,
23342 cx: &mut Context<Self>,
23343 ) -> Option<BackgroundHighlight> {
23344 let text_highlights = self.background_highlights.remove(&key)?;
23345 if !text_highlights.1.is_empty() {
23346 self.scrollbar_marker_state.dirty = true;
23347 cx.notify();
23348 }
23349 Some(text_highlights)
23350 }
23351
23352 pub fn highlight_gutter<T: 'static>(
23353 &mut self,
23354 ranges: impl Into<Vec<Range<Anchor>>>,
23355 color_fetcher: fn(&App) -> Hsla,
23356 cx: &mut Context<Self>,
23357 ) {
23358 self.gutter_highlights
23359 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23360 cx.notify();
23361 }
23362
23363 pub fn clear_gutter_highlights<T: 'static>(
23364 &mut self,
23365 cx: &mut Context<Self>,
23366 ) -> Option<GutterHighlight> {
23367 cx.notify();
23368 self.gutter_highlights.remove(&TypeId::of::<T>())
23369 }
23370
23371 pub fn insert_gutter_highlight<T: 'static>(
23372 &mut self,
23373 range: Range<Anchor>,
23374 color_fetcher: fn(&App) -> Hsla,
23375 cx: &mut Context<Self>,
23376 ) {
23377 let snapshot = self.buffer().read(cx).snapshot(cx);
23378 let mut highlights = self
23379 .gutter_highlights
23380 .remove(&TypeId::of::<T>())
23381 .map(|(_, highlights)| highlights)
23382 .unwrap_or_default();
23383 let ix = highlights.binary_search_by(|highlight| {
23384 Ordering::Equal
23385 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23386 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23387 });
23388 if let Err(ix) = ix {
23389 highlights.insert(ix, range);
23390 }
23391 self.gutter_highlights
23392 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23393 }
23394
23395 pub fn remove_gutter_highlights<T: 'static>(
23396 &mut self,
23397 ranges_to_remove: Vec<Range<Anchor>>,
23398 cx: &mut Context<Self>,
23399 ) {
23400 let snapshot = self.buffer().read(cx).snapshot(cx);
23401 let Some((color_fetcher, mut gutter_highlights)) =
23402 self.gutter_highlights.remove(&TypeId::of::<T>())
23403 else {
23404 return;
23405 };
23406 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23407 gutter_highlights.retain(|highlight| {
23408 while let Some(range_to_remove) = ranges_to_remove.peek() {
23409 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23410 Ordering::Less | Ordering::Equal => {
23411 ranges_to_remove.next();
23412 }
23413 Ordering::Greater => {
23414 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23415 Ordering::Less | Ordering::Equal => {
23416 return false;
23417 }
23418 Ordering::Greater => break,
23419 }
23420 }
23421 }
23422 }
23423
23424 true
23425 });
23426 self.gutter_highlights
23427 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23428 }
23429
23430 #[cfg(any(test, feature = "test-support"))]
23431 pub fn all_text_highlights(
23432 &self,
23433 window: &mut Window,
23434 cx: &mut Context<Self>,
23435 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23436 let snapshot = self.snapshot(window, cx);
23437 self.display_map.update(cx, |display_map, _| {
23438 display_map
23439 .all_text_highlights()
23440 .map(|(_, highlight)| {
23441 let (style, ranges) = highlight.as_ref();
23442 (
23443 *style,
23444 ranges
23445 .iter()
23446 .map(|range| range.clone().to_display_points(&snapshot))
23447 .collect(),
23448 )
23449 })
23450 .collect()
23451 })
23452 }
23453
23454 #[cfg(any(test, feature = "test-support"))]
23455 pub fn all_text_background_highlights(
23456 &self,
23457 window: &mut Window,
23458 cx: &mut Context<Self>,
23459 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23460 let snapshot = self.snapshot(window, cx);
23461 let buffer = &snapshot.buffer_snapshot();
23462 let start = buffer.anchor_before(MultiBufferOffset(0));
23463 let end = buffer.anchor_after(buffer.len());
23464 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23465 }
23466
23467 #[cfg(any(test, feature = "test-support"))]
23468 pub fn sorted_background_highlights_in_range(
23469 &self,
23470 search_range: Range<Anchor>,
23471 display_snapshot: &DisplaySnapshot,
23472 theme: &Theme,
23473 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23474 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23475 res.sort_by(|a, b| {
23476 a.0.start
23477 .cmp(&b.0.start)
23478 .then_with(|| a.0.end.cmp(&b.0.end))
23479 .then_with(|| a.1.cmp(&b.1))
23480 });
23481 res
23482 }
23483
23484 #[cfg(any(test, feature = "test-support"))]
23485 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23486 let snapshot = self.buffer().read(cx).snapshot(cx);
23487
23488 let highlights = self
23489 .background_highlights
23490 .get(&HighlightKey::BufferSearchHighlights);
23491
23492 if let Some((_color, ranges)) = highlights {
23493 ranges
23494 .iter()
23495 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23496 .collect_vec()
23497 } else {
23498 vec![]
23499 }
23500 }
23501
23502 fn document_highlights_for_position<'a>(
23503 &'a self,
23504 position: Anchor,
23505 buffer: &'a MultiBufferSnapshot,
23506 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23507 let read_highlights = self
23508 .background_highlights
23509 .get(&HighlightKey::DocumentHighlightRead)
23510 .map(|h| &h.1);
23511 let write_highlights = self
23512 .background_highlights
23513 .get(&HighlightKey::DocumentHighlightWrite)
23514 .map(|h| &h.1);
23515 let left_position = position.bias_left(buffer);
23516 let right_position = position.bias_right(buffer);
23517 read_highlights
23518 .into_iter()
23519 .chain(write_highlights)
23520 .flat_map(move |ranges| {
23521 let start_ix = match ranges.binary_search_by(|probe| {
23522 let cmp = probe.end.cmp(&left_position, buffer);
23523 if cmp.is_ge() {
23524 Ordering::Greater
23525 } else {
23526 Ordering::Less
23527 }
23528 }) {
23529 Ok(i) | Err(i) => i,
23530 };
23531
23532 ranges[start_ix..]
23533 .iter()
23534 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23535 })
23536 }
23537
23538 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23539 self.background_highlights
23540 .get(&key)
23541 .is_some_and(|(_, highlights)| !highlights.is_empty())
23542 }
23543
23544 /// Returns all background highlights for a given range.
23545 ///
23546 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23547 pub fn background_highlights_in_range(
23548 &self,
23549 search_range: Range<Anchor>,
23550 display_snapshot: &DisplaySnapshot,
23551 theme: &Theme,
23552 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23553 let mut results = Vec::new();
23554 for (color_fetcher, ranges) in self.background_highlights.values() {
23555 let start_ix = match ranges.binary_search_by(|probe| {
23556 let cmp = probe
23557 .end
23558 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23559 if cmp.is_gt() {
23560 Ordering::Greater
23561 } else {
23562 Ordering::Less
23563 }
23564 }) {
23565 Ok(i) | Err(i) => i,
23566 };
23567 for (index, range) in ranges[start_ix..].iter().enumerate() {
23568 if range
23569 .start
23570 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23571 .is_ge()
23572 {
23573 break;
23574 }
23575
23576 let color = color_fetcher(&(start_ix + index), theme);
23577 let start = range.start.to_display_point(display_snapshot);
23578 let end = range.end.to_display_point(display_snapshot);
23579 results.push((start..end, color))
23580 }
23581 }
23582 results
23583 }
23584
23585 pub fn gutter_highlights_in_range(
23586 &self,
23587 search_range: Range<Anchor>,
23588 display_snapshot: &DisplaySnapshot,
23589 cx: &App,
23590 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23591 let mut results = Vec::new();
23592 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23593 let color = color_fetcher(cx);
23594 let start_ix = match ranges.binary_search_by(|probe| {
23595 let cmp = probe
23596 .end
23597 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23598 if cmp.is_gt() {
23599 Ordering::Greater
23600 } else {
23601 Ordering::Less
23602 }
23603 }) {
23604 Ok(i) | Err(i) => i,
23605 };
23606 for range in &ranges[start_ix..] {
23607 if range
23608 .start
23609 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23610 .is_ge()
23611 {
23612 break;
23613 }
23614
23615 let start = range.start.to_display_point(display_snapshot);
23616 let end = range.end.to_display_point(display_snapshot);
23617 results.push((start..end, color))
23618 }
23619 }
23620 results
23621 }
23622
23623 /// Get the text ranges corresponding to the redaction query
23624 pub fn redacted_ranges(
23625 &self,
23626 search_range: Range<Anchor>,
23627 display_snapshot: &DisplaySnapshot,
23628 cx: &App,
23629 ) -> Vec<Range<DisplayPoint>> {
23630 display_snapshot
23631 .buffer_snapshot()
23632 .redacted_ranges(search_range, |file| {
23633 if let Some(file) = file {
23634 file.is_private()
23635 && EditorSettings::get(
23636 Some(SettingsLocation {
23637 worktree_id: file.worktree_id(cx),
23638 path: file.path().as_ref(),
23639 }),
23640 cx,
23641 )
23642 .redact_private_values
23643 } else {
23644 false
23645 }
23646 })
23647 .map(|range| {
23648 range.start.to_display_point(display_snapshot)
23649 ..range.end.to_display_point(display_snapshot)
23650 })
23651 .collect()
23652 }
23653
23654 pub fn highlight_text_key(
23655 &mut self,
23656 key: HighlightKey,
23657 ranges: Vec<Range<Anchor>>,
23658 style: HighlightStyle,
23659 merge: bool,
23660 cx: &mut Context<Self>,
23661 ) {
23662 self.display_map.update(cx, |map, cx| {
23663 map.highlight_text(key, ranges, style, merge, cx);
23664 });
23665 cx.notify();
23666 }
23667
23668 pub fn highlight_text(
23669 &mut self,
23670 key: HighlightKey,
23671 ranges: Vec<Range<Anchor>>,
23672 style: HighlightStyle,
23673 cx: &mut Context<Self>,
23674 ) {
23675 self.display_map.update(cx, |map, cx| {
23676 map.highlight_text(key, ranges, style, false, cx)
23677 });
23678 cx.notify();
23679 }
23680
23681 pub fn text_highlights<'a>(
23682 &'a self,
23683 key: HighlightKey,
23684 cx: &'a App,
23685 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23686 self.display_map.read(cx).text_highlights(key)
23687 }
23688
23689 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23690 let cleared = self
23691 .display_map
23692 .update(cx, |map, _| map.clear_highlights(key));
23693 if cleared {
23694 cx.notify();
23695 }
23696 }
23697
23698 pub fn clear_highlights_with(
23699 &mut self,
23700 f: &mut dyn FnMut(&HighlightKey) -> bool,
23701 cx: &mut Context<Self>,
23702 ) {
23703 let cleared = self
23704 .display_map
23705 .update(cx, |map, _| map.clear_highlights_with(f));
23706 if cleared {
23707 cx.notify();
23708 }
23709 }
23710
23711 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23712 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23713 && self.focus_handle.is_focused(window)
23714 }
23715
23716 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23717 self.show_cursor_when_unfocused = is_enabled;
23718 cx.notify();
23719 }
23720
23721 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23722 cx.notify();
23723 }
23724
23725 fn on_debug_session_event(
23726 &mut self,
23727 _session: Entity<Session>,
23728 event: &SessionEvent,
23729 cx: &mut Context<Self>,
23730 ) {
23731 if let SessionEvent::InvalidateInlineValue = event {
23732 self.refresh_inline_values(cx);
23733 }
23734 }
23735
23736 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23737 let Some(semantics) = self.semantics_provider.clone() else {
23738 return;
23739 };
23740
23741 if !self.inline_value_cache.enabled {
23742 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23743 self.splice_inlays(&inlays, Vec::new(), cx);
23744 return;
23745 }
23746
23747 let current_execution_position = self
23748 .highlighted_rows
23749 .get(&TypeId::of::<ActiveDebugLine>())
23750 .and_then(|lines| lines.last().map(|line| line.range.end));
23751
23752 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23753 let inline_values = editor
23754 .update(cx, |editor, cx| {
23755 let Some(current_execution_position) = current_execution_position else {
23756 return Some(Task::ready(Ok(Vec::new())));
23757 };
23758
23759 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23760 let snapshot = buffer.snapshot(cx);
23761
23762 let excerpt = snapshot.excerpt_containing(
23763 current_execution_position..current_execution_position,
23764 )?;
23765
23766 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23767 })?;
23768
23769 if current_execution_position
23770 .text_anchor
23771 .buffer_id
23772 .is_some_and(|id| id != buffer.read(cx).remote_id())
23773 {
23774 return Some(Task::ready(Ok(Vec::new())));
23775 }
23776
23777 let range =
23778 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23779
23780 semantics.inline_values(buffer, range, cx)
23781 })
23782 .ok()
23783 .flatten()?
23784 .await
23785 .context("refreshing debugger inlays")
23786 .log_err()?;
23787
23788 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23789
23790 for (buffer_id, inline_value) in inline_values
23791 .into_iter()
23792 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23793 {
23794 buffer_inline_values
23795 .entry(buffer_id)
23796 .or_default()
23797 .push(inline_value);
23798 }
23799
23800 editor
23801 .update(cx, |editor, cx| {
23802 let snapshot = editor.buffer.read(cx).snapshot(cx);
23803 let mut new_inlays = Vec::default();
23804
23805 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23806 let buffer_id = buffer_snapshot.remote_id();
23807 buffer_inline_values
23808 .get(&buffer_id)
23809 .into_iter()
23810 .flatten()
23811 .for_each(|hint| {
23812 let inlay = Inlay::debugger(
23813 post_inc(&mut editor.next_inlay_id),
23814 Anchor::in_buffer(excerpt_id, hint.position),
23815 hint.text(),
23816 );
23817 if !inlay.text().chars().contains(&'\n') {
23818 new_inlays.push(inlay);
23819 }
23820 });
23821 }
23822
23823 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23824 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23825
23826 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23827 })
23828 .ok()?;
23829 Some(())
23830 });
23831 }
23832
23833 fn on_buffer_event(
23834 &mut self,
23835 multibuffer: &Entity<MultiBuffer>,
23836 event: &multi_buffer::Event,
23837 window: &mut Window,
23838 cx: &mut Context<Self>,
23839 ) {
23840 match event {
23841 multi_buffer::Event::Edited {
23842 edited_buffer,
23843 is_local,
23844 } => {
23845 self.scrollbar_marker_state.dirty = true;
23846 self.active_indent_guides_state.dirty = true;
23847 self.refresh_active_diagnostics(cx);
23848 self.refresh_code_actions(window, cx);
23849 self.refresh_single_line_folds(window, cx);
23850 let snapshot = self.snapshot(window, cx);
23851 self.refresh_matching_bracket_highlights(&snapshot, cx);
23852 self.refresh_outline_symbols_at_cursor(cx);
23853 self.refresh_sticky_headers(&snapshot, cx);
23854 if *is_local && self.has_active_edit_prediction() {
23855 self.update_visible_edit_prediction(window, cx);
23856 }
23857
23858 // Clean up orphaned review comments after edits
23859 self.cleanup_orphaned_review_comments(cx);
23860
23861 if let Some(buffer) = edited_buffer {
23862 if buffer.read(cx).file().is_none() {
23863 cx.emit(EditorEvent::TitleChanged);
23864 }
23865
23866 if self.project.is_some() {
23867 let buffer_id = buffer.read(cx).remote_id();
23868 self.register_buffer(buffer_id, cx);
23869 self.update_lsp_data(Some(buffer_id), window, cx);
23870 self.refresh_inlay_hints(
23871 InlayHintRefreshReason::BufferEdited(buffer_id),
23872 cx,
23873 );
23874 }
23875 }
23876
23877 cx.emit(EditorEvent::BufferEdited);
23878 cx.emit(SearchEvent::MatchesInvalidated);
23879
23880 let Some(project) = &self.project else { return };
23881 let (telemetry, is_via_ssh) = {
23882 let project = project.read(cx);
23883 let telemetry = project.client().telemetry().clone();
23884 let is_via_ssh = project.is_via_remote_server();
23885 (telemetry, is_via_ssh)
23886 };
23887 telemetry.log_edit_event("editor", is_via_ssh);
23888 }
23889 multi_buffer::Event::ExcerptsAdded {
23890 buffer,
23891 predecessor,
23892 excerpts,
23893 } => {
23894 let buffer_id = buffer.read(cx).remote_id();
23895 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23896 && let Some(project) = &self.project
23897 {
23898 update_uncommitted_diff_for_buffer(
23899 cx.entity(),
23900 project,
23901 [buffer.clone()],
23902 self.buffer.clone(),
23903 cx,
23904 )
23905 .detach();
23906 }
23907 self.semantic_token_state
23908 .invalidate_buffer(&buffer.read(cx).remote_id());
23909 self.update_lsp_data(Some(buffer_id), window, cx);
23910 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23911 self.refresh_runnables(None, window, cx);
23912 self.colorize_brackets(false, cx);
23913 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
23914 cx.emit(EditorEvent::ExcerptsAdded {
23915 buffer: buffer.clone(),
23916 predecessor: *predecessor,
23917 excerpts: excerpts.clone(),
23918 });
23919 }
23920 multi_buffer::Event::ExcerptsRemoved {
23921 ids,
23922 removed_buffer_ids,
23923 } => {
23924 if let Some(inlay_hints) = &mut self.inlay_hints {
23925 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23926 }
23927 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23928 for buffer_id in removed_buffer_ids {
23929 self.registered_buffers.remove(buffer_id);
23930 self.clear_runnables(Some(*buffer_id));
23931 self.semantic_token_state.invalidate_buffer(buffer_id);
23932 self.display_map.update(cx, |display_map, cx| {
23933 display_map.invalidate_semantic_highlights(*buffer_id);
23934 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
23935 });
23936 }
23937
23938 self.display_map.update(cx, |display_map, cx| {
23939 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
23940 });
23941
23942 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23943 cx.emit(EditorEvent::ExcerptsRemoved {
23944 ids: ids.clone(),
23945 removed_buffer_ids: removed_buffer_ids.clone(),
23946 });
23947 }
23948 multi_buffer::Event::ExcerptsEdited {
23949 excerpt_ids,
23950 buffer_ids,
23951 } => {
23952 self.display_map.update(cx, |map, cx| {
23953 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23954 });
23955 cx.emit(EditorEvent::ExcerptsEdited {
23956 ids: excerpt_ids.clone(),
23957 });
23958 }
23959 multi_buffer::Event::ExcerptsExpanded { ids } => {
23960 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23961 self.refresh_document_highlights(cx);
23962 let snapshot = multibuffer.read(cx).snapshot(cx);
23963 for id in ids {
23964 self.bracket_fetched_tree_sitter_chunks.remove(id);
23965 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
23966 self.semantic_token_state
23967 .invalidate_buffer(&buffer.remote_id());
23968 }
23969 }
23970 self.colorize_brackets(false, cx);
23971 self.update_lsp_data(None, window, cx);
23972 self.refresh_runnables(None, window, cx);
23973 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23974 }
23975 multi_buffer::Event::Reparsed(buffer_id) => {
23976 self.refresh_runnables(Some(*buffer_id), window, cx);
23977 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
23978 self.colorize_brackets(true, cx);
23979 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23980
23981 cx.emit(EditorEvent::Reparsed(*buffer_id));
23982 }
23983 multi_buffer::Event::DiffHunksToggled => {
23984 self.refresh_runnables(None, window, cx);
23985 }
23986 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23987 if !is_fresh_language {
23988 self.registered_buffers.remove(&buffer_id);
23989 }
23990 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23991 cx.emit(EditorEvent::Reparsed(*buffer_id));
23992 self.update_edit_prediction_settings(cx);
23993 cx.notify();
23994 }
23995 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
23996 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
23997 multi_buffer::Event::FileHandleChanged
23998 | multi_buffer::Event::Reloaded
23999 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24000 multi_buffer::Event::DiagnosticsUpdated => {
24001 self.update_diagnostics_state(window, cx);
24002 }
24003 _ => {}
24004 };
24005 }
24006
24007 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24008 if !self.diagnostics_enabled() {
24009 return;
24010 }
24011 self.refresh_active_diagnostics(cx);
24012 self.refresh_inline_diagnostics(true, window, cx);
24013 self.scrollbar_marker_state.dirty = true;
24014 cx.notify();
24015 }
24016
24017 pub fn start_temporary_diff_override(&mut self) {
24018 self.load_diff_task.take();
24019 self.temporary_diff_override = true;
24020 }
24021
24022 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24023 self.temporary_diff_override = false;
24024 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24025 self.buffer.update(cx, |buffer, cx| {
24026 buffer.set_all_diff_hunks_collapsed(cx);
24027 });
24028
24029 if let Some(project) = self.project.clone() {
24030 self.load_diff_task = Some(
24031 update_uncommitted_diff_for_buffer(
24032 cx.entity(),
24033 &project,
24034 self.buffer.read(cx).all_buffers(),
24035 self.buffer.clone(),
24036 cx,
24037 )
24038 .shared(),
24039 );
24040 }
24041 }
24042
24043 fn on_display_map_changed(
24044 &mut self,
24045 _: Entity<DisplayMap>,
24046 _: &mut Window,
24047 cx: &mut Context<Self>,
24048 ) {
24049 cx.notify();
24050 }
24051
24052 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24053 if !self.mode.is_full() {
24054 return None;
24055 }
24056
24057 let theme_settings = theme::ThemeSettings::get_global(cx);
24058 let theme = cx.theme();
24059 let accent_colors = theme.accents().clone();
24060
24061 let accent_overrides = theme_settings
24062 .theme_overrides
24063 .get(theme.name.as_ref())
24064 .map(|theme_style| &theme_style.accents)
24065 .into_iter()
24066 .flatten()
24067 .chain(
24068 theme_settings
24069 .experimental_theme_overrides
24070 .as_ref()
24071 .map(|overrides| &overrides.accents)
24072 .into_iter()
24073 .flatten(),
24074 )
24075 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24076 .collect();
24077
24078 Some(AccentData {
24079 colors: accent_colors,
24080 overrides: accent_overrides,
24081 })
24082 }
24083
24084 fn fetch_applicable_language_settings(
24085 &self,
24086 cx: &App,
24087 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24088 if !self.mode.is_full() {
24089 return HashMap::default();
24090 }
24091
24092 self.buffer().read(cx).all_buffers().into_iter().fold(
24093 HashMap::default(),
24094 |mut acc, buffer| {
24095 let buffer = buffer.read(cx);
24096 let language = buffer.language().map(|language| language.name());
24097 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24098 let file = buffer.file();
24099 v.insert(language_settings(language, file, cx).into_owned());
24100 }
24101 acc
24102 },
24103 )
24104 }
24105
24106 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24107 let new_language_settings = self.fetch_applicable_language_settings(cx);
24108 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24109 self.applicable_language_settings = new_language_settings;
24110
24111 let new_accents = self.fetch_accent_data(cx);
24112 let accents_changed = new_accents != self.accent_data;
24113 self.accent_data = new_accents;
24114
24115 if self.diagnostics_enabled() {
24116 let new_severity = EditorSettings::get_global(cx)
24117 .diagnostics_max_severity
24118 .unwrap_or(DiagnosticSeverity::Hint);
24119 self.set_max_diagnostics_severity(new_severity, cx);
24120 }
24121 self.refresh_runnables(None, window, cx);
24122 self.update_edit_prediction_settings(cx);
24123 self.refresh_edit_prediction(true, false, window, cx);
24124 self.refresh_inline_values(cx);
24125
24126 let old_cursor_shape = self.cursor_shape;
24127 let old_show_breadcrumbs = self.show_breadcrumbs;
24128
24129 {
24130 let editor_settings = EditorSettings::get_global(cx);
24131 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24132 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24133 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24134 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24135 }
24136
24137 if old_cursor_shape != self.cursor_shape {
24138 cx.emit(EditorEvent::CursorShapeChanged);
24139 }
24140
24141 if old_show_breadcrumbs != self.show_breadcrumbs {
24142 cx.emit(EditorEvent::BreadcrumbsChanged);
24143 }
24144
24145 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24146 let project_settings = ProjectSettings::get_global(cx);
24147 (
24148 project_settings.session.restore_unsaved_buffers,
24149 project_settings.diagnostics.inline.enabled,
24150 project_settings.git.inline_blame.enabled,
24151 )
24152 };
24153 self.buffer_serialization = self
24154 .should_serialize_buffer()
24155 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24156
24157 if self.mode.is_full() {
24158 if self.show_inline_diagnostics != show_inline_diagnostics {
24159 self.show_inline_diagnostics = show_inline_diagnostics;
24160 self.refresh_inline_diagnostics(false, window, cx);
24161 }
24162
24163 if self.git_blame_inline_enabled != inline_blame_enabled {
24164 self.toggle_git_blame_inline_internal(false, window, cx);
24165 }
24166
24167 let minimap_settings = EditorSettings::get_global(cx).minimap;
24168 if self.minimap_visibility != MinimapVisibility::Disabled {
24169 if self.minimap_visibility.settings_visibility()
24170 != minimap_settings.minimap_enabled()
24171 {
24172 self.set_minimap_visibility(
24173 MinimapVisibility::for_mode(self.mode(), cx),
24174 window,
24175 cx,
24176 );
24177 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24178 minimap_entity.update(cx, |minimap_editor, cx| {
24179 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24180 })
24181 }
24182 }
24183
24184 if language_settings_changed || accents_changed {
24185 self.colorize_brackets(true, cx);
24186 }
24187
24188 if language_settings_changed {
24189 self.clear_disabled_lsp_folding_ranges(window, cx);
24190 self.refresh_document_symbols(None, cx);
24191 }
24192
24193 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24194 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24195 }) {
24196 if !inlay_splice.is_empty() {
24197 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24198 }
24199 self.refresh_document_colors(None, window, cx);
24200 }
24201
24202 self.refresh_inlay_hints(
24203 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24204 self.selections.newest_anchor().head(),
24205 &self.buffer.read(cx).snapshot(cx),
24206 cx,
24207 )),
24208 cx,
24209 );
24210
24211 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24212 .global_lsp_settings
24213 .semantic_token_rules
24214 .clone();
24215 let semantic_token_rules_changed = self
24216 .semantic_token_state
24217 .update_rules(new_semantic_token_rules);
24218 if language_settings_changed || semantic_token_rules_changed {
24219 self.invalidate_semantic_tokens(None);
24220 self.refresh_semantic_tokens(None, None, cx);
24221 }
24222 }
24223
24224 cx.notify();
24225 }
24226
24227 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24228 if !self.mode.is_full() {
24229 return;
24230 }
24231
24232 let new_accents = self.fetch_accent_data(cx);
24233 if new_accents != self.accent_data {
24234 self.accent_data = new_accents;
24235 self.colorize_brackets(true, cx);
24236 }
24237
24238 self.invalidate_semantic_tokens(None);
24239 self.refresh_semantic_tokens(None, None, cx);
24240 }
24241
24242 pub fn set_searchable(&mut self, searchable: bool) {
24243 self.searchable = searchable;
24244 }
24245
24246 pub fn searchable(&self) -> bool {
24247 self.searchable
24248 }
24249
24250 pub fn open_excerpts_in_split(
24251 &mut self,
24252 _: &OpenExcerptsSplit,
24253 window: &mut Window,
24254 cx: &mut Context<Self>,
24255 ) {
24256 self.open_excerpts_common(None, true, window, cx)
24257 }
24258
24259 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24260 self.open_excerpts_common(None, false, window, cx)
24261 }
24262
24263 pub(crate) fn open_excerpts_common(
24264 &mut self,
24265 jump_data: Option<JumpData>,
24266 split: bool,
24267 window: &mut Window,
24268 cx: &mut Context<Self>,
24269 ) {
24270 if self.buffer.read(cx).is_singleton() {
24271 cx.propagate();
24272 return;
24273 }
24274
24275 let mut new_selections_by_buffer = HashMap::default();
24276 match &jump_data {
24277 Some(JumpData::MultiBufferPoint {
24278 excerpt_id,
24279 position,
24280 anchor,
24281 line_offset_from_top,
24282 }) => {
24283 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24284 if let Some(buffer) = multi_buffer_snapshot
24285 .buffer_id_for_excerpt(*excerpt_id)
24286 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24287 {
24288 let buffer_snapshot = buffer.read(cx).snapshot();
24289 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24290 language::ToPoint::to_point(anchor, &buffer_snapshot)
24291 } else {
24292 buffer_snapshot.clip_point(*position, Bias::Left)
24293 };
24294 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24295 new_selections_by_buffer.insert(
24296 buffer,
24297 (
24298 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24299 Some(*line_offset_from_top),
24300 ),
24301 );
24302 }
24303 }
24304 Some(JumpData::MultiBufferRow {
24305 row,
24306 line_offset_from_top,
24307 }) => {
24308 let point = MultiBufferPoint::new(row.0, 0);
24309 if let Some((buffer, buffer_point, _)) =
24310 self.buffer.read(cx).point_to_buffer_point(point, cx)
24311 {
24312 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24313 new_selections_by_buffer
24314 .entry(buffer)
24315 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24316 .0
24317 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24318 }
24319 }
24320 None => {
24321 let selections = self
24322 .selections
24323 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24324 let multi_buffer = self.buffer.read(cx);
24325 for selection in selections {
24326 for (snapshot, range, _, anchor) in multi_buffer
24327 .snapshot(cx)
24328 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24329 {
24330 if let Some(anchor) = anchor {
24331 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24332 else {
24333 continue;
24334 };
24335 let offset = text::ToOffset::to_offset(
24336 &anchor.text_anchor,
24337 &buffer_handle.read(cx).snapshot(),
24338 );
24339 let range = BufferOffset(offset)..BufferOffset(offset);
24340 new_selections_by_buffer
24341 .entry(buffer_handle)
24342 .or_insert((Vec::new(), None))
24343 .0
24344 .push(range)
24345 } else {
24346 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24347 else {
24348 continue;
24349 };
24350 new_selections_by_buffer
24351 .entry(buffer_handle)
24352 .or_insert((Vec::new(), None))
24353 .0
24354 .push(range)
24355 }
24356 }
24357 }
24358 }
24359 }
24360
24361 if self.delegate_open_excerpts {
24362 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24363 .into_iter()
24364 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24365 .collect();
24366 if !selections_by_buffer.is_empty() {
24367 cx.emit(EditorEvent::OpenExcerptsRequested {
24368 selections_by_buffer,
24369 split,
24370 });
24371 }
24372 return;
24373 }
24374
24375 let Some(workspace) = self.workspace() else {
24376 cx.propagate();
24377 return;
24378 };
24379
24380 new_selections_by_buffer
24381 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24382
24383 if new_selections_by_buffer.is_empty() {
24384 return;
24385 }
24386
24387 Self::open_buffers_in_workspace(
24388 workspace.downgrade(),
24389 new_selections_by_buffer,
24390 split,
24391 window,
24392 cx,
24393 );
24394 }
24395
24396 pub(crate) fn open_buffers_in_workspace(
24397 workspace: WeakEntity<Workspace>,
24398 new_selections_by_buffer: HashMap<
24399 Entity<language::Buffer>,
24400 (Vec<Range<BufferOffset>>, Option<u32>),
24401 >,
24402 split: bool,
24403 window: &mut Window,
24404 cx: &mut App,
24405 ) {
24406 // We defer the pane interaction because we ourselves are a workspace item
24407 // and activating a new item causes the pane to call a method on us reentrantly,
24408 // which panics if we're on the stack.
24409 window.defer(cx, move |window, cx| {
24410 workspace
24411 .update(cx, |workspace, cx| {
24412 let pane = if split {
24413 workspace.adjacent_pane(window, cx)
24414 } else {
24415 workspace.active_pane().clone()
24416 };
24417
24418 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24419 let buffer_read = buffer.read(cx);
24420 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24421 (true, project::File::from_dyn(Some(file)).is_some())
24422 } else {
24423 (false, false)
24424 };
24425
24426 // If project file is none workspace.open_project_item will fail to open the excerpt
24427 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24428 // so we check if there's a tab match in that case first
24429 let editor = (!has_file || !is_project_file)
24430 .then(|| {
24431 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24432 // so `workspace.open_project_item` will never find them, always opening a new editor.
24433 // Instead, we try to activate the existing editor in the pane first.
24434 let (editor, pane_item_index, pane_item_id) =
24435 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24436 let editor = item.downcast::<Editor>()?;
24437 let singleton_buffer =
24438 editor.read(cx).buffer().read(cx).as_singleton()?;
24439 if singleton_buffer == buffer {
24440 Some((editor, i, item.item_id()))
24441 } else {
24442 None
24443 }
24444 })?;
24445 pane.update(cx, |pane, cx| {
24446 pane.activate_item(pane_item_index, true, true, window, cx);
24447 if !PreviewTabsSettings::get_global(cx)
24448 .enable_preview_from_multibuffer
24449 {
24450 pane.unpreview_item_if_preview(pane_item_id);
24451 }
24452 });
24453 Some(editor)
24454 })
24455 .flatten()
24456 .unwrap_or_else(|| {
24457 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24458 .enable_keep_preview_on_code_navigation;
24459 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24460 .enable_preview_from_multibuffer;
24461 workspace.open_project_item::<Self>(
24462 pane.clone(),
24463 buffer,
24464 true,
24465 true,
24466 keep_old_preview,
24467 allow_new_preview,
24468 window,
24469 cx,
24470 )
24471 });
24472
24473 editor.update(cx, |editor, cx| {
24474 if has_file && !is_project_file {
24475 editor.set_read_only(true);
24476 }
24477 let autoscroll = match scroll_offset {
24478 Some(scroll_offset) => {
24479 Autoscroll::top_relative(scroll_offset as usize)
24480 }
24481 None => Autoscroll::newest(),
24482 };
24483 let nav_history = editor.nav_history.take();
24484 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24485 let Some((excerpt_id, _, buffer_snapshot)) =
24486 multibuffer_snapshot.as_singleton()
24487 else {
24488 return;
24489 };
24490 editor.change_selections(
24491 SelectionEffects::scroll(autoscroll),
24492 window,
24493 cx,
24494 |s| {
24495 s.select_ranges(ranges.into_iter().map(|range| {
24496 let range = buffer_snapshot.anchor_before(range.start)
24497 ..buffer_snapshot.anchor_after(range.end);
24498 multibuffer_snapshot
24499 .anchor_range_in_excerpt(excerpt_id, range)
24500 .unwrap()
24501 }));
24502 },
24503 );
24504 editor.nav_history = nav_history;
24505 });
24506 }
24507 })
24508 .ok();
24509 });
24510 }
24511
24512 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24513 let snapshot = self.buffer.read(cx).read(cx);
24514 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24515 Some(
24516 ranges
24517 .iter()
24518 .map(move |range| {
24519 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24520 })
24521 .collect(),
24522 )
24523 }
24524
24525 fn selection_replacement_ranges(
24526 &self,
24527 range: Range<MultiBufferOffsetUtf16>,
24528 cx: &mut App,
24529 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24530 let selections = self
24531 .selections
24532 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24533 let newest_selection = selections
24534 .iter()
24535 .max_by_key(|selection| selection.id)
24536 .unwrap();
24537 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24538 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24539 let snapshot = self.buffer.read(cx).read(cx);
24540 selections
24541 .into_iter()
24542 .map(|mut selection| {
24543 selection.start.0.0 =
24544 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24545 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24546 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24547 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24548 })
24549 .collect()
24550 }
24551
24552 fn report_editor_event(
24553 &self,
24554 reported_event: ReportEditorEvent,
24555 file_extension: Option<String>,
24556 cx: &App,
24557 ) {
24558 if cfg!(any(test, feature = "test-support")) {
24559 return;
24560 }
24561
24562 let Some(project) = &self.project else { return };
24563
24564 // If None, we are in a file without an extension
24565 let file = self
24566 .buffer
24567 .read(cx)
24568 .as_singleton()
24569 .and_then(|b| b.read(cx).file());
24570 let file_extension = file_extension.or(file
24571 .as_ref()
24572 .and_then(|file| Path::new(file.file_name(cx)).extension())
24573 .and_then(|e| e.to_str())
24574 .map(|a| a.to_string()));
24575
24576 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24577 .map(|vim_mode| vim_mode.0)
24578 .unwrap_or(false);
24579
24580 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24581 let copilot_enabled = edit_predictions_provider
24582 == language::language_settings::EditPredictionProvider::Copilot;
24583 let copilot_enabled_for_language = self
24584 .buffer
24585 .read(cx)
24586 .language_settings(cx)
24587 .show_edit_predictions;
24588
24589 let project = project.read(cx);
24590 let event_type = reported_event.event_type();
24591
24592 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24593 telemetry::event!(
24594 event_type,
24595 type = if auto_saved {"autosave"} else {"manual"},
24596 file_extension,
24597 vim_mode,
24598 copilot_enabled,
24599 copilot_enabled_for_language,
24600 edit_predictions_provider,
24601 is_via_ssh = project.is_via_remote_server(),
24602 );
24603 } else {
24604 telemetry::event!(
24605 event_type,
24606 file_extension,
24607 vim_mode,
24608 copilot_enabled,
24609 copilot_enabled_for_language,
24610 edit_predictions_provider,
24611 is_via_ssh = project.is_via_remote_server(),
24612 );
24613 };
24614 }
24615
24616 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24617 /// with each line being an array of {text, highlight} objects.
24618 fn copy_highlight_json(
24619 &mut self,
24620 _: &CopyHighlightJson,
24621 window: &mut Window,
24622 cx: &mut Context<Self>,
24623 ) {
24624 #[derive(Serialize)]
24625 struct Chunk<'a> {
24626 text: String,
24627 highlight: Option<&'a str>,
24628 }
24629
24630 let snapshot = self.buffer.read(cx).snapshot(cx);
24631 let range = self
24632 .selected_text_range(false, window, cx)
24633 .and_then(|selection| {
24634 if selection.range.is_empty() {
24635 None
24636 } else {
24637 Some(
24638 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24639 selection.range.start,
24640 )))
24641 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24642 selection.range.end,
24643 ))),
24644 )
24645 }
24646 })
24647 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24648
24649 let chunks = snapshot.chunks(range, true);
24650 let mut lines = Vec::new();
24651 let mut line: VecDeque<Chunk> = VecDeque::new();
24652
24653 let Some(style) = self.style.as_ref() else {
24654 return;
24655 };
24656
24657 for chunk in chunks {
24658 let highlight = chunk
24659 .syntax_highlight_id
24660 .and_then(|id| id.name(&style.syntax));
24661 let mut chunk_lines = chunk.text.split('\n').peekable();
24662 while let Some(text) = chunk_lines.next() {
24663 let mut merged_with_last_token = false;
24664 if let Some(last_token) = line.back_mut()
24665 && last_token.highlight == highlight
24666 {
24667 last_token.text.push_str(text);
24668 merged_with_last_token = true;
24669 }
24670
24671 if !merged_with_last_token {
24672 line.push_back(Chunk {
24673 text: text.into(),
24674 highlight,
24675 });
24676 }
24677
24678 if chunk_lines.peek().is_some() {
24679 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24680 line.pop_front();
24681 }
24682 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24683 line.pop_back();
24684 }
24685
24686 lines.push(mem::take(&mut line));
24687 }
24688 }
24689 }
24690
24691 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24692 return;
24693 };
24694 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24695 }
24696
24697 pub fn open_context_menu(
24698 &mut self,
24699 _: &OpenContextMenu,
24700 window: &mut Window,
24701 cx: &mut Context<Self>,
24702 ) {
24703 self.request_autoscroll(Autoscroll::newest(), cx);
24704 let position = self
24705 .selections
24706 .newest_display(&self.display_snapshot(cx))
24707 .start;
24708 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24709 }
24710
24711 pub fn replay_insert_event(
24712 &mut self,
24713 text: &str,
24714 relative_utf16_range: Option<Range<isize>>,
24715 window: &mut Window,
24716 cx: &mut Context<Self>,
24717 ) {
24718 if !self.input_enabled {
24719 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24720 return;
24721 }
24722 if let Some(relative_utf16_range) = relative_utf16_range {
24723 let selections = self
24724 .selections
24725 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24726 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24727 let new_ranges = selections.into_iter().map(|range| {
24728 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24729 range
24730 .head()
24731 .0
24732 .0
24733 .saturating_add_signed(relative_utf16_range.start),
24734 ));
24735 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24736 range
24737 .head()
24738 .0
24739 .0
24740 .saturating_add_signed(relative_utf16_range.end),
24741 ));
24742 start..end
24743 });
24744 s.select_ranges(new_ranges);
24745 });
24746 }
24747
24748 self.handle_input(text, window, cx);
24749 }
24750
24751 pub fn is_focused(&self, window: &Window) -> bool {
24752 self.focus_handle.is_focused(window)
24753 }
24754
24755 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24756 cx.emit(EditorEvent::Focused);
24757
24758 if let Some(descendant) = self
24759 .last_focused_descendant
24760 .take()
24761 .and_then(|descendant| descendant.upgrade())
24762 {
24763 window.focus(&descendant, cx);
24764 } else {
24765 if let Some(blame) = self.blame.as_ref() {
24766 blame.update(cx, GitBlame::focus)
24767 }
24768
24769 self.blink_manager.update(cx, BlinkManager::enable);
24770 self.show_cursor_names(window, cx);
24771 self.buffer.update(cx, |buffer, cx| {
24772 buffer.finalize_last_transaction(cx);
24773 if self.leader_id.is_none() {
24774 buffer.set_active_selections(
24775 &self.selections.disjoint_anchors_arc(),
24776 self.selections.line_mode(),
24777 self.cursor_shape,
24778 cx,
24779 );
24780 }
24781 });
24782
24783 if let Some(position_map) = self.last_position_map.clone() {
24784 EditorElement::mouse_moved(
24785 self,
24786 &MouseMoveEvent {
24787 position: window.mouse_position(),
24788 pressed_button: None,
24789 modifiers: window.modifiers(),
24790 },
24791 &position_map,
24792 None,
24793 window,
24794 cx,
24795 );
24796 }
24797 }
24798 }
24799
24800 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24801 cx.emit(EditorEvent::FocusedIn)
24802 }
24803
24804 fn handle_focus_out(
24805 &mut self,
24806 event: FocusOutEvent,
24807 _window: &mut Window,
24808 cx: &mut Context<Self>,
24809 ) {
24810 if event.blurred != self.focus_handle {
24811 self.last_focused_descendant = Some(event.blurred);
24812 }
24813 self.selection_drag_state = SelectionDragState::None;
24814 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24815 }
24816
24817 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24818 self.blink_manager.update(cx, BlinkManager::disable);
24819 self.buffer
24820 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24821
24822 if let Some(blame) = self.blame.as_ref() {
24823 blame.update(cx, GitBlame::blur)
24824 }
24825 if !self.hover_state.focused(window, cx) {
24826 hide_hover(self, cx);
24827 }
24828 if !self
24829 .context_menu
24830 .borrow()
24831 .as_ref()
24832 .is_some_and(|context_menu| context_menu.focused(window, cx))
24833 {
24834 self.hide_context_menu(window, cx);
24835 }
24836 self.take_active_edit_prediction(cx);
24837 cx.emit(EditorEvent::Blurred);
24838 cx.notify();
24839 }
24840
24841 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24842 let mut pending: String = window
24843 .pending_input_keystrokes()
24844 .into_iter()
24845 .flatten()
24846 .filter_map(|keystroke| keystroke.key_char.clone())
24847 .collect();
24848
24849 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24850 pending = "".to_string();
24851 }
24852
24853 let existing_pending = self
24854 .text_highlights(HighlightKey::PendingInput, cx)
24855 .map(|(_, ranges)| ranges.to_vec());
24856 if existing_pending.is_none() && pending.is_empty() {
24857 return;
24858 }
24859 let transaction =
24860 self.transact(window, cx, |this, window, cx| {
24861 let selections = this
24862 .selections
24863 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24864 let edits = selections
24865 .iter()
24866 .map(|selection| (selection.end..selection.end, pending.clone()));
24867 this.edit(edits, cx);
24868 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24869 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24870 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24871 }));
24872 });
24873 if let Some(existing_ranges) = existing_pending {
24874 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24875 this.edit(edits, cx);
24876 }
24877 });
24878
24879 let snapshot = self.snapshot(window, cx);
24880 let ranges = self
24881 .selections
24882 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24883 .into_iter()
24884 .map(|selection| {
24885 snapshot.buffer_snapshot().anchor_after(selection.end)
24886 ..snapshot
24887 .buffer_snapshot()
24888 .anchor_before(selection.end + pending.len())
24889 })
24890 .collect();
24891
24892 if pending.is_empty() {
24893 self.clear_highlights(HighlightKey::PendingInput, cx);
24894 } else {
24895 self.highlight_text(
24896 HighlightKey::PendingInput,
24897 ranges,
24898 HighlightStyle {
24899 underline: Some(UnderlineStyle {
24900 thickness: px(1.),
24901 color: None,
24902 wavy: false,
24903 }),
24904 ..Default::default()
24905 },
24906 cx,
24907 );
24908 }
24909
24910 self.ime_transaction = self.ime_transaction.or(transaction);
24911 if let Some(transaction) = self.ime_transaction {
24912 self.buffer.update(cx, |buffer, cx| {
24913 buffer.group_until_transaction(transaction, cx);
24914 });
24915 }
24916
24917 if self
24918 .text_highlights(HighlightKey::PendingInput, cx)
24919 .is_none()
24920 {
24921 self.ime_transaction.take();
24922 }
24923 }
24924
24925 pub fn register_action_renderer(
24926 &mut self,
24927 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24928 ) -> Subscription {
24929 let id = self.next_editor_action_id.post_inc();
24930 self.editor_actions
24931 .borrow_mut()
24932 .insert(id, Box::new(listener));
24933
24934 let editor_actions = self.editor_actions.clone();
24935 Subscription::new(move || {
24936 editor_actions.borrow_mut().remove(&id);
24937 })
24938 }
24939
24940 pub fn register_action<A: Action>(
24941 &mut self,
24942 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24943 ) -> Subscription {
24944 let id = self.next_editor_action_id.post_inc();
24945 let listener = Arc::new(listener);
24946 self.editor_actions.borrow_mut().insert(
24947 id,
24948 Box::new(move |_, window, _| {
24949 let listener = listener.clone();
24950 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24951 let action = action.downcast_ref().unwrap();
24952 if phase == DispatchPhase::Bubble {
24953 listener(action, window, cx)
24954 }
24955 })
24956 }),
24957 );
24958
24959 let editor_actions = self.editor_actions.clone();
24960 Subscription::new(move || {
24961 editor_actions.borrow_mut().remove(&id);
24962 })
24963 }
24964
24965 pub fn file_header_size(&self) -> u32 {
24966 FILE_HEADER_HEIGHT
24967 }
24968
24969 pub fn restore(
24970 &mut self,
24971 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24972 window: &mut Window,
24973 cx: &mut Context<Self>,
24974 ) {
24975 self.buffer().update(cx, |multi_buffer, cx| {
24976 for (buffer_id, changes) in revert_changes {
24977 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24978 buffer.update(cx, |buffer, cx| {
24979 buffer.edit(
24980 changes
24981 .into_iter()
24982 .map(|(range, text)| (range, text.to_string())),
24983 None,
24984 cx,
24985 );
24986 });
24987 }
24988 }
24989 });
24990 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24991 selections.refresh()
24992 });
24993 }
24994
24995 pub fn to_pixel_point(
24996 &mut self,
24997 source: Anchor,
24998 editor_snapshot: &EditorSnapshot,
24999 window: &mut Window,
25000 cx: &mut App,
25001 ) -> Option<gpui::Point<Pixels>> {
25002 let source_point = source.to_display_point(editor_snapshot);
25003 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25004 }
25005
25006 pub fn display_to_pixel_point(
25007 &mut self,
25008 source: DisplayPoint,
25009 editor_snapshot: &EditorSnapshot,
25010 window: &mut Window,
25011 cx: &mut App,
25012 ) -> Option<gpui::Point<Pixels>> {
25013 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25014 let text_layout_details = self.text_layout_details(window, cx);
25015 let scroll_top = text_layout_details
25016 .scroll_anchor
25017 .scroll_position(editor_snapshot)
25018 .y;
25019
25020 if source.row().as_f64() < scroll_top.floor() {
25021 return None;
25022 }
25023 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25024 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25025 Some(gpui::Point::new(source_x, source_y))
25026 }
25027
25028 pub fn has_visible_completions_menu(&self) -> bool {
25029 !self.edit_prediction_preview_is_active()
25030 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25031 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25032 })
25033 }
25034
25035 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25036 if self.mode.is_minimap() {
25037 return;
25038 }
25039 self.addons
25040 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25041 }
25042
25043 pub fn unregister_addon<T: Addon>(&mut self) {
25044 self.addons.remove(&std::any::TypeId::of::<T>());
25045 }
25046
25047 pub fn addon<T: Addon>(&self) -> Option<&T> {
25048 let type_id = std::any::TypeId::of::<T>();
25049 self.addons
25050 .get(&type_id)
25051 .and_then(|item| item.to_any().downcast_ref::<T>())
25052 }
25053
25054 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25055 let type_id = std::any::TypeId::of::<T>();
25056 self.addons
25057 .get_mut(&type_id)
25058 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25059 }
25060
25061 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25062 let text_layout_details = self.text_layout_details(window, cx);
25063 let style = &text_layout_details.editor_style;
25064 let font_id = window.text_system().resolve_font(&style.text.font());
25065 let font_size = style.text.font_size.to_pixels(window.rem_size());
25066 let line_height = style.text.line_height_in_pixels(window.rem_size());
25067 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25068 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25069
25070 CharacterDimensions {
25071 em_width,
25072 em_advance,
25073 line_height,
25074 }
25075 }
25076
25077 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25078 self.load_diff_task.clone()
25079 }
25080
25081 fn read_metadata_from_db(
25082 &mut self,
25083 item_id: u64,
25084 workspace_id: WorkspaceId,
25085 window: &mut Window,
25086 cx: &mut Context<Editor>,
25087 ) {
25088 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25089 && !self.mode.is_minimap()
25090 && WorkspaceSettings::get(None, cx).restore_on_startup
25091 != RestoreOnStartupBehavior::EmptyTab
25092 {
25093 let buffer_snapshot = OnceCell::new();
25094
25095 // Get file path for path-based fold lookup
25096 let file_path: Option<Arc<Path>> =
25097 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25098 project::File::from_dyn(buffer.read(cx).file())
25099 .map(|file| Arc::from(file.abs_path(cx)))
25100 });
25101
25102 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25103 let (folds, needs_migration) = if let Some(ref path) = file_path {
25104 if let Some(folds) = DB.get_file_folds(workspace_id, path).log_err()
25105 && !folds.is_empty()
25106 {
25107 (Some(folds), false)
25108 } else if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
25109 && !folds.is_empty()
25110 {
25111 // Found old editor_folds data, will migrate to file_folds
25112 (Some(folds), true)
25113 } else {
25114 (None, false)
25115 }
25116 } else {
25117 // No file path, try editor_folds as fallback
25118 let folds = DB.get_editor_folds(item_id, workspace_id).log_err();
25119 (folds.filter(|f| !f.is_empty()), false)
25120 };
25121
25122 if let Some(folds) = folds {
25123 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25124 let snapshot_len = snapshot.len().0;
25125
25126 // Helper: search for fingerprint in buffer, return offset if found
25127 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25128 // Ensure we start at a character boundary (defensive)
25129 let search_start = snapshot
25130 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25131 .0;
25132 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25133
25134 let mut byte_offset = search_start;
25135 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25136 if byte_offset > search_end {
25137 break;
25138 }
25139 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25140 return Some(byte_offset);
25141 }
25142 byte_offset += ch.len_utf8();
25143 }
25144 None
25145 };
25146
25147 // Track search position to handle duplicate fingerprints correctly.
25148 // Folds are stored in document order, so we advance after each match.
25149 let mut search_start = 0usize;
25150
25151 // Collect db_folds for migration (only folds with valid fingerprints)
25152 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25153
25154 let valid_folds: Vec<_> = folds
25155 .into_iter()
25156 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25157 // Skip folds without fingerprints (old data before migration)
25158 let sfp = start_fp?;
25159 let efp = end_fp?;
25160 let efp_len = efp.len();
25161
25162 // Fast path: check if fingerprints match at stored offsets
25163 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25164 let start_matches = stored_start < snapshot_len
25165 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25166 let efp_check_pos = stored_end.saturating_sub(efp_len);
25167 let end_matches = efp_check_pos >= stored_start
25168 && stored_end <= snapshot_len
25169 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25170
25171 let (new_start, new_end) = if start_matches && end_matches {
25172 // Offsets unchanged, use stored values
25173 (stored_start, stored_end)
25174 } else if sfp == efp {
25175 // Short fold: identical fingerprints can only match once per search
25176 // Use stored fold length to compute new_end
25177 let new_start = find_fingerprint(&sfp, search_start)?;
25178 let fold_len = stored_end - stored_start;
25179 let new_end = new_start + fold_len;
25180 (new_start, new_end)
25181 } else {
25182 // Slow path: search for fingerprints in buffer
25183 let new_start = find_fingerprint(&sfp, search_start)?;
25184 // Search for end_fp after start, then add efp_len to get actual fold end
25185 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25186 let new_end = efp_pos + efp_len;
25187 (new_start, new_end)
25188 };
25189
25190 // Advance search position for next fold
25191 search_start = new_end;
25192
25193 // Validate fold makes sense (end must be after start)
25194 if new_end <= new_start {
25195 return None;
25196 }
25197
25198 // Collect for migration if needed
25199 if needs_migration {
25200 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25201 }
25202
25203 Some(
25204 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25205 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25206 )
25207 })
25208 .collect();
25209
25210 if !valid_folds.is_empty() {
25211 self.fold_ranges(valid_folds, false, window, cx);
25212
25213 // Migrate from editor_folds to file_folds if we loaded from old table
25214 if needs_migration {
25215 if let Some(ref path) = file_path {
25216 let path = path.clone();
25217 cx.spawn(async move |_, _| {
25218 DB.save_file_folds(workspace_id, path, db_folds_for_migration)
25219 .await
25220 .log_err();
25221 })
25222 .detach();
25223 }
25224 }
25225 }
25226 }
25227
25228 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25229 && !selections.is_empty()
25230 {
25231 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25232 // skip adding the initial selection to selection history
25233 self.selection_history.mode = SelectionHistoryMode::Skipping;
25234 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25235 s.select_ranges(selections.into_iter().map(|(start, end)| {
25236 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25237 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25238 }));
25239 });
25240 self.selection_history.mode = SelectionHistoryMode::Normal;
25241 };
25242 }
25243
25244 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25245 }
25246
25247 /// Load folds from the file_folds database table by file path.
25248 /// Used when manually opening a file that was previously closed.
25249 fn load_folds_from_db(
25250 &mut self,
25251 workspace_id: WorkspaceId,
25252 file_path: PathBuf,
25253 window: &mut Window,
25254 cx: &mut Context<Editor>,
25255 ) {
25256 if self.mode.is_minimap()
25257 || WorkspaceSettings::get(None, cx).restore_on_startup
25258 == RestoreOnStartupBehavior::EmptyTab
25259 {
25260 return;
25261 }
25262
25263 let Some(folds) = DB.get_file_folds(workspace_id, &file_path).log_err() else {
25264 return;
25265 };
25266 if folds.is_empty() {
25267 return;
25268 }
25269
25270 let snapshot = self.buffer.read(cx).snapshot(cx);
25271 let snapshot_len = snapshot.len().0;
25272
25273 // Helper: search for fingerprint in buffer, return offset if found
25274 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25275 let search_start = snapshot
25276 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25277 .0;
25278 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25279
25280 let mut byte_offset = search_start;
25281 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25282 if byte_offset > search_end {
25283 break;
25284 }
25285 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25286 return Some(byte_offset);
25287 }
25288 byte_offset += ch.len_utf8();
25289 }
25290 None
25291 };
25292
25293 let mut search_start = 0usize;
25294
25295 let valid_folds: Vec<_> = folds
25296 .into_iter()
25297 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25298 let sfp = start_fp?;
25299 let efp = end_fp?;
25300 let efp_len = efp.len();
25301
25302 let start_matches = stored_start < snapshot_len
25303 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25304 let efp_check_pos = stored_end.saturating_sub(efp_len);
25305 let end_matches = efp_check_pos >= stored_start
25306 && stored_end <= snapshot_len
25307 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25308
25309 let (new_start, new_end) = if start_matches && end_matches {
25310 (stored_start, stored_end)
25311 } else if sfp == efp {
25312 let new_start = find_fingerprint(&sfp, search_start)?;
25313 let fold_len = stored_end - stored_start;
25314 let new_end = new_start + fold_len;
25315 (new_start, new_end)
25316 } else {
25317 let new_start = find_fingerprint(&sfp, search_start)?;
25318 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25319 let new_end = efp_pos + efp_len;
25320 (new_start, new_end)
25321 };
25322
25323 search_start = new_end;
25324
25325 if new_end <= new_start {
25326 return None;
25327 }
25328
25329 Some(
25330 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25331 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25332 )
25333 })
25334 .collect();
25335
25336 if !valid_folds.is_empty() {
25337 self.fold_ranges(valid_folds, false, window, cx);
25338 }
25339 }
25340
25341 fn lsp_data_enabled(&self) -> bool {
25342 self.enable_lsp_data && self.mode().is_full()
25343 }
25344
25345 fn update_lsp_data(
25346 &mut self,
25347 for_buffer: Option<BufferId>,
25348 window: &mut Window,
25349 cx: &mut Context<'_, Self>,
25350 ) {
25351 if !self.lsp_data_enabled() {
25352 return;
25353 }
25354
25355 if let Some(buffer_id) = for_buffer {
25356 self.pull_diagnostics(buffer_id, window, cx);
25357 }
25358 self.refresh_semantic_tokens(for_buffer, None, cx);
25359 self.refresh_document_colors(for_buffer, window, cx);
25360 self.refresh_folding_ranges(for_buffer, window, cx);
25361 self.refresh_document_symbols(for_buffer, cx);
25362 }
25363
25364 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25365 if !self.lsp_data_enabled() {
25366 return;
25367 }
25368 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25369 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25370 }
25371 }
25372
25373 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25374 if !self.lsp_data_enabled() {
25375 return;
25376 }
25377
25378 if !self.registered_buffers.contains_key(&buffer_id)
25379 && let Some(project) = self.project.as_ref()
25380 {
25381 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25382 project.update(cx, |project, cx| {
25383 self.registered_buffers.insert(
25384 buffer_id,
25385 project.register_buffer_with_language_servers(&buffer, cx),
25386 );
25387 });
25388 } else {
25389 self.registered_buffers.remove(&buffer_id);
25390 }
25391 }
25392 }
25393
25394 fn create_style(&self, cx: &App) -> EditorStyle {
25395 let settings = ThemeSettings::get_global(cx);
25396
25397 let mut text_style = match self.mode {
25398 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25399 color: cx.theme().colors().editor_foreground,
25400 font_family: settings.ui_font.family.clone(),
25401 font_features: settings.ui_font.features.clone(),
25402 font_fallbacks: settings.ui_font.fallbacks.clone(),
25403 font_size: rems(0.875).into(),
25404 font_weight: settings.ui_font.weight,
25405 line_height: relative(settings.buffer_line_height.value()),
25406 ..Default::default()
25407 },
25408 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25409 color: cx.theme().colors().editor_foreground,
25410 font_family: settings.buffer_font.family.clone(),
25411 font_features: settings.buffer_font.features.clone(),
25412 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25413 font_size: settings.buffer_font_size(cx).into(),
25414 font_weight: settings.buffer_font.weight,
25415 line_height: relative(settings.buffer_line_height.value()),
25416 ..Default::default()
25417 },
25418 };
25419 if let Some(text_style_refinement) = &self.text_style_refinement {
25420 text_style.refine(text_style_refinement)
25421 }
25422
25423 let background = match self.mode {
25424 EditorMode::SingleLine => cx.theme().system().transparent,
25425 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25426 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25427 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25428 };
25429
25430 EditorStyle {
25431 background,
25432 border: cx.theme().colors().border,
25433 local_player: cx.theme().players().local(),
25434 text: text_style,
25435 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25436 syntax: cx.theme().syntax().clone(),
25437 status: cx.theme().status().clone(),
25438 inlay_hints_style: make_inlay_hints_style(cx),
25439 edit_prediction_styles: make_suggestion_styles(cx),
25440 unnecessary_code_fade: settings.unnecessary_code_fade,
25441 show_underlines: self.diagnostics_enabled(),
25442 }
25443 }
25444
25445 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25446 let multibuffer = self.buffer().read(cx);
25447 let is_singleton = multibuffer.is_singleton();
25448 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25449 let buffer = multibuffer.buffer(*buffer_id)?;
25450
25451 let buffer = buffer.read(cx);
25452 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25453 let mut breadcrumbs = if is_singleton {
25454 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25455 buffer
25456 .snapshot()
25457 .resolve_file_path(
25458 self.project
25459 .as_ref()
25460 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25461 .unwrap_or_default(),
25462 cx,
25463 )
25464 .unwrap_or_else(|| {
25465 if multibuffer.is_singleton() {
25466 multibuffer.title(cx).to_string()
25467 } else {
25468 "untitled".to_string()
25469 }
25470 })
25471 });
25472 vec![HighlightedText {
25473 text: text.into(),
25474 highlights: vec![],
25475 }]
25476 } else {
25477 vec![]
25478 };
25479
25480 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25481 text: symbol.text.clone().into(),
25482 highlights: symbol.highlight_ranges.clone(),
25483 }));
25484 Some(breadcrumbs)
25485 }
25486
25487 fn disable_lsp_data(&mut self) {
25488 self.enable_lsp_data = false;
25489 }
25490
25491 fn disable_runnables(&mut self) {
25492 self.enable_runnables = false;
25493 }
25494
25495 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25496 self.register_visible_buffers(cx);
25497 self.colorize_brackets(false, cx);
25498 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25499 if !self.buffer().read(cx).is_singleton() {
25500 self.update_lsp_data(None, window, cx);
25501 self.refresh_runnables(None, window, cx);
25502 }
25503 }
25504}
25505
25506fn edit_for_markdown_paste<'a>(
25507 buffer: &MultiBufferSnapshot,
25508 range: Range<MultiBufferOffset>,
25509 to_insert: &'a str,
25510 url: Option<url::Url>,
25511) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25512 if url.is_none() {
25513 return (range, Cow::Borrowed(to_insert));
25514 };
25515
25516 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25517
25518 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25519 Cow::Borrowed(to_insert)
25520 } else {
25521 Cow::Owned(format!("[{old_text}]({to_insert})"))
25522 };
25523 (range, new_text)
25524}
25525
25526fn process_completion_for_edit(
25527 completion: &Completion,
25528 intent: CompletionIntent,
25529 buffer: &Entity<Buffer>,
25530 cursor_position: &text::Anchor,
25531 cx: &mut Context<Editor>,
25532) -> CompletionEdit {
25533 let buffer = buffer.read(cx);
25534 let buffer_snapshot = buffer.snapshot();
25535 let (snippet, new_text) = if completion.is_snippet() {
25536 let mut snippet_source = completion.new_text.clone();
25537 // Workaround for typescript language server issues so that methods don't expand within
25538 // strings and functions with type expressions. The previous point is used because the query
25539 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25540 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25541 let previous_point = if previous_point.column > 0 {
25542 cursor_position.to_previous_offset(&buffer_snapshot)
25543 } else {
25544 cursor_position.to_offset(&buffer_snapshot)
25545 };
25546 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25547 && scope.prefers_label_for_snippet_in_completion()
25548 && let Some(label) = completion.label()
25549 && matches!(
25550 completion.kind(),
25551 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25552 )
25553 {
25554 snippet_source = label;
25555 }
25556 match Snippet::parse(&snippet_source).log_err() {
25557 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25558 None => (None, completion.new_text.clone()),
25559 }
25560 } else {
25561 (None, completion.new_text.clone())
25562 };
25563
25564 let mut range_to_replace = {
25565 let replace_range = &completion.replace_range;
25566 if let CompletionSource::Lsp {
25567 insert_range: Some(insert_range),
25568 ..
25569 } = &completion.source
25570 {
25571 debug_assert_eq!(
25572 insert_range.start, replace_range.start,
25573 "insert_range and replace_range should start at the same position"
25574 );
25575 debug_assert!(
25576 insert_range
25577 .start
25578 .cmp(cursor_position, &buffer_snapshot)
25579 .is_le(),
25580 "insert_range should start before or at cursor position"
25581 );
25582 debug_assert!(
25583 replace_range
25584 .start
25585 .cmp(cursor_position, &buffer_snapshot)
25586 .is_le(),
25587 "replace_range should start before or at cursor position"
25588 );
25589
25590 let should_replace = match intent {
25591 CompletionIntent::CompleteWithInsert => false,
25592 CompletionIntent::CompleteWithReplace => true,
25593 CompletionIntent::Complete | CompletionIntent::Compose => {
25594 let insert_mode =
25595 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25596 .completions
25597 .lsp_insert_mode;
25598 match insert_mode {
25599 LspInsertMode::Insert => false,
25600 LspInsertMode::Replace => true,
25601 LspInsertMode::ReplaceSubsequence => {
25602 let mut text_to_replace = buffer.chars_for_range(
25603 buffer.anchor_before(replace_range.start)
25604 ..buffer.anchor_after(replace_range.end),
25605 );
25606 let mut current_needle = text_to_replace.next();
25607 for haystack_ch in completion.label.text.chars() {
25608 if let Some(needle_ch) = current_needle
25609 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25610 {
25611 current_needle = text_to_replace.next();
25612 }
25613 }
25614 current_needle.is_none()
25615 }
25616 LspInsertMode::ReplaceSuffix => {
25617 if replace_range
25618 .end
25619 .cmp(cursor_position, &buffer_snapshot)
25620 .is_gt()
25621 {
25622 let range_after_cursor = *cursor_position..replace_range.end;
25623 let text_after_cursor = buffer
25624 .text_for_range(
25625 buffer.anchor_before(range_after_cursor.start)
25626 ..buffer.anchor_after(range_after_cursor.end),
25627 )
25628 .collect::<String>()
25629 .to_ascii_lowercase();
25630 completion
25631 .label
25632 .text
25633 .to_ascii_lowercase()
25634 .ends_with(&text_after_cursor)
25635 } else {
25636 true
25637 }
25638 }
25639 }
25640 }
25641 };
25642
25643 if should_replace {
25644 replace_range.clone()
25645 } else {
25646 insert_range.clone()
25647 }
25648 } else {
25649 replace_range.clone()
25650 }
25651 };
25652
25653 if range_to_replace
25654 .end
25655 .cmp(cursor_position, &buffer_snapshot)
25656 .is_lt()
25657 {
25658 range_to_replace.end = *cursor_position;
25659 }
25660
25661 let replace_range = range_to_replace.to_offset(buffer);
25662 CompletionEdit {
25663 new_text,
25664 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25665 snippet,
25666 }
25667}
25668
25669struct CompletionEdit {
25670 new_text: String,
25671 replace_range: Range<BufferOffset>,
25672 snippet: Option<Snippet>,
25673}
25674
25675fn comment_delimiter_for_newline(
25676 start_point: &Point,
25677 buffer: &MultiBufferSnapshot,
25678 language: &LanguageScope,
25679) -> Option<Arc<str>> {
25680 let delimiters = language.line_comment_prefixes();
25681 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25682 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25683
25684 let num_of_whitespaces = snapshot
25685 .chars_for_range(range.clone())
25686 .take_while(|c| c.is_whitespace())
25687 .count();
25688 let comment_candidate = snapshot
25689 .chars_for_range(range.clone())
25690 .skip(num_of_whitespaces)
25691 .take(max_len_of_delimiter + 2)
25692 .collect::<String>();
25693 let (delimiter, trimmed_len, is_repl) = delimiters
25694 .iter()
25695 .filter_map(|delimiter| {
25696 let prefix = delimiter.trim_end();
25697 if comment_candidate.starts_with(prefix) {
25698 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
25699 {
25700 stripped_comment.starts_with(" %%")
25701 } else {
25702 false
25703 };
25704 Some((delimiter, prefix.len(), is_repl))
25705 } else {
25706 None
25707 }
25708 })
25709 .max_by_key(|(_, len, _)| *len)?;
25710
25711 if let Some(BlockCommentConfig {
25712 start: block_start, ..
25713 }) = language.block_comment()
25714 {
25715 let block_start_trimmed = block_start.trim_end();
25716 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25717 let line_content = snapshot
25718 .chars_for_range(range.clone())
25719 .skip(num_of_whitespaces)
25720 .take(block_start_trimmed.len())
25721 .collect::<String>();
25722
25723 if line_content.starts_with(block_start_trimmed) {
25724 return None;
25725 }
25726 }
25727 }
25728
25729 let cursor_is_placed_after_comment_marker =
25730 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25731 if cursor_is_placed_after_comment_marker {
25732 if !is_repl {
25733 return Some(delimiter.clone());
25734 }
25735
25736 let line_content_after_cursor: String = snapshot
25737 .chars_for_range(range)
25738 .skip(start_point.column as usize)
25739 .collect();
25740
25741 if line_content_after_cursor.trim().is_empty() {
25742 return None;
25743 } else {
25744 return Some(delimiter.clone());
25745 }
25746 } else {
25747 None
25748 }
25749}
25750
25751fn documentation_delimiter_for_newline(
25752 start_point: &Point,
25753 buffer: &MultiBufferSnapshot,
25754 language: &LanguageScope,
25755 newline_config: &mut NewlineConfig,
25756) -> Option<Arc<str>> {
25757 let BlockCommentConfig {
25758 start: start_tag,
25759 end: end_tag,
25760 prefix: delimiter,
25761 tab_size: len,
25762 } = language.documentation_comment()?;
25763 let is_within_block_comment = buffer
25764 .language_scope_at(*start_point)
25765 .is_some_and(|scope| scope.override_name() == Some("comment"));
25766 if !is_within_block_comment {
25767 return None;
25768 }
25769
25770 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25771
25772 let num_of_whitespaces = snapshot
25773 .chars_for_range(range.clone())
25774 .take_while(|c| c.is_whitespace())
25775 .count();
25776
25777 // 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.
25778 let column = start_point.column;
25779 let cursor_is_after_start_tag = {
25780 let start_tag_len = start_tag.len();
25781 let start_tag_line = snapshot
25782 .chars_for_range(range.clone())
25783 .skip(num_of_whitespaces)
25784 .take(start_tag_len)
25785 .collect::<String>();
25786 if start_tag_line.starts_with(start_tag.as_ref()) {
25787 num_of_whitespaces + start_tag_len <= column as usize
25788 } else {
25789 false
25790 }
25791 };
25792
25793 let cursor_is_after_delimiter = {
25794 let delimiter_trim = delimiter.trim_end();
25795 let delimiter_line = snapshot
25796 .chars_for_range(range.clone())
25797 .skip(num_of_whitespaces)
25798 .take(delimiter_trim.len())
25799 .collect::<String>();
25800 if delimiter_line.starts_with(delimiter_trim) {
25801 num_of_whitespaces + delimiter_trim.len() <= column as usize
25802 } else {
25803 false
25804 }
25805 };
25806
25807 let mut needs_extra_line = false;
25808 let mut extra_line_additional_indent = IndentSize::spaces(0);
25809
25810 let cursor_is_before_end_tag_if_exists = {
25811 let mut char_position = 0u32;
25812 let mut end_tag_offset = None;
25813
25814 'outer: for chunk in snapshot.text_for_range(range) {
25815 if let Some(byte_pos) = chunk.find(&**end_tag) {
25816 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25817 end_tag_offset = Some(char_position + chars_before_match);
25818 break 'outer;
25819 }
25820 char_position += chunk.chars().count() as u32;
25821 }
25822
25823 if let Some(end_tag_offset) = end_tag_offset {
25824 let cursor_is_before_end_tag = column <= end_tag_offset;
25825 if cursor_is_after_start_tag {
25826 if cursor_is_before_end_tag {
25827 needs_extra_line = true;
25828 }
25829 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25830 if cursor_is_at_start_of_end_tag {
25831 extra_line_additional_indent.len = *len;
25832 }
25833 }
25834 cursor_is_before_end_tag
25835 } else {
25836 true
25837 }
25838 };
25839
25840 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25841 && cursor_is_before_end_tag_if_exists
25842 {
25843 let additional_indent = if cursor_is_after_start_tag {
25844 IndentSize::spaces(*len)
25845 } else {
25846 IndentSize::spaces(0)
25847 };
25848
25849 *newline_config = NewlineConfig::Newline {
25850 additional_indent,
25851 extra_line_additional_indent: if needs_extra_line {
25852 Some(extra_line_additional_indent)
25853 } else {
25854 None
25855 },
25856 prevent_auto_indent: true,
25857 };
25858 Some(delimiter.clone())
25859 } else {
25860 None
25861 }
25862}
25863
25864const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25865
25866fn list_delimiter_for_newline(
25867 start_point: &Point,
25868 buffer: &MultiBufferSnapshot,
25869 language: &LanguageScope,
25870 newline_config: &mut NewlineConfig,
25871) -> Option<Arc<str>> {
25872 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25873
25874 let num_of_whitespaces = snapshot
25875 .chars_for_range(range.clone())
25876 .take_while(|c| c.is_whitespace())
25877 .count();
25878
25879 let task_list_entries: Vec<_> = language
25880 .task_list()
25881 .into_iter()
25882 .flat_map(|config| {
25883 config
25884 .prefixes
25885 .iter()
25886 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25887 })
25888 .collect();
25889 let unordered_list_entries: Vec<_> = language
25890 .unordered_list()
25891 .iter()
25892 .map(|marker| (marker.as_ref(), marker.as_ref()))
25893 .collect();
25894
25895 let all_entries: Vec<_> = task_list_entries
25896 .into_iter()
25897 .chain(unordered_list_entries)
25898 .collect();
25899
25900 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25901 let candidate: String = snapshot
25902 .chars_for_range(range.clone())
25903 .skip(num_of_whitespaces)
25904 .take(max_prefix_len)
25905 .collect();
25906
25907 if let Some((prefix, continuation)) = all_entries
25908 .iter()
25909 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25910 .max_by_key(|(prefix, _)| prefix.len())
25911 {
25912 let end_of_prefix = num_of_whitespaces + prefix.len();
25913 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25914 let has_content_after_marker = snapshot
25915 .chars_for_range(range)
25916 .skip(end_of_prefix)
25917 .any(|c| !c.is_whitespace());
25918
25919 if has_content_after_marker && cursor_is_after_prefix {
25920 return Some((*continuation).into());
25921 }
25922
25923 if start_point.column as usize == end_of_prefix {
25924 if num_of_whitespaces == 0 {
25925 *newline_config = NewlineConfig::ClearCurrentLine;
25926 } else {
25927 *newline_config = NewlineConfig::UnindentCurrentLine {
25928 continuation: (*continuation).into(),
25929 };
25930 }
25931 }
25932
25933 return None;
25934 }
25935 }
25936
25937 let candidate: String = snapshot
25938 .chars_for_range(range.clone())
25939 .skip(num_of_whitespaces)
25940 .take(ORDERED_LIST_MAX_MARKER_LEN)
25941 .collect();
25942
25943 for ordered_config in language.ordered_list() {
25944 let regex = match Regex::new(&ordered_config.pattern) {
25945 Ok(r) => r,
25946 Err(_) => continue,
25947 };
25948
25949 if let Some(captures) = regex.captures(&candidate) {
25950 let full_match = captures.get(0)?;
25951 let marker_len = full_match.len();
25952 let end_of_prefix = num_of_whitespaces + marker_len;
25953 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25954
25955 let has_content_after_marker = snapshot
25956 .chars_for_range(range)
25957 .skip(end_of_prefix)
25958 .any(|c| !c.is_whitespace());
25959
25960 if has_content_after_marker && cursor_is_after_prefix {
25961 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25962 let continuation = ordered_config
25963 .format
25964 .replace("{1}", &(number + 1).to_string());
25965 return Some(continuation.into());
25966 }
25967
25968 if start_point.column as usize == end_of_prefix {
25969 let continuation = ordered_config.format.replace("{1}", "1");
25970 if num_of_whitespaces == 0 {
25971 *newline_config = NewlineConfig::ClearCurrentLine;
25972 } else {
25973 *newline_config = NewlineConfig::UnindentCurrentLine {
25974 continuation: continuation.into(),
25975 };
25976 }
25977 }
25978
25979 return None;
25980 }
25981 }
25982
25983 None
25984}
25985
25986fn is_list_prefix_row(
25987 row: MultiBufferRow,
25988 buffer: &MultiBufferSnapshot,
25989 language: &LanguageScope,
25990) -> bool {
25991 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25992 return false;
25993 };
25994
25995 let num_of_whitespaces = snapshot
25996 .chars_for_range(range.clone())
25997 .take_while(|c| c.is_whitespace())
25998 .count();
25999
26000 let task_list_prefixes: Vec<_> = language
26001 .task_list()
26002 .into_iter()
26003 .flat_map(|config| {
26004 config
26005 .prefixes
26006 .iter()
26007 .map(|p| p.as_ref())
26008 .collect::<Vec<_>>()
26009 })
26010 .collect();
26011 let unordered_list_markers: Vec<_> = language
26012 .unordered_list()
26013 .iter()
26014 .map(|marker| marker.as_ref())
26015 .collect();
26016 let all_prefixes: Vec<_> = task_list_prefixes
26017 .into_iter()
26018 .chain(unordered_list_markers)
26019 .collect();
26020 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26021 let candidate: String = snapshot
26022 .chars_for_range(range.clone())
26023 .skip(num_of_whitespaces)
26024 .take(max_prefix_len)
26025 .collect();
26026 if all_prefixes
26027 .iter()
26028 .any(|prefix| candidate.starts_with(*prefix))
26029 {
26030 return true;
26031 }
26032 }
26033
26034 let ordered_list_candidate: String = snapshot
26035 .chars_for_range(range)
26036 .skip(num_of_whitespaces)
26037 .take(ORDERED_LIST_MAX_MARKER_LEN)
26038 .collect();
26039 for ordered_config in language.ordered_list() {
26040 let regex = match Regex::new(&ordered_config.pattern) {
26041 Ok(r) => r,
26042 Err(_) => continue,
26043 };
26044 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26045 return captures.get(0).is_some();
26046 }
26047 }
26048
26049 false
26050}
26051
26052#[derive(Debug)]
26053enum NewlineConfig {
26054 /// Insert newline with optional additional indent and optional extra blank line
26055 Newline {
26056 additional_indent: IndentSize,
26057 extra_line_additional_indent: Option<IndentSize>,
26058 prevent_auto_indent: bool,
26059 },
26060 /// Clear the current line
26061 ClearCurrentLine,
26062 /// Unindent the current line and add continuation
26063 UnindentCurrentLine { continuation: Arc<str> },
26064}
26065
26066impl NewlineConfig {
26067 fn has_extra_line(&self) -> bool {
26068 matches!(
26069 self,
26070 Self::Newline {
26071 extra_line_additional_indent: Some(_),
26072 ..
26073 }
26074 )
26075 }
26076
26077 fn insert_extra_newline_brackets(
26078 buffer: &MultiBufferSnapshot,
26079 range: Range<MultiBufferOffset>,
26080 language: &language::LanguageScope,
26081 ) -> bool {
26082 let leading_whitespace_len = buffer
26083 .reversed_chars_at(range.start)
26084 .take_while(|c| c.is_whitespace() && *c != '\n')
26085 .map(|c| c.len_utf8())
26086 .sum::<usize>();
26087 let trailing_whitespace_len = buffer
26088 .chars_at(range.end)
26089 .take_while(|c| c.is_whitespace() && *c != '\n')
26090 .map(|c| c.len_utf8())
26091 .sum::<usize>();
26092 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26093
26094 language.brackets().any(|(pair, enabled)| {
26095 let pair_start = pair.start.trim_end();
26096 let pair_end = pair.end.trim_start();
26097
26098 enabled
26099 && pair.newline
26100 && buffer.contains_str_at(range.end, pair_end)
26101 && buffer.contains_str_at(
26102 range.start.saturating_sub_usize(pair_start.len()),
26103 pair_start,
26104 )
26105 })
26106 }
26107
26108 fn insert_extra_newline_tree_sitter(
26109 buffer: &MultiBufferSnapshot,
26110 range: Range<MultiBufferOffset>,
26111 ) -> bool {
26112 let (buffer, range) = match buffer
26113 .range_to_buffer_ranges(range.start..=range.end)
26114 .as_slice()
26115 {
26116 [(buffer, range, _)] => (*buffer, range.clone()),
26117 _ => return false,
26118 };
26119 let pair = {
26120 let mut result: Option<BracketMatch<usize>> = None;
26121
26122 for pair in buffer
26123 .all_bracket_ranges(range.start.0..range.end.0)
26124 .filter(move |pair| {
26125 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26126 })
26127 {
26128 let len = pair.close_range.end - pair.open_range.start;
26129
26130 if let Some(existing) = &result {
26131 let existing_len = existing.close_range.end - existing.open_range.start;
26132 if len > existing_len {
26133 continue;
26134 }
26135 }
26136
26137 result = Some(pair);
26138 }
26139
26140 result
26141 };
26142 let Some(pair) = pair else {
26143 return false;
26144 };
26145 pair.newline_only
26146 && buffer
26147 .chars_for_range(pair.open_range.end..range.start.0)
26148 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26149 .all(|c| c.is_whitespace() && c != '\n')
26150 }
26151}
26152
26153fn update_uncommitted_diff_for_buffer(
26154 editor: Entity<Editor>,
26155 project: &Entity<Project>,
26156 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26157 buffer: Entity<MultiBuffer>,
26158 cx: &mut App,
26159) -> Task<()> {
26160 let mut tasks = Vec::new();
26161 project.update(cx, |project, cx| {
26162 for buffer in buffers {
26163 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26164 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26165 }
26166 }
26167 });
26168 cx.spawn(async move |cx| {
26169 let diffs = future::join_all(tasks).await;
26170 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26171 return;
26172 }
26173
26174 buffer.update(cx, |buffer, cx| {
26175 for diff in diffs.into_iter().flatten() {
26176 buffer.add_diff(diff, cx);
26177 }
26178 });
26179 })
26180}
26181
26182fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26183 let tab_size = tab_size.get() as usize;
26184 let mut width = offset;
26185
26186 for ch in text.chars() {
26187 width += if ch == '\t' {
26188 tab_size - (width % tab_size)
26189 } else {
26190 1
26191 };
26192 }
26193
26194 width - offset
26195}
26196
26197#[cfg(test)]
26198mod tests {
26199 use super::*;
26200
26201 #[test]
26202 fn test_string_size_with_expanded_tabs() {
26203 let nz = |val| NonZeroU32::new(val).unwrap();
26204 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26205 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26206 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26207 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26208 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26209 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26210 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26211 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26212 }
26213}
26214
26215/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26216struct WordBreakingTokenizer<'a> {
26217 input: &'a str,
26218}
26219
26220impl<'a> WordBreakingTokenizer<'a> {
26221 fn new(input: &'a str) -> Self {
26222 Self { input }
26223 }
26224}
26225
26226fn is_char_ideographic(ch: char) -> bool {
26227 use unicode_script::Script::*;
26228 use unicode_script::UnicodeScript;
26229 matches!(ch.script(), Han | Tangut | Yi)
26230}
26231
26232fn is_grapheme_ideographic(text: &str) -> bool {
26233 text.chars().any(is_char_ideographic)
26234}
26235
26236fn is_grapheme_whitespace(text: &str) -> bool {
26237 text.chars().any(|x| x.is_whitespace())
26238}
26239
26240fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26241 text.chars()
26242 .next()
26243 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26244}
26245
26246#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26247enum WordBreakToken<'a> {
26248 Word { token: &'a str, grapheme_len: usize },
26249 InlineWhitespace { token: &'a str, grapheme_len: usize },
26250 Newline,
26251}
26252
26253impl<'a> Iterator for WordBreakingTokenizer<'a> {
26254 /// Yields a span, the count of graphemes in the token, and whether it was
26255 /// whitespace. Note that it also breaks at word boundaries.
26256 type Item = WordBreakToken<'a>;
26257
26258 fn next(&mut self) -> Option<Self::Item> {
26259 use unicode_segmentation::UnicodeSegmentation;
26260 if self.input.is_empty() {
26261 return None;
26262 }
26263
26264 let mut iter = self.input.graphemes(true).peekable();
26265 let mut offset = 0;
26266 let mut grapheme_len = 0;
26267 if let Some(first_grapheme) = iter.next() {
26268 let is_newline = first_grapheme == "\n";
26269 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26270 offset += first_grapheme.len();
26271 grapheme_len += 1;
26272 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26273 if let Some(grapheme) = iter.peek().copied()
26274 && should_stay_with_preceding_ideograph(grapheme)
26275 {
26276 offset += grapheme.len();
26277 grapheme_len += 1;
26278 }
26279 } else {
26280 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26281 let mut next_word_bound = words.peek().copied();
26282 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26283 next_word_bound = words.next();
26284 }
26285 while let Some(grapheme) = iter.peek().copied() {
26286 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26287 break;
26288 };
26289 if is_grapheme_whitespace(grapheme) != is_whitespace
26290 || (grapheme == "\n") != is_newline
26291 {
26292 break;
26293 };
26294 offset += grapheme.len();
26295 grapheme_len += 1;
26296 iter.next();
26297 }
26298 }
26299 let token = &self.input[..offset];
26300 self.input = &self.input[offset..];
26301 if token == "\n" {
26302 Some(WordBreakToken::Newline)
26303 } else if is_whitespace {
26304 Some(WordBreakToken::InlineWhitespace {
26305 token,
26306 grapheme_len,
26307 })
26308 } else {
26309 Some(WordBreakToken::Word {
26310 token,
26311 grapheme_len,
26312 })
26313 }
26314 } else {
26315 None
26316 }
26317 }
26318}
26319
26320#[test]
26321fn test_word_breaking_tokenizer() {
26322 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26323 ("", &[]),
26324 (" ", &[whitespace(" ", 2)]),
26325 ("Ʒ", &[word("Ʒ", 1)]),
26326 ("Ǽ", &[word("Ǽ", 1)]),
26327 ("⋑", &[word("⋑", 1)]),
26328 ("⋑⋑", &[word("⋑⋑", 2)]),
26329 (
26330 "原理,进而",
26331 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26332 ),
26333 (
26334 "hello world",
26335 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26336 ),
26337 (
26338 "hello, world",
26339 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26340 ),
26341 (
26342 " hello world",
26343 &[
26344 whitespace(" ", 2),
26345 word("hello", 5),
26346 whitespace(" ", 1),
26347 word("world", 5),
26348 ],
26349 ),
26350 (
26351 "这是什么 \n 钢笔",
26352 &[
26353 word("这", 1),
26354 word("是", 1),
26355 word("什", 1),
26356 word("么", 1),
26357 whitespace(" ", 1),
26358 newline(),
26359 whitespace(" ", 1),
26360 word("钢", 1),
26361 word("笔", 1),
26362 ],
26363 ),
26364 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26365 ];
26366
26367 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26368 WordBreakToken::Word {
26369 token,
26370 grapheme_len,
26371 }
26372 }
26373
26374 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26375 WordBreakToken::InlineWhitespace {
26376 token,
26377 grapheme_len,
26378 }
26379 }
26380
26381 fn newline() -> WordBreakToken<'static> {
26382 WordBreakToken::Newline
26383 }
26384
26385 for (input, result) in tests {
26386 assert_eq!(
26387 WordBreakingTokenizer::new(input)
26388 .collect::<Vec<_>>()
26389 .as_slice(),
26390 *result,
26391 );
26392 }
26393}
26394
26395fn wrap_with_prefix(
26396 first_line_prefix: String,
26397 subsequent_lines_prefix: String,
26398 unwrapped_text: String,
26399 wrap_column: usize,
26400 tab_size: NonZeroU32,
26401 preserve_existing_whitespace: bool,
26402) -> String {
26403 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26404 let subsequent_lines_prefix_len =
26405 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26406 let mut wrapped_text = String::new();
26407 let mut current_line = first_line_prefix;
26408 let mut is_first_line = true;
26409
26410 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26411 let mut current_line_len = first_line_prefix_len;
26412 let mut in_whitespace = false;
26413 for token in tokenizer {
26414 let have_preceding_whitespace = in_whitespace;
26415 match token {
26416 WordBreakToken::Word {
26417 token,
26418 grapheme_len,
26419 } => {
26420 in_whitespace = false;
26421 let current_prefix_len = if is_first_line {
26422 first_line_prefix_len
26423 } else {
26424 subsequent_lines_prefix_len
26425 };
26426 if current_line_len + grapheme_len > wrap_column
26427 && current_line_len != current_prefix_len
26428 {
26429 wrapped_text.push_str(current_line.trim_end());
26430 wrapped_text.push('\n');
26431 is_first_line = false;
26432 current_line = subsequent_lines_prefix.clone();
26433 current_line_len = subsequent_lines_prefix_len;
26434 }
26435 current_line.push_str(token);
26436 current_line_len += grapheme_len;
26437 }
26438 WordBreakToken::InlineWhitespace {
26439 mut token,
26440 mut grapheme_len,
26441 } => {
26442 in_whitespace = true;
26443 if have_preceding_whitespace && !preserve_existing_whitespace {
26444 continue;
26445 }
26446 if !preserve_existing_whitespace {
26447 // Keep a single whitespace grapheme as-is
26448 if let Some(first) =
26449 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26450 {
26451 token = first;
26452 } else {
26453 token = " ";
26454 }
26455 grapheme_len = 1;
26456 }
26457 let current_prefix_len = if is_first_line {
26458 first_line_prefix_len
26459 } else {
26460 subsequent_lines_prefix_len
26461 };
26462 if current_line_len + grapheme_len > wrap_column {
26463 wrapped_text.push_str(current_line.trim_end());
26464 wrapped_text.push('\n');
26465 is_first_line = false;
26466 current_line = subsequent_lines_prefix.clone();
26467 current_line_len = subsequent_lines_prefix_len;
26468 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26469 current_line.push_str(token);
26470 current_line_len += grapheme_len;
26471 }
26472 }
26473 WordBreakToken::Newline => {
26474 in_whitespace = true;
26475 let current_prefix_len = if is_first_line {
26476 first_line_prefix_len
26477 } else {
26478 subsequent_lines_prefix_len
26479 };
26480 if preserve_existing_whitespace {
26481 wrapped_text.push_str(current_line.trim_end());
26482 wrapped_text.push('\n');
26483 is_first_line = false;
26484 current_line = subsequent_lines_prefix.clone();
26485 current_line_len = subsequent_lines_prefix_len;
26486 } else if have_preceding_whitespace {
26487 continue;
26488 } else if current_line_len + 1 > wrap_column
26489 && current_line_len != current_prefix_len
26490 {
26491 wrapped_text.push_str(current_line.trim_end());
26492 wrapped_text.push('\n');
26493 is_first_line = false;
26494 current_line = subsequent_lines_prefix.clone();
26495 current_line_len = subsequent_lines_prefix_len;
26496 } else if current_line_len != current_prefix_len {
26497 current_line.push(' ');
26498 current_line_len += 1;
26499 }
26500 }
26501 }
26502 }
26503
26504 if !current_line.is_empty() {
26505 wrapped_text.push_str(¤t_line);
26506 }
26507 wrapped_text
26508}
26509
26510#[test]
26511fn test_wrap_with_prefix() {
26512 assert_eq!(
26513 wrap_with_prefix(
26514 "# ".to_string(),
26515 "# ".to_string(),
26516 "abcdefg".to_string(),
26517 4,
26518 NonZeroU32::new(4).unwrap(),
26519 false,
26520 ),
26521 "# abcdefg"
26522 );
26523 assert_eq!(
26524 wrap_with_prefix(
26525 "".to_string(),
26526 "".to_string(),
26527 "\thello world".to_string(),
26528 8,
26529 NonZeroU32::new(4).unwrap(),
26530 false,
26531 ),
26532 "hello\nworld"
26533 );
26534 assert_eq!(
26535 wrap_with_prefix(
26536 "// ".to_string(),
26537 "// ".to_string(),
26538 "xx \nyy zz aa bb cc".to_string(),
26539 12,
26540 NonZeroU32::new(4).unwrap(),
26541 false,
26542 ),
26543 "// xx yy zz\n// aa bb cc"
26544 );
26545 assert_eq!(
26546 wrap_with_prefix(
26547 String::new(),
26548 String::new(),
26549 "这是什么 \n 钢笔".to_string(),
26550 3,
26551 NonZeroU32::new(4).unwrap(),
26552 false,
26553 ),
26554 "这是什\n么 钢\n笔"
26555 );
26556 assert_eq!(
26557 wrap_with_prefix(
26558 String::new(),
26559 String::new(),
26560 format!("foo{}bar", '\u{2009}'), // thin space
26561 80,
26562 NonZeroU32::new(4).unwrap(),
26563 false,
26564 ),
26565 format!("foo{}bar", '\u{2009}')
26566 );
26567}
26568
26569pub trait CollaborationHub {
26570 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26571 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26572 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26573}
26574
26575impl CollaborationHub for Entity<Project> {
26576 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26577 self.read(cx).collaborators()
26578 }
26579
26580 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26581 self.read(cx).user_store().read(cx).participant_indices()
26582 }
26583
26584 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26585 let this = self.read(cx);
26586 let user_ids = this.collaborators().values().map(|c| c.user_id);
26587 this.user_store().read(cx).participant_names(user_ids, cx)
26588 }
26589}
26590
26591pub trait SemanticsProvider {
26592 fn hover(
26593 &self,
26594 buffer: &Entity<Buffer>,
26595 position: text::Anchor,
26596 cx: &mut App,
26597 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26598
26599 fn inline_values(
26600 &self,
26601 buffer_handle: Entity<Buffer>,
26602 range: Range<text::Anchor>,
26603 cx: &mut App,
26604 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26605
26606 fn applicable_inlay_chunks(
26607 &self,
26608 buffer: &Entity<Buffer>,
26609 ranges: &[Range<text::Anchor>],
26610 cx: &mut App,
26611 ) -> Vec<Range<BufferRow>>;
26612
26613 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26614
26615 fn inlay_hints(
26616 &self,
26617 invalidate: InvalidationStrategy,
26618 buffer: Entity<Buffer>,
26619 ranges: Vec<Range<text::Anchor>>,
26620 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26621 cx: &mut App,
26622 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26623
26624 fn semantic_tokens(
26625 &self,
26626 buffer: Entity<Buffer>,
26627 refresh: Option<RefreshForServer>,
26628 cx: &mut App,
26629 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
26630
26631 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26632
26633 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26634
26635 fn document_highlights(
26636 &self,
26637 buffer: &Entity<Buffer>,
26638 position: text::Anchor,
26639 cx: &mut App,
26640 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26641
26642 fn definitions(
26643 &self,
26644 buffer: &Entity<Buffer>,
26645 position: text::Anchor,
26646 kind: GotoDefinitionKind,
26647 cx: &mut App,
26648 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26649
26650 fn range_for_rename(
26651 &self,
26652 buffer: &Entity<Buffer>,
26653 position: text::Anchor,
26654 cx: &mut App,
26655 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26656
26657 fn perform_rename(
26658 &self,
26659 buffer: &Entity<Buffer>,
26660 position: text::Anchor,
26661 new_name: String,
26662 cx: &mut App,
26663 ) -> Option<Task<Result<ProjectTransaction>>>;
26664}
26665
26666pub trait CompletionProvider {
26667 fn completions(
26668 &self,
26669 excerpt_id: ExcerptId,
26670 buffer: &Entity<Buffer>,
26671 buffer_position: text::Anchor,
26672 trigger: CompletionContext,
26673 window: &mut Window,
26674 cx: &mut Context<Editor>,
26675 ) -> Task<Result<Vec<CompletionResponse>>>;
26676
26677 fn resolve_completions(
26678 &self,
26679 _buffer: Entity<Buffer>,
26680 _completion_indices: Vec<usize>,
26681 _completions: Rc<RefCell<Box<[Completion]>>>,
26682 _cx: &mut Context<Editor>,
26683 ) -> Task<Result<bool>> {
26684 Task::ready(Ok(false))
26685 }
26686
26687 fn apply_additional_edits_for_completion(
26688 &self,
26689 _buffer: Entity<Buffer>,
26690 _completions: Rc<RefCell<Box<[Completion]>>>,
26691 _completion_index: usize,
26692 _push_to_history: bool,
26693 _all_commit_ranges: Vec<Range<language::Anchor>>,
26694 _cx: &mut Context<Editor>,
26695 ) -> Task<Result<Option<language::Transaction>>> {
26696 Task::ready(Ok(None))
26697 }
26698
26699 fn is_completion_trigger(
26700 &self,
26701 buffer: &Entity<Buffer>,
26702 position: language::Anchor,
26703 text: &str,
26704 trigger_in_words: bool,
26705 cx: &mut Context<Editor>,
26706 ) -> bool;
26707
26708 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26709
26710 fn sort_completions(&self) -> bool {
26711 true
26712 }
26713
26714 fn filter_completions(&self) -> bool {
26715 true
26716 }
26717
26718 fn show_snippets(&self) -> bool {
26719 false
26720 }
26721}
26722
26723pub trait CodeActionProvider {
26724 fn id(&self) -> Arc<str>;
26725
26726 fn code_actions(
26727 &self,
26728 buffer: &Entity<Buffer>,
26729 range: Range<text::Anchor>,
26730 window: &mut Window,
26731 cx: &mut App,
26732 ) -> Task<Result<Vec<CodeAction>>>;
26733
26734 fn apply_code_action(
26735 &self,
26736 buffer_handle: Entity<Buffer>,
26737 action: CodeAction,
26738 excerpt_id: ExcerptId,
26739 push_to_history: bool,
26740 window: &mut Window,
26741 cx: &mut App,
26742 ) -> Task<Result<ProjectTransaction>>;
26743}
26744
26745impl CodeActionProvider for Entity<Project> {
26746 fn id(&self) -> Arc<str> {
26747 "project".into()
26748 }
26749
26750 fn code_actions(
26751 &self,
26752 buffer: &Entity<Buffer>,
26753 range: Range<text::Anchor>,
26754 _window: &mut Window,
26755 cx: &mut App,
26756 ) -> Task<Result<Vec<CodeAction>>> {
26757 self.update(cx, |project, cx| {
26758 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26759 let code_actions = project.code_actions(buffer, range, None, cx);
26760 cx.background_spawn(async move {
26761 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26762 Ok(code_lens_actions
26763 .context("code lens fetch")?
26764 .into_iter()
26765 .flatten()
26766 .chain(
26767 code_actions
26768 .context("code action fetch")?
26769 .into_iter()
26770 .flatten(),
26771 )
26772 .collect())
26773 })
26774 })
26775 }
26776
26777 fn apply_code_action(
26778 &self,
26779 buffer_handle: Entity<Buffer>,
26780 action: CodeAction,
26781 _excerpt_id: ExcerptId,
26782 push_to_history: bool,
26783 _window: &mut Window,
26784 cx: &mut App,
26785 ) -> Task<Result<ProjectTransaction>> {
26786 self.update(cx, |project, cx| {
26787 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26788 })
26789 }
26790}
26791
26792fn snippet_completions(
26793 project: &Project,
26794 buffer: &Entity<Buffer>,
26795 buffer_anchor: text::Anchor,
26796 classifier: CharClassifier,
26797 cx: &mut App,
26798) -> Task<Result<CompletionResponse>> {
26799 let languages = buffer.read(cx).languages_at(buffer_anchor);
26800 let snippet_store = project.snippets().read(cx);
26801
26802 let scopes: Vec<_> = languages
26803 .iter()
26804 .filter_map(|language| {
26805 let language_name = language.lsp_id();
26806 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26807
26808 if snippets.is_empty() {
26809 None
26810 } else {
26811 Some((language.default_scope(), snippets))
26812 }
26813 })
26814 .collect();
26815
26816 if scopes.is_empty() {
26817 return Task::ready(Ok(CompletionResponse {
26818 completions: vec![],
26819 display_options: CompletionDisplayOptions::default(),
26820 is_incomplete: false,
26821 }));
26822 }
26823
26824 let snapshot = buffer.read(cx).text_snapshot();
26825 let executor = cx.background_executor().clone();
26826
26827 cx.background_spawn(async move {
26828 let is_word_char = |c| classifier.is_word(c);
26829
26830 let mut is_incomplete = false;
26831 let mut completions: Vec<Completion> = Vec::new();
26832
26833 const MAX_PREFIX_LEN: usize = 128;
26834 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26835 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26836 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26837
26838 let max_buffer_window: String = snapshot
26839 .text_for_range(window_start..buffer_offset)
26840 .collect();
26841
26842 if max_buffer_window.is_empty() {
26843 return Ok(CompletionResponse {
26844 completions: vec![],
26845 display_options: CompletionDisplayOptions::default(),
26846 is_incomplete: true,
26847 });
26848 }
26849
26850 for (_scope, snippets) in scopes.into_iter() {
26851 // Sort snippets by word count to match longer snippet prefixes first.
26852 let mut sorted_snippet_candidates = snippets
26853 .iter()
26854 .enumerate()
26855 .flat_map(|(snippet_ix, snippet)| {
26856 snippet
26857 .prefix
26858 .iter()
26859 .enumerate()
26860 .map(move |(prefix_ix, prefix)| {
26861 let word_count =
26862 snippet_candidate_suffixes(prefix, &is_word_char).count();
26863 ((snippet_ix, prefix_ix), prefix, word_count)
26864 })
26865 })
26866 .collect_vec();
26867 sorted_snippet_candidates
26868 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26869
26870 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26871
26872 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
26873 .take(
26874 sorted_snippet_candidates
26875 .first()
26876 .map(|(_, _, word_count)| *word_count)
26877 .unwrap_or_default(),
26878 )
26879 .collect_vec();
26880
26881 const MAX_RESULTS: usize = 100;
26882 // Each match also remembers how many characters from the buffer it consumed
26883 let mut matches: Vec<(StringMatch, usize)> = vec![];
26884
26885 let mut snippet_list_cutoff_index = 0;
26886 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26887 let word_count = buffer_index + 1;
26888 // Increase `snippet_list_cutoff_index` until we have all of the
26889 // snippets with sufficiently many words.
26890 while sorted_snippet_candidates
26891 .get(snippet_list_cutoff_index)
26892 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26893 *snippet_word_count >= word_count
26894 })
26895 {
26896 snippet_list_cutoff_index += 1;
26897 }
26898
26899 // Take only the candidates with at least `word_count` many words
26900 let snippet_candidates_at_word_len =
26901 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26902
26903 let candidates = snippet_candidates_at_word_len
26904 .iter()
26905 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26906 .enumerate() // index in `sorted_snippet_candidates`
26907 // First char must match
26908 .filter(|(_ix, prefix)| {
26909 itertools::equal(
26910 prefix
26911 .chars()
26912 .next()
26913 .into_iter()
26914 .flat_map(|c| c.to_lowercase()),
26915 buffer_window
26916 .chars()
26917 .next()
26918 .into_iter()
26919 .flat_map(|c| c.to_lowercase()),
26920 )
26921 })
26922 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26923 .collect::<Vec<StringMatchCandidate>>();
26924
26925 matches.extend(
26926 fuzzy::match_strings(
26927 &candidates,
26928 &buffer_window,
26929 buffer_window.chars().any(|c| c.is_uppercase()),
26930 true,
26931 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26932 &Default::default(),
26933 executor.clone(),
26934 )
26935 .await
26936 .into_iter()
26937 .map(|string_match| (string_match, buffer_window.len())),
26938 );
26939
26940 if matches.len() >= MAX_RESULTS {
26941 break;
26942 }
26943 }
26944
26945 let to_lsp = |point: &text::Anchor| {
26946 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26947 point_to_lsp(end)
26948 };
26949 let lsp_end = to_lsp(&buffer_anchor);
26950
26951 if matches.len() >= MAX_RESULTS {
26952 is_incomplete = true;
26953 }
26954
26955 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26956 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26957 sorted_snippet_candidates[string_match.candidate_id];
26958 let snippet = &snippets[snippet_index];
26959 let start = buffer_offset - buffer_window_len;
26960 let start = snapshot.anchor_before(start);
26961 let range = start..buffer_anchor;
26962 let lsp_start = to_lsp(&start);
26963 let lsp_range = lsp::Range {
26964 start: lsp_start,
26965 end: lsp_end,
26966 };
26967 Completion {
26968 replace_range: range,
26969 new_text: snippet.body.clone(),
26970 source: CompletionSource::Lsp {
26971 insert_range: None,
26972 server_id: LanguageServerId(usize::MAX),
26973 resolved: true,
26974 lsp_completion: Box::new(lsp::CompletionItem {
26975 label: snippet.prefix.first().unwrap().clone(),
26976 kind: Some(CompletionItemKind::SNIPPET),
26977 label_details: snippet.description.as_ref().map(|description| {
26978 lsp::CompletionItemLabelDetails {
26979 detail: Some(description.clone()),
26980 description: None,
26981 }
26982 }),
26983 insert_text_format: Some(InsertTextFormat::SNIPPET),
26984 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26985 lsp::InsertReplaceEdit {
26986 new_text: snippet.body.clone(),
26987 insert: lsp_range,
26988 replace: lsp_range,
26989 },
26990 )),
26991 filter_text: Some(snippet.body.clone()),
26992 sort_text: Some(char::MAX.to_string()),
26993 ..lsp::CompletionItem::default()
26994 }),
26995 lsp_defaults: None,
26996 },
26997 label: CodeLabel {
26998 text: matching_prefix.clone(),
26999 runs: Vec::new(),
27000 filter_range: 0..matching_prefix.len(),
27001 },
27002 icon_path: None,
27003 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27004 single_line: snippet.name.clone().into(),
27005 plain_text: snippet
27006 .description
27007 .clone()
27008 .map(|description| description.into()),
27009 }),
27010 insert_text_mode: None,
27011 confirm: None,
27012 match_start: Some(start),
27013 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27014 }
27015 }));
27016 }
27017
27018 Ok(CompletionResponse {
27019 completions,
27020 display_options: CompletionDisplayOptions::default(),
27021 is_incomplete,
27022 })
27023 })
27024}
27025
27026impl CompletionProvider for Entity<Project> {
27027 fn completions(
27028 &self,
27029 _excerpt_id: ExcerptId,
27030 buffer: &Entity<Buffer>,
27031 buffer_position: text::Anchor,
27032 options: CompletionContext,
27033 _window: &mut Window,
27034 cx: &mut Context<Editor>,
27035 ) -> Task<Result<Vec<CompletionResponse>>> {
27036 self.update(cx, |project, cx| {
27037 let task = project.completions(buffer, buffer_position, options, cx);
27038 cx.background_spawn(task)
27039 })
27040 }
27041
27042 fn resolve_completions(
27043 &self,
27044 buffer: Entity<Buffer>,
27045 completion_indices: Vec<usize>,
27046 completions: Rc<RefCell<Box<[Completion]>>>,
27047 cx: &mut Context<Editor>,
27048 ) -> Task<Result<bool>> {
27049 self.update(cx, |project, cx| {
27050 project.lsp_store().update(cx, |lsp_store, cx| {
27051 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27052 })
27053 })
27054 }
27055
27056 fn apply_additional_edits_for_completion(
27057 &self,
27058 buffer: Entity<Buffer>,
27059 completions: Rc<RefCell<Box<[Completion]>>>,
27060 completion_index: usize,
27061 push_to_history: bool,
27062 all_commit_ranges: Vec<Range<language::Anchor>>,
27063 cx: &mut Context<Editor>,
27064 ) -> Task<Result<Option<language::Transaction>>> {
27065 self.update(cx, |project, cx| {
27066 project.lsp_store().update(cx, |lsp_store, cx| {
27067 lsp_store.apply_additional_edits_for_completion(
27068 buffer,
27069 completions,
27070 completion_index,
27071 push_to_history,
27072 all_commit_ranges,
27073 cx,
27074 )
27075 })
27076 })
27077 }
27078
27079 fn is_completion_trigger(
27080 &self,
27081 buffer: &Entity<Buffer>,
27082 position: language::Anchor,
27083 text: &str,
27084 trigger_in_words: bool,
27085 cx: &mut Context<Editor>,
27086 ) -> bool {
27087 let mut chars = text.chars();
27088 let char = if let Some(char) = chars.next() {
27089 char
27090 } else {
27091 return false;
27092 };
27093 if chars.next().is_some() {
27094 return false;
27095 }
27096
27097 let buffer = buffer.read(cx);
27098 let snapshot = buffer.snapshot();
27099 let classifier = snapshot
27100 .char_classifier_at(position)
27101 .scope_context(Some(CharScopeContext::Completion));
27102 if trigger_in_words && classifier.is_word(char) {
27103 return true;
27104 }
27105
27106 buffer.completion_triggers().contains(text)
27107 }
27108
27109 fn show_snippets(&self) -> bool {
27110 true
27111 }
27112}
27113
27114impl SemanticsProvider for WeakEntity<Project> {
27115 fn hover(
27116 &self,
27117 buffer: &Entity<Buffer>,
27118 position: text::Anchor,
27119 cx: &mut App,
27120 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27121 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27122 .ok()
27123 }
27124
27125 fn document_highlights(
27126 &self,
27127 buffer: &Entity<Buffer>,
27128 position: text::Anchor,
27129 cx: &mut App,
27130 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27131 self.update(cx, |project, cx| {
27132 project.document_highlights(buffer, position, cx)
27133 })
27134 .ok()
27135 }
27136
27137 fn definitions(
27138 &self,
27139 buffer: &Entity<Buffer>,
27140 position: text::Anchor,
27141 kind: GotoDefinitionKind,
27142 cx: &mut App,
27143 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27144 self.update(cx, |project, cx| match kind {
27145 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27146 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27147 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27148 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27149 })
27150 .ok()
27151 }
27152
27153 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27154 self.update(cx, |project, cx| {
27155 if project
27156 .active_debug_session(cx)
27157 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27158 {
27159 return true;
27160 }
27161
27162 buffer.update(cx, |buffer, cx| {
27163 project.any_language_server_supports_inlay_hints(buffer, cx)
27164 })
27165 })
27166 .unwrap_or(false)
27167 }
27168
27169 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27170 self.update(cx, |project, cx| {
27171 buffer.update(cx, |buffer, cx| {
27172 project.any_language_server_supports_semantic_tokens(buffer, cx)
27173 })
27174 })
27175 .unwrap_or(false)
27176 }
27177
27178 fn inline_values(
27179 &self,
27180 buffer_handle: Entity<Buffer>,
27181 range: Range<text::Anchor>,
27182 cx: &mut App,
27183 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27184 self.update(cx, |project, cx| {
27185 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27186
27187 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27188 })
27189 .ok()
27190 .flatten()
27191 }
27192
27193 fn applicable_inlay_chunks(
27194 &self,
27195 buffer: &Entity<Buffer>,
27196 ranges: &[Range<text::Anchor>],
27197 cx: &mut App,
27198 ) -> Vec<Range<BufferRow>> {
27199 self.update(cx, |project, cx| {
27200 project.lsp_store().update(cx, |lsp_store, cx| {
27201 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27202 })
27203 })
27204 .unwrap_or_default()
27205 }
27206
27207 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27208 self.update(cx, |project, cx| {
27209 project.lsp_store().update(cx, |lsp_store, _| {
27210 lsp_store.invalidate_inlay_hints(for_buffers)
27211 })
27212 })
27213 .ok();
27214 }
27215
27216 fn inlay_hints(
27217 &self,
27218 invalidate: InvalidationStrategy,
27219 buffer: Entity<Buffer>,
27220 ranges: Vec<Range<text::Anchor>>,
27221 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27222 cx: &mut App,
27223 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27224 self.update(cx, |project, cx| {
27225 project.lsp_store().update(cx, |lsp_store, cx| {
27226 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27227 })
27228 })
27229 .ok()
27230 }
27231
27232 fn semantic_tokens(
27233 &self,
27234 buffer: Entity<Buffer>,
27235 refresh: Option<RefreshForServer>,
27236 cx: &mut App,
27237 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27238 self.update(cx, |this, cx| {
27239 this.lsp_store().update(cx, |lsp_store, cx| {
27240 lsp_store.semantic_tokens(buffer, refresh, cx)
27241 })
27242 })
27243 .ok()
27244 }
27245
27246 fn range_for_rename(
27247 &self,
27248 buffer: &Entity<Buffer>,
27249 position: text::Anchor,
27250 cx: &mut App,
27251 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27252 self.update(cx, |project, cx| {
27253 let buffer = buffer.clone();
27254 let task = project.prepare_rename(buffer.clone(), position, cx);
27255 cx.spawn(async move |_, cx| {
27256 Ok(match task.await? {
27257 PrepareRenameResponse::Success(range) => Some(range),
27258 PrepareRenameResponse::InvalidPosition => None,
27259 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27260 // Fallback on using TreeSitter info to determine identifier range
27261 buffer.read_with(cx, |buffer, _| {
27262 let snapshot = buffer.snapshot();
27263 let (range, kind) = snapshot.surrounding_word(position, None);
27264 if kind != Some(CharKind::Word) {
27265 return None;
27266 }
27267 Some(
27268 snapshot.anchor_before(range.start)
27269 ..snapshot.anchor_after(range.end),
27270 )
27271 })
27272 }
27273 })
27274 })
27275 })
27276 .ok()
27277 }
27278
27279 fn perform_rename(
27280 &self,
27281 buffer: &Entity<Buffer>,
27282 position: text::Anchor,
27283 new_name: String,
27284 cx: &mut App,
27285 ) -> Option<Task<Result<ProjectTransaction>>> {
27286 self.update(cx, |project, cx| {
27287 project.perform_rename(buffer.clone(), position, new_name, cx)
27288 })
27289 .ok()
27290 }
27291}
27292
27293fn consume_contiguous_rows(
27294 contiguous_row_selections: &mut Vec<Selection<Point>>,
27295 selection: &Selection<Point>,
27296 display_map: &DisplaySnapshot,
27297 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27298) -> (MultiBufferRow, MultiBufferRow) {
27299 contiguous_row_selections.push(selection.clone());
27300 let start_row = starting_row(selection, display_map);
27301 let mut end_row = ending_row(selection, display_map);
27302
27303 while let Some(next_selection) = selections.peek() {
27304 if next_selection.start.row <= end_row.0 {
27305 end_row = ending_row(next_selection, display_map);
27306 contiguous_row_selections.push(selections.next().unwrap().clone());
27307 } else {
27308 break;
27309 }
27310 }
27311 (start_row, end_row)
27312}
27313
27314fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27315 if selection.start.column > 0 {
27316 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27317 } else {
27318 MultiBufferRow(selection.start.row)
27319 }
27320}
27321
27322fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27323 if next_selection.end.column > 0 || next_selection.is_empty() {
27324 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27325 } else {
27326 MultiBufferRow(next_selection.end.row)
27327 }
27328}
27329
27330impl EditorSnapshot {
27331 pub fn remote_selections_in_range<'a>(
27332 &'a self,
27333 range: &'a Range<Anchor>,
27334 collaboration_hub: &dyn CollaborationHub,
27335 cx: &'a App,
27336 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27337 let participant_names = collaboration_hub.user_names(cx);
27338 let participant_indices = collaboration_hub.user_participant_indices(cx);
27339 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27340 let collaborators_by_replica_id = collaborators_by_peer_id
27341 .values()
27342 .map(|collaborator| (collaborator.replica_id, collaborator))
27343 .collect::<HashMap<_, _>>();
27344 self.buffer_snapshot()
27345 .selections_in_range(range, false)
27346 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27347 if replica_id == ReplicaId::AGENT {
27348 Some(RemoteSelection {
27349 replica_id,
27350 selection,
27351 cursor_shape,
27352 line_mode,
27353 collaborator_id: CollaboratorId::Agent,
27354 user_name: Some("Agent".into()),
27355 color: cx.theme().players().agent(),
27356 })
27357 } else {
27358 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27359 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27360 let user_name = participant_names.get(&collaborator.user_id).cloned();
27361 Some(RemoteSelection {
27362 replica_id,
27363 selection,
27364 cursor_shape,
27365 line_mode,
27366 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27367 user_name,
27368 color: if let Some(index) = participant_index {
27369 cx.theme().players().color_for_participant(index.0)
27370 } else {
27371 cx.theme().players().absent()
27372 },
27373 })
27374 }
27375 })
27376 }
27377
27378 pub fn hunks_for_ranges(
27379 &self,
27380 ranges: impl IntoIterator<Item = Range<Point>>,
27381 ) -> Vec<MultiBufferDiffHunk> {
27382 let mut hunks = Vec::new();
27383 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27384 HashMap::default();
27385 for query_range in ranges {
27386 let query_rows =
27387 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27388 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27389 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27390 ) {
27391 // Include deleted hunks that are adjacent to the query range, because
27392 // otherwise they would be missed.
27393 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27394 if hunk.status().is_deleted() {
27395 intersects_range |= hunk.row_range.start == query_rows.end;
27396 intersects_range |= hunk.row_range.end == query_rows.start;
27397 }
27398 if intersects_range {
27399 if !processed_buffer_rows
27400 .entry(hunk.buffer_id)
27401 .or_default()
27402 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27403 {
27404 continue;
27405 }
27406 hunks.push(hunk);
27407 }
27408 }
27409 }
27410
27411 hunks
27412 }
27413
27414 fn display_diff_hunks_for_rows<'a>(
27415 &'a self,
27416 display_rows: Range<DisplayRow>,
27417 folded_buffers: &'a HashSet<BufferId>,
27418 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27419 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27420 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27421
27422 self.buffer_snapshot()
27423 .diff_hunks_in_range(buffer_start..buffer_end)
27424 .filter_map(|hunk| {
27425 if folded_buffers.contains(&hunk.buffer_id)
27426 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27427 {
27428 return None;
27429 }
27430
27431 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27432 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27433 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27434 let line_len = self.buffer_snapshot().line_len(last_row);
27435 Point::new(last_row.0, line_len)
27436 } else {
27437 Point::new(hunk.row_range.end.0, 0)
27438 };
27439
27440 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27441 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27442
27443 let display_hunk = if hunk_display_start.column() != 0 {
27444 DisplayDiffHunk::Folded {
27445 display_row: hunk_display_start.row(),
27446 }
27447 } else {
27448 let mut end_row = hunk_display_end.row();
27449 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27450 end_row.0 += 1;
27451 }
27452 let is_created_file = hunk.is_created_file();
27453
27454 DisplayDiffHunk::Unfolded {
27455 status: hunk.status(),
27456 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27457 ..hunk.diff_base_byte_range.end.0,
27458 word_diffs: hunk.word_diffs,
27459 display_row_range: hunk_display_start.row()..end_row,
27460 multi_buffer_range: Anchor::range_in_buffer(
27461 hunk.excerpt_id,
27462 hunk.buffer_range,
27463 ),
27464 is_created_file,
27465 }
27466 };
27467
27468 Some(display_hunk)
27469 })
27470 }
27471
27472 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27473 self.display_snapshot
27474 .buffer_snapshot()
27475 .language_at(position)
27476 }
27477
27478 pub fn is_focused(&self) -> bool {
27479 self.is_focused
27480 }
27481
27482 pub fn placeholder_text(&self) -> Option<String> {
27483 self.placeholder_display_snapshot
27484 .as_ref()
27485 .map(|display_map| display_map.text())
27486 }
27487
27488 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27489 self.scroll_anchor.scroll_position(&self.display_snapshot)
27490 }
27491
27492 pub fn gutter_dimensions(
27493 &self,
27494 font_id: FontId,
27495 font_size: Pixels,
27496 style: &EditorStyle,
27497 window: &mut Window,
27498 cx: &App,
27499 ) -> GutterDimensions {
27500 if self.show_gutter
27501 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27502 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27503 {
27504 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27505 matches!(
27506 ProjectSettings::get_global(cx).git.git_gutter,
27507 GitGutterSetting::TrackedFiles
27508 )
27509 });
27510 let gutter_settings = EditorSettings::get_global(cx).gutter;
27511 let show_line_numbers = self
27512 .show_line_numbers
27513 .unwrap_or(gutter_settings.line_numbers);
27514 let line_gutter_width = if show_line_numbers {
27515 // Avoid flicker-like gutter resizes when the line number gains another digit by
27516 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27517 let min_width_for_number_on_gutter =
27518 ch_advance * gutter_settings.min_line_number_digits as f32;
27519 self.max_line_number_width(style, window)
27520 .max(min_width_for_number_on_gutter)
27521 } else {
27522 0.0.into()
27523 };
27524
27525 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27526 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27527
27528 let git_blame_entries_width =
27529 self.git_blame_gutter_max_author_length
27530 .map(|max_author_length| {
27531 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27532 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27533
27534 /// The number of characters to dedicate to gaps and margins.
27535 const SPACING_WIDTH: usize = 4;
27536
27537 let max_char_count = max_author_length.min(renderer.max_author_length())
27538 + ::git::SHORT_SHA_LENGTH
27539 + MAX_RELATIVE_TIMESTAMP.len()
27540 + SPACING_WIDTH;
27541
27542 ch_advance * max_char_count
27543 });
27544
27545 let is_singleton = self.buffer_snapshot().is_singleton();
27546
27547 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27548 left_padding += if !is_singleton {
27549 ch_width * 4.0
27550 } else if show_runnables || show_breakpoints {
27551 ch_width * 3.0
27552 } else if show_git_gutter && show_line_numbers {
27553 ch_width * 2.0
27554 } else if show_git_gutter || show_line_numbers {
27555 ch_width
27556 } else {
27557 px(0.)
27558 };
27559
27560 let shows_folds = is_singleton && gutter_settings.folds;
27561
27562 let right_padding = if shows_folds && show_line_numbers {
27563 ch_width * 4.0
27564 } else if shows_folds || (!is_singleton && show_line_numbers) {
27565 ch_width * 3.0
27566 } else if show_line_numbers {
27567 ch_width
27568 } else {
27569 px(0.)
27570 };
27571
27572 GutterDimensions {
27573 left_padding,
27574 right_padding,
27575 width: line_gutter_width + left_padding + right_padding,
27576 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27577 git_blame_entries_width,
27578 }
27579 } else if self.offset_content {
27580 GutterDimensions::default_with_margin(font_id, font_size, cx)
27581 } else {
27582 GutterDimensions::default()
27583 }
27584 }
27585
27586 pub fn render_crease_toggle(
27587 &self,
27588 buffer_row: MultiBufferRow,
27589 row_contains_cursor: bool,
27590 editor: Entity<Editor>,
27591 window: &mut Window,
27592 cx: &mut App,
27593 ) -> Option<AnyElement> {
27594 let folded = self.is_line_folded(buffer_row);
27595 let mut is_foldable = false;
27596
27597 if let Some(crease) = self
27598 .crease_snapshot
27599 .query_row(buffer_row, self.buffer_snapshot())
27600 {
27601 is_foldable = true;
27602 match crease {
27603 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27604 if let Some(render_toggle) = render_toggle {
27605 let toggle_callback =
27606 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27607 if folded {
27608 editor.update(cx, |editor, cx| {
27609 editor.fold_at(buffer_row, window, cx)
27610 });
27611 } else {
27612 editor.update(cx, |editor, cx| {
27613 editor.unfold_at(buffer_row, window, cx)
27614 });
27615 }
27616 });
27617 return Some((render_toggle)(
27618 buffer_row,
27619 folded,
27620 toggle_callback,
27621 window,
27622 cx,
27623 ));
27624 }
27625 }
27626 }
27627 }
27628
27629 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27630
27631 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27632 Some(
27633 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27634 .toggle_state(folded)
27635 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27636 if folded {
27637 this.unfold_at(buffer_row, window, cx);
27638 } else {
27639 this.fold_at(buffer_row, window, cx);
27640 }
27641 }))
27642 .into_any_element(),
27643 )
27644 } else {
27645 None
27646 }
27647 }
27648
27649 pub fn render_crease_trailer(
27650 &self,
27651 buffer_row: MultiBufferRow,
27652 window: &mut Window,
27653 cx: &mut App,
27654 ) -> Option<AnyElement> {
27655 let folded = self.is_line_folded(buffer_row);
27656 if let Crease::Inline { render_trailer, .. } = self
27657 .crease_snapshot
27658 .query_row(buffer_row, self.buffer_snapshot())?
27659 {
27660 let render_trailer = render_trailer.as_ref()?;
27661 Some(render_trailer(buffer_row, folded, window, cx))
27662 } else {
27663 None
27664 }
27665 }
27666
27667 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27668 let digit_count = self.widest_line_number().ilog10() + 1;
27669 column_pixels(style, digit_count as usize, window)
27670 }
27671
27672 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27673 ///
27674 /// This is positive if `base` is before `line`.
27675 fn relative_line_delta(
27676 &self,
27677 current_selection_head: DisplayRow,
27678 first_visible_row: DisplayRow,
27679 consider_wrapped_lines: bool,
27680 ) -> i64 {
27681 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27682 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27683
27684 if consider_wrapped_lines {
27685 let wrap_snapshot = self.wrap_snapshot();
27686 let base_wrap_row = wrap_snapshot
27687 .make_wrap_point(current_selection_head, Bias::Left)
27688 .row();
27689 let wrap_row = wrap_snapshot
27690 .make_wrap_point(first_visible_row, Bias::Left)
27691 .row();
27692
27693 wrap_row.0 as i64 - base_wrap_row.0 as i64
27694 } else {
27695 let fold_snapshot = self.fold_snapshot();
27696 let base_fold_row = fold_snapshot
27697 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27698 .row();
27699 let fold_row = fold_snapshot
27700 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27701 .row();
27702
27703 fold_row as i64 - base_fold_row as i64
27704 }
27705 }
27706
27707 /// Returns the unsigned relative line number to display for each row in `rows`.
27708 ///
27709 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27710 pub fn calculate_relative_line_numbers(
27711 &self,
27712 rows: &Range<DisplayRow>,
27713 current_selection_head: DisplayRow,
27714 count_wrapped_lines: bool,
27715 ) -> HashMap<DisplayRow, u32> {
27716 let initial_offset =
27717 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27718
27719 self.row_infos(rows.start)
27720 .take(rows.len())
27721 .enumerate()
27722 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27723 .filter(|(_row, row_info)| {
27724 row_info.buffer_row.is_some()
27725 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27726 })
27727 .enumerate()
27728 .filter_map(|(i, (row, row_info))| {
27729 // We want to ensure here that the current line has absolute
27730 // numbering, even if we are in a soft-wrapped line. With the
27731 // exception that if we are in a deleted line, we should number this
27732 // relative with 0, as otherwise it would have no line number at all
27733 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27734
27735 (relative_line_number != 0
27736 || row_info
27737 .diff_status
27738 .is_some_and(|status| status.is_deleted()))
27739 .then_some((row, relative_line_number))
27740 })
27741 .collect()
27742 }
27743}
27744
27745pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27746 let font_size = style.text.font_size.to_pixels(window.rem_size());
27747 let layout = window.text_system().shape_line(
27748 SharedString::from(" ".repeat(column)),
27749 font_size,
27750 &[TextRun {
27751 len: column,
27752 font: style.text.font(),
27753 color: Hsla::default(),
27754 ..Default::default()
27755 }],
27756 None,
27757 );
27758
27759 layout.width
27760}
27761
27762impl Deref for EditorSnapshot {
27763 type Target = DisplaySnapshot;
27764
27765 fn deref(&self) -> &Self::Target {
27766 &self.display_snapshot
27767 }
27768}
27769
27770#[derive(Clone, Debug, PartialEq, Eq)]
27771pub enum EditorEvent {
27772 /// Emitted when the stored review comments change (added, removed, or updated).
27773 ReviewCommentsChanged {
27774 /// The new total count of review comments.
27775 total_count: usize,
27776 },
27777 InputIgnored {
27778 text: Arc<str>,
27779 },
27780 InputHandled {
27781 utf16_range_to_replace: Option<Range<isize>>,
27782 text: Arc<str>,
27783 },
27784 ExcerptsAdded {
27785 buffer: Entity<Buffer>,
27786 predecessor: ExcerptId,
27787 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27788 },
27789 ExcerptsRemoved {
27790 ids: Vec<ExcerptId>,
27791 removed_buffer_ids: Vec<BufferId>,
27792 },
27793 BufferFoldToggled {
27794 ids: Vec<ExcerptId>,
27795 folded: bool,
27796 },
27797 ExcerptsEdited {
27798 ids: Vec<ExcerptId>,
27799 },
27800 ExcerptsExpanded {
27801 ids: Vec<ExcerptId>,
27802 },
27803 ExpandExcerptsRequested {
27804 excerpt_ids: Vec<ExcerptId>,
27805 lines: u32,
27806 direction: ExpandExcerptDirection,
27807 },
27808 StageOrUnstageRequested {
27809 stage: bool,
27810 hunks: Vec<MultiBufferDiffHunk>,
27811 },
27812 OpenExcerptsRequested {
27813 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
27814 split: bool,
27815 },
27816 RestoreRequested {
27817 hunks: Vec<MultiBufferDiffHunk>,
27818 },
27819 BufferEdited,
27820 Edited {
27821 transaction_id: clock::Lamport,
27822 },
27823 Reparsed(BufferId),
27824 Focused,
27825 FocusedIn,
27826 Blurred,
27827 DirtyChanged,
27828 Saved,
27829 TitleChanged,
27830 SelectionsChanged {
27831 local: bool,
27832 },
27833 ScrollPositionChanged {
27834 local: bool,
27835 autoscroll: bool,
27836 },
27837 TransactionUndone {
27838 transaction_id: clock::Lamport,
27839 },
27840 TransactionBegun {
27841 transaction_id: clock::Lamport,
27842 },
27843 CursorShapeChanged,
27844 BreadcrumbsChanged,
27845 OutlineSymbolsChanged,
27846 PushedToNavHistory {
27847 anchor: Anchor,
27848 is_deactivate: bool,
27849 },
27850}
27851
27852impl EventEmitter<EditorEvent> for Editor {}
27853
27854impl Focusable for Editor {
27855 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27856 self.focus_handle.clone()
27857 }
27858}
27859
27860impl Render for Editor {
27861 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27862 EditorElement::new(&cx.entity(), self.create_style(cx))
27863 }
27864}
27865
27866impl EntityInputHandler for Editor {
27867 fn text_for_range(
27868 &mut self,
27869 range_utf16: Range<usize>,
27870 adjusted_range: &mut Option<Range<usize>>,
27871 _: &mut Window,
27872 cx: &mut Context<Self>,
27873 ) -> Option<String> {
27874 let snapshot = self.buffer.read(cx).read(cx);
27875 let start = snapshot.clip_offset_utf16(
27876 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27877 Bias::Left,
27878 );
27879 let end = snapshot.clip_offset_utf16(
27880 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27881 Bias::Right,
27882 );
27883 if (start.0.0..end.0.0) != range_utf16 {
27884 adjusted_range.replace(start.0.0..end.0.0);
27885 }
27886 Some(snapshot.text_for_range(start..end).collect())
27887 }
27888
27889 fn selected_text_range(
27890 &mut self,
27891 ignore_disabled_input: bool,
27892 _: &mut Window,
27893 cx: &mut Context<Self>,
27894 ) -> Option<UTF16Selection> {
27895 // Prevent the IME menu from appearing when holding down an alphabetic key
27896 // while input is disabled.
27897 if !ignore_disabled_input && !self.input_enabled {
27898 return None;
27899 }
27900
27901 let selection = self
27902 .selections
27903 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27904 let range = selection.range();
27905
27906 Some(UTF16Selection {
27907 range: range.start.0.0..range.end.0.0,
27908 reversed: selection.reversed,
27909 })
27910 }
27911
27912 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27913 let snapshot = self.buffer.read(cx).read(cx);
27914 let range = self
27915 .text_highlights(HighlightKey::InputComposition, cx)?
27916 .1
27917 .first()?;
27918 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27919 }
27920
27921 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27922 self.clear_highlights(HighlightKey::InputComposition, cx);
27923 self.ime_transaction.take();
27924 }
27925
27926 fn replace_text_in_range(
27927 &mut self,
27928 range_utf16: Option<Range<usize>>,
27929 text: &str,
27930 window: &mut Window,
27931 cx: &mut Context<Self>,
27932 ) {
27933 if !self.input_enabled {
27934 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27935 return;
27936 }
27937
27938 self.transact(window, cx, |this, window, cx| {
27939 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27940 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27941 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27942 Some(this.selection_replacement_ranges(range_utf16, cx))
27943 } else {
27944 this.marked_text_ranges(cx)
27945 };
27946
27947 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27948 let newest_selection_id = this.selections.newest_anchor().id;
27949 this.selections
27950 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27951 .iter()
27952 .zip(ranges_to_replace.iter())
27953 .find_map(|(selection, range)| {
27954 if selection.id == newest_selection_id {
27955 Some(
27956 (range.start.0.0 as isize - selection.head().0.0 as isize)
27957 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27958 )
27959 } else {
27960 None
27961 }
27962 })
27963 });
27964
27965 cx.emit(EditorEvent::InputHandled {
27966 utf16_range_to_replace: range_to_replace,
27967 text: text.into(),
27968 });
27969
27970 if let Some(new_selected_ranges) = new_selected_ranges {
27971 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27972 selections.select_ranges(new_selected_ranges)
27973 });
27974 this.backspace(&Default::default(), window, cx);
27975 }
27976
27977 this.handle_input(text, window, cx);
27978 });
27979
27980 if let Some(transaction) = self.ime_transaction {
27981 self.buffer.update(cx, |buffer, cx| {
27982 buffer.group_until_transaction(transaction, cx);
27983 });
27984 }
27985
27986 self.unmark_text(window, cx);
27987 }
27988
27989 fn replace_and_mark_text_in_range(
27990 &mut self,
27991 range_utf16: Option<Range<usize>>,
27992 text: &str,
27993 new_selected_range_utf16: Option<Range<usize>>,
27994 window: &mut Window,
27995 cx: &mut Context<Self>,
27996 ) {
27997 if !self.input_enabled {
27998 return;
27999 }
28000
28001 let transaction = self.transact(window, cx, |this, window, cx| {
28002 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28003 let snapshot = this.buffer.read(cx).read(cx);
28004 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28005 for marked_range in &mut marked_ranges {
28006 marked_range.end = marked_range.start + relative_range_utf16.end;
28007 marked_range.start += relative_range_utf16.start;
28008 marked_range.start =
28009 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28010 marked_range.end =
28011 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28012 }
28013 }
28014 Some(marked_ranges)
28015 } else if let Some(range_utf16) = range_utf16 {
28016 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28017 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28018 Some(this.selection_replacement_ranges(range_utf16, cx))
28019 } else {
28020 None
28021 };
28022
28023 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28024 let newest_selection_id = this.selections.newest_anchor().id;
28025 this.selections
28026 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28027 .iter()
28028 .zip(ranges_to_replace.iter())
28029 .find_map(|(selection, range)| {
28030 if selection.id == newest_selection_id {
28031 Some(
28032 (range.start.0.0 as isize - selection.head().0.0 as isize)
28033 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28034 )
28035 } else {
28036 None
28037 }
28038 })
28039 });
28040
28041 cx.emit(EditorEvent::InputHandled {
28042 utf16_range_to_replace: range_to_replace,
28043 text: text.into(),
28044 });
28045
28046 if let Some(ranges) = ranges_to_replace {
28047 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28048 s.select_ranges(ranges)
28049 });
28050 }
28051
28052 let marked_ranges = {
28053 let snapshot = this.buffer.read(cx).read(cx);
28054 this.selections
28055 .disjoint_anchors_arc()
28056 .iter()
28057 .map(|selection| {
28058 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28059 })
28060 .collect::<Vec<_>>()
28061 };
28062
28063 if text.is_empty() {
28064 this.unmark_text(window, cx);
28065 } else {
28066 this.highlight_text(
28067 HighlightKey::InputComposition,
28068 marked_ranges.clone(),
28069 HighlightStyle {
28070 underline: Some(UnderlineStyle {
28071 thickness: px(1.),
28072 color: None,
28073 wavy: false,
28074 }),
28075 ..Default::default()
28076 },
28077 cx,
28078 );
28079 }
28080
28081 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28082 let use_autoclose = this.use_autoclose;
28083 let use_auto_surround = this.use_auto_surround;
28084 this.set_use_autoclose(false);
28085 this.set_use_auto_surround(false);
28086 this.handle_input(text, window, cx);
28087 this.set_use_autoclose(use_autoclose);
28088 this.set_use_auto_surround(use_auto_surround);
28089
28090 if let Some(new_selected_range) = new_selected_range_utf16 {
28091 let snapshot = this.buffer.read(cx).read(cx);
28092 let new_selected_ranges = marked_ranges
28093 .into_iter()
28094 .map(|marked_range| {
28095 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28096 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28097 insertion_start.0 + new_selected_range.start,
28098 ));
28099 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28100 insertion_start.0 + new_selected_range.end,
28101 ));
28102 snapshot.clip_offset_utf16(new_start, Bias::Left)
28103 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28104 })
28105 .collect::<Vec<_>>();
28106
28107 drop(snapshot);
28108 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28109 selections.select_ranges(new_selected_ranges)
28110 });
28111 }
28112 });
28113
28114 self.ime_transaction = self.ime_transaction.or(transaction);
28115 if let Some(transaction) = self.ime_transaction {
28116 self.buffer.update(cx, |buffer, cx| {
28117 buffer.group_until_transaction(transaction, cx);
28118 });
28119 }
28120
28121 if self
28122 .text_highlights(HighlightKey::InputComposition, cx)
28123 .is_none()
28124 {
28125 self.ime_transaction.take();
28126 }
28127 }
28128
28129 fn bounds_for_range(
28130 &mut self,
28131 range_utf16: Range<usize>,
28132 element_bounds: gpui::Bounds<Pixels>,
28133 window: &mut Window,
28134 cx: &mut Context<Self>,
28135 ) -> Option<gpui::Bounds<Pixels>> {
28136 let text_layout_details = self.text_layout_details(window, cx);
28137 let CharacterDimensions {
28138 em_width,
28139 em_advance,
28140 line_height,
28141 } = self.character_dimensions(window, cx);
28142
28143 let snapshot = self.snapshot(window, cx);
28144 let scroll_position = snapshot.scroll_position();
28145 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28146
28147 let start =
28148 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28149 let x = Pixels::from(
28150 ScrollOffset::from(
28151 snapshot.x_for_display_point(start, &text_layout_details)
28152 + self.gutter_dimensions.full_width(),
28153 ) - scroll_left,
28154 );
28155 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28156
28157 Some(Bounds {
28158 origin: element_bounds.origin + point(x, y),
28159 size: size(em_width, line_height),
28160 })
28161 }
28162
28163 fn character_index_for_point(
28164 &mut self,
28165 point: gpui::Point<Pixels>,
28166 _window: &mut Window,
28167 _cx: &mut Context<Self>,
28168 ) -> Option<usize> {
28169 let position_map = self.last_position_map.as_ref()?;
28170 if !position_map.text_hitbox.contains(&point) {
28171 return None;
28172 }
28173 let display_point = position_map.point_for_position(point).previous_valid;
28174 let anchor = position_map
28175 .snapshot
28176 .display_point_to_anchor(display_point, Bias::Left);
28177 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28178 Some(utf16_offset.0.0)
28179 }
28180
28181 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28182 self.expects_character_input
28183 }
28184}
28185
28186trait SelectionExt {
28187 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28188 fn spanned_rows(
28189 &self,
28190 include_end_if_at_line_start: bool,
28191 map: &DisplaySnapshot,
28192 ) -> Range<MultiBufferRow>;
28193}
28194
28195impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28196 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28197 let start = self
28198 .start
28199 .to_point(map.buffer_snapshot())
28200 .to_display_point(map);
28201 let end = self
28202 .end
28203 .to_point(map.buffer_snapshot())
28204 .to_display_point(map);
28205 if self.reversed {
28206 end..start
28207 } else {
28208 start..end
28209 }
28210 }
28211
28212 fn spanned_rows(
28213 &self,
28214 include_end_if_at_line_start: bool,
28215 map: &DisplaySnapshot,
28216 ) -> Range<MultiBufferRow> {
28217 let start = self.start.to_point(map.buffer_snapshot());
28218 let mut end = self.end.to_point(map.buffer_snapshot());
28219 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28220 end.row -= 1;
28221 }
28222
28223 let buffer_start = map.prev_line_boundary(start).0;
28224 let buffer_end = map.next_line_boundary(end).0;
28225 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28226 }
28227}
28228
28229impl<T: InvalidationRegion> InvalidationStack<T> {
28230 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28231 where
28232 S: Clone + ToOffset,
28233 {
28234 while let Some(region) = self.last() {
28235 let all_selections_inside_invalidation_ranges =
28236 if selections.len() == region.ranges().len() {
28237 selections
28238 .iter()
28239 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28240 .all(|(selection, invalidation_range)| {
28241 let head = selection.head().to_offset(buffer);
28242 invalidation_range.start <= head && invalidation_range.end >= head
28243 })
28244 } else {
28245 false
28246 };
28247
28248 if all_selections_inside_invalidation_ranges {
28249 break;
28250 } else {
28251 self.pop();
28252 }
28253 }
28254 }
28255}
28256
28257#[derive(Clone)]
28258struct ErasedEditorImpl(Entity<Editor>);
28259
28260impl ui_input::ErasedEditor for ErasedEditorImpl {
28261 fn text(&self, cx: &App) -> String {
28262 self.0.read(cx).text(cx)
28263 }
28264
28265 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28266 self.0.update(cx, |this, cx| {
28267 this.set_text(text, window, cx);
28268 })
28269 }
28270
28271 fn clear(&self, window: &mut Window, cx: &mut App) {
28272 self.0.update(cx, |this, cx| this.clear(window, cx));
28273 }
28274
28275 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28276 self.0.update(cx, |this, cx| {
28277 this.set_placeholder_text(text, window, cx);
28278 });
28279 }
28280
28281 fn focus_handle(&self, cx: &App) -> FocusHandle {
28282 self.0.read(cx).focus_handle(cx)
28283 }
28284
28285 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28286 let settings = ThemeSettings::get_global(cx);
28287 let theme_color = cx.theme().colors();
28288
28289 let text_style = TextStyle {
28290 font_family: settings.ui_font.family.clone(),
28291 font_features: settings.ui_font.features.clone(),
28292 font_size: rems(0.875).into(),
28293 font_weight: settings.ui_font.weight,
28294 font_style: FontStyle::Normal,
28295 line_height: relative(1.2),
28296 color: theme_color.text,
28297 ..Default::default()
28298 };
28299 let editor_style = EditorStyle {
28300 background: theme_color.ghost_element_background,
28301 local_player: cx.theme().players().local(),
28302 syntax: cx.theme().syntax().clone(),
28303 text: text_style,
28304 ..Default::default()
28305 };
28306 EditorElement::new(&self.0, editor_style).into_any()
28307 }
28308
28309 fn as_any(&self) -> &dyn Any {
28310 &self.0
28311 }
28312
28313 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28314 self.0.update(cx, |editor, cx| {
28315 let editor_offset = editor.buffer().read(cx).len(cx);
28316 editor.change_selections(
28317 SelectionEffects::scroll(Autoscroll::Next),
28318 window,
28319 cx,
28320 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28321 );
28322 });
28323 }
28324
28325 fn subscribe(
28326 &self,
28327 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28328 window: &mut Window,
28329 cx: &mut App,
28330 ) -> Subscription {
28331 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28332 let event = match event {
28333 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28334 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28335 _ => return,
28336 };
28337 (callback)(event, window, cx);
28338 })
28339 }
28340
28341 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28342 self.0.update(cx, |editor, cx| {
28343 editor.set_masked(masked, cx);
28344 });
28345 }
28346}
28347impl<T> Default for InvalidationStack<T> {
28348 fn default() -> Self {
28349 Self(Default::default())
28350 }
28351}
28352
28353impl<T> Deref for InvalidationStack<T> {
28354 type Target = Vec<T>;
28355
28356 fn deref(&self) -> &Self::Target {
28357 &self.0
28358 }
28359}
28360
28361impl<T> DerefMut for InvalidationStack<T> {
28362 fn deref_mut(&mut self) -> &mut Self::Target {
28363 &mut self.0
28364 }
28365}
28366
28367impl InvalidationRegion for SnippetState {
28368 fn ranges(&self) -> &[Range<Anchor>] {
28369 &self.ranges[self.active_index]
28370 }
28371}
28372
28373fn edit_prediction_edit_text(
28374 current_snapshot: &BufferSnapshot,
28375 edits: &[(Range<Anchor>, impl AsRef<str>)],
28376 edit_preview: &EditPreview,
28377 include_deletions: bool,
28378 cx: &App,
28379) -> HighlightedText {
28380 let edits = edits
28381 .iter()
28382 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28383 .collect::<Vec<_>>();
28384
28385 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28386}
28387
28388fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28389 // Fallback for providers that don't provide edit_preview (like Copilot)
28390 // Just show the raw edit text with basic styling
28391 let mut text = String::new();
28392 let mut highlights = Vec::new();
28393
28394 let insertion_highlight_style = HighlightStyle {
28395 color: Some(cx.theme().colors().text),
28396 ..Default::default()
28397 };
28398
28399 for (_, edit_text) in edits {
28400 let start_offset = text.len();
28401 text.push_str(edit_text);
28402 let end_offset = text.len();
28403
28404 if start_offset < end_offset {
28405 highlights.push((start_offset..end_offset, insertion_highlight_style));
28406 }
28407 }
28408
28409 HighlightedText {
28410 text: text.into(),
28411 highlights,
28412 }
28413}
28414
28415pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28416 match severity {
28417 lsp::DiagnosticSeverity::ERROR => colors.error,
28418 lsp::DiagnosticSeverity::WARNING => colors.warning,
28419 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28420 lsp::DiagnosticSeverity::HINT => colors.info,
28421 _ => colors.ignored,
28422 }
28423}
28424
28425pub fn styled_runs_for_code_label<'a>(
28426 label: &'a CodeLabel,
28427 syntax_theme: &'a theme::SyntaxTheme,
28428 local_player: &'a theme::PlayerColor,
28429) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28430 let fade_out = HighlightStyle {
28431 fade_out: Some(0.35),
28432 ..Default::default()
28433 };
28434
28435 let mut prev_end = label.filter_range.end;
28436 label
28437 .runs
28438 .iter()
28439 .enumerate()
28440 .flat_map(move |(ix, (range, highlight_id))| {
28441 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28442 HighlightStyle {
28443 color: Some(local_player.cursor),
28444 ..Default::default()
28445 }
28446 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28447 HighlightStyle {
28448 background_color: Some(local_player.selection),
28449 ..Default::default()
28450 }
28451 } else if let Some(style) = highlight_id.style(syntax_theme) {
28452 style
28453 } else {
28454 return Default::default();
28455 };
28456 let muted_style = style.highlight(fade_out);
28457
28458 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28459 if range.start >= label.filter_range.end {
28460 if range.start > prev_end {
28461 runs.push((prev_end..range.start, fade_out));
28462 }
28463 runs.push((range.clone(), muted_style));
28464 } else if range.end <= label.filter_range.end {
28465 runs.push((range.clone(), style));
28466 } else {
28467 runs.push((range.start..label.filter_range.end, style));
28468 runs.push((label.filter_range.end..range.end, muted_style));
28469 }
28470 prev_end = cmp::max(prev_end, range.end);
28471
28472 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28473 runs.push((prev_end..label.text.len(), fade_out));
28474 }
28475
28476 runs
28477 })
28478}
28479
28480pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28481 let mut prev_index = 0;
28482 let mut prev_codepoint: Option<char> = None;
28483 text.char_indices()
28484 .chain([(text.len(), '\0')])
28485 .filter_map(move |(index, codepoint)| {
28486 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28487 let is_boundary = index == text.len()
28488 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28489 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28490 if is_boundary {
28491 let chunk = &text[prev_index..index];
28492 prev_index = index;
28493 Some(chunk)
28494 } else {
28495 None
28496 }
28497 })
28498}
28499
28500/// Given a string of text immediately before the cursor, iterates over possible
28501/// strings a snippet could match to. More precisely: returns an iterator over
28502/// suffixes of `text` created by splitting at word boundaries (before & after
28503/// every non-word character).
28504///
28505/// Shorter suffixes are returned first.
28506pub(crate) fn snippet_candidate_suffixes<'a>(
28507 text: &'a str,
28508 is_word_char: &'a dyn Fn(char) -> bool,
28509) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28510 let mut prev_index = text.len();
28511 let mut prev_codepoint = None;
28512 text.char_indices()
28513 .rev()
28514 .chain([(0, '\0')])
28515 .filter_map(move |(index, codepoint)| {
28516 let prev_index = std::mem::replace(&mut prev_index, index);
28517 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28518 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28519 None
28520 } else {
28521 let chunk = &text[prev_index..]; // go to end of string
28522 Some(chunk)
28523 }
28524 })
28525}
28526
28527pub trait RangeToAnchorExt: Sized {
28528 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28529
28530 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28531 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28532 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28533 }
28534}
28535
28536impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28537 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28538 let start_offset = self.start.to_offset(snapshot);
28539 let end_offset = self.end.to_offset(snapshot);
28540 if start_offset == end_offset {
28541 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28542 } else {
28543 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28544 }
28545 }
28546}
28547
28548pub trait RowExt {
28549 fn as_f64(&self) -> f64;
28550
28551 fn next_row(&self) -> Self;
28552
28553 fn previous_row(&self) -> Self;
28554
28555 fn minus(&self, other: Self) -> u32;
28556}
28557
28558impl RowExt for DisplayRow {
28559 fn as_f64(&self) -> f64 {
28560 self.0 as _
28561 }
28562
28563 fn next_row(&self) -> Self {
28564 Self(self.0 + 1)
28565 }
28566
28567 fn previous_row(&self) -> Self {
28568 Self(self.0.saturating_sub(1))
28569 }
28570
28571 fn minus(&self, other: Self) -> u32 {
28572 self.0 - other.0
28573 }
28574}
28575
28576impl RowExt for MultiBufferRow {
28577 fn as_f64(&self) -> f64 {
28578 self.0 as _
28579 }
28580
28581 fn next_row(&self) -> Self {
28582 Self(self.0 + 1)
28583 }
28584
28585 fn previous_row(&self) -> Self {
28586 Self(self.0.saturating_sub(1))
28587 }
28588
28589 fn minus(&self, other: Self) -> u32 {
28590 self.0 - other.0
28591 }
28592}
28593
28594trait RowRangeExt {
28595 type Row;
28596
28597 fn len(&self) -> usize;
28598
28599 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28600}
28601
28602impl RowRangeExt for Range<MultiBufferRow> {
28603 type Row = MultiBufferRow;
28604
28605 fn len(&self) -> usize {
28606 (self.end.0 - self.start.0) as usize
28607 }
28608
28609 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28610 (self.start.0..self.end.0).map(MultiBufferRow)
28611 }
28612}
28613
28614impl RowRangeExt for Range<DisplayRow> {
28615 type Row = DisplayRow;
28616
28617 fn len(&self) -> usize {
28618 (self.end.0 - self.start.0) as usize
28619 }
28620
28621 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28622 (self.start.0..self.end.0).map(DisplayRow)
28623 }
28624}
28625
28626/// If select range has more than one line, we
28627/// just point the cursor to range.start.
28628fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28629 if range.start.row == range.end.row {
28630 range
28631 } else {
28632 range.start..range.start
28633 }
28634}
28635pub struct KillRing(ClipboardItem);
28636impl Global for KillRing {}
28637
28638const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28639
28640enum BreakpointPromptEditAction {
28641 Log,
28642 Condition,
28643 HitCondition,
28644}
28645
28646struct BreakpointPromptEditor {
28647 pub(crate) prompt: Entity<Editor>,
28648 editor: WeakEntity<Editor>,
28649 breakpoint_anchor: Anchor,
28650 breakpoint: Breakpoint,
28651 edit_action: BreakpointPromptEditAction,
28652 block_ids: HashSet<CustomBlockId>,
28653 editor_margins: Arc<Mutex<EditorMargins>>,
28654 _subscriptions: Vec<Subscription>,
28655}
28656
28657impl BreakpointPromptEditor {
28658 const MAX_LINES: u8 = 4;
28659
28660 fn new(
28661 editor: WeakEntity<Editor>,
28662 breakpoint_anchor: Anchor,
28663 breakpoint: Breakpoint,
28664 edit_action: BreakpointPromptEditAction,
28665 window: &mut Window,
28666 cx: &mut Context<Self>,
28667 ) -> Self {
28668 let base_text = match edit_action {
28669 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28670 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28671 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28672 }
28673 .map(|msg| msg.to_string())
28674 .unwrap_or_default();
28675
28676 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28678
28679 let prompt = cx.new(|cx| {
28680 let mut prompt = Editor::new(
28681 EditorMode::AutoHeight {
28682 min_lines: 1,
28683 max_lines: Some(Self::MAX_LINES as usize),
28684 },
28685 buffer,
28686 None,
28687 window,
28688 cx,
28689 );
28690 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28691 prompt.set_show_cursor_when_unfocused(false, cx);
28692 prompt.set_placeholder_text(
28693 match edit_action {
28694 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28695 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28696 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28697 },
28698 window,
28699 cx,
28700 );
28701
28702 prompt
28703 });
28704
28705 Self {
28706 prompt,
28707 editor,
28708 breakpoint_anchor,
28709 breakpoint,
28710 edit_action,
28711 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28712 block_ids: Default::default(),
28713 _subscriptions: vec![],
28714 }
28715 }
28716
28717 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28718 self.block_ids.extend(block_ids)
28719 }
28720
28721 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28722 if let Some(editor) = self.editor.upgrade() {
28723 let message = self
28724 .prompt
28725 .read(cx)
28726 .buffer
28727 .read(cx)
28728 .as_singleton()
28729 .expect("A multi buffer in breakpoint prompt isn't possible")
28730 .read(cx)
28731 .as_rope()
28732 .to_string();
28733
28734 editor.update(cx, |editor, cx| {
28735 editor.edit_breakpoint_at_anchor(
28736 self.breakpoint_anchor,
28737 self.breakpoint.clone(),
28738 match self.edit_action {
28739 BreakpointPromptEditAction::Log => {
28740 BreakpointEditAction::EditLogMessage(message.into())
28741 }
28742 BreakpointPromptEditAction::Condition => {
28743 BreakpointEditAction::EditCondition(message.into())
28744 }
28745 BreakpointPromptEditAction::HitCondition => {
28746 BreakpointEditAction::EditHitCondition(message.into())
28747 }
28748 },
28749 cx,
28750 );
28751
28752 editor.remove_blocks(self.block_ids.clone(), None, cx);
28753 cx.focus_self(window);
28754 });
28755 }
28756 }
28757
28758 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28759 self.editor
28760 .update(cx, |editor, cx| {
28761 editor.remove_blocks(self.block_ids.clone(), None, cx);
28762 window.focus(&editor.focus_handle, cx);
28763 })
28764 .log_err();
28765 }
28766
28767 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28768 let settings = ThemeSettings::get_global(cx);
28769 let text_style = TextStyle {
28770 color: if self.prompt.read(cx).read_only(cx) {
28771 cx.theme().colors().text_disabled
28772 } else {
28773 cx.theme().colors().text
28774 },
28775 font_family: settings.buffer_font.family.clone(),
28776 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28777 font_size: settings.buffer_font_size(cx).into(),
28778 font_weight: settings.buffer_font.weight,
28779 line_height: relative(settings.buffer_line_height.value()),
28780 ..Default::default()
28781 };
28782 EditorElement::new(
28783 &self.prompt,
28784 EditorStyle {
28785 background: cx.theme().colors().editor_background,
28786 local_player: cx.theme().players().local(),
28787 text: text_style,
28788 ..Default::default()
28789 },
28790 )
28791 }
28792
28793 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
28794 let focus_handle = self.prompt.focus_handle(cx);
28795 IconButton::new("cancel", IconName::Close)
28796 .icon_color(Color::Muted)
28797 .shape(IconButtonShape::Square)
28798 .tooltip(move |_window, cx| {
28799 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
28800 })
28801 .on_click(cx.listener(|this, _, window, cx| {
28802 this.cancel(&menu::Cancel, window, cx);
28803 }))
28804 }
28805
28806 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
28807 let focus_handle = self.prompt.focus_handle(cx);
28808 IconButton::new("confirm", IconName::Return)
28809 .icon_color(Color::Muted)
28810 .shape(IconButtonShape::Square)
28811 .tooltip(move |_window, cx| {
28812 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
28813 })
28814 .on_click(cx.listener(|this, _, window, cx| {
28815 this.confirm(&menu::Confirm, window, cx);
28816 }))
28817 }
28818}
28819
28820impl Render for BreakpointPromptEditor {
28821 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28822 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
28823 let editor_margins = *self.editor_margins.lock();
28824 let gutter_dimensions = editor_margins.gutter;
28825 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
28826 let right_padding = editor_margins.right + px(9.);
28827 h_flex()
28828 .key_context("Editor")
28829 .bg(cx.theme().colors().editor_background)
28830 .border_y_1()
28831 .border_color(cx.theme().status().info_border)
28832 .size_full()
28833 .py(window.line_height() / 2.5)
28834 .pr(right_padding)
28835 .on_action(cx.listener(Self::confirm))
28836 .on_action(cx.listener(Self::cancel))
28837 .child(
28838 WithRemSize::new(ui_font_size)
28839 .h_full()
28840 .w(left_gutter_width)
28841 .flex()
28842 .flex_row()
28843 .flex_shrink_0()
28844 .items_center()
28845 .justify_center()
28846 .gap_1()
28847 .child(self.render_close_button(cx)),
28848 )
28849 .child(
28850 h_flex()
28851 .w_full()
28852 .justify_between()
28853 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28854 .child(
28855 WithRemSize::new(ui_font_size)
28856 .flex()
28857 .flex_row()
28858 .items_center()
28859 .child(self.render_confirm_button(cx)),
28860 ),
28861 )
28862 }
28863}
28864
28865impl Focusable for BreakpointPromptEditor {
28866 fn focus_handle(&self, cx: &App) -> FocusHandle {
28867 self.prompt.focus_handle(cx)
28868 }
28869}
28870
28871fn all_edits_insertions_or_deletions(
28872 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28873 snapshot: &MultiBufferSnapshot,
28874) -> bool {
28875 let mut all_insertions = true;
28876 let mut all_deletions = true;
28877
28878 for (range, new_text) in edits.iter() {
28879 let range_is_empty = range.to_offset(snapshot).is_empty();
28880 let text_is_empty = new_text.is_empty();
28881
28882 if range_is_empty != text_is_empty {
28883 if range_is_empty {
28884 all_deletions = false;
28885 } else {
28886 all_insertions = false;
28887 }
28888 } else {
28889 return false;
28890 }
28891
28892 if !all_insertions && !all_deletions {
28893 return false;
28894 }
28895 }
28896 all_insertions || all_deletions
28897}
28898
28899struct MissingEditPredictionKeybindingTooltip;
28900
28901impl Render for MissingEditPredictionKeybindingTooltip {
28902 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28903 ui::tooltip_container(cx, |container, cx| {
28904 container
28905 .flex_shrink_0()
28906 .max_w_80()
28907 .min_h(rems_from_px(124.))
28908 .justify_between()
28909 .child(
28910 v_flex()
28911 .flex_1()
28912 .text_ui_sm(cx)
28913 .child(Label::new("Conflict with Accept Keybinding"))
28914 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28915 )
28916 .child(
28917 h_flex()
28918 .pb_1()
28919 .gap_1()
28920 .items_end()
28921 .w_full()
28922 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28923 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28924 }))
28925 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28926 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28927 })),
28928 )
28929 })
28930 }
28931}
28932
28933#[derive(Debug, Clone, Copy, PartialEq)]
28934pub struct LineHighlight {
28935 pub background: Background,
28936 pub border: Option<gpui::Hsla>,
28937 pub include_gutter: bool,
28938 pub type_id: Option<TypeId>,
28939}
28940
28941struct LineManipulationResult {
28942 pub new_text: String,
28943 pub line_count_before: usize,
28944 pub line_count_after: usize,
28945}
28946
28947fn render_diff_hunk_controls(
28948 row: u32,
28949 status: &DiffHunkStatus,
28950 hunk_range: Range<Anchor>,
28951 is_created_file: bool,
28952 line_height: Pixels,
28953 editor: &Entity<Editor>,
28954 _window: &mut Window,
28955 cx: &mut App,
28956) -> AnyElement {
28957 h_flex()
28958 .h(line_height)
28959 .mr_1()
28960 .gap_1()
28961 .px_0p5()
28962 .pb_1()
28963 .border_x_1()
28964 .border_b_1()
28965 .border_color(cx.theme().colors().border_variant)
28966 .rounded_b_lg()
28967 .bg(cx.theme().colors().editor_background)
28968 .gap_1()
28969 .block_mouse_except_scroll()
28970 .shadow_md()
28971 .child(if status.has_secondary_hunk() {
28972 Button::new(("stage", row as u64), "Stage")
28973 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28974 .tooltip({
28975 let focus_handle = editor.focus_handle(cx);
28976 move |_window, cx| {
28977 Tooltip::for_action_in(
28978 "Stage Hunk",
28979 &::git::ToggleStaged,
28980 &focus_handle,
28981 cx,
28982 )
28983 }
28984 })
28985 .on_click({
28986 let editor = editor.clone();
28987 move |_event, _window, cx| {
28988 editor.update(cx, |editor, cx| {
28989 editor.stage_or_unstage_diff_hunks(
28990 true,
28991 vec![hunk_range.start..hunk_range.start],
28992 cx,
28993 );
28994 });
28995 }
28996 })
28997 } else {
28998 Button::new(("unstage", row as u64), "Unstage")
28999 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29000 .tooltip({
29001 let focus_handle = editor.focus_handle(cx);
29002 move |_window, cx| {
29003 Tooltip::for_action_in(
29004 "Unstage Hunk",
29005 &::git::ToggleStaged,
29006 &focus_handle,
29007 cx,
29008 )
29009 }
29010 })
29011 .on_click({
29012 let editor = editor.clone();
29013 move |_event, _window, cx| {
29014 editor.update(cx, |editor, cx| {
29015 editor.stage_or_unstage_diff_hunks(
29016 false,
29017 vec![hunk_range.start..hunk_range.start],
29018 cx,
29019 );
29020 });
29021 }
29022 })
29023 })
29024 .child(
29025 Button::new(("restore", row as u64), "Restore")
29026 .tooltip({
29027 let focus_handle = editor.focus_handle(cx);
29028 move |_window, cx| {
29029 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29030 }
29031 })
29032 .on_click({
29033 let editor = editor.clone();
29034 move |_event, window, cx| {
29035 editor.update(cx, |editor, cx| {
29036 let snapshot = editor.snapshot(window, cx);
29037 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29038 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29039 });
29040 }
29041 })
29042 .disabled(is_created_file),
29043 )
29044 .when(
29045 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29046 |el| {
29047 el.child(
29048 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29049 .shape(IconButtonShape::Square)
29050 .icon_size(IconSize::Small)
29051 // .disabled(!has_multiple_hunks)
29052 .tooltip({
29053 let focus_handle = editor.focus_handle(cx);
29054 move |_window, cx| {
29055 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29056 }
29057 })
29058 .on_click({
29059 let editor = editor.clone();
29060 move |_event, window, cx| {
29061 editor.update(cx, |editor, cx| {
29062 let snapshot = editor.snapshot(window, cx);
29063 let position =
29064 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29065 editor.go_to_hunk_before_or_after_position(
29066 &snapshot,
29067 position,
29068 Direction::Next,
29069 true,
29070 window,
29071 cx,
29072 );
29073 editor.expand_selected_diff_hunks(cx);
29074 });
29075 }
29076 }),
29077 )
29078 .child(
29079 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29080 .shape(IconButtonShape::Square)
29081 .icon_size(IconSize::Small)
29082 // .disabled(!has_multiple_hunks)
29083 .tooltip({
29084 let focus_handle = editor.focus_handle(cx);
29085 move |_window, cx| {
29086 Tooltip::for_action_in(
29087 "Previous Hunk",
29088 &GoToPreviousHunk,
29089 &focus_handle,
29090 cx,
29091 )
29092 }
29093 })
29094 .on_click({
29095 let editor = editor.clone();
29096 move |_event, window, cx| {
29097 editor.update(cx, |editor, cx| {
29098 let snapshot = editor.snapshot(window, cx);
29099 let point =
29100 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29101 editor.go_to_hunk_before_or_after_position(
29102 &snapshot,
29103 point,
29104 Direction::Prev,
29105 true,
29106 window,
29107 cx,
29108 );
29109 editor.expand_selected_diff_hunks(cx);
29110 });
29111 }
29112 }),
29113 )
29114 },
29115 )
29116 .into_any_element()
29117}
29118
29119pub fn multibuffer_context_lines(cx: &App) -> u32 {
29120 EditorSettings::try_get(cx)
29121 .map(|settings| settings.excerpt_context_lines)
29122 .unwrap_or(2)
29123 .min(32)
29124}